{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "slc-title-0000-4000-8000-000000000000",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Cancelamento probabilístico de erros com cones de luz sombreados\"\n",
        "description: \"Utilize o cancelamento probabilístico de erros (PEC) com cones de luz sombreados para reduzir a sobrecarga de amostragem do PEC e mitigar erros na estimativa do valor esperado no hardware\"\n",
        "---\n",
        "\n",
        "{/* cspell:ignore samplomatic samplex Trotterized Trotter Lindblad lightcone lightcones Eddins reverser broadcastable postselection postselect antinoise eigval atol starmap unmitigated NoiseLearner PauliLindblad QubitSparsePauli Kingston kicked qubit canonical isa plm qspl Rzz pauli TREX levelname xticks expvals markerfacecolor ncols */}\n",
        "\n",
        "<span id=\"probabilistic-error-cancellation-with-shaded-lightcones\" />\n",
        "\n",
        "# Cancelamento probabilístico de erros com cones de luz sombreados\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": "slc-lout-0000-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 é o cancelamento probabilístico de erros (PEC) e por que sua sobrecarga de amostragem $\\gamma^2$ cresce exponencialmente com o ruído total que atua no circuito\n",
        "* Como os cones de luz sombreados (SLC) limitam a contribuição de cada termo de ruído para o observável alvo, de modo que você possa alocar o orçamento de mitigação onde for mais importante — e como, além da redução da sobrecarga de viés zero já proporcionada por um cone de luz puramente geométrico, o sombreamento permite trocar um viés residual limitado por uma sobrecarga de amostragem ainda menor\n",
        "* Como aprender o ruído da camada com `NoiseLearnerV3` e injetar antirruído por meio de `samplomatic` e da `Executor` primitiva\n",
        "* Como combinar PEC e PEC+SLC com TREX e pós-seleção para estimar um valor esperado em hardware\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": "slc-bkgd-0000-4000-8000-000000000002",
      "metadata": {},
      "source": [
        "<span id=\"background\" />\n",
        "\n",
        "## Segundo plano\n",
        "\n",
        "Este tutorial mostra como mitigar erros usando o complemento Shaded Light Cone (SLC). Este complemento é uma evolução da [técnica de cancelamento probabilístico de erros (PEC)](/docs/guides/error-mitigation-and-suppression-techniques#probabilistic-error-cancellation-pec), na qual o usuário identifica o ruído de camadas específicas em um circuito e, em seguida, cancela esse ruído por meio da aplicação de portas de qubit único e técnicas de pós-processamento. Em comparação com outros métodos, o PEC oferece limites mais robustos para o viés do resultado mitigado, mas tende a apresentar uma sobrecarga maior em termos de tempo de processamento da QPU. Durante o PEC, para compensar a atenuação do valor esperado causada pelo ruído, o resultado médio é redimensionado por um fator de $\\gamma = \\exp(\\sum_{l,\\sigma} 2\\lambda_{l,\\sigma})$, em que $\\lambda_{l,\\sigma}$ é a taxa de erro de ruído aprendida de Pauli $\\sigma$ na camada $l$ do circuito. Esse redimensionamento aumenta a variância em um fator de $\\gamma^2$ e, portanto, também multiplica o número de execuções de circuitos necessárias na QPU por $\\gamma^2$, o que chamamos de custo de amostragem ou sobrecarga de amostragem. Como o número de qubits ( $\\gamma$ ) cresce exponencialmente, o PEC costuma se limitar a circuitos rasos ou com poucos qubits. Saiba mais sobre o PEC no artigo “[Cancelamento probabilístico de erros com modelos esparsos de Pauli-Lindblad em processadores quânticos sujeitos a ruído](https://arxiv.org/abs/2201.09866) ”.\n",
        "\n",
        "Se conseguirmos identificar erros que não precisam ser mitigados, poderemos reduzir esse custo de amostragem exponencialmente. Um primeiro passo nessa direção é a implementação de uma mitigação de erros com consciência local, que utiliza um “cone de luz” convencional de cálculo rápido para reduzir a sobrecarga do PEC, limitando a sensibilidade de um observável aos erros em todo o circuito, ampliando assim a viabilidade do PEC para escalas maiores em alguns problemas. Erros fora desse cone de luz não podem afetar o resultado medido e, portanto, podem ser excluídos do procedimento de cancelamento de erros. Essa exclusão reduz a sobrecarga da amostragem, em alguns casos de forma substancial, sem introduzir viés adicional. Em particular, para medir um observável local $O$ de um circuito de profundidade fixa, a sobrecarga de amostragem necessária acaba se estabilizando ao aumentar o número de qubits no circuito (ver Fig. 2b em “[Locality and error mitigation of quantum circuits](https://arxiv.org/abs/2303.06496) ”).\n",
        "\n",
        "Os cones de luz sombreados (SLC) vão além, utilizando simulações clássicas para restringir de forma mais precisa a sensibilidade a erros em todo o circuito. Isso troca um pouco do tempo da QPU pelo tempo da CPU e reduz a sobrecarga de amostragem necessária para renormalizar o viés. Em vez de um limite rígido, a cada erro potencial no circuito é atribuído um “grau” que define o limite superior da suscetibilidade do valor esperado da variável observável a esse erro. (Para os limites propagados para trás apresentados a seguir, essa suscetibilidade também depende do estado inicial do circuito — o estado em que todos os valores são zero em fluxos de trabalho típicos e neste tutorial.) Essa caracterização refinada permite aplicações mais eficientes e direcionadas da PEC, com menor variância, ao mesmo tempo em que oferece ao usuário a capacidade de ajustar de forma controlada o viés na estimativa do observável. Consulte [“Sombreamento Lightcone para mitigação de erros quânticos com aceleração clássica”](https://arxiv.org/abs/2409.04401) para obter mais detalhes.\n",
        "\n",
        "Nosso fluxo de trabalho para o complemento SLC utiliza a [`samplomatic`](https://qiskit.github.io/samplomatic/guides/index.html) biblioteca em conjunto com as `QuantumProgram` classes e `Executor` adicionadas ao Qiskit Runtime na página `qiskit-ibm-runtime`v0.47.0, permitindo que os usuários tenham um controle mais modular das configurações de execução para supressão e mitigação de erros, mantendo a facilidade de uso. Leia mais no guia [do modelo de execução direcionada](/docs/guides/directed-execution-model).\n",
        "\n",
        "<span id=\"slc-error-mitigation-workflow-at-a-glance\" />\n",
        "\n",
        "### Visão geral do fluxo de trabalho de mitigação de erros do SLC\n",
        "\n",
        "Para modelar o ruído da QPU, utilizamos um modelo de ruído esparso de Pauli-Lindblad com taxas de erro de Pauli de um e dois qubits, geradas localmente em cada qubit e em cada borda do dispositivo. Com essa escolha, o fluxo de trabalho de mitigação de erros do SLC apresentado neste tutorial é o seguinte:\n",
        "\n",
        "a. CPU — Impacto por erro limitado de erros de Pauli de um e dois qubits\n",
        "\n",
        "1. Propagação direta (efeito de ligação na grande quantidade observável). Propague cada erro até o final do circuito e calcule seu comutador com o observável.\n",
        "   * Truncar os termos do operador durante a evolução para manter o cálculo gerenciável.\n",
        "   * Restrinja ainda mais esses limites por meio de uma retropropagação aproximada da observável, com base nos limites de velocidade quântica.\n",
        "2. Propagação para trás (efeito de ligação no estado inicial). Propague cada erro até o início do circuito e calcule seu comutador com o estado inicial.\n",
        "\n",
        "b. QPU — Conheça os índices de ruído. Use `NoiseLearnerV3` para estimar as taxas do modelo de ruído de Pauli-Lindblad.\n",
        "\n",
        "c. CPU — Priorizar medidas de mitigação\n",
        "\n",
        "1. Atualizar os limites combinados com as taxas de ruído aprendidas. Combinar os limites de avanço e retrocesso calculados anteriormente e atualizá-los com as taxas de ruído aprendidas.\n",
        "2. Classifique os componentes de ruído a serem mitigados utilizando os limites calculados e as taxas aprendidas. Priorize cada possível erro de ruído com base em seu impacto estimado no viés e no custo associado à sua correção.\n",
        "\n",
        "d. QPU — Insira o filtro antirruído e execute. Execute o circuito de interesse com o antirruído (ruído inverso) especificado por meio de `Box` anotações.\n",
        "\n",
        "e. CPU — Estimativa observável. Calcule o valor esperado, aplicando uma pós-seleção baseada em medições para reduzir o impacto do ruído não markoviano.\n",
        "\n",
        "<span id=\"noise-learning-overview\" />\n",
        "\n",
        "### Visão geral do aprendizado com ruído\n",
        "\n",
        "O aprendizado de ruído é uma etapa comum em vários métodos de mitigação de erros, realizada pelo “[noise learner](/docs/guides/noise-learning) ”; ele também aparece no [tutorial](/docs/tutorials/probabilistic-error-amplification) sobre amplificação probabilística de erros. `NoiseLearnerV3`Nesse contexto, é possível identificar especificamente as camadas de ruído a serem aprendidas como [`CircuitInstruction`](/docs/api/qiskit/qiskit.circuit.CircuitInstruction) objetos, de modo que se possa calcular os limites de ruído SLC desejados para cada camada, conforme descrito acima. O modelo de Pauli-Lindblad fornece coeficientes a serem utilizados na priorização PEC+SLC. `generate_noise_model_paulis`A forma como os portões são agrupados em camadas pode ser determinada por meio das `generate_boxing_pass_manager` funções de conveniência e `find_unique_box_instructions` , e, em seguida, inserida na função utilidade SLC, conforme descrito na Etapa 2 abaixo.\n",
        "\n",
        "| **Parte 1**                                                                                                      | **Parte 2**                                                                                                              | **Parte 3**                                                                                                      |\n",
        "| ---------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------- |\n",
        "| Camadas de portas de dois qubits com giro de Pauli                                                               | Repita pares de camadas idênticas e aprenda o ruído                                                                      | Calcular a fidelidade (erro para cada canal de ruído)                                                            |\n",
        "| ![Giro de Pauli](https://quantum.cloud.ibm.com/docs/images/tutorials/pec-with-shaded-lightcones/paulitwirl.avif) | ![Aprenda sobre camadas](https://quantum.cloud.ibm.com/docs/images/tutorials/pec-with-shaded-lightcones/learnlayer.avif) | ![Ajuste de curva](https://quantum.cloud.ibm.com/docs/images/tutorials/pec-with-shaded-lightcones/curvefit.avif) |\n",
        "\n",
        "<span id=\"post-processing-overview\" />\n",
        "\n",
        "### Visão geral do pós-processamento\n",
        "\n",
        "Após executar o código no hardware quântico utilizando as `samplomatic` estruturas e `Executor` , convertemos nossas medições de sequência de bits no valor observável desejado. Neste tutorial, demonstramos o fluxo de trabalho em um circuito de Ising espelhado (construído na Etapa 1 abaixo), no qual o valor esperado ideal da observável medida é 1, pois a segunda metade do circuito anula a primeira. Ao calcular o valor observável com a `executor_expectation_values` função, aplicamos algumas técnicas de pós-processamento que reduzem o impacto do ruído. Isso inclui a remoção de imagens afetadas por ruído não markoviano, a mitigação de erros de leitura e a consideração dos detalhes da nossa implementação do PEC. Os detalhes são abordados na Etapa 4 abaixo.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "slc-reqs-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",
        "* Complemento “Shaded lightcones” do Qiskit v0.1 ou versão posterior (`pip install qiskit-addon-slc`)\n",
        "* Complemento Qiskit utils v0.3 ou versão posterior (`pip install qiskit-addon-utils`)\n",
        "* Samplomatic v0.13 ou versão posterior (`pip install samplomatic`)\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "slc-setup-000-4000-8000-000000000004",
      "metadata": {},
      "source": [
        "<span id=\"setup\" />\n",
        "\n",
        "## Instalação\n",
        "\n",
        "Primeiro, importe os pacotes e as funções necessárias para executar este notebook.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "id": "946458b7-ab59-448c-8268-f62d8f53bc6d",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "env: OMP_NUM_THREADS=1\n"
          ]
        }
      ],
      "source": [
        "from multiprocessing import set_start_method\n",
        "\n",
        "# Setting this value prevents itertools.starmap deadlock on UNIX systems\n",
        "set_start_method(\"spawn\")\n",
        "\n",
        "# Needed to prevent PySCF from parallelizing internally (SLC only)\n",
        "%set_env OMP_NUM_THREADS=1"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "slc-imports-00-4000-8000-000000000005",
      "metadata": {},
      "outputs": [],
      "source": [
        "import numpy as np\n",
        "from matplotlib import pyplot as plt\n",
        "\n",
        "from qiskit import QuantumCircuit\n",
        "from qiskit.quantum_info import SparsePauliOp\n",
        "from qiskit.transpiler import generate_preset_pass_manager, PassManager\n",
        "\n",
        "from qiskit_ibm_runtime import (\n",
        "    QiskitRuntimeService,\n",
        "    QuantumProgram,\n",
        "    Executor,\n",
        "    NoiseLearnerV3,\n",
        ")\n",
        "\n",
        "import samplomatic\n",
        "from samplomatic.utils import find_unique_box_instructions\n",
        "from samplomatic.transpiler import generate_boxing_pass_manager\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 (\n",
        "    gamma_from_noisy_boxes,\n",
        "    trex_factors,\n",
        ")\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",
        "\n",
        "from qiskit_addon_slc.bounds import (\n",
        "    compute_backward_bounds,\n",
        "    compute_forward_bounds,\n",
        "    compute_local_scales,\n",
        "    merge_bounds,\n",
        "    tighten_with_speed_limit,\n",
        ")\n",
        "from qiskit_addon_slc.utils import (\n",
        "    generate_noise_model_paulis,\n",
        "    map_modifier_ref_to_ref,\n",
        ")\n",
        "from qiskit_addon_slc.visualization import draw_shaded_lightcone"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "slc-sim-0000-4000-8000-000000000006",
      "metadata": {},
      "source": [
        "<span id=\"small-scale-simulator-example\" />\n",
        "\n",
        "## Exemplo de simulador em pequena escala\n",
        "\n",
        "Assim como outros métodos de mitigação de erros baseados em aprendizado, o PEC com cones de luz sombreados mitiga o ruído *físico* de um processador quântico específico; portanto, depende de recursos 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 em cada camada única de dois qubits. Em um simulador silencioso, não há ruído para ser cancelado.\n",
        "* O `Executor` primitivo amostra os circuitos girados e com injeção de antirruído gerados por `samplomatic` em um backend.\n",
        "\n",
        "O cálculo do limite do cone de luz sombreado é clássico, mas só faz sentido em relação às taxas de ruído do hardware aprendidas, que definem o orçamento de mitigação e a sobrecarga de amostragem. Por esses motivos, deixamos de lado o exemplo do simulador em pequena escala e demonstramos o fluxo de trabalho completo do PEC+SLC diretamente no hardware, com cada etapa do padrão do Qiskit detalhada a seguir.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "slc-hw1-0000-4000-8000-000000000007",
      "metadata": {},
      "source": [
        "<span id=\"large-scale-hardware-example\" />\n",
        "\n",
        "## Exemplo de hardware em grande escala\n",
        "\n",
        "Executamos o fluxo de trabalho completo do PEC+SLC em um circuito de Ising espelhado de 20 qubits, executado no hardwar IBM Quantum®, seguindo as quatro etapas de um padrão do Qiskit.\n",
        "\n",
        "<span id=\"step-1-map-the-problem\" />\n",
        "\n",
        "### Etapa 1: Identificar o problema\n",
        "\n",
        "Nossa demonstração está organizada da seguinte forma:\n",
        "\n",
        "1. Optamos por realizar a evolução temporal sob um hamiltoniano de cadeia de Ising do tipo “ 1D ”. A cadeia de Ising do modelo “ 1D ” apresenta uma estrutura de circuito bem densa, o que é conveniente para demonstrar implementações do PEC.\n",
        "2. Optamos por medir a grandeza observável $\\langle X_6 Z_{13}\\rangle$, que contém um componente $X$ e um componente $Z$. Isso é útil para destacar como os limites do SLC tratam diferentes tipos de erros de maneiras distintas.\n",
        "3. Queremos que o valor esperado ideal dessa observável seja exatamente 1, para que seja fácil avaliar a qualidade das estimativas mitigadas. Ao espelhar o circuito, já estamos quase lá: para cada porta na segunda metade do circuito, há uma porta inversa na primeira metade, de modo que o circuito ideal atua como a identidade.\n",
        "4. Por fim, para que o valor esperado ideal seja 1, é necessário que o circuito comece — e, portanto, graças ao espelhamento, idealmente também termine — em um estado próprio +1 do observável. Como o observável contém um componente não- $Z$, e a `Executor` primitiva representa a base de medição desejada no final do circuito, inserimos as portas de preparação de base correspondentes no início do circuito espelho por meio de uma `prepare_basis` função. Utilizamos a `get_measurement_bases` função para identificar quais portas são necessárias e onde posicioná-las, ao mesmo tempo em que acompanhamos as sutilezas relacionadas ao índice dos qubits decorrentes das `box` convenções de anotação, conforme discutido na seção sobre a preparação de medições em base canônica.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "id": "5bfefe99-5958-486e-b8ca-ef2573c7f1e5",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Width of the Ising chain (a free parameter of this demonstration)\n",
        "num_qubits = 20\n",
        "\n",
        "# The observable to measure: one X component (on qubit 6) and one Z component\n",
        "# (on qubit 13), as motivated in \"Step 1: Map the problem\" above\n",
        "target_obs_sparse = [(\"XZ\", [6, 13], 1.0)]"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "id": "e5d4d6d5-9016-4c9a-916b-5075ff459fbf",
      "metadata": {},
      "outputs": [],
      "source": [
        "observable = SparsePauliOp.from_sparse_list(\n",
        "    target_obs_sparse, num_qubits=num_qubits\n",
        ")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 5,
      "id": "bb40bad8-cb18-4542-9e18-edf5fdfb5452",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Determine the measurement basis each qubit requires for this observable.\n",
        "# The reverser is used during post-processing in Step 4.\n",
        "bases_virt, reverser_virt = get_measurement_bases(observable)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 6,
      "id": "5b92fd4e-c559-4c56-8bf6-a6f2e397550c",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Free parameters of the demonstration circuit: the number of Trotter steps\n",
        "# sets the circuit depth, and rx_angle is the transverse-field rotation angle\n",
        "# applied in each step (pi/4 makes the dynamics non-Clifford)\n",
        "num_trotter_steps = 10\n",
        "rx_angle = np.pi / 4"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 7,
      "id": "cc2b001f-c006-45f1-9942-dcbdabefbec4",
      "metadata": {},
      "outputs": [],
      "source": [
        "def construct_ising_circuit(\n",
        "    num_qubits: int,\n",
        "    num_trotter_steps: int,\n",
        "    rx_angle: float,\n",
        ") -> QuantumCircuit:\n",
        "    circuit = QuantumCircuit(num_qubits)\n",
        "\n",
        "    for _ 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",
        "\n",
        "    return circuit\n",
        "\n",
        "\n",
        "def prepare_basis(\n",
        "    circuit: QuantumCircuit, basis: list[int]\n",
        ") -> QuantumCircuit:\n",
        "    # basis is a list of integer values from 0 to 3. These map to the basis measurement as:\n",
        "    # 0 = I; 1 = Z; 2 = X; 3 = Y\n",
        "    assert len(basis) == circuit.num_qubits\n",
        "\n",
        "    out_circ = circuit.copy_empty_like()\n",
        "    for qb, bas in enumerate(basis):\n",
        "        if bas == 2:\n",
        "            out_circ.h(qb)\n",
        "        elif bas == 3:\n",
        "            out_circ.rx(-np.pi / 2, qb)\n",
        "\n",
        "    out_circ.barrier()\n",
        "    out_circ.compose(circuit, inplace=True)\n",
        "    return out_circ\n",
        "\n",
        "\n",
        "def mirror_circuit(circuit: QuantumCircuit) -> QuantumCircuit:\n",
        "    mirror_circ = circuit.copy_empty_like()\n",
        "    mirror_circ.compose(circuit.inverse(), inplace=True)\n",
        "    mirror_circ.barrier()\n",
        "    mirror_circ.compose(circuit, inplace=True)\n",
        "    mirror_circ.measure_active()\n",
        "    return mirror_circ"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 8,
      "id": "67553310-f1f0-4736-85ad-bfebd3c00414",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Instantiate the mirrored circuit and prepend the basis-preparation gates\n",
        "circuit = construct_ising_circuit(num_qubits, num_trotter_steps, rx_angle)\n",
        "mirrored_circuit = mirror_circuit(circuit)\n",
        "mirrored_circuit = prepare_basis(mirrored_circuit, bases_virt[0])"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 9,
      "id": "0dab284e-dbed-4d2f-92aa-c6c9c0e909a6",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/0dab284e-dbed-4d2f-92aa-c6c9c0e909a6-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 9,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "mirrored_circuit.draw(\n",
        "    \"mpl\", fold=-1, scale=0.3, idle_wires=False, measure_arrows=False\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "slc-step2-000-4000-8000-000000000013",
      "metadata": {},
      "source": [
        "<span id=\"step-2-optimize\" />\n",
        "\n",
        "### Etapa 2: Otimizar\n",
        "\n",
        "Otimizamos os detalhes relacionados ao circuito a ser executado, à variável observável a ser medida e aos parâmetros de aprendizado de ruído. Como ponto de partida, selecionamos o processador Heron menos ocupado e o instanciamos com as portas fracionárias ativadas. Essas portas fracionárias permitem uma maior sensibilidade em algumas de nossas filtragens pós-seleção.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 10,
      "id": "slc-creds-000-4000-8000-000000000014",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Selected backend: ibm_kingston\n"
          ]
        }
      ],
      "source": [
        "# Initialize the Qiskit Runtime service using your saved credentials\n",
        "service = QiskitRuntimeService()\n",
        "\n",
        "# Select the least busy Heron device. This tutorial targets Heron processors:\n",
        "# the usage estimate and the non-Markovian-noise post-selection discussed in\n",
        "# Step 4 are specific to this processor family.\n",
        "backend = service.least_busy(\n",
        "    operational=True,\n",
        "    simulator=False,\n",
        "    filters=lambda b: b.processor_type[\"family\"] == \"Heron\",\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": "81dd2580-d4a8-45bb-bd05-707c64aa4ac7",
      "metadata": {},
      "source": [
        "Primeiro, vamos transpilá-lo para instruções ISA, [conforme necessário para a execução em nossas QPUs](/docs/guides/transpile#instruction-set-architecture). A escolha dos qubits físicos a serem utilizados tem um grande impacto na qualidade dos resultados, pois os tempos de coerência e as fidelidades das portas variam dentro de um mesmo dispositivo e sofrem desvios ao longo do tempo. Para um experimento bem planejado, pode valer a pena selecionar manualmente uma cadeia de qubits com base em experimentos rápidos de benchmarking (veja, por exemplo, o tutorial “[Benchmarking em tempo real para seleção de qubits](/docs/tutorials/real-time-benchmarking-for-qubit-selection) ”). Para os fins deste tutorial, utilizamos o transpiler do Qiskit para selecionar um layout com base no mapa de acoplamento do backend e em seus dados de calibração mais recentes.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 11,
      "id": "a7505de6-70ef-4996-9f6b-0025f267e920",
      "metadata": {
        "scrolled": true
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Selected layout: [71, 58, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 36, 21, 22, 23, 16, 3, 2]\n"
          ]
        }
      ],
      "source": [
        "isa_pm = generate_preset_pass_manager(backend=backend, optimization_level=1)\n",
        "\n",
        "isa_circuit = isa_pm.run(mirrored_circuit)\n",
        "\n",
        "# The chain of physical qubits selected by the transpiler\n",
        "layout = isa_circuit.layout.final_index_layout()\n",
        "print(f\"Selected layout: {layout}\")\n",
        "\n",
        "isa_observable = observable.apply_layout(\n",
        "    layout, num_qubits=isa_circuit.num_qubits\n",
        ")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 12,
      "id": "87f19e08-0cae-4959-9215-354ab099ab02",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/87f19e08-0cae-4959-9215-354ab099ab02-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 12,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "wire_order = layout + [\n",
        "    q for q in range(isa_circuit.num_qubits) if q not in layout\n",
        "]\n",
        "isa_circuit.draw(\n",
        "    \"mpl\",\n",
        "    fold=-1,\n",
        "    scale=0.3,\n",
        "    idle_wires=False,\n",
        "    wire_order=wire_order,\n",
        "    measure_arrows=False,\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "7e7135e7-1e86-4d16-b806-30d4a9225e88",
      "metadata": {},
      "source": [
        "<span id=\"box-the-circuit\" />\n",
        "\n",
        "### Enquadrar o circuito\n",
        "\n",
        "Para facilitar a implementação, utilizamos a `generate_boxing_pass_manager` etapa de transpilação, que coloca as instruções do circuito em caixas anotadas. Essas caixas indicam claramente onde, no caso do PEC, o sinal antirruído deve ser injetado no circuito. Para obter mais detalhes sobre as configurações, consulte a [documentação do](https://qiskit.github.io/samplomatic/) Samplomatic.\n",
        "\n",
        "Observe que o fluxo de trabalho do SLC exige o uso do `inject_noise_strategy=\"individual_modification\"` mais adiante no processo, pois isso nos permite identificar de forma exclusiva as `InjectNoise` anotações das `BoxOp` instruções no circuito. (As caixas sem nenhuma `InjectNoise` anotação não podem ser identificadas dessa forma.)\n",
        "\n",
        "<Admonition type=\"caution\">\n",
        "  Atualmente, os cálculos de ligação do SLC partem do pressuposto de que as `InjectNoise` anotações são colocadas com `site=\"after\"` (consulte [qiskit-addon-slc#33](https://github.com/Qiskit/qiskit-addon-slc/issues/33) ). Como o valor padrão dessa configuração mudou nas versões mais recentes de `samplomatic` (consulte [samplomatic#372](https://github.com/Qiskit/samplomatic/pull/372) ), definimos `inject_noise_site=\"after\"` explicitamente a seguir.\n",
        "</Admonition>\n",
        "\n",
        "A `find_unique_box_instructions` função percorre o circuito encapsulado fornecido e identifica aqueles que possuem camadas ou medições exclusivas de dois qubits ( 2Q ), com o objetivo de aprendizado de ruído e injeção de ruído.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 13,
      "id": "440f818c-9417-4855-b31b-db47b8595618",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Box circuit with Twirl and InjectNoise annotations\n",
        "boxes_pm = generate_boxing_pass_manager(\n",
        "    twirling_strategy=\"active\",\n",
        "    inject_noise_strategy=\"individual_modification\",\n",
        "    inject_noise_site=\"after\",\n",
        "    inject_noise_targets=\"gates\",\n",
        "    measure_annotations=\"all\",\n",
        ")\n",
        "\n",
        "\n",
        "boxed_circuit = boxes_pm.run(isa_circuit)\n",
        "\n",
        "# Find the unique instructions (layers) from boxed circuit\n",
        "unique_2q_instructions = find_unique_box_instructions(\n",
        "    boxed_circuit, normalize_annotations=None, undress_boxes=True\n",
        ")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 14,
      "id": "d662b076-005e-46c7-826d-4c29a4937404",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/d662b076-005e-46c7-826d-4c29a4937404-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 14,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "boxed_circuit.draw(\n",
        "    \"mpl\",\n",
        "    fold=-1,\n",
        "    scale=0.3,\n",
        "    idle_wires=False,\n",
        "    wire_order=wire_order,\n",
        "    measure_arrows=False,\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "aa8794ef-7906-4ee3-a9b0-e0399b3e9129",
      "metadata": {},
      "source": [
        "<span id=\"prepare-canonical-bases-measurements\" />\n",
        "\n",
        "### Preparar medições de bases canônicas\n",
        "\n",
        "Observe que devemos ter um cuidado especial para acompanhar a ordem dos qubits. A seguir, apresentamos o conceito de `canonical_qubits` como forma de atualizar adequadamente a ordem dos qubits ao fornecê-la ao Executor, tendo em vista a maneira como a ordem dos qubits é capturada durante o encapsulamento de circuitos e a identificação de instruções únicas. Consulte a documentação [sobre a convenção de ordenação de qubits](https://qiskit.github.io/samplomatic/guides/samplex_io.html#qubit-ordering-convention) para obter mais detalhes.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 15,
      "id": "e242702b-0274-4ebf-837d-4319a6772339",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Determine the canonical qubits order\n",
        "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",
        "\n",
        "# map canonical qubit to physical (isa) qubit\n",
        "c_2_p = {c: p for c, p in enumerate(canonical_qubits)}\n",
        "# map physical (isa) qubit to virtual qubit (index in original circuit)\n",
        "p_2_v = {p: v for v, p in enumerate(layout)}\n",
        "# compute map between virtual and canonical qubit indices.\n",
        "c_2_v = {c: p_2_v[p] for c, p in c_2_p.items()}\n",
        "\n",
        "assert len(c_2_v) == num_qubits\n",
        "\n",
        "bases_canon = [\n",
        "    np.array([base_i[c_2_v[c]] for c in range(num_qubits)], dtype=np.uint8)\n",
        "    for base_i in bases_virt\n",
        "]"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "61d2762c",
      "metadata": {},
      "source": [
        "<span id=\"workflow-for-lightcone-shading-noise-learning-and-anti-noise-injection\" />\n",
        "\n",
        "### Fluxo de trabalho para sombreamento por cone de luz, aprendizado de ruído e injeção antirruído\n",
        "\n",
        "<Admonition type=\"note\">\n",
        "  Neste tutorial, executamos os cálculos de limite SLC antes que o aprendizado do ruído seja concluído; assim, o circuito a ser mitigado é executado o mais próximo possível, em termos de tempo, do modelo de ruído aprendido. Em princípio, esse fluxo de trabalho pode ser ainda mais paralelizado: uma tarefa de aprendizado de ruído pode ser executada enquanto, paralelamente, os limites de ruído são estimados. Para um circuito quântico arbitrário, o cálculo limitado pelo ruído pode variar com uma dependência exponencial fraca; assim, a paralelização desse cálculo (por exemplo, em vários núcleos de CPU) resulta em limites mais restritos para um determinado orçamento de tempo de computação, e as execuções na QPU e os cálculos limitados podem, por sua vez, ser paralelizados para se obter o fluxo de trabalho mais eficiente.\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "4a2ec5ed-c339-4273-b407-6bb46b9ede19",
      "metadata": {},
      "source": [
        "<span id=\"predict-to-be-learned-noise-model-paulis\" />\n",
        "\n",
        "#### Prever os Paulis do modelo de ruído a ser aprendido\n",
        "\n",
        "Antes de executar qualquer aprendizado com ruído no hardware, primeiro enumeramos quais termos de erro de Pauli nosso modelo de ruído conterá. Lembre-se de que modelamos o ruído de cada camada específica como um modelo esparso de Pauli-Lindblad, com uma taxa de erro para cada Pauli de peso um ( $X$, $Y$ ou $Z$ ) em cada qubit ativo, e uma para cada Pauli de peso dois em cada par conectado de qubits ativos. A `generate_noise_model_paulis` função percorre cada camada em caixa única do circuito fornecido e gera exatamente essa lista de termos de Pauli, levando em consideração a conectividade dos qubits do circuito.\n",
        "\n",
        "Em outras palavras, essa etapa prevê a *estrutura* do modelo de ruído que `NoiseLearnerV3` , posteriormente, será preenchida com as taxas aprendidas. Conhecer essa estrutura com antecedência é o que nos permite calcular um limite superior e um limite inferior para cada erro potencial antes mesmo de a aprendizagem com ruído ser executada.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 16,
      "id": "e6783d37-c91f-4c6e-af4e-fb8f37d79e6b",
      "metadata": {},
      "outputs": [],
      "source": [
        "noise_model_paulis = generate_noise_model_paulis(\n",
        "    unique_2q_instructions, backend.coupling_map, boxed_circuit\n",
        ")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 17,
      "id": "f9fd1389-8c61-4585-85a6-ad17ab7a2f1c",
      "metadata": {},
      "outputs": [],
      "source": [
        "noise_model_rates = {ref: None for ref in noise_model_paulis}"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "942dd468-b2c5-49db-ada5-bcc45024df83",
      "metadata": {},
      "source": [
        "<span id=\"a-compute-forward-bounds\" />\n",
        "\n",
        "##### a. Calcular os limites para a frente\n",
        "\n",
        "A `compute_forward_bounds` função avalia as relações de comutação entre as portas em cada camada e os termos de Pauli gerados anteriormente, considerando como os erros de propagação para frente afetam o observável desejado $A$. Para as portas que comutam com os termos de Pauli, nada é feito. No caso das portas de Clifford, elas são deslocadas para o início do circuito. Para portas que não sejam do tipo Clifford, calculamos uma aproximação de sua influência sobre as observáveis-alvo, a fim de que estas sejam posteriormente priorizadas para o cancelamento de ruído (após a fusão de todos os limites). Esse limite é obtido aplicando-se, em primeiro lugar, a norma de L2 (ou seja, a raiz quadrada da soma dos quadrados dos coeficientes dos termos de Pauli relevantes). Quando há muitos termos de qubits envolvidos, recorremos a um limite menos restritivo que utiliza a desigualdade triangular.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "slc-bparam-00-4000-8000-000000000030",
      "metadata": {},
      "source": [
        "<span id=\"set-the-bound-computation-parameters\" />\n",
        "\n",
        "#### Definir os parâmetros de cálculo de intervalos\n",
        "\n",
        "As configurações a seguir controlam os cálculos clássicos de limites:\n",
        "\n",
        "* `slc_atol`: a tolerância absoluta dos cálculos. Os termos do operador com coeficientes abaixo desse limite são considerados insignificantes e descartados.\n",
        "* `slc_eigval_max_qubits`: o número máximo de qubits para o qual é avaliado o limite mais restrito, baseado em valores próprios ( L2-norm ). Os operadores suportados por um número de qubits superior a esse voltam a seguir o limite mais flexível da desigualdade triangular descrito acima.\n",
        "* `slc_evolution_max_terms`: o número máximo de termos de Pauli mantidos durante a propagação dos operadores pelo circuito. O truncamento dos termos menores mantém o cálculo viável, em troca de limites ligeiramente menos precisos.\n",
        "* `slc_num_processes`: o número de processos da CPU entre os quais o cálculo limitante é paralelizado.\n",
        "* `slc_timeout`: o tempo previsto (em segundos) para o cálculo do limite. A qualquer camada que não seja alcançada dentro desse limite é atribuído o valor trivial de 2.\n",
        "\n",
        "<Admonition type=\"note\">\n",
        "  Neste tutorial, o tempo limite é atingido *propositalmente* : mantemos esse tempo `slc_timeout` deliberadamente curto para que o notebook seja executado rapidamente e para demonstrar que atingir o tempo limite é seguro. Os tons calculados são limites superiores em cada etapa; portanto, atingir o tempo limite apenas torna os limites mais amplos (triviais) para as camadas que não foram alcançadas; isso nunca subestima o efeito de um erro. Isso aparece nas visualizações abaixo como as camadas mais à esquerda sendo preenchidas inteiramente pelo limite trivial. Aumentar o tempo limite (ou o número de processos) estreita os limites e reduz ainda mais a sobrecarga da amostragem.\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 18,
      "id": "79fe4a21-b80d-496b-ba20-f16d9b11834d",
      "metadata": {},
      "outputs": [],
      "source": [
        "slc_atol = 1e-8\n",
        "slc_eigval_max_qubits = 18\n",
        "slc_evolution_max_terms = 1000\n",
        "slc_num_processes = 8\n",
        "slc_timeout = 60"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 19,
      "id": "705fcb9f-c753-45a6-97dd-9ff8ecb07018",
      "metadata": {
        "scrolled": true
      },
      "outputs": [
        {
          "name": "stderr",
          "output_type": "stream",
          "text": [
            "Bounds computation timed out.\n"
          ]
        }
      ],
      "source": [
        "forward_bounds = compute_forward_bounds(\n",
        "    boxed_circuit,\n",
        "    noise_model_paulis,\n",
        "    isa_observable,\n",
        "    evolution_max_terms=slc_evolution_max_terms,\n",
        "    eigval_max_qubits=slc_eigval_max_qubits,\n",
        "    atol=slc_atol,\n",
        "    num_processes=slc_num_processes,\n",
        "    timeout=slc_timeout,\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "aaf7c188-c85b-42cf-a096-0a5dc72a68e3",
      "metadata": {},
      "source": [
        "<span id=\"visualize-the-slc-for-manual-inspection\" />\n",
        "\n",
        "#### Visualize o SLC para inspeção manual\n",
        "\n",
        "É possível interpretar o comportamento dos limites sombreados examinando como as medições e os termos de Pauli interagem com os erros locais. Esses padrões são característicos desse problema de evolução temporal do hamiltoniano de Ising com “kick” e também aparecem no artigo [“Lightcone Shading for Classically Accelerated Quantum Error Mitigation”](https://arxiv.org/abs/2409.04401), apresentando várias características reveladoras:\n",
        "\n",
        "* Podemos distinguir claramente os dois cones decorrentes dos dois Paulis não identitários no observável.\n",
        "* Podemos observar que a medição X no qubit 6 comuta com o erro X na camada mais à direita.\n",
        "* Podemos observar que o Z de Pauli no qubit 13 comuta com o erro Z na camada mais à direita.\n",
        "* Quando atingirmos o tempo limite especificado acima, as camadas restantes à esquerda serão preenchidas inteiramente com limites triviais de dois.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 20,
      "id": "285edaef-8e79-41eb-a563-4675ea75d5b1",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/285edaef-8e79-41eb-a563-4675ea75d5b1-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/285edaef-8e79-41eb-a563-4675ea75d5b1-1.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/285edaef-8e79-41eb-a563-4675ea75d5b1-2.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "for p in \"XYZ\":\n",
        "    display(\n",
        "        draw_shaded_lightcone(\n",
        "            boxed_circuit,\n",
        "            forward_bounds,\n",
        "            noise_model_paulis,\n",
        "            pauli_filter=p,\n",
        "            scale=0.15,\n",
        "            fold=-1,\n",
        "            idle_wires=False,\n",
        "            wire_order=wire_order,\n",
        "            measure_arrows=False,\n",
        "        )\n",
        "    )"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "1aee6b29-8077-4019-9e37-3e80292aadf6",
      "metadata": {},
      "source": [
        "<span id=\"b-tighten-forward-bounds-with-quantum-speed-limits\" />\n",
        "\n",
        "#### b. Aperfeiçoar os limites superiores com os limites de velocidade quântica\n",
        "\n",
        "Em seguida, restringimos os limites utilizando a `tighten_with_speed_limit` função. A extensão geométrica do cone de luz do observável já está totalmente considerada nos limites para a frente calculados acima. O que essa função acrescenta é uma etapa de autoconsistência que aproveita a velocidade limitada com que a informação pode fluir pelo circuito — o cone de luz cresce, no máximo, um qubit por camada de portas de dois qubits — para estreitar os limites calculados anteriormente em relação uns aos outros. A informação flui nos dois sentidos:\n",
        "\n",
        "1. Dos limites do comutador aos limites do observável retropropagado: se o observável comuta com um erro de “ $Z$ ” em um qubit, então ele não pode ter uma componente significativa de “ $X$ ” ou “ $Y$ ” nesse qubit.\n",
        "2. Dos limites do observável retropropagado aos limites do comutador: se o observável tiver componentes $X$ e $Y$ insignificantes em um qubit, então ele deve quase comutar com $Z$ nesse qubit.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 21,
      "id": "e060d547-79da-4f54-bf5e-677f70306040",
      "metadata": {
        "scrolled": true
      },
      "outputs": [],
      "source": [
        "forward_bounds_tighter = tighten_with_speed_limit(\n",
        "    forward_bounds, boxed_circuit, noise_model_paulis, isa_observable\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "85f3c9c2-d60c-4044-a496-dae88c3dd749",
      "metadata": {},
      "source": [
        "<span id=\"visualize-the-slc-for-manual-inspection\" />\n",
        "\n",
        "#### Visualize o SLC para inspeção manual\n",
        "\n",
        "Em princípio, esse ajuste proporciona uma transição mais suave entre os limites calculados e os limites triviais atribuídos às camadas após o tempo limite. Neste exemplo, o efeito é quase imperceptível, pois os cones de luz já se espalharam até as bordas do circuito quando o tempo limite é atingido.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 22,
      "id": "6ba4b397-e903-4b85-9482-31a96c689dc0",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/6ba4b397-e903-4b85-9482-31a96c689dc0-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/6ba4b397-e903-4b85-9482-31a96c689dc0-1.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/6ba4b397-e903-4b85-9482-31a96c689dc0-2.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "for p in \"XYZ\":\n",
        "    display(\n",
        "        draw_shaded_lightcone(\n",
        "            boxed_circuit,\n",
        "            forward_bounds_tighter,\n",
        "            noise_model_paulis,\n",
        "            pauli_filter=p,\n",
        "            scale=0.15,\n",
        "            fold=-1,\n",
        "            idle_wires=False,\n",
        "            wire_order=wire_order,\n",
        "            measure_arrows=False,\n",
        "        )\n",
        "    )"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ce0f9cf4-565a-4f18-baa5-bec5c7b354a2",
      "metadata": {},
      "source": [
        "<span id=\"c-compute-backward-bounds\" />\n",
        "\n",
        "#### c. Calcular os limites posteriores\n",
        "\n",
        "Esta parte da previsão de ruído avalia como um erro em uma camada específica pode afetar o estado de entrada $\\rho$. A `compute_backward_bounds` função primeiro inverte o circuito e remove as portas de medição e, em seguida, prossegue com uma análise semelhante à realizada para os cálculos no sentido direto.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 23,
      "id": "ff895191-feda-4958-a58a-3d1fb973cb68",
      "metadata": {
        "scrolled": true
      },
      "outputs": [],
      "source": [
        "backward_bounds = compute_backward_bounds(\n",
        "    boxed_circuit,\n",
        "    noise_model_paulis,\n",
        "    evolution_max_terms=slc_evolution_max_terms,\n",
        "    num_processes=slc_num_processes,\n",
        "    timeout=slc_timeout,\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c707d352-37fe-4c53-82a3-a1207b7a7bfc",
      "metadata": {},
      "source": [
        "<span id=\"visualize-the-slc-for-manual-inspection\" />\n",
        "\n",
        "#### Visualize o SLC para inspeção manual\n",
        "\n",
        "Ao calcular os limites posteriores, podemos observar como a estrutura do estado inicial determina o comportamento inicial da propagação de erros:\n",
        "\n",
        "* Podemos ver claramente como os erros Z inicialmente comutam com o estado inicial |0⟩.\n",
        "* Somente no qubit 6, onde inicializamos o estado próprio +1 da base X, um erro Z não comuta, enquanto um erro X sim comuta.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 24,
      "id": "40d0eba9-5e0b-4b1d-8fb2-c239e262062f",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/40d0eba9-5e0b-4b1d-8fb2-c239e262062f-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/40d0eba9-5e0b-4b1d-8fb2-c239e262062f-1.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/40d0eba9-5e0b-4b1d-8fb2-c239e262062f-2.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "for p in \"XYZ\":\n",
        "    display(\n",
        "        draw_shaded_lightcone(\n",
        "            boxed_circuit,\n",
        "            backward_bounds,\n",
        "            noise_model_paulis,\n",
        "            pauli_filter=p,\n",
        "            scale=0.15,\n",
        "            fold=-1,\n",
        "            idle_wires=False,\n",
        "            wire_order=wire_order,\n",
        "            measure_arrows=False,\n",
        "        )\n",
        "    )"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c8960e51-733e-4458-b77c-287ba3abdb8d",
      "metadata": {},
      "source": [
        "<span id=\"preview-merged-bounds-without-learned-noise-rates\" />\n",
        "\n",
        "#### Visualizar limites mesclados sem taxas de ruído aprendidas\n",
        "\n",
        "A `merged_bounds` função determina o ponto no circuito em que a mudança dos limites de retrocesso para os limites de avanço minimiza o viés total estimado na variável observável desejada. Esse viés é calculado como a soma das contribuições do sentido reverso para todos os pontos de ruído anteriores a esse ponto, mais as contribuições do sentido direto para todos os pontos de ruído posteriores a ele. Atualmente, isso é feito de maneira uniforme para todos os qubits.\n",
        "\n",
        "<Admonition type=\"note\" title=\"Nota importante\">\n",
        "  O ponto ideal para mudar do limite traseiro para o limite dianteiro depende das taxas de ruído aprendidas. Nesta fase, ainda não definimos nenhuma taxa: `noise_model_rates` foi inicializado em `None` para cada camada, o que `merge_bounds` interpreta como taxas de ruído uniformes. Os limites combinados mostrados aqui são, portanto, apenas uma prévia; nós os recalculamos com as taxas reais aprendidas na Etapa 3.\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 25,
      "id": "b5cd8752-3bb5-44ee-97a8-6edaf5946e9d",
      "metadata": {},
      "outputs": [
        {
          "name": "stderr",
          "output_type": "stream",
          "text": [
            "Missing noise rates. Partitioning backward/forward commutator bounds by assuming uniform error rates.\n",
            "Optimal spacetime partitioning not implemented!Just partitioning list of noisy boxes.\n"
          ]
        }
      ],
      "source": [
        "merged_bounds = merge_bounds(\n",
        "    boxed_circuit,\n",
        "    forward_bounds_tighter,\n",
        "    backward_bounds,\n",
        "    noise_model_rates,\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ace27801-b949-44e0-801d-e74ccb2f877a",
      "metadata": {},
      "source": [
        "<span id=\"visualize-the-slc-for-manual-inspection\" />\n",
        "\n",
        "### Visualize o SLC para inspeção manual\n",
        "\n",
        "Após a fusão dos limites traseiro e dianteiro restrito, o comportamento dos SLCs combinados fica claro:\n",
        "\n",
        "* A função acima nos indica que é escolhida uma partição na qual ocorre a transição de limites traseiros para limites dianteiros mais restritos.\n",
        "* Podemos observar abaixo que os SLCs agora contêm limites superiores parciais para trás e limites inferiores parciais mais restritos para a frente.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 26,
      "id": "eb2948bc-fa27-4108-b03c-0291d3b2f7c8",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/eb2948bc-fa27-4108-b03c-0291d3b2f7c8-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/eb2948bc-fa27-4108-b03c-0291d3b2f7c8-1.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/eb2948bc-fa27-4108-b03c-0291d3b2f7c8-2.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "for p in \"XYZ\":\n",
        "    display(\n",
        "        draw_shaded_lightcone(\n",
        "            boxed_circuit,\n",
        "            merged_bounds,\n",
        "            noise_model_paulis,\n",
        "            pauli_filter=p,\n",
        "            scale=0.15,\n",
        "            fold=-1,\n",
        "            idle_wires=False,\n",
        "            wire_order=wire_order,\n",
        "            measure_arrows=False,\n",
        "        )\n",
        "    )"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "slc-step3-000-4000-8000-000000000050",
      "metadata": {},
      "source": [
        "<span id=\"step-3-execute\" />\n",
        "\n",
        "### Etapa 3: Executar\n",
        "\n",
        "Nesta seção, iniciamos a parte do fluxo de trabalho que utiliza um dispositivo quântico real. Para esse método de mitigação de erros baseado em aprendizado, há duas etapas:\n",
        "\n",
        "1. Aprenda o som usando `NoiseLearnerV3`.\n",
        "2. Executar um circuito de mitigação de erros utilizando as `samplomatic` estruturas e `Executor` .\n",
        "\n",
        "Com os erros limitados do nosso circuito quântico, determinamos as taxas de ruído associadas para priorizar nosso orçamento de erros, calcular a sobrecarga de amostragem e executar o código em uma QPU.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "slc-nlmd-0000-4000-8000-000000000051",
      "metadata": {},
      "source": [
        "<span id=\"a-learn-the-noise-rates\" />\n",
        "\n",
        "### a. Conheça os índices de ruído\n",
        "\n",
        "O alvo de aprendizado de ruído caracteriza os processos de ruído que afetam os portões em um ou mais circuitos de interesse, com base no [modelo de ruído](https://arxiv.org/abs/2201.09866) esparso de Pauli-Lindblad. O `run()` método inicia um trabalho de aprendizado com ruído para as camadas exclusivas de dois qubits fornecidas, utilizando as opções especificadas na configuração do noise-learner. Essas opções controlam a estratégia de “Pauli-twirling”, o número de randomizações e tentativas, as profundidades de aprendizado e a pós-seleção.\n",
        "\n",
        "Também escolhemos deliberadamente o nível de aprofundamento do aprendizado. Uma constatação prática para a mitigação baseada em aprendizado com `samplomatic` é que é altamente benéfico que a profundidade máxima do aprendizado corresponda à profundidade do circuito que se deseja mitigar. Como os NLv3`layer_pair_depths` são medidos em *pares* de camadas (uma camada mais sua inversa), definimos o valor mais profundo como metade da profundidade da camada de dois qubits do circuito.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 27,
      "id": "slc-psflag-00-4000-8000-000000000052",
      "metadata": {},
      "outputs": [],
      "source": [
        "post_selection_enabled = True"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 28,
      "id": "slc-nlopt-00-4000-8000-000000000053",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Match the deepest noise-learning depth to the depth of the circuit being\n",
        "# mitigated. NLv3 ``layer_pair_depths`` are measured in layer pairs (a layer\n",
        "# plus its inverse), so the deepest value is half the circuit's two-qubit-layer\n",
        "# depth. Learning to this depth markedly improves the quality of the mitigation.\n",
        "#\n",
        "# We measure the two-qubit-layer depth on the pre-boxed ISA circuit: after\n",
        "# boxing, every two-qubit gate is hidden inside a full-width ``BoxOp``, so a\n",
        "# ``num_qubits == 2`` filter on ``boxed_circuit`` matches nothing (and\n",
        "# ``QuantumCircuit.depth`` does not recurse into boxes).\n",
        "depth_2q = isa_circuit.depth(lambda instr: instr.operation.num_qubits == 2)\n",
        "max_layer_pair_depth = depth_2q // 2  # dividing by 2 since we want pairs\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",
        "noise_learner_options = {\n",
        "    \"num_randomizations\": 64,\n",
        "    \"shots_per_randomization\": 128,\n",
        "    \"layer_pair_depths\": layer_pair_depths,\n",
        "    \"post_selection\": {\n",
        "        \"enable\": post_selection_enabled,\n",
        "        \"strategy\": \"edge\",\n",
        "        \"x_pulse_type\": \"rx\",\n",
        "    },\n",
        "    \"environment\": {\"job_tags\": [\"TUT_SLC\"]},\n",
        "}\n",
        "\n",
        "noise_learner = NoiseLearnerV3(backend, noise_learner_options)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 29,
      "id": "slc-nlrun-00-4000-8000-000000000054",
      "metadata": {},
      "outputs": [],
      "source": [
        "noise_learner_job = noise_learner.run(unique_2q_instructions)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 30,
      "id": "db624045-7abe-41f1-a60c-1ba141aaa6f6",
      "metadata": {},
      "outputs": [],
      "source": [
        "noise_learner_result = noise_learner_job.result()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 31,
      "id": "b08c37a6-5acb-41e9-a76c-abc0195d87f7",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Minimum fraction of shots kept for noise learning experiments: 0.71\n"
          ]
        }
      ],
      "source": [
        "if post_selection_enabled:\n",
        "    print(\n",
        "        \"Minimum fraction of shots kept for noise learning experiments: \",\n",
        "        end=\"\",\n",
        "    )\n",
        "    print(\n",
        "        f\"{min([min(d.values()) for d in [nlr.metadata['post_selection']['fraction_kept'] for nlr in noise_learner_result[:2]]]):.2f}\"\n",
        "    )"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 32,
      "id": "99144d7f-b891-4115-8cb3-ecdbedd10a73",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Get a dict mapping each InjectNoise.ref to its learned PauliLindbladMap\n",
        "refs_2_plm = noise_learner_result.to_dict(\n",
        "    unique_2q_instructions, require_refs=False\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "e6a790a3-bc11-46e2-8cb4-2e43257926c8",
      "metadata": {},
      "source": [
        "<span id=\"bi-update-merged-bounds-with-actual-learned-noise-rates\" />\n",
        "\n",
        "### b.i. Atualizar os limites combinados com as taxas de ruído reais aprendidas\n",
        "\n",
        "Agora que o modelo específico de ruído foi aprendido, podemos aplicar as taxas de ruído aprendidas aos limites de ruído previstos e obter uma determinação final de quais limites têm maior impacto na minimização do viés.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 33,
      "id": "bba8fca6-5339-4d4d-be01-45ca8bf94a5a",
      "metadata": {},
      "outputs": [
        {
          "name": "stderr",
          "output_type": "stream",
          "text": [
            "Optimal spacetime partitioning not implemented!Just partitioning list of noisy boxes.\n"
          ]
        }
      ],
      "source": [
        "merged_bounds = merge_bounds(\n",
        "    boxed_circuit,\n",
        "    forward_bounds_tighter,\n",
        "    backward_bounds,\n",
        "    refs_2_plm,\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "649b9640-e89f-4021-9f7d-f1e136c4eb6d",
      "metadata": {},
      "source": [
        "<span id=\"bii-compute-the-local_scales-for-the-hardware-execution\" />\n",
        "\n",
        "#### b.ii. Calcule o `local_scales` para a execução em hardware\n",
        "\n",
        "`compute_local_scales` analisa cada possível erro de ruído no circuito e estima em que medida esse erro poderia distorcer a medição final, bem como quanto custaria corrigi-lo. Em seguida, ele classifica os erros de acordo com a importância de mitigá-los e seleciona o subconjunto que reduz o viés ao máximo, mantendo-se dentro do orçamento permitido para o custo de amostragem (ou atingindo a precisão desejada). O resultado é um conjunto de fatores de escala que indicam quais erros serão ativamente mitigados e quais não serão mitigados (`local_scales`), juntamente com a sobrecarga total prevista do custo de amostragem (`sampling_costs`) e o viés remanescente (`residual_bias_bound`).\n",
        "\n",
        "A capacidade de controlar a polarização residual desejada é uma característica fundamental da implementação do PEC no SLC. Enquanto na [implementação](https://arxiv.org/abs/2201.09866) original a sobrecarga de amostragem sempre visava um viés nulo, podemos ajustar a sobrecarga de amostragem necessária, em troca de um viés residual esperado. Isso ajuda o usuário a permanecer dentro de um orçamento fixo de amostragem, o que pode ser particularmente útil na fase inicial de prototipagem de um fluxo de trabalho.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 34,
      "id": "8914e00e-ba21-41dc-9e64-b942bb23fde1",
      "metadata": {},
      "outputs": [],
      "source": [
        "id_map = map_modifier_ref_to_ref(boxed_circuit)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 35,
      "id": "4c971d31-f2b4-4d17-8a83-4f6d14f81f6f",
      "metadata": {
        "scrolled": true
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Full PEC gamma=12.750876317157042, sampling cost (gamma^2) = 162.58484685543633\n"
          ]
        }
      ],
      "source": [
        "summed_rates = 0.0\n",
        "for box_id, noise_id in id_map.items():\n",
        "    learned_plm = refs_2_plm[noise_id]\n",
        "    summed_rates += np.sum(learned_plm.rates)\n",
        "    # print(f\"{box_id}:\\tgamma = {np.exp(2 * summed_rates):1.6e}\\tsampling cost = {np.exp(4 * summed_rates):1.6e}\")\n",
        "total_gamma = np.exp(2 * summed_rates)\n",
        "print(\n",
        "    f\"Full PEC gamma={total_gamma}, sampling cost (gamma^2) = {total_gamma**2}\"\n",
        ")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 36,
      "id": "fb6f2aa0-8a35-420c-aae3-6fc5e9aa8747",
      "metadata": {},
      "outputs": [],
      "source": [
        "biases = []\n",
        "costs = []\n",
        "for bias in [0.0] + np.arange(0.001, 0.102, 0.01).tolist():\n",
        "    _, cost_, bias_ = compute_local_scales(\n",
        "        boxed_circuit,\n",
        "        merged_bounds,\n",
        "        refs_2_plm,\n",
        "        sampling_cost_budget=np.inf,\n",
        "        bias_tolerance=bias,\n",
        "    )\n",
        "    biases.append(bias_)\n",
        "    costs.append(cost_)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "slc-trade-00-4000-8000-000000000065",
      "metadata": {},
      "source": [
        "<span id=\"trade-off-sampling-overhead-against-residual-bias\" />\n",
        "\n",
        "#### Equilibrar a sobrecarga da amostragem com o viés residual\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 37,
      "id": "slc-ovplot-0-4000-8000-000000000066",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "Text(0.5, 0.98, 'PEC sampling overhead reduction due to SLC')"
            ]
          },
          "execution_count": 37,
          "metadata": {},
          "output_type": "execute_result"
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/slc-ovplot-0-4000-8000-000000000066-1.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "xticks = np.arange(0, 11)\n",
        "\n",
        "fig, ax = plt.subplots()\n",
        "ax.scatter(\n",
        "    [0], [total_gamma**2], marker=\"D\", c=\"tab:orange\", label=\"full PEC\"\n",
        ")\n",
        "ax.plot(\n",
        "    100 * np.array(biases),\n",
        "    np.array(costs),\n",
        "    \"o-\",\n",
        "    c=\"tab:blue\",\n",
        "    label=\"PEC+SLC\",\n",
        ")\n",
        "ax.set_yscale(\"log\")\n",
        "ax.set_xticks(xticks, [f\"{x:.1f}\" for x in xticks])\n",
        "\n",
        "ax.set_xlabel(\"Remaining bias [%]\")\n",
        "ax.set_ylabel(r\"Sampling overhead, $\\gamma^2$\")\n",
        "ax.grid()\n",
        "ax.legend()\n",
        "fig.suptitle(\"PEC sampling overhead reduction due to SLC\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 38,
      "id": "1df482f9-c12c-4ea7-a71e-7ffc0837b5a6",
      "metadata": {},
      "outputs": [],
      "source": [
        "chosen_bias_thres = 0.1"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 39,
      "id": "slc-lscale-0-4000-8000-000000000068",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "PEC+SLC sampling cost (gamma^2) = 48.13646509622307 w/ remaining bias = 10.0%\n"
          ]
        }
      ],
      "source": [
        "local_scales, sampling_cost, residual_bias_bound = compute_local_scales(\n",
        "    boxed_circuit,\n",
        "    merged_bounds,\n",
        "    refs_2_plm,\n",
        "    sampling_cost_budget=np.inf,\n",
        "    bias_tolerance=chosen_bias_thres,\n",
        ")\n",
        "print(\n",
        "    f\"PEC+SLC sampling cost (gamma^2) = {sampling_cost} \"\n",
        "    f\"w/ remaining bias = {100 * residual_bias_bound:.1f}%\"\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "8b03c0ed-d026-4b29-bc09-68c4d0c94889",
      "metadata": {},
      "source": [
        "<span id=\"c-execute-the-circuit-of-interest-with-antinoise\" />\n",
        "\n",
        "### c. Execute o circuito em questão com o antinoise\n",
        "\n",
        "<span id=\"ci-prepare-template-circuit-by-using-samplex\" />\n",
        "\n",
        "#### c.i. Prepare o circuito modelo utilizando `samplex`\n",
        "\n",
        "O `samplex` é um resultado do `build` método do Samplomatic, que codifica todas as informações necessárias para gerar parâmetros aleatórios para `template_circuit`. Esses dados são então utilizados para configurar os `QuantumProgram` objetos, que, por sua vez, são executados em uma QPU com a `Executor` primitiva. Cada um deles `QuantumProgram` pode conter vários itens, que você pode considerar como um par de `template` e `samplex`.\n",
        "\n",
        "Consulte o tutorial [“Hello samplomatic”](https://github.com/qiskit-community/qdc-challenges-2025/blob/main/day3_tutorials/Track_A/hello_samplomatic/Samplomatic%20-%20Hello%20World.ipynb) para obter mais detalhes.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 40,
      "id": "a68109cc-3b38-40e0-b145-edaa06b9934d",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Build template circuit and samplex for later use with the \"Executor\"\n",
        "template_circuit, samplex = samplomatic.build(boxed_circuit)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 41,
      "id": "0de4922b-b958-48d7-b63a-819edfb354bd",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Set up postselection if it's been enabled\n",
        "if post_selection_enabled:\n",
        "    # Set up post selection PM (to add PS instructions)\n",
        "    post_selection_pm = PassManager(\n",
        "        [\n",
        "            AddSpectatorMeasures(backend.coupling_map),\n",
        "            AddPostSelectionMeasures(x_pulse_type=\"rx\"),\n",
        "        ]\n",
        "    )\n",
        "    final_template_circuit = post_selection_pm.run(template_circuit)\n",
        "else:\n",
        "    final_template_circuit = template_circuit"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "4f00c61a-4c4d-40d9-9a41-11ea8e9a9337",
      "metadata": {},
      "source": [
        "<span id=\"cii-set-up-the-quantumprogram\" />\n",
        "\n",
        "#### c.ii. Configure o `QuantumProgram`\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 42,
      "id": "a63d8881-2bbb-4c99-afd3-43f03ee04e5c",
      "metadata": {},
      "outputs": [],
      "source": [
        "num_randomizations = 4096\n",
        "shots_per_randomization = 64\n",
        "chunk_size = 256"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 43,
      "id": "038a04c9-20ba-432c-bfec-cf69e67ac82f",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Set up QuantumProgram\n",
        "program = QuantumProgram(shots=shots_per_randomization, noise_maps=refs_2_plm)\n",
        "\n",
        "# no EM\n",
        "\n",
        "# Collect up a dict of the other arguments that need to be bound to samplex_inputs\n",
        "samplex_inputs = {\n",
        "    f\"noise_scales.{ref}\": float(0) for ref in local_scales.keys()\n",
        "}\n",
        "samplex_inputs |= {\"basis_changes\": {\"basis0\": bases_canon[0]}}\n",
        "\n",
        "# Convert samplex_inputs into a dict to pass to QuantumProgram\n",
        "samplex_arguments = (\n",
        "    samplex.inputs().bind(**samplex_inputs).make_broadcastable()\n",
        ")\n",
        "\n",
        "program.append_samplex_item(\n",
        "    circuit=final_template_circuit,\n",
        "    samplex=samplex,\n",
        "    samplex_arguments=samplex_arguments,\n",
        "    shape=(num_randomizations,),\n",
        "    chunk_size=chunk_size,\n",
        ")\n",
        "\n",
        "# plain PEC\n",
        "\n",
        "# Collect a dict of the other arguments that need to be bound to samplex_inputs\n",
        "samplex_inputs = {\n",
        "    f\"noise_scales.{ref}\": float(-1) for ref in local_scales.keys()\n",
        "}\n",
        "samplex_inputs |= {\"basis_changes\": {\"basis0\": bases_canon[0]}}\n",
        "\n",
        "# Convert samplex_inputs into a dict to pass to QuantumProgram\n",
        "samplex_arguments = (\n",
        "    samplex.inputs().bind(**samplex_inputs).make_broadcastable()\n",
        ")\n",
        "\n",
        "program.append_samplex_item(\n",
        "    circuit=final_template_circuit,\n",
        "    samplex=samplex,\n",
        "    samplex_arguments=samplex_arguments,\n",
        "    shape=(num_randomizations,),\n",
        "    chunk_size=chunk_size,\n",
        ")\n",
        "\n",
        "# PEC+SLC\n",
        "\n",
        "# Collect a dict of the other arguments that need to be bound to samplex_inputs\n",
        "samplex_inputs = {\n",
        "    f\"noise_scales.{ref}\": float(-1) for ref in local_scales.keys()\n",
        "}\n",
        "samplex_inputs |= {\"basis_changes\": {\"basis0\": bases_canon[0]}}\n",
        "samplex_inputs |= {\"local_scales\": local_scales}\n",
        "\n",
        "# Convert samplex_inputs into a dict to pass to QuantumProgram\n",
        "samplex_arguments = (\n",
        "    samplex.inputs().bind(**samplex_inputs).make_broadcastable()\n",
        ")\n",
        "\n",
        "program.append_samplex_item(\n",
        "    circuit=final_template_circuit,\n",
        "    samplex=samplex,\n",
        "    samplex_arguments=samplex_arguments,\n",
        "    shape=(num_randomizations,),\n",
        "    chunk_size=chunk_size,\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "3100fff6-9fe9-49ad-9662-549cea099e9b",
      "metadata": {},
      "source": [
        "<span id=\"ciii-execute-program-with-the-executor-primitive\" />\n",
        "\n",
        "#### c.iii. Executar o programa com a `Executor` primitiva\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 44,
      "id": "2a1c0fe1-8761-462b-a1d9-b951d5224164",
      "metadata": {},
      "outputs": [],
      "source": [
        "executor = Executor(backend)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 45,
      "id": "slc-exec-000-4000-8000-000000000078",
      "metadata": {},
      "outputs": [],
      "source": [
        "job_exec = executor.run(program)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 46,
      "id": "70dd5d10-9cc5-4341-9642-18ba3a0ee58b",
      "metadata": {},
      "outputs": [],
      "source": [
        "results_exec = job_exec.result()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "slc-step4-000-4000-8000-000000000080",
      "metadata": {},
      "source": [
        "<span id=\"step-4-post-process\" />\n",
        "\n",
        "### Etapa 4: Pós-processamento\n",
        "\n",
        "Ao calcularmos o valor esperado final de interesse utilizando `executor_expectation_values`, implementamos algumas técnicas de pós-processamento para ajudar a garantir que obtenhamos resultados da mais alta qualidade possível. Primeiro, aplicamos nosso [método de extinção de erros de leitura com rotação (TREX)](/docs/guides/error-mitigation-and-suppression-techniques#twirled-readout-error-extinction-trex), que leva em conta quaisquer erros que ocorram durante o processo de leitura. Em seguida, corrigimos os erros decorrentes do ruído não markoviano em nossos backends Heron utilizando um método de pós-seleção. Esse método mede os qubits ativos e os qubits espectadores, em seguida aplica uma rotação lenta a cada qubit e, por fim, realiza uma nova medição. Nos casos em que as duas medições não confirmam a inversão do qubit, conforme esperado, esses experimentos são descartados mediante a aplicação de um `mask` do `PostSelector`. No cálculo da máscara, é possível definir uma estratégia específica para filtrar com base em nós de qubit único ou arestas de observador vizinhas, o que pode influenciar tanto o número de tentativas filtradas quanto a qualidade dos resultados.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 47,
      "id": "147bcd34-f79e-4abb-821d-5ea9d8388c9f",
      "metadata": {},
      "outputs": [],
      "source": [
        "measurement_noise_map = noise_learner_result[2].to_pauli_lindblad_map()\n",
        "trex_scale_factors = trex_factors(measurement_noise_map, reverser_virt)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 48,
      "id": "981353e7-95a9-48bb-92c8-87b931e2b295",
      "metadata": {},
      "outputs": [],
      "source": [
        "post_selection_strategy = \"node\""
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 49,
      "id": "826afe02-3308-4d20-a9e1-ac0b2ba25eca",
      "metadata": {},
      "outputs": [],
      "source": [
        "def post_process_conv(datum, steps=16, gamma=None, ps=False, trex=False):\n",
        "    meas = datum[\"meas\"]\n",
        "    flips = datum[\"measurement_flips.meas\"]\n",
        "    signs = datum.get(\"pauli_signs\", None)\n",
        "\n",
        "    meas_basis_axis = None\n",
        "    avg_axis = 0\n",
        "\n",
        "    mask = None\n",
        "    if ps and post_selection_enabled:\n",
        "        # Post-select the results\n",
        "        post_selector = PostSelector.from_circuit(\n",
        "            circuit=final_template_circuit, coupling_map=backend.coupling_map\n",
        "        )\n",
        "\n",
        "        # Compute the ps mask for filtering results\n",
        "        mask = post_selector.compute_mask(\n",
        "            datum, strategy=post_selection_strategy\n",
        "        )\n",
        "\n",
        "        # Compute fraction of shots kept from post selection\n",
        "        total_num_shots = num_randomizations * shots_per_randomization\n",
        "        ps_ratio = np.sum(mask) * 100 / total_num_shots / len(bases_canon)\n",
        "        print(\n",
        "            f\"With {post_selection_strategy}-based post selection ({ps_ratio:.1f}% of shots kept):\"\n",
        "        )\n",
        "\n",
        "    results = []\n",
        "    for i in range(steps, num_randomizations + 1, steps):\n",
        "        # Compute mitigated expvals w/out post-selection\n",
        "        res = executor_expectation_values(\n",
        "            meas[:i],\n",
        "            reverser_virt,\n",
        "            meas_basis_axis,\n",
        "            avg_axis=avg_axis,\n",
        "            measurement_flips=flips[:i],\n",
        "            pauli_signs=signs[:i] if signs is not None else None,\n",
        "            postselect_mask=mask[:i] if mask is not None else None,\n",
        "            rescale_factors=trex_scale_factors if trex else None,\n",
        "            gamma_factor=gamma,\n",
        "        )\n",
        "        results.append(res[0])\n",
        "    return results"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 50,
      "id": "57b68d19-cda9-4688-b07e-0c7f9da5f252",
      "metadata": {},
      "outputs": [],
      "source": [
        "gamma_pec = gamma_from_noisy_boxes(refs_2_plm, id_map)\n",
        "gamma_slc = gamma_from_noisy_boxes(refs_2_plm, id_map, local_scales)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 51,
      "id": "b5244ee6-98b2-43da-8238-3d6fb0f4cdd0",
      "metadata": {},
      "outputs": [],
      "source": [
        "steps = 16"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 52,
      "id": "95bedd10-5ce9-4145-9b20-1459aa8e7db9",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "With node-based post selection (24.1% of shots kept):\n",
            "With node-based post selection (24.2% of shots kept):\n"
          ]
        }
      ],
      "source": [
        "results = {}\n",
        "\n",
        "for label, result_idx, gamma, use_ps, use_trex in [\n",
        "    (\"PEC\", 1, gamma_pec, True, True),\n",
        "    (\"PEC+SLC\", 2, gamma_slc, True, True),\n",
        "    (\"Unmitigated\", 0, None, False, False),\n",
        "]:\n",
        "    res = post_process_conv(\n",
        "        results_exec[result_idx],\n",
        "        steps=steps,\n",
        "        gamma=gamma,\n",
        "        ps=use_ps,\n",
        "        trex=use_trex,\n",
        "    )\n",
        "    results[label] = res"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "1d778145-9ead-4b9f-82a3-cec8aa8c8307",
      "metadata": {},
      "source": [
        "A partir da análise dos resultados experimentais, podemos comparar diretamente o comportamento das diferentes abordagens: PEC, PEC combinada com SLC e a linha de base sem medidas de mitigação. Alguns detalhes específicos a serem destacados:\n",
        "\n",
        "* O resultado não ajustado fica bem fora da faixa de viés de 10% (consulte 0.35 ) e não é afetado pelo número de randomizações.\n",
        "* Neste dispositivo, o PEC completo acarreta uma sobrecarga de amostragem de $\\gamma^2 \\approx 1.6\\times10^2$. Após grandes oscilações iniciais, a estimativa do PEC simples recupera o sinal e se estabiliza em torno de 1.1, ultrapassando ligeiramente o valor exato. Observe que a faixa de viés de 10% se aplica apenas ao estimador PEC+SLC: o PEC simples é, em princípio, insesgado; portanto, esse desvio residual não é coberto por uma garantia de viés, mas reflete, ao contrário, flutuações estatísticas e imperfeições no modelo de ruído aprendido, o que é razoável para um problema dessa magnitude.\n",
        "* O SLC reduz ainda mais a sobrecarga \\~3.4-fold (para $\\gamma^2 \\approx 48$, resultando em um limite de viés residual de cerca de 10%). O PEC+SLC também apresenta fortes flutuações com poucas randomizações, chegando inicialmente até mesmo à região não física, mas sua estimativa converge de forma constante para o valor exato, estabilizando-se próximo a 1.0, bem dentro da banda. Ela iguala ou supera a precisão do PEC simples a cerca de um terço do custo de amostragem, demonstrando a vantagem do sombreamento do cone de luz.\n",
        "* As barras de erro de ambas as estimativas mitigadas se estreitam à medida que as randomizações se acumulam; para um número fixo de randomizações, o estimador PEC+SLC apresenta a menor variância estatística, o que é consistente com seu menor valor de “ $\\gamma^2$ ”.\n",
        "\n",
        "Observe que as taxas de ruído aprendidas (e, portanto, as sobrecargas e as estimativas mitigadas) dependem do backend e de sua calibração no momento da execução; portanto, executar novamente este notebook pode gerar valores quantitativamente diferentes.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 53,
      "id": "8df6612b-d611-4201-b1f4-6d8de4a192ad",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "(-50.0, 4100.0)"
            ]
          },
          "execution_count": 53,
          "metadata": {},
          "output_type": "execute_result"
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/8df6612b-d611-4201-b1f4-6d8de4a192ad-1.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "fig, ax = plt.subplots(1, 1, figsize=(12, 6))\n",
        "\n",
        "ax.axhline(1.0, color=\"black\", label=\"Exact\")\n",
        "ax.fill_between(\n",
        "    [-50, 4100], -10, 0, color=\"grey\", alpha=0.25, label=\"Unphysical\"\n",
        ")\n",
        "ax.fill_between([-50, 4100], 1, 10, color=\"grey\", alpha=0.25)\n",
        "ax.fill_between(\n",
        "    [-50, 4100], 0.9, 1.1, color=\"red\", alpha=0.25, label=\"10% bias\"\n",
        ")\n",
        "\n",
        "for label, res in results.items():\n",
        "    ax.errorbar(\n",
        "        list(range(steps, num_randomizations + 1, steps)),\n",
        "        [r[0] for r in res],\n",
        "        yerr=[r[1] for r in res],\n",
        "        alpha=0.75,\n",
        "        marker=\"o\",\n",
        "        linestyle=\"\",\n",
        "        markerfacecolor=\"none\",\n",
        "        label=label,\n",
        "    )\n",
        "\n",
        "ax.set_ylabel(r\"$\\langle X_{6}Z_{13}\\rangle$\")\n",
        "ax.set_xlabel(\"# randomizations\")\n",
        "ax.grid()\n",
        "\n",
        "ax.legend(ncols=2)\n",
        "ax.set_ylim([-0.1, 2.0])\n",
        "ax.set_xlim([-50, 4100])"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "slc-next-000-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",
        "  * A documentação [`qiskit-addon-slc`](https://github.com/Qiskit/qiskit-addon-slc) e [`samplomatic`](https://github.com/Qiskit/samplomatic) a documentação\n",
        "  * [Sombreamento por cone de luz para mitigação de erros quânticos com aceleração clássica](https://arxiv.org/abs/2409.04401)\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
}