{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "44d97249",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Entrelacamento de longo alcance com circuitos dinâmicos\"\n",
        "description: \"Este tutorial implementa um CNOT de longo alcance utilizando circuitos dinâmicos com pares de Bell, medição e alimentação direta, e compara-o com uma abordagem unitária direta.\"\n",
        "---\n",
        "\n",
        "{/* cspell:ignore fontsize hcords, ycords, xcords, ecolor, elinewidth, allcr, braket, frameon */}\n",
        "\n",
        "<span id=\"long-range-entanglement-with-dynamic-circuits\" />\n",
        "\n",
        "# Entrelacamento de longo alcance com circuitos dinâmicos\n",
        "\n",
        "*Estimativa de uso: 4 minutos em um processador Heron r2. (OBSERVAÇÃO: essa é apenas uma estimativa. Seu tempo de execução pode variar)*\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "57f65bca",
      "metadata": {},
      "source": [
        "<span id=\"learning-outcomes\" />\n",
        "\n",
        "## Resultados do aprendizado\n",
        "\n",
        "Ao concluir este tutorial, você terá aprendido o seguinte:\n",
        "\n",
        "* Como implementar uma porta CNOT de longo alcance utilizando circuitos dinâmicos com medições no meio do circuito (MCMs) e alimentação direta clássica;\n",
        "* Como implementar a porta equivalente utilizando uma abordagem baseada em SWAP unitário;\n",
        "* Como comparar ambas as abordagens medindo a fidelidade da porta em função da distância entre os qubits.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "748eee3b",
      "metadata": {},
      "source": [
        "<span id=\"prerequisites\" />\n",
        "\n",
        "## Pré-requisitos\n",
        "\n",
        "Recomendamos que os usuários estejam familiarizados com os seguintes tópicos antes de seguir com este tutorial:\n",
        "\n",
        "* [Conceitos básicos de computação quântica](/learning/courses/basics-of-quantum-information), incluindo estados de Bell, entrelaçamento e portas quânticas;\n",
        "* Conhecimento de [circuitos dinâmicos](/docs/guides/classical-feedforward-and-control-flow) (medições no meio do circuito e alimentação direta clássica);\n",
        "* Conhecimentos básicos sobre [Qiskit SDK](/docs/guides) e [Qiskit Runtime](/docs/guides/compute-services#qiskit-runtime), além de acesso a uma [conta](/docs/guides/cloud-setup) no IBM Quantum®.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "b05b669f",
      "metadata": {},
      "source": [
        "<span id=\"background\" />\n",
        "\n",
        "## Segundo plano\n",
        "\n",
        "O emaranhamento de longo alcance entre qubits distantes é um desafio em dispositivos com conectividade limitada. Este tutorial mostra como os circuitos dinâmicos podem gerar esse emaranhamento implementando uma porta LRCX (long-range controlled-X) usando um protocolo baseado em medição.\n",
        "\n",
        "Seguindo a abordagem de Elisa Bäumer et al. em [1](#ref-1), o método usa a medição do meio do circuito e o feedforward para obter portas de profundidade constante, independentemente da separação dos qubits. Ele cria pares de Bell intermediários, mede um qubit de cada par e aplica portas condicionadas de forma clássica para propagar o emaranhamento pelo dispositivo. Isso evita longas cadeias SWAP, reduzindo a profundidade do circuito e a exposição a erros de porta de dois qubits.\n",
        "\n",
        "Neste artigo, adaptamos o protocolo para um hardwar IBM Quantum e e avaliamos seu desempenho em função da distância entre o controlador e o alvo, comparando-o com uma linha de base unitaria baseada no SWAP.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c6cd3174",
      "metadata": {},
      "source": [
        "<span id=\"requirements\" />\n",
        "\n",
        "## Requisitos\n",
        "\n",
        "Antes de iniciar este tutorial, verifique se você tem os seguintes itens instalados:\n",
        "\n",
        "* Qiskit SDK v2.0 ou posterior, com suporte [para visualização](/docs/api/qiskit/visualization)\n",
        "* Qiskit Runtime v0.37 ou posterior (`pip install qiskit-ibm-runtime`)\n",
        "* Qiskit Aer v0.17 ou versão posterior (`pip install qiskit-aer`)\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "39e4440a",
      "metadata": {},
      "source": [
        "<span id=\"setup\" />\n",
        "\n",
        "## Instalação\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "5b0aa550",
      "metadata": {},
      "outputs": [],
      "source": [
        "from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister\n",
        "from qiskit.circuit.classical import expr\n",
        "from qiskit.transpiler import generate_preset_pass_manager\n",
        "from qiskit.visualization import plot_circuit_layout\n",
        "from qiskit_ibm_runtime import (\n",
        "    QiskitRuntimeService,\n",
        "    Batch,\n",
        "    SamplerV2 as Sampler,\n",
        ")\n",
        "import matplotlib.pyplot as plt\n",
        "import numpy as np"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "6733ceeb",
      "metadata": {},
      "source": [
        "<span id=\"small-scale-simulator-example\" />\n",
        "\n",
        "## Exemplo de simulador em pequena escala\n",
        "\n",
        "Antes de executar o código na QPU real, verificamos se tanto os circuitos dinâmicos quanto os unitários produzem um estado de Bell ideal em um simulador sem ruído. Utilizamos o Qiskit Runtime`Sampler` com `AerSimulator` o modo backend, a uma distância de apenas 6.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "28145093",
      "metadata": {},
      "source": [
        "<span id=\"step-1-map-classical-inputs-to-a-quantum-problem\" />\n",
        "\n",
        "### Passo 1: Mapear entradas clássicas para um problema quântico\n",
        "\n",
        "Implementamos agora uma porta CNOT de longo alcance entre dois qubits distantes, seguindo a construção de circuito dinâmico mostrada abaixo (adaptada da Fig. 1a na Ref. [1](#ref-1) ). A ideia central é utilizar um “barramento” de qubits auxiliares, inicializados em $|0\\rangle$, para mediar o teletransporte por portas de longo alcance.\n",
        "\n",
        "![Circuito CNOT de longo alcance](https://quantum.cloud.ibm.com/docs/images/tutorials/long-range-entanglement/dynamic_vs_unitary_long_range_illustration.avif)\n",
        "\n",
        "Conforme ilustrado na figura, o processo funciona da seguinte forma:\n",
        "\n",
        "1. Prepare uma cadeia de pares de Bell conectando os qubits de controle e de destino por meio de ancillas intermediárias.\n",
        "2. Realize medições de Bell entre qubits vizinhos não emaranhados, trocando o emaranhamento passo a passo até que o controle e o alvo compartilhem um par de Bell.\n",
        "3. Use esse par de Bell para teletransporte de porta, transformando um CNOT local em um CNOT determinístico de longo alcance em profundidade constante.\n",
        "\n",
        "Essa abordagem substitui as longas cadeias SWAP por um protocolo de profundidade constante, reduzindo a exposição a erros de porta de dois qubits e tornando a operação escalonável com o tamanho do dispositivo.\n",
        "\n",
        "A seguir, vamos primeiro analisar a implementação do circuito dinâmico do circuito LRCX. No final, também forneceremos uma implementação baseada em unidade para comparação, a fim de destacar as vantagens dos circuitos dinâmicos nesse cenário.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "89597fcf",
      "metadata": {},
      "source": [
        "<span id=\"initialize-circuit\" />\n",
        "\n",
        "#### Inicializar circuito\n",
        "\n",
        "Começamos com um problema quântico simples que servirá de base para a comparação. Mais especificamente, inicializamos um circuito com um qubit de controle no índice 0 e aplicamos uma porta de Hadamard a ele. Isso produz um estado de superposição que, quando seguido por uma operação X controlada, gera um estado de Bell $(|00\\rangle + |11\\rangle)/\\sqrt{2}$ entre os qubits de controle e os qubits-alvo.\n",
        "\n",
        "Nesta fase, ainda não estamos construindo o próprio LRCX (long-range controlled-X). Em vez disso, nosso objetivo é definir um circuito inicial claro e mínimo que destaque a função da LRCX. Na Etapa 2, mostraremos como o LRCX pode ser implementado como uma otimização usando circuitos dinâmicos e compararemos seu desempenho com um equivalente unitário. É importante ressaltar que o protocolo LRCX pode ser aplicado a qualquer circuito inicial. Aqui, usamos essa configuração simples de Hadamard para facilitar a demonstração.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "0446b8e8",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/long-range-entanglement/extracted-outputs/0446b8e8-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 20,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "distance = 6  # The distance of the CNOT gate, with the convention that a distance of zero is a nearest-neighbor CNOT.\n",
        "\n",
        "\n",
        "def initialize_circuit(distance):\n",
        "    assert distance >= 0\n",
        "    control = 0  # control qubit\n",
        "    n = distance  # number of qubits between target and control\n",
        "\n",
        "    qr = QuantumRegister(\n",
        "        n + 2, name=\"q\"\n",
        "    )  # Circuit with n qubits between control and target\n",
        "    cr = ClassicalRegister(\n",
        "        2, name=\"cr\"\n",
        "    )  # Classical register for measuring control and target qubits\n",
        "\n",
        "    k = int(n / 2)  # Number of Bell States to be used\n",
        "\n",
        "    allcr = [cr]\n",
        "    if (\n",
        "        distance > 1\n",
        "    ):  # This classical register will be used to store ZZ measurements.\n",
        "        # It is only used for long-range CX gates with distance > 1\n",
        "        c1 = ClassicalRegister(\n",
        "            k, name=\"c1\"\n",
        "        )  # Classical register needed for post processing\n",
        "        allcr.append(c1)\n",
        "    if (\n",
        "        distance > 0\n",
        "    ):  # This classical register will be used to store XX measurements.\n",
        "        # It is only used if distance > 0\n",
        "        c2 = ClassicalRegister(\n",
        "            n - k, name=\"c2\"\n",
        "        )  # Classical register needed for post processing\n",
        "        allcr.append(c2)\n",
        "\n",
        "    qc = QuantumCircuit(qr, *allcr, name=\"CNOT\")\n",
        "\n",
        "    # Apply a Hadamard gate to the control qubit such that the\n",
        "    # long-range CNOT gate will prepare a\n",
        "    # Bell state (|00> + |11>)/sqrt(2)\n",
        "    qc.h(control)\n",
        "\n",
        "    return qc\n",
        "\n",
        "\n",
        "qc = initialize_circuit(distance)\n",
        "qc.draw(fold=-1, output=\"mpl\", scale=0.5)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "e761cfc1",
      "metadata": {},
      "source": [
        "<span id=\"step-2-optimize-problem-for-quantum-hardware-execution\" />\n",
        "\n",
        "### Etapa 2: Otimizar o problema para execução em hardware quântico\n",
        "\n",
        "Nesta etapa, mostramos como construir o circuito LRCX usando circuitos dinâmicos. O objetivo é otimizar o circuito para execução no hardware, reduzindo a profundidade em comparação com uma implementação puramente unitária. Para ilustrar os benefícios, exibiremos a construção dinâmica do LRCX e seu equivalente unitário e, posteriormente, compararemos seu desempenho após a transpilação. É importante ressaltar que, embora aqui apliquemos o LRCX a um problema simples inicializado por Hadamard, o protocolo pode ser aplicado a qualquer circuito em que seja necessário um CNOT de longo alcance.\n",
        "\n",
        "<span id=\"prepare-bell-pairs\" />\n",
        "\n",
        "#### Preparar pares de sinos\n",
        "\n",
        "Começamos criando uma cadeia de pares de Bell ao longo do caminho entre os qubits de controle e de destino. Se a distância for ímpar, primeiro aplicamos um CNOT do controle ao seu vizinho, que é o CNOT que será teletransportado. Para uma distância uniforme, esse CNOT será aplicado após a etapa de preparação do par de sinos. Em seguida, a cadeia de pares Bell entrelaça pares sucessivos de qubits, estabelecendo o recurso necessário para transportar as informações de controle pelo dispositivo.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "id": "4df8ebba",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/long-range-entanglement/extracted-outputs/4df8ebba-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 4,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "# Determine where to start the Bell pair chain and add an extra CNOT when n is odd\n",
        "def check_even(n: int) -> int:\n",
        "    \"\"\"Return 1 if n is even, else 2.\"\"\"\n",
        "    return 1 if n % 2 == 0 else 2\n",
        "\n",
        "\n",
        "def prepare_bell_pairs(qc, add_barriers=True):\n",
        "    n = qc.num_qubits - 2  # number of qubits between target and control\n",
        "    k = int(n / 2)\n",
        "\n",
        "    if add_barriers:\n",
        "        qc.barrier()\n",
        "\n",
        "    x0 = check_even(n)\n",
        "    if n % 2 != 0:\n",
        "        qc.cx(0, 1)\n",
        "\n",
        "    # Create k Bell pairs\n",
        "    for i in range(k):\n",
        "        qc.h(x0 + 2 * i)\n",
        "        qc.cx(x0 + 2 * i, x0 + 2 * i + 1)\n",
        "    return qc\n",
        "\n",
        "\n",
        "qc = prepare_bell_pairs(qc)\n",
        "qc.draw(output=\"mpl\", fold=-1, scale=0.5)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "b80ea657",
      "metadata": {},
      "source": [
        "<span id=\"measure-neighboring-qubit-pairs-in-the-bell-basis\" />\n",
        "\n",
        "#### Medir pares de qubits vizinhos na base de Bell\n",
        "\n",
        "Em seguida, medimos os qubits vizinhos *não emaranhados* na base de Bell (medições de dois qubits de $XX$ e $ZZ$ ). Isso cria um par de Bell de longo alcance entre o qubit de destino e o qubit adjacente ao controle (até as correções de Pauli, que serão implementadas via feedforward na próxima etapa). Em paralelo, implementamos a medição de emaranhamento que teletransporta a porta CNOT para atuar no qubit de destino pretendido.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "8eed9e57",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/long-range-entanglement/extracted-outputs/8eed9e57-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 5,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "def measure_bell_basis(qc, add_barriers=True):\n",
        "    n = qc.num_qubits - 2  # number of qubits between target and control\n",
        "    k = int(n / 2)\n",
        "\n",
        "    if n > 1:\n",
        "        _, c1, c2 = qc.cregs\n",
        "    elif n > 0:\n",
        "        _, c2 = qc.cregs\n",
        "\n",
        "    # Determine where to start the Bell pair chain and add an extra CNOT\n",
        "    # when n is odd\n",
        "    x0 = 1 if n % 2 == 0 else 2\n",
        "\n",
        "    # Entangling layer that implements the Bell measurement\n",
        "    # (and additionally adds the CNOT to be\n",
        "    # teleported, if n is even)\n",
        "    for i in range(k + 1):\n",
        "        qc.cx(x0 - 1 + 2 * i, x0 + 2 * i)\n",
        "\n",
        "    for i in range(1, k + x0):\n",
        "        if i == 1:\n",
        "            qc.h(2 * i + 1 - x0)\n",
        "        else:\n",
        "            qc.h(2 * i + 1 - x0)\n",
        "\n",
        "    if add_barriers:\n",
        "        qc.barrier()\n",
        "\n",
        "    # Map the ZZ measurements onto classical register c1\n",
        "    for i in range(k):\n",
        "        if i == 0:\n",
        "            qc.measure(2 * i + x0, c1[i])\n",
        "        else:\n",
        "            qc.measure(2 * i + x0, c1[i])\n",
        "\n",
        "    # Map the XX measurements onto classical register c2\n",
        "    for i in range(1, k + x0):\n",
        "        if i == 1:\n",
        "            qc.measure(2 * i + 1 - x0, c2[i - 1])\n",
        "        else:\n",
        "            qc.measure(2 * i + 1 - x0, c2[i - 1])\n",
        "    return qc\n",
        "\n",
        "\n",
        "qc = measure_bell_basis(qc)\n",
        "qc.draw(output=\"mpl\", fold=-1, scale=0.5)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "f0d7059b",
      "metadata": {},
      "source": [
        "<span id=\"apply-feedforward-corrections-to-correct-pauli-byproduct-operators\" />\n",
        "\n",
        "#### Aplicar correções antecipadas para corrigir os operadores de subprodutos de Pauli\n",
        "\n",
        "As medições da base de Bell introduzem subprodutos de Pauli que devem ser corrigidos usando os resultados registrados. Isso é feito em duas etapas. Primeiro, precisamos computar a paridade de todas as medições do $ZZ$, que é então usada para aplicar condicionalmente uma porta $X$ ao qubit de destino. Da mesma forma, a paridade das medições do $XX$ é calculada e usada para aplicar condicionalmente uma porta $Z$ ao qubit de controle.\n",
        "\n",
        "Com a nova estrutura de expressão clássica do Qiskit, essas paridades podem ser calculadas diretamente na camada de processamento clássico do circuito. Em vez de aplicar uma sequência de portas condicionais individuais para cada bit de medição, podemos criar uma única expressão clássica que represente o XOR (paridade) de todos os resultados de medição relevantes. Essa expressão é então usada como a condição em um único bloco `if_test` , permitindo que as portas de correção sejam aplicadas em profundidade constante. Essa abordagem simplifica o circuito e garante que as correções de feedforward não introduzam latência adicional desnecessária.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 6,
      "id": "4915791a",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/long-range-entanglement/extracted-outputs/4915791a-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 6,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "def apply_ffwd_corrections(qc):\n",
        "    control = 0  # control qubit\n",
        "    target = qc.num_qubits - 1  # target qubit\n",
        "    n = qc.num_qubits - 2  # number of qubits between target and control\n",
        "\n",
        "    k = int(n / 2)\n",
        "    x0 = check_even(n)\n",
        "\n",
        "    if n > 1:\n",
        "        _, c1, c2 = qc.cregs\n",
        "    elif n > 0:\n",
        "        _, c2 = qc.cregs\n",
        "\n",
        "    # First, let's compute the parity of all ZZ measurements\n",
        "    for i in range(k):\n",
        "        if i == 0:\n",
        "            parity_ZZ = expr.lift(\n",
        "                c1[i]\n",
        "            )  # Store the value of the first ZZ measurement in parity_ZZ\n",
        "        else:\n",
        "            parity_ZZ = expr.bit_xor(\n",
        "                c1[i], parity_ZZ\n",
        "            )  # Successively compute the parity via XOR operations\n",
        "\n",
        "    for i in range(1, k + x0):\n",
        "        if i == 1:\n",
        "            parity_XX = expr.lift(\n",
        "                c2[i - 1]\n",
        "            )  # Store the value of the first XX measurement in parity_XX\n",
        "        else:\n",
        "            parity_XX = expr.bit_xor(\n",
        "                c2[i - 1], parity_XX\n",
        "            )  # Successively compute the parity via XOR operations\n",
        "\n",
        "    if n > 0:\n",
        "        with qc.if_test(parity_XX):\n",
        "            qc.z(control)\n",
        "\n",
        "    if n > 1:\n",
        "        with qc.if_test(parity_ZZ):\n",
        "            qc.x(target)\n",
        "    return qc\n",
        "\n",
        "\n",
        "qc = apply_ffwd_corrections(qc)\n",
        "qc.draw(output=\"mpl\", fold=-1, scale=0.5)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "6d22740b",
      "metadata": {},
      "source": [
        "<span id=\"measure-control-and-target-qubits\" />\n",
        "\n",
        "#### Controle de medição e qubits-alvo\n",
        "\n",
        "Definimos uma função auxiliar que permite a medição dos qubits de controle e de destino nas bases $XX$, $YY$ ou $ZZ$. Para verificar o estado de Bell $(|00\\rangle + |11\\rangle)/\\sqrt{2}$, os valores de expectativa de $XX$ e $ZZ$ devem ser ambos $+1$, pois são estabilizadores do estado. A medição $YY$ também é suportada aqui e será usada abaixo ao calcular a fidelidade.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "d087d7c1",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/long-range-entanglement/extracted-outputs/d087d7c1-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "def measure_in_basis(qc, basis=\"XX\", add_barrier=True):\n",
        "    control = 0  # control qubit\n",
        "    target = qc.num_qubits - 1  # target qubit\n",
        "\n",
        "    assert basis in [\"XX\", \"YY\", \"ZZ\"]\n",
        "\n",
        "    qc = (\n",
        "        qc.copy()\n",
        "    )  # We copy the circuit because we want to measure in different bases\n",
        "    cr = qc.cregs[0]\n",
        "\n",
        "    if add_barrier:\n",
        "        qc.barrier()\n",
        "\n",
        "    if basis == \"XX\":\n",
        "        qc.h(control)\n",
        "        qc.h(target)\n",
        "    elif basis == \"YY\":\n",
        "        qc.sdg(control)\n",
        "        qc.sdg(target)\n",
        "        qc.h(control)\n",
        "        qc.h(target)\n",
        "\n",
        "    qc.measure(control, cr[0])\n",
        "    qc.measure(target, cr[1])\n",
        "    return qc\n",
        "\n",
        "\n",
        "qc_YY = measure_in_basis(qc.copy(), basis=\"YY\")\n",
        "qc_YY.draw(\n",
        "    output=\"mpl\", fold=-1, scale=0.5\n",
        ")  # Circuit for measuring in the YY basis"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "072f8605",
      "metadata": {},
      "source": [
        "<span id=\"put-it-all-together\" />\n",
        "\n",
        "#### Junte tudo\n",
        "\n",
        "Combinamos as várias etapas definidas acima para criar um portão CX de longo alcance nas duas extremidades de uma linha unidimensional ( 1D ). As etapas são as seguintes:\n",
        "\n",
        "* Inicializando o qubit de controle em $|+\\rangle$\n",
        "* Preparação dos pares de sinos\n",
        "* Medição de pares de qubits vizinhos\n",
        "* Aplicação de correções de feedforward dependentes dos MCMs\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "11fc8adc",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/long-range-entanglement/extracted-outputs/11fc8adc-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "def lrcx(distance, prep_barrier=True, pre_measure_barrier=True):\n",
        "    qc = initialize_circuit(distance)\n",
        "    qc = prepare_bell_pairs(qc, prep_barrier)\n",
        "    qc = measure_bell_basis(qc, pre_measure_barrier)\n",
        "    qc = apply_ffwd_corrections(qc)\n",
        "    return qc\n",
        "\n",
        "\n",
        "qc = lrcx(distance)\n",
        "# Apply the measurement in the XX, YY, and ZZ bases\n",
        "qc_XX, qc_YY, qc_ZZ = [\n",
        "    measure_in_basis(qc, basis=basis) for basis in [\"XX\", \"YY\", \"ZZ\"]\n",
        "]\n",
        "\n",
        "qc_YY.draw(\n",
        "    output=\"mpl\", fold=-1, scale=0.5\n",
        ")  # Circuit for measuring in the YY basis"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "2b1f3f70",
      "metadata": {},
      "source": [
        "<span id=\"unitary-based-implementation-swapping-the-qubits-to-the-middle\" />\n",
        "\n",
        "#### Implementação baseada em unitário, trocando os qubits para o meio\n",
        "\n",
        "Para fins de comparação, examinamos primeiro o caso em que uma porta CNOT de longo alcance é implementada utilizando conexões entre vizinhos mais próximos e portas unitárias. Na figura a seguir, à esquerda, encontra-se um circuito para uma porta CNOT de longo alcance que abrange uma cadeia de n qubits do tipo “ 1D ”, sujeita apenas a conexões entre vizinhos mais próximos. No meio está uma decomposição unitária equivalente que pode ser implementada com portas CNOT locais; a profundidade do circuito é de $O(n)$.\n",
        "\n",
        "![Circuito CNOT de longo alcance](https://quantum.cloud.ibm.com/docs/images/tutorials/long-range-entanglement/dynamic_vs_unitary_long_range_illustration.avif)\n",
        "\n",
        "O circuito do meio pode ser implementado da seguinte forma:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "3f816591",
      "metadata": {},
      "outputs": [],
      "source": [
        "def cnot_unitary(distance):\n",
        "    \"\"\"Generate a long range CNOT gate using local CNOTs on a 1D\n",
        "    chain of qubits subject to n\n",
        "    nearest-neighbor connections only.\n",
        "\n",
        "\n",
        "    Args:\n",
        "        distance (int) : The distance of the CNOT gate,\n",
        "        with the convention that\n",
        "        a distance of 0 is a nearest-neighbor CNOT.\n",
        "\n",
        "    Returns:\n",
        "        QuantumCircuit: A Quantum Circuit implementing a\n",
        "        long-range CNOT gate\n",
        "        between qubit 0 and qubit distance+1\n",
        "    \"\"\"\n",
        "    assert distance >= 0\n",
        "    n = distance  # number of qubits between target and control\n",
        "\n",
        "    qr = QuantumRegister(\n",
        "        n + 2, name=\"q\"\n",
        "    )  # Circuit with n qubits between control and target\n",
        "    cr = ClassicalRegister(\n",
        "        2, name=\"cr\"\n",
        "    )  # Classical register for measuring control and target qubits\n",
        "\n",
        "    qc = QuantumCircuit(qr, cr, name=\"CNOT_unitary\")\n",
        "\n",
        "    control_qubit = 0\n",
        "\n",
        "    qc.h(control_qubit)  # Prepare the control qubit in the |+> state\n",
        "\n",
        "    k = int(n / 2)\n",
        "    qc.barrier()\n",
        "    for i in range(control_qubit, control_qubit + k):\n",
        "        qc.cx(i, i + 1)\n",
        "        qc.cx(i + 1, i)\n",
        "        qc.cx(-i - 1, -i - 2)\n",
        "        qc.cx(-i - 2, -i - 1)\n",
        "    if n % 2 == 1:\n",
        "        qc.cx(k + 2, k + 1)\n",
        "        qc.cx(k + 1, k + 2)\n",
        "    qc.barrier()\n",
        "    qc.cx(k, k + 1)\n",
        "    for i in range(control_qubit, control_qubit + k):\n",
        "        qc.cx(k - i, k - 1 - i)\n",
        "        qc.cx(k - 1 - i, k - i)\n",
        "        qc.cx(k + i + 1, k + i + 2)\n",
        "        qc.cx(k + i + 2, k + i + 1)\n",
        "    if n % 2 == 1:\n",
        "        qc.cx(-2, -1)\n",
        "        qc.cx(-1, -2)\n",
        "\n",
        "    return qc\n",
        "\n",
        "\n",
        "qc_uni = cnot_unitary(distance)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c0c99c20",
      "metadata": {},
      "source": [
        "Agora monte os circuitos que medem nas bases de $XX$, $YY$ e $ZZ$, exatamente como fizemos com os circuitos dinâmicos acima.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "b899e143",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/long-range-entanglement/extracted-outputs/b899e143-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "# Apply the measurement in the XX, YY, and ZZ bases\n",
        "qc_uni_XX, qc_uni_YY, qc_uni_ZZ = [\n",
        "    measure_in_basis(qc_uni, basis=basis) for basis in [\"XX\", \"YY\", \"ZZ\"]\n",
        "]\n",
        "\n",
        "qc_uni_YY.draw(\n",
        "    output=\"mpl\", fold=-1, scale=0.5\n",
        ")  # Circuit for measuring in the YY basis"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "81e55064",
      "metadata": {},
      "source": [
        "Agora que criamos tanto os circuitos dinâmicos quanto os circuitos unitários para um exemplo em pequena escala com `distance=6`, procedemos à sua transpilagem para executá-los inicialmente em um simulador sem ruído.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 10,
      "id": "091e9537",
      "metadata": {},
      "outputs": [],
      "source": [
        "from qiskit_aer import AerSimulator\n",
        "\n",
        "aer_backend = AerSimulator()\n",
        "pm_sim = generate_preset_pass_manager(\n",
        "    optimization_level=0, backend=aer_backend\n",
        ")\n",
        "\n",
        "# Dynamic circuits\n",
        "isa_sim_dyn = pm_sim.run([qc_XX, qc_YY, qc_ZZ])\n",
        "\n",
        "# Unitary circuits\n",
        "isa_sim_uni = pm_sim.run([qc_uni_XX, qc_uni_YY, qc_uni_ZZ])"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "fe2fce0e",
      "metadata": {},
      "source": [
        "<span id=\"step-3-execute-using-qiskit-primitives\" />\n",
        "\n",
        "### Passo 3: Execute usando Qiskit primitives\n",
        "\n",
        "Agora podemos executar o experimento no backend do simulador sem ruído. Utilizamos o Sampler do Qiskit Runtime com o modo de backend AerSimulator para executar os circuitos.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 13,
      "id": "689c9a46",
      "metadata": {},
      "outputs": [],
      "source": [
        "sampler_sim = Sampler(mode=aer_backend)\n",
        "sim_job = sampler_sim.run(isa_sim_dyn + isa_sim_uni)\n",
        "sim_results = sim_job.result()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "5287f612",
      "metadata": {},
      "source": [
        "<span id=\"step-4-post-process-and-return-result-in-desired-classical-format\" />\n",
        "\n",
        "### Etapa 4: Pós-processamento e retorno do resultado no formato clássico desejado\n",
        "\n",
        "Após a execução bem-sucedida dos experimentos, procedemos agora ao pós-processamento dos dados de medição para extrair métricas significativas.\n",
        "Nesta etapa, fazemos o seguinte:\n",
        "\n",
        "* Definir métricas de qualidade para avaliar o desempenho da CX de longo prazo.\n",
        "* Calcule os valores de expectativa dos operadores Pauli a partir de resultados de medição brutos.\n",
        "* Use-os para calcular a fidelidade do estado de Bell gerado.\n",
        "\n",
        "Em uma simulação sem ruído, verificaremos se a métrica de fidelidade é $1$ para os circuitos construídos. Nas experiências realizadas com QPUs reais, essa análise fornecerá uma visão clara do desempenho dos circuitos dinâmicos em comparação com a implementação unitarária de referência.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "4ba303da",
      "metadata": {},
      "source": [
        "<span id=\"quality-metrics\" />\n",
        "\n",
        "#### Métricas de qualidade\n",
        "\n",
        "Para avaliar o sucesso do protocolo CX de longo alcance, medimos o grau de proximidade entre o estado de saída e o estado de Bell ideal. Uma maneira prática de quantificar isso é calcular a fidelidade do estado utilizando os valores esperados dos operadores de Pauli. Podemos calcular a fidelidade de um estado de Bell no estado de controle e no estado alvo após conhecer $\\braket{XX}$, $\\braket{YY}$ e $\\braket{ZZ}$. Em particular,\n",
        "\n",
        "$ F = \\frac{1}{4} (1 + \\braket{XX} - \\braket{YY} + \\braket{ZZ})$\n",
        "\n",
        "Para calcular esses valores de expectativa a partir de dados de medição brutos, definimos um conjunto de funções auxiliares:\n",
        "\n",
        "* **`compute_ZZ_expectation`**: Com base nas contagens de medição, calcula o valor de expectativa de um operador Pauli de dois qubits na base $Z$.\n",
        "* **`compute_fidelity`**: Combina os valores de expectativa de $XX$, $YY$ e $ZZ$ na expressão de fidelidade acima.\n",
        "* **`get_counts_from_bitarray`**: Utilitário para extrair contagens de objetos de resultados de backend.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 36,
      "id": "5645a9be",
      "metadata": {},
      "outputs": [],
      "source": [
        "def compute_ZZ_expectation(counts):\n",
        "    total = sum(counts.values())\n",
        "    expectation = 0\n",
        "    for bitstring, count in counts.items():\n",
        "        # Ensure bitstring is 2 bits\n",
        "        z1 = (-1) ** (int(bitstring[-1]))\n",
        "        z2 = (-1) ** (int(bitstring[-2]))\n",
        "        expectation += z1 * z2 * count\n",
        "    return expectation / total\n",
        "\n",
        "\n",
        "def compute_fidelity(counts_xx, counts_yy, counts_zz):\n",
        "    xx, yy, zz = [\n",
        "        compute_ZZ_expectation(c) for c in [counts_xx, counts_yy, counts_zz]\n",
        "    ]\n",
        "    return 1 / 4 * (1 + xx - yy + zz)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 37,
      "id": "ade0c4e6",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Dynamic fidelity (distance=6): 1.0000\n",
            "Unitary fidelity (distance=6): 1.0000\n"
          ]
        }
      ],
      "source": [
        "# Dynamic fidelity\n",
        "counts_xx = sim_results[0].data.cr.get_counts()\n",
        "counts_yy = sim_results[1].data.cr.get_counts()\n",
        "counts_zz = sim_results[2].data.cr.get_counts()\n",
        "fidelity_dyn = compute_fidelity(counts_xx, counts_yy, counts_zz)\n",
        "\n",
        "# Unitary fidelity\n",
        "counts_xx = sim_results[3].data.cr.get_counts()\n",
        "counts_yy = sim_results[4].data.cr.get_counts()\n",
        "counts_zz = sim_results[5].data.cr.get_counts()\n",
        "fidelity_uni = compute_fidelity(counts_xx, counts_yy, counts_zz)\n",
        "\n",
        "print(f\"Dynamic fidelity (distance={distance}): {fidelity_dyn:.4f}\")\n",
        "print(f\"Unitary fidelity (distance={distance}): {fidelity_uni:.4f}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "aaa66ebb",
      "metadata": {},
      "source": [
        "Como era de se esperar em uma simulação sem ruído, as fidelidades tanto nos circuitos dinâmicos quanto nos circuitos unitários são $1$.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "9ac23eb2",
      "metadata": {},
      "source": [
        "<span id=\"large-scale-hardware-example\" />\n",
        "\n",
        "## Exemplo de hardware em grande escala\n",
        "\n",
        "Aqui, reunimos todos esses detalhes em um único fluxo de trabalho em escala maior, que é então executado em hardware quântico real.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ad4224f2",
      "metadata": {},
      "source": [
        "<span id=\"generate-circuits-for-different-distances\" />\n",
        "\n",
        "### Gerar circuitos para diferentes distâncias\n",
        "\n",
        "Atualmente, geramos circuitos CX de longo alcance para uma variedade de distâncias entre qubits de até 60 qubits. Para cada distância, construímos circuitos que medem nas bases $XX$, $YY$ e $ZZ$, que serão posteriormente utilizados para calcular as fidelidades.\n",
        "\n",
        "A lista de distâncias inclui separações de curto e longo alcance, com `distance = 0` correspondendo a um CX de vizinho mais próximo. Essas mesmas distâncias também serão usadas para gerar os circuitos unitários correspondentes posteriormente para comparação.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "72c70b11",
      "metadata": {},
      "outputs": [],
      "source": [
        "# -------------------------Step 1-------------------------\n",
        "distances = [\n",
        "    0,\n",
        "    1,\n",
        "    2,\n",
        "    3,\n",
        "    6,\n",
        "    11,\n",
        "    16,\n",
        "    21,\n",
        "    28,\n",
        "    35,\n",
        "    44,\n",
        "    55,\n",
        "    60,\n",
        "]  # Distances for long range CX. distance of 0 is a nearest-neighbor CX\n",
        "distances.sort()\n",
        "assert min(distances) >= 0\n",
        "basis_list = [\"XX\", \"YY\", \"ZZ\"]\n",
        "\n",
        "# Dynamic circuits\n",
        "circuits_dyn = []\n",
        "for distance in distances:\n",
        "    for basis in basis_list:\n",
        "        circuits_dyn.append(\n",
        "            measure_in_basis(lrcx(distance, prep_barrier=False), basis=basis)\n",
        "        )\n",
        "print(f\"Number of circuits: {len(circuits_dyn)}\")\n",
        "\n",
        "# Unitary circuits\n",
        "circuits_uni = []\n",
        "for distance in distances:\n",
        "    for basis in basis_list:\n",
        "        circuits_uni.append(\n",
        "            measure_in_basis(cnot_unitary(distance), basis=basis)\n",
        "        )\n",
        "\n",
        "print(f\"Number of circuits: {len(circuits_uni)}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "698132a5",
      "metadata": {},
      "source": [
        "Agora que temos circuitos dinâmicos e unitários para uma série de distâncias, estamos prontos para a transpilação. Primeiro, precisamos selecionar um dispositivo de backend.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 24,
      "id": "e0476bef",
      "metadata": {},
      "outputs": [],
      "source": [
        "# -------------------------Step 2-------------------------\n",
        "# Set up access to IBM Quantum devices\n",
        "from qiskit.circuit import IfElseOp\n",
        "\n",
        "service = QiskitRuntimeService()\n",
        "backend = service.least_busy(\n",
        "    operational=True, simulator=False, min_num_qubits=156\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "dad36db5",
      "metadata": {},
      "source": [
        "A etapa a seguir garante que o backend seja compatível com a instrução `if_else` , necessária para a versão mais recente dos circuitos dinâmicos. Como esse recurso ainda está em acesso antecipado, adicionamos explicitamente o `IfElseOp` ao destino de backend se ele ainda não estiver disponível.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 25,
      "id": "66805cb4",
      "metadata": {},
      "outputs": [],
      "source": [
        "if \"if_else\" not in backend.target.operation_names:\n",
        "    backend.target.add_instruction(IfElseOp, name=\"if_else\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "f15cf5e8",
      "metadata": {},
      "source": [
        "<span id=\"use-layer-fidelity-string-for-selecting-1d-chain\" />\n",
        "\n",
        "#### Use a string Layer Fidelity para selecionar uma cadei 1D\n",
        "\n",
        "Como queremos comparar o desempenho de circuitos dinâmicos e unitários em uma cadeia 1D, usamos a cadeia Layer Fidelity para selecionar uma topologia linear da melhor cadeia de qubits do dispositivo. Isso garante que ambos os tipos de circuitos sejam transpilados sob as mesmas restrições de conectividade, permitindo uma comparação justa de seu desempenho.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "258e3fa1",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "[11, 12, 13, 14, 15, 19, 35, 34, 33, 39, 53, 54, 55, 59, 75, 74, 73, 72, 71, 70, 69, 68, 67, 66, 65, 64, 63, 62, 61, 76, 81, 82, 83, 84, 85, 86, 87, 97, 107, 108, 109, 110, 111, 98, 91, 92, 93, 94, 95, 99, 115, 114, 113, 119, 133, 132, 131, 138, 151, 150, 149, 148]\n"
          ]
        }
      ],
      "source": [
        "# This selects best qubits for longest distance and uses\n",
        "# the same control for all lengths\n",
        "lf_qubits = backend.properties().to_dict()[\n",
        "    \"general_qlists\"\n",
        "]  # best linear chain qubits\n",
        "chosen_layouts = {\n",
        "    distance: [\n",
        "        val[\"qubits\"]\n",
        "        for val in lf_qubits\n",
        "        if val[\"name\"] == f\"lf_{distances[-1] + 2}\"\n",
        "    ][0][: distance + 2]\n",
        "    for distance in distances\n",
        "}\n",
        "print(chosen_layouts[max(distances)])  # best qubits at each distance"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "5886f5c9",
      "metadata": {},
      "outputs": [],
      "source": [
        "isa_circuits_dyn = []\n",
        "isa_circuits_uni = []\n",
        "\n",
        "# Using the same initial layouts for both circuits for better\n",
        "# apples to apples comparison\n",
        "for qc in circuits_dyn:\n",
        "    pm = generate_preset_pass_manager(\n",
        "        optimization_level=1,\n",
        "        backend=backend,\n",
        "        initial_layout=chosen_layouts[qc.num_qubits - 2],\n",
        "    )\n",
        "    isa_circuits_dyn.append(pm.run(qc))\n",
        "\n",
        "for qc in circuits_uni:\n",
        "    pm = generate_preset_pass_manager(\n",
        "        optimization_level=1,\n",
        "        backend=backend,\n",
        "        initial_layout=chosen_layouts[qc.num_qubits - 2],\n",
        "    )\n",
        "    isa_circuits_uni.append(pm.run(qc))"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "c77c3fd3",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "2Q depth: 2\n"
          ]
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/long-range-entanglement/extracted-outputs/c77c3fd3-1.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 28,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "print(\n",
        "    f\"2Q depth: \"\n",
        "    f\"{isa_circuits_dyn[14].depth(lambda x: x.operation.num_qubits == 2)}\"\n",
        ")\n",
        "isa_circuits_dyn[14].draw(\"mpl\", fold=-1, idle_wires=0)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "7e5fc240",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "2Q depth: 13\n"
          ]
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/long-range-entanglement/extracted-outputs/7e5fc240-1.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 29,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "print(\n",
        "    f\"2Q depth: \"\n",
        "    f\"{isa_circuits_uni[14].depth(lambda x: x.operation.num_qubits == 2)}\"\n",
        ")\n",
        "isa_circuits_uni[14].draw(\"mpl\", fold=-1, idle_wires=False)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "b6995ce7",
      "metadata": {},
      "source": [
        "<span id=\"visualize-qubits-used-for-the-lrcx-circuit\" />\n",
        "\n",
        "### Visualize os qubits usados para o circuito LRCX\n",
        "\n",
        "Nesta seção, examinamos como o circuito LRCX é mapeado no hardware. Começamos visualizando os qubits físicos usados no circuito e, em seguida, estudamos como a distância entre o controle e o alvo no layout afeta o número de operações.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "2d090f8a",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/long-range-entanglement/extracted-outputs/2d090f8a-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 30,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "# Note: the qubit coordinates must be hard-coded.\n",
        "# The backend API does not currently provide this information directly.\n",
        "# If using a different backend, you will need to\n",
        "# adjust the coordinates accordingly,\n",
        "# or set the qubit_coordinates = None to use the default layout coordinates.\n",
        "\n",
        "\n",
        "def _heron_coords_r2():\n",
        "    \"\"\"Generate coordinates for the Heron layout in R2. Note\"\"\"\n",
        "    cord_map = np.array(\n",
        "        [\n",
        "            [\n",
        "                0,\n",
        "                1,\n",
        "                2,\n",
        "                3,\n",
        "                4,\n",
        "                5,\n",
        "                6,\n",
        "                7,\n",
        "                8,\n",
        "                9,\n",
        "                10,\n",
        "                11,\n",
        "                12,\n",
        "                13,\n",
        "                14,\n",
        "                15,\n",
        "                3,\n",
        "                7,\n",
        "                11,\n",
        "                15,\n",
        "                0,\n",
        "                1,\n",
        "                2,\n",
        "                3,\n",
        "                4,\n",
        "                5,\n",
        "                6,\n",
        "                7,\n",
        "                8,\n",
        "                9,\n",
        "                10,\n",
        "                11,\n",
        "                12,\n",
        "                13,\n",
        "                14,\n",
        "                15,\n",
        "                1,\n",
        "                5,\n",
        "                9,\n",
        "                13,\n",
        "                0,\n",
        "                1,\n",
        "                2,\n",
        "                3,\n",
        "                4,\n",
        "                5,\n",
        "                6,\n",
        "                7,\n",
        "                8,\n",
        "                9,\n",
        "                10,\n",
        "                11,\n",
        "                12,\n",
        "                13,\n",
        "                14,\n",
        "                15,\n",
        "                3,\n",
        "                7,\n",
        "                11,\n",
        "                15,\n",
        "                0,\n",
        "                1,\n",
        "                2,\n",
        "                3,\n",
        "                4,\n",
        "                5,\n",
        "                6,\n",
        "                7,\n",
        "                8,\n",
        "                9,\n",
        "                10,\n",
        "                11,\n",
        "                12,\n",
        "                13,\n",
        "                14,\n",
        "                15,\n",
        "                1,\n",
        "                5,\n",
        "                9,\n",
        "                13,\n",
        "                0,\n",
        "                1,\n",
        "                2,\n",
        "                3,\n",
        "                4,\n",
        "                5,\n",
        "                6,\n",
        "                7,\n",
        "                8,\n",
        "                9,\n",
        "                10,\n",
        "                11,\n",
        "                12,\n",
        "                13,\n",
        "                14,\n",
        "                15,\n",
        "                3,\n",
        "                7,\n",
        "                11,\n",
        "                15,\n",
        "                0,\n",
        "                1,\n",
        "                2,\n",
        "                3,\n",
        "                4,\n",
        "                5,\n",
        "                6,\n",
        "                7,\n",
        "                8,\n",
        "                9,\n",
        "                10,\n",
        "                11,\n",
        "                12,\n",
        "                13,\n",
        "                14,\n",
        "                15,\n",
        "                1,\n",
        "                5,\n",
        "                9,\n",
        "                13,\n",
        "                0,\n",
        "                1,\n",
        "                2,\n",
        "                3,\n",
        "                4,\n",
        "                5,\n",
        "                6,\n",
        "                7,\n",
        "                8,\n",
        "                9,\n",
        "                10,\n",
        "                11,\n",
        "                12,\n",
        "                13,\n",
        "                14,\n",
        "                15,\n",
        "                3,\n",
        "                7,\n",
        "                11,\n",
        "                15,\n",
        "                0,\n",
        "                1,\n",
        "                2,\n",
        "                3,\n",
        "                4,\n",
        "                5,\n",
        "                6,\n",
        "                7,\n",
        "                8,\n",
        "                9,\n",
        "                10,\n",
        "                11,\n",
        "                12,\n",
        "                13,\n",
        "                14,\n",
        "                15,\n",
        "            ],\n",
        "            -1\n",
        "            * np.array([j for i in range(15) for j in [i] * [16, 4][i % 2]]),\n",
        "        ],\n",
        "        dtype=int,\n",
        "    )\n",
        "\n",
        "    hcords = []\n",
        "    ycords = cord_map[0]\n",
        "    xcords = cord_map[1]\n",
        "    for i in range(156):\n",
        "        hcords.append([xcords[i] + 1, np.abs(ycords[i]) + 1])\n",
        "\n",
        "    return hcords\n",
        "\n",
        "\n",
        "# Visualize the active qubits in the circuit layout\n",
        "plot_circuit_layout(\n",
        "    circuit=isa_circuits_uni[-1],\n",
        "    backend=backend,\n",
        "    view=\"physical\",\n",
        "    qubit_coordinates=_heron_coords_r2(),\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "4d75038a",
      "metadata": {},
      "source": [
        "Em seguida, executamos o experimento no backend real. Também utilizamos o processamento em lote para executar o experimento de forma eficiente em várias tentativas. A realização de ensaios repetidos permite calcular médias para uma comparação mais precisa entre os métodos unitário e dinâmico, bem como quantificar sua variabilidade por meio da comparação dos desvios entre as execuções.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 31,
      "id": "e6f7811d",
      "metadata": {},
      "outputs": [],
      "source": [
        "# -------------------------Step 3-------------------------\n",
        "num_trials = 10\n",
        "jobs_uni = []\n",
        "jobs_dyn = []\n",
        "with Batch(backend=backend) as batch:\n",
        "    sampler = Sampler(mode=batch)\n",
        "    sampler.options.environment.job_tags = [\"TUT_LRE\"]\n",
        "    for _ in range(num_trials):\n",
        "        jobs_uni.append(sampler.run(isa_circuits_uni, shots=1024))\n",
        "        jobs_dyn.append(sampler.run(isa_circuits_dyn, shots=1024))"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "388b6a87",
      "metadata": {},
      "source": [
        "Calculamos a fidelidade dos circuitos CX dinâmicos de longo alcance. Para cada distância, extraímos os resultados das medições nas bases de dados $\\braket{XX}$, $\\braket{YY}$ e $\\braket{ZZ}$. Esses resultados são combinados utilizando as funções auxiliares definidas anteriormente para calcular a fidelidade de acordo com $F = \\tfrac{1}{4} \\big( 1 + \\langle XX \\rangle - \\langle YY \\rangle + \\langle ZZ \\rangle \\big)$. Isso fornece a fidelidade observada do protocolo executado dinamicamente em cada distância.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 32,
      "id": "10245513",
      "metadata": {},
      "outputs": [],
      "source": [
        "# -------------------------Step 4-------------------------\n",
        "fidelities_dyn = []\n",
        "\n",
        "# loop over trials\n",
        "for job in jobs_dyn:\n",
        "    result_dyn = job.result()\n",
        "    trial_fidelities = []\n",
        "    # loop over all distances\n",
        "    for ind, dist in enumerate(distances):\n",
        "        counts_xx = result_dyn[ind * 3].data.cr.get_counts()\n",
        "        counts_yy = result_dyn[ind * 3 + 1].data.cr.get_counts()\n",
        "        counts_zz = result_dyn[ind * 3 + 2].data.cr.get_counts()\n",
        "        trial_fidelities.append(\n",
        "            compute_fidelity(counts_xx, counts_yy, counts_zz)\n",
        "        )\n",
        "    fidelities_dyn.append(trial_fidelities)\n",
        "# average over trials for each distance\n",
        "avg_fidelities_dyn = np.mean(fidelities_dyn, axis=0)\n",
        "std_fidelities_dyn = np.std(fidelities_dyn, axis=0)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "dabcde16",
      "metadata": {},
      "source": [
        "Agora, calculamos a fidelidade dos circuitos CX unitários de longo alcance e fazemos isso da mesma forma que fizemos com os circuitos dinâmicos acima.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 33,
      "id": "decaf83a",
      "metadata": {},
      "outputs": [],
      "source": [
        "fidelities_uni = []\n",
        "\n",
        "# loop over trials\n",
        "for job in jobs_uni:\n",
        "    result_uni = job.result()\n",
        "    trial_fidelities = []\n",
        "    # loop over all distances\n",
        "    for ind, dist in enumerate(distances):\n",
        "        counts_xx = result_uni[ind * 3].data.cr.get_counts()\n",
        "        counts_yy = result_uni[ind * 3 + 1].data.cr.get_counts()\n",
        "        counts_zz = result_uni[ind * 3 + 2].data.cr.get_counts()\n",
        "        trial_fidelities.append(\n",
        "            compute_fidelity(counts_xx, counts_yy, counts_zz)\n",
        "        )\n",
        "    fidelities_uni.append(trial_fidelities)\n",
        "# average over trials for each distance\n",
        "avg_fidelities_uni = np.mean(fidelities_uni, axis=0)\n",
        "std_fidelities_uni = np.std(fidelities_uni, axis=0)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a9dadc4e",
      "metadata": {},
      "source": [
        "<span id=\"plot-the-results\" />\n",
        "\n",
        "### Plote os resultados\n",
        "\n",
        "Para apreciar os resultados visualmente, o gráfico abaixo apresenta as fidelidades estimadas das portas, medidas em diferentes distâncias entre qubits entrelaçados para cada método.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 34,
      "id": "724da22d",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/long-range-entanglement/extracted-outputs/724da22d-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "fig, ax = plt.subplots()\n",
        "\n",
        "# Unitary with error bars\n",
        "ax.errorbar(\n",
        "    distances,\n",
        "    avg_fidelities_uni,\n",
        "    yerr=std_fidelities_uni,\n",
        "    fmt=\"o-.\",\n",
        "    color=\"c\",\n",
        "    ecolor=\"c\",\n",
        "    elinewidth=1,\n",
        "    capsize=4,\n",
        "    label=\"Unitary\",\n",
        ")\n",
        "# Dynamic with error bars\n",
        "ax.errorbar(\n",
        "    distances,\n",
        "    avg_fidelities_dyn,\n",
        "    yerr=std_fidelities_dyn,\n",
        "    fmt=\"o-.\",\n",
        "    color=\"m\",\n",
        "    ecolor=\"m\",\n",
        "    elinewidth=1,\n",
        "    capsize=4,\n",
        "    label=\"Dynamic\",\n",
        ")\n",
        "# Random gate baseline\n",
        "ax.axhline(y=1 / 4, linestyle=\"--\", color=\"gray\", label=\"Random gate\")\n",
        "\n",
        "legend = ax.legend(frameon=True)\n",
        "for text in legend.get_texts():\n",
        "    text.set_color(\"black\")\n",
        "legend.get_frame().set_facecolor(\"white\")\n",
        "legend.get_frame().set_edgecolor(\"black\")\n",
        "ax.set_title(\n",
        "    \"Bell State Fidelity vs Control–Target Separation\", color=\"black\"\n",
        ")\n",
        "ax.set_xlabel(\"Distance\", color=\"black\")\n",
        "ax.set_ylabel(\"Bell state fidelity\", color=\"black\")\n",
        "ax.grid(linestyle=\":\", linewidth=0.6, alpha=0.4, color=\"gray\")\n",
        "ax.set_ylim((0.2, 1))\n",
        "ax.set_facecolor(\"white\")\n",
        "fig.patch.set_facecolor(\"white\")\n",
        "for spine in ax.spines.values():\n",
        "    spine.set_visible(True)\n",
        "    spine.set_color(\"black\")\n",
        "ax.tick_params(axis=\"x\", colors=\"black\")\n",
        "ax.tick_params(axis=\"y\", colors=\"black\")\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "bb2545e7",
      "metadata": {},
      "source": [
        "Com base no gráfico de fidelidade acima, o LRCX não superou de forma consistente a implementação unitária direta. Na verdade, para separações curtas entre controle e alvo, o circuito unitário obteve maior fidelidade. No entanto, em separações maiores, o circuito dinâmico começa a alcançar melhor fidelidade do que a implementação unitária. Esse comportamento não é inesperado no hardware atual: embora os circuitos dinâmicos reduzam a profundidade do circuito, evitando longas cadeias de SWAP, eles introduzem um tempo de circuito adicional a partir de medições no meio do circuito, feedforward clássico e atrasos no caminho de controle. A latência adicional aumenta a decoerência e os erros de leitura, que podem superar a economia de profundidade em distâncias curtas.\n",
        "\n",
        "No entanto, observamos um ponto de cruzamento em que a abordagem dinâmica supera a unitária. Esse é um resultado direto do dimensionamento diferente: a profundidade do circuito unitário cresce linearmente com a distância entre os qubits, enquanto a profundidade do circuito dinâmico permanece constante.\n",
        "\n",
        "**Pontos importantes:**\n",
        "\n",
        "* **Benefício imediato dos circuitos dinâmicos:** A principal motivação atual é a redução da *profundidade de dois qubits*, não necessariamente o aumento da fidelidade.\n",
        "* **Por que a fidelidade pode ser pior atualmente:** O aumento do tempo de circuito das operações clássicas e de medição geralmente domina, especialmente quando a separação entre o controle e o alvo é pequena.\n",
        "* **Olhando para o futuro:** À medida que o hardware melhora, especificamente a leitura mais rápida, a latência de controle clássico mais curta e a redução da sobrecarga no meio do circuito, devemos esperar que essas reduções de profundidade e duração se traduzam em ganhos mensuráveis de fidelidade.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "3dcff343",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/long-range-entanglement/extracted-outputs/3dcff343-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "# Compute metrics for each distance, skipping the basis circuits since\n",
        "# they are identical for each distance\n",
        "depths_2q_dyn = [\n",
        "    c.depth(lambda x: x.operation.num_qubits == 2)\n",
        "    for c in isa_circuits_dyn[::3]\n",
        "]\n",
        "meas_dyn = [\n",
        "    sum(1 for instr in c.data if instr.operation.name == \"measure\")\n",
        "    for c in isa_circuits_dyn[::3]\n",
        "]\n",
        "\n",
        "depths_2q_uni = [\n",
        "    c.depth(lambda x: x.operation.num_qubits == 2)\n",
        "    for c in isa_circuits_uni[::3]\n",
        "]\n",
        "meas_uni = [\n",
        "    sum(1 for instr in c.data if instr.operation.name == \"measure\")\n",
        "    for c in isa_circuits_uni[::3]\n",
        "]\n",
        "\n",
        "fig, axes = plt.subplots(1, 2, figsize=(12, 5))\n",
        "\n",
        "axes[0].plot(\n",
        "    distances, depths_2q_uni, \"o-.\", color=\"c\", label=\"Unitary (2Q depth)\"\n",
        ")\n",
        "axes[0].plot(\n",
        "    distances, depths_2q_dyn, \"o-.\", color=\"m\", label=\"Dynamic (2Q depth)\"\n",
        ")\n",
        "axes[0].set_xlabel(\"Number of qubits between control and target\")\n",
        "axes[0].set_ylabel(\"Two-qubit depth\")\n",
        "axes[0].grid(True, linestyle=\":\", linewidth=0.6, alpha=0.4)\n",
        "axes[0].legend()\n",
        "\n",
        "axes[1].plot(\n",
        "    distances, meas_uni, \"o-.\", color=\"c\", label=\"Unitary (# measurements)\"\n",
        ")\n",
        "axes[1].plot(\n",
        "    distances, meas_dyn, \"o-.\", color=\"m\", label=\"Dynamic (# measurements)\"\n",
        ")\n",
        "axes[1].set_xlabel(\"Number of qubits between control and target\")\n",
        "axes[1].set_ylabel(\"Number of measurements\")\n",
        "axes[1].grid(True, linestyle=\":\", linewidth=0.6, alpha=0.4)\n",
        "axes[1].legend()\n",
        "\n",
        "fig.suptitle(\"Scaling of Unitary vs Dynamic LRCX with Distance\", fontsize=12)\n",
        "\n",
        "plt.tight_layout()\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "55326bf8",
      "metadata": {},
      "source": [
        "Esse gráfico de profundidade de dois qubits destaca a principal vantagem do LRCX implementado com circuitos dinâmicos: o desempenho permanece essencialmente constante à medida que a separação entre os qubits de controle e de destino aumenta. Em contrapartida, a implementação unitária cresce linearmente com a distância devido às cadeias SWAP necessárias. A profundidade captura o dimensionamento lógico das operações de dois qubits, enquanto a contagem de medições reflete a sobrecarga adicional dos circuitos dinâmicos. Essas medições são eficientes, pois são realizadas em paralelo, mas ainda assim introduzem um custo fixo no hardware atual.\n",
        "\n",
        "Por que a fidelidade pode ser pior atualmente: O aumento do tempo de circuito das operações clássicas e de medição geralmente domina, especialmente quando a separação entre o controle e o alvo é pequena. Por exemplo, o comprimento médio de leitura em um processador Heron r2 é de 2.280 ns, enquanto o comprimento da porta 2Q é de apenas 68 ns.\n",
        "\n",
        "À medida que as latências clássicas e de medição melhoram, esperamos que o dimensionamento de profundidade constante e de medição constante dos circuitos dinâmicos produza fidelidade clara e vantagens de tempo de execução em circuitos maiores.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "dae8ad4a",
      "metadata": {},
      "source": [
        "<span id=\"next-steps\" />\n",
        "\n",
        "## Próximas etapas\n",
        "\n",
        "Se você achou este trabalho interessante, talvez se interesse pelos seguintes materiais:\n",
        "\n",
        "<Admonition type=\"tip\" title=\"Recomendações\">\n",
        "  * [Circuitos dinâmicos de referência com pares de Bell cortados](/docs/tutorials/edc-cut-bell-pair-benchmarking)\n",
        "  * [Simulação do hamiltoniano de Ising com impulsos usando circuitos dinâmicos](/docs/tutorials/dc-hex-ising)\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "cc5af2f9",
      "metadata": {},
      "source": [
        "<span id=\"references\" />\n",
        "\n",
        "## Referências\n",
        "\n",
        "<a id=\"ref-1\" />\n",
        "\n",
        "\\[1] Efficient Long-Range Entanglement using Dynamic Circuits, por *Elisa Bäumer, Vinay Tripathi, Derek S. Wang, Patrick Rall, Edward H. Chen, Swarnadeep Majumder, Alireza Seif, Zlatko K. Minev*. IBM Quantum, (2023).\n",
        "[https://arxiv.org/abs/2308.13065](https://arxiv.org/abs/2308.13065)\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": 1,
    "qpuSeconds": 240
  },
  "nbformat": 4,
  "nbformat_minor": 5
}