{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "9857bace",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Circuitos dinâmicos de referência com pares Bell cortados\"\n",
        "description: \"Avalie os recursos dinâmicos do circuito com o protocolo de par de Bell cortado\"\n",
        "---\n",
        "\n",
        "{/* cspell:ignore LOCC Ristè bitstr textcoords xytext fontsize Carrera */}\n",
        "\n",
        "<span id=\"benchmark-dynamic-circuits-with-cut-bell-pairs\" />\n",
        "\n",
        "# Circuitos dinâmicos de referência com pares Bell cortados\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "d08a5e51",
      "metadata": {},
      "source": [
        "*Estimativa de uso: 22 segundos em um processador r2 Heron (NOTA: Esta é apenas uma estimativa. O tempo de execução pode variar.)*\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "0b9b92c8",
      "metadata": {},
      "source": [
        "<span id=\"learning-outcomes\" />\n",
        "\n",
        "## Resultados do aprendizado\n",
        "\n",
        "Após concluir este tutorial, os usuários deverão compreender:\n",
        "\n",
        "* Como construir circuitos dinâmicos com medições no meio do circuito e alimentação direta clássica para teletransportar o entrelaçamento entre qubits distantes;\n",
        "* Como calcular e interpretar a métrica de erro para quantificar a fidelidade do par de Bell em um dispositivo;\n",
        "* Como identificar quais pares de qubits são mais adequados para operações baseadas em LOCC utilizando os resultados dos testes de desempenho.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "bff9db42",
      "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](/docs/guides/execute-dynamic-circuits#mid-circuit-measurements) 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": "77dca78b",
      "metadata": {},
      "source": [
        "<span id=\"background\" />\n",
        "\n",
        "## Segundo plano\n",
        "\n",
        "O hardware quântico é normalmente limitado a interações locais, mas muitos algoritmos exigem o entrelaçamento de qubits distantes ou mesmo [qubits em processadores separados](#references). Os circuitos dinâmicos — ou seja, circuitos com medição no meio do circuito e feedforward — oferecem uma maneira de superar essas limitações usando comunicação clássica em tempo real para implementar efetivamente operações quânticas não locais. Nessa abordagem, os resultados das medições de uma parte de um circuito (ou de uma QPU) podem acionar condicionalmente portas em outra parte, permitindo-nos teletransportar o entrelaçamento por longas distâncias. Isso forma a base dos esquemas **de operações locais e comunicação clássica (LOCC)**, nos quais consumimos estados de recursos entrelaçados (pares de Bell) e comunicamos os resultados das medições de forma clássica para conectar qubits distantes.\n",
        "\n",
        "Um uso promissor do LOCC é a realização de portas CNOT virtuais de longo alcance por teletransporte, conforme mostrado no [tutorial](/docs/tutorials/long-range-entanglement) sobre entrelaçamento de longo alcance. Em vez de um CNOT direto de longo alcance (que a conectividade do hardware pode não permitir), criamos pares de Bell e realizamos uma implementação de porta baseada em teletransporte. No entanto, a fidelidade dessas operações depende das características do hardware. A decoerência do qubit durante o atraso necessário (enquanto se aguarda os resultados da medição) e a latência da comunicação clássica podem degradar o estado entrelaçado. Além disso, os erros nas medições intermediárias do circuito são mais difíceis de corrigir do que os erros nas medições finais, pois se propagam para o resto do circuito através das portas condicionais.\n",
        "\n",
        "Na [experiência](#references) de referência, os autores introduzem um parâmetro de referência de fidelidade do par de Bell para identificar quais partes de um dispositivo são mais adequadas para o entrelaçamento baseado em LOCC. A ideia é executar um pequeno circuito dinâmico em cada grupo de quatro qubits conectados no processador. Este circuito de quatro qubits cria primeiro um par de Bell nos dois qubits centrais e, em seguida, utiliza-os como recurso para entrelaçar os dois qubits laterais por meio do LOCC. Concretamente, os qubits 1 e 2 são preparados localmente em um par de Bell *não cortado* (um par de Bell criado diretamente com um Hadamard e um CNOT, sem teletransporte) e, em seguida, uma rotina de teletransporte utiliza esse par de Bell para entrelaçar os qubits 0 e 3. Os qubits 1 e 2 são medidos durante a execução do circuito e, com base nesses resultados, são aplicadas correções de Pauli (um X no qubit 3 e um Z no qubit 0). Os qubits 0 e 3 ficam, então, em um estado de Bell no final do circuito.\n",
        "\n",
        "Para quantificar a qualidade desse par entrelaçado final, medimos seus estabilizadores: especificamente, a paridade na $Z$ base ( $Z_0Z_3$ ) e na $X$ base ( $X_0X_3$ ). Para um par Bell perfeito, ambas as expectativas são iguais a +1. Na prática, o ruído do hardware reduzirá esses valores. Portanto, repetimos o circuito duas vezes para cada par de qubits: um circuito mede os qubits 0 e 3 na $Z$ base e outro os mede na $X$ base. A partir dos resultados, obtemos uma estimativa de $\\langle Z_0Z_3\\rangle$ e $\\langle X_0X_3\\rangle$ para esse par de qubits. Utilizamos o erro quadrático médio (MSE) desses estabilizadores em relação ao valor ideal (1) como uma métrica simples da fidelidade do entrelaçamento. Um MSE mais baixo significa que os dois qubits alcançaram um estado de Bell mais próximo do ideal (maior fidelidade), enquanto um MSE mais alto indica mais erros. Ao analisar essa experiência em todo o dispositivo, podemos comparar a capacidade de medição e feedforward de diferentes grupos de qubits e identificar os melhores pares de qubits para operações LOCC.\n",
        "\n",
        "Este tutorial demonstra a experiência em um IBM Quantum® dispositivo para ilustrar como circuitos dinâmicos podem ser usados para gerar e avaliar o entrelaçamento entre qubits distantes. Iremos mapear todas as cadeias lineares de quatro qubits no dispositivo, executar o circuito de teletransporte em cada uma delas e, em seguida, visualizar a distribuição dos valores MSE. Este procedimento completo mostra como aproveitar Qiskit Runtime os recursos de circuitos dinâmicos para informar escolhas conscientes de hardware para cortar circuitos ou distribuir algoritmos quânticos em um sistema modular.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "152c479f",
      "metadata": {},
      "source": [
        "<span id=\"requirements\" />\n",
        "\n",
        "## Requisitos\n",
        "\n",
        "Antes de iniciar este tutorial, certifique-se de que você tem o seguinte instalado:\n",
        "\n",
        "* Qiskit SDK v2.0 ou posterior, com suporte [para visualização](/docs/api/qiskit/visualization)\n",
        "* v0.40Qiskit Runtime ou mais tarde (`pip install qiskit-ibm-runtime`)\n",
        "* Qiskit Aer v0.17 ou versão posterior (`pip install qiskit-aer`)\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "e67f9466",
      "metadata": {},
      "source": [
        "<span id=\"setup\" />\n",
        "\n",
        "## Instalação\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "b59e4534",
      "metadata": {},
      "outputs": [],
      "source": [
        "from qiskit import QuantumCircuit\n",
        "\n",
        "from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2 as Sampler\n",
        "from qiskit.transpiler import generate_preset_pass_manager\n",
        "\n",
        "import numpy as np\n",
        "import matplotlib.pyplot as plt\n",
        "\n",
        "\n",
        "def create_bell_stab(initial_layouts):\n",
        "    \"\"\"\n",
        "    Create a circuit for a 1D chain of qubits (number\n",
        "    of qubits must be a multiple of 4), where a middle\n",
        "    Bell pair is consumed to create a Bell at the edge.\n",
        "    Takes as input a list of lists, where each element\n",
        "    of the list is a 1D chain of physical qubits that is\n",
        "    used as the initial_layout for the transpiled circuit.\n",
        "    Returns a list of length-2 tuples, each tuple\n",
        "    contains a circuit to measure the ZZ stabilizer and\n",
        "    a circuit to measure the XX stabilizer of the edge\n",
        "    Bell state.\n",
        "    \"\"\"\n",
        "    bell_circuits = []\n",
        "    for (\n",
        "        initial_layout\n",
        "    ) in initial_layouts:  # Iterate over chains of physical qubits\n",
        "        assert (\n",
        "            len(initial_layout) % 4 == 0\n",
        "        ), \"The length of the chain must be a multiple of 4, \"\n",
        "        f\"len(inital_layout)={len(initial_layout)}\"\n",
        "        num_pairs = len(initial_layout) // 4\n",
        "\n",
        "        bell_parallel = QuantumCircuit(4 * num_pairs, 4 * num_pairs)\n",
        "\n",
        "        for pair_idx in range(num_pairs):\n",
        "            (q0, q1, q2, q3) = (\n",
        "                pair_idx * 4,\n",
        "                pair_idx * 4 + 1,\n",
        "                pair_idx * 4 + 2,\n",
        "                pair_idx * 4 + 3,\n",
        "            )\n",
        "            (c0, c1) = pair_idx * 4, pair_idx * 4 + 3  # edge qubits\n",
        "            (ca0, ca1) = pair_idx * 4 + 1, pair_idx * 4 + 2  # middle qubits\n",
        "\n",
        "            bell_parallel.h(q0)\n",
        "            bell_parallel.h(q1)\n",
        "            bell_parallel.cx(q1, q2)\n",
        "            bell_parallel.cx(q0, q1)\n",
        "            bell_parallel.cx(q2, q3)\n",
        "            bell_parallel.h(q2)\n",
        "\n",
        "        # add barrier BEFORE measurements and add id in conditional\n",
        "        bell_parallel.barrier()\n",
        "        for pair_idx in range(num_pairs):\n",
        "            (q0, q1, q2, q3) = (\n",
        "                pair_idx * 4,\n",
        "                pair_idx * 4 + 1,\n",
        "                pair_idx * 4 + 2,\n",
        "                pair_idx * 4 + 3,\n",
        "            )\n",
        "            (ca0, ca1) = pair_idx * 4 + 1, pair_idx * 4 + 2  # middle qubits\n",
        "\n",
        "            bell_parallel.measure(q1, ca0)\n",
        "            bell_parallel.measure(q2, ca1)\n",
        "        # bell_parallel.barrier() #remove barrier after measurement\n",
        "\n",
        "        for pair_idx in range(num_pairs):\n",
        "            (q0, q1, q2, q3) = (\n",
        "                pair_idx * 4,\n",
        "                pair_idx * 4 + 1,\n",
        "                pair_idx * 4 + 2,\n",
        "                pair_idx * 4 + 3,\n",
        "            )\n",
        "            (ca0, ca1) = pair_idx * 4 + 1, pair_idx * 4 + 2  # middle qubits\n",
        "            with bell_parallel.if_test((ca0, 1)):\n",
        "                bell_parallel.x(q3)\n",
        "            with bell_parallel.if_test((ca1, 1)):\n",
        "                bell_parallel.z(q0)\n",
        "                bell_parallel.id(q0)  # add id here for correct alignment\n",
        "\n",
        "        bell_zz = bell_parallel.copy()\n",
        "        bell_zz.barrier()\n",
        "        bell_xx = bell_parallel.copy()\n",
        "        bell_xx.barrier()\n",
        "        for pair_idx in range(num_pairs):\n",
        "            (q0, q1, q2, q3) = (\n",
        "                pair_idx * 4,\n",
        "                pair_idx * 4 + 1,\n",
        "                pair_idx * 4 + 2,\n",
        "                pair_idx * 4 + 3,\n",
        "            )\n",
        "            bell_xx.h(q0)\n",
        "            bell_xx.h(q3)\n",
        "        bell_xx.barrier()\n",
        "        for pair_idx in range(num_pairs):\n",
        "            (q0, q1, q2, q3) = (\n",
        "                pair_idx * 4,\n",
        "                pair_idx * 4 + 1,\n",
        "                pair_idx * 4 + 2,\n",
        "                pair_idx * 4 + 3,\n",
        "            )\n",
        "            (c0, c1) = pair_idx * 4, pair_idx * 4 + 3  # edge qubits\n",
        "\n",
        "            bell_zz.measure(q0, c0)\n",
        "            bell_zz.measure(q3, c1)\n",
        "\n",
        "            bell_xx.measure(q0, c0)\n",
        "            bell_xx.measure(q3, c1)\n",
        "\n",
        "        bell_circuits.append(bell_zz)\n",
        "        bell_circuits.append(bell_xx)\n",
        "\n",
        "    return bell_circuits\n",
        "\n",
        "\n",
        "def get_mse(result, initial_layouts):\n",
        "    \"\"\"\n",
        "    given a result object and the initial layouts,\n",
        "    returns a dict of layouts and their mse\n",
        "    \"\"\"\n",
        "    layout_mse = {}\n",
        "    for layout_idx, initial_layout in enumerate(initial_layouts):\n",
        "        layout_mse[tuple(initial_layout)] = {}\n",
        "\n",
        "        num_pairs = len(initial_layout) // 4\n",
        "\n",
        "        counts_zz = result[2 * layout_idx].data.c.get_counts()\n",
        "        total_shots = sum(counts_zz.values())\n",
        "\n",
        "        # Get ZZ expectation value\n",
        "        exp_zz_list = []\n",
        "        for pair_idx in range(num_pairs):\n",
        "            exp_zz = 0\n",
        "            for bitstr, shots in counts_zz.items():\n",
        "                bitstr = bitstr[::-1]  # reverse order to big endian\n",
        "                b1, b0 = (\n",
        "                    bitstr[pair_idx * 4],\n",
        "                    bitstr[pair_idx * 4 + 3],\n",
        "                )  # parse bitstring to get edge measurements for each 4-q chain\n",
        "                z_val0 = 1 if b0 == \"0\" else -1\n",
        "                z_val1 = 1 if b1 == \"0\" else -1\n",
        "                exp_zz += z_val0 * z_val1 * shots\n",
        "            exp_zz /= total_shots\n",
        "            exp_zz_list.append(exp_zz)\n",
        "\n",
        "        counts_xx = result[2 * layout_idx + 1].data.c.get_counts()\n",
        "        total_shots = sum(counts_xx.values())\n",
        "\n",
        "        # Get XX expectation value\n",
        "        exp_xx_list = []\n",
        "        for pair_idx in range(num_pairs):\n",
        "            exp_xx = 0\n",
        "            for bitstr, shots in counts_xx.items():\n",
        "                bitstr = bitstr[::-1]  # reverse order to big endian\n",
        "                b1, b0 = (\n",
        "                    bitstr[pair_idx * 4],\n",
        "                    bitstr[pair_idx * 4 + 3],\n",
        "                )  # parse bitstring to get edge measurements for each 4-q chain\n",
        "                x_val0 = 1 if b0 == \"0\" else -1\n",
        "                x_val1 = 1 if b1 == \"0\" else -1\n",
        "                exp_xx += x_val0 * x_val1 * shots\n",
        "            exp_xx /= total_shots\n",
        "            exp_xx_list.append(exp_xx)\n",
        "\n",
        "        mse_list = [\n",
        "            ((exp_zz - 1) ** 2 + (exp_xx - 1) ** 2) / 2\n",
        "            for exp_zz, exp_xx in zip(exp_zz_list, exp_xx_list)\n",
        "        ]\n",
        "\n",
        "        print(f\"layout {initial_layout}\")\n",
        "        for idx in range(num_pairs):\n",
        "            layout_mse[tuple(initial_layout)][\n",
        "                tuple(initial_layout[4 * idx : 4 * idx + 4])\n",
        "            ] = mse_list[idx]\n",
        "            print(\n",
        "                f\"qubits: {initial_layout[4*idx:4*idx+4]}, mse:, \"\n",
        "                f\"{round(mse_list[idx],4)}\"\n",
        "            )\n",
        "            # print(f'exp_zz: {round(exp_zz_list[idx],4)},\n",
        "            # exp_xx: {round(exp_xx_list[idx],4)}')\n",
        "        print(\" \")\n",
        "    return layout_mse\n",
        "\n",
        "\n",
        "def plot_mse_ecdfs(layouts_mse, combine_layouts=False):\n",
        "    \"\"\"\n",
        "    Plot CDF of MSE data for multiple layouts.\n",
        "    Optionally combine all data in a single CDF\n",
        "    \"\"\"\n",
        "\n",
        "    if not combine_layouts:\n",
        "        for initial_layout, layouts in layouts_mse.items():\n",
        "            sorted_layouts = dict(\n",
        "                sorted(layouts.items(), key=lambda item: item[1])\n",
        "            )  # sort layouts by mse\n",
        "\n",
        "            # get layouts and mses\n",
        "            layout_list = list(sorted_layouts.keys())\n",
        "            mse_list = np.asarray(list(sorted_layouts.values()))\n",
        "\n",
        "            # convert to numpy\n",
        "            x = np.array(mse_list)\n",
        "            y = np.arange(1, len(x) + 1) / len(x)\n",
        "\n",
        "            # Prepend (x[0], 0) to start CDF at zero\n",
        "            x = np.insert(x, 0, x[0])\n",
        "            y = np.insert(y, 0, 0)\n",
        "\n",
        "            # Create the plot\n",
        "            plt.plot(\n",
        "                x,\n",
        "                y,\n",
        "                marker=\"x\",\n",
        "                linestyle=\"-\",\n",
        "                label=f\"qubits: {initial_layout}\",\n",
        "            )\n",
        "\n",
        "            # add qubits labels for the edge pairs\n",
        "            for xi, yi, q in zip(x[1:], y[1:], layout_list):\n",
        "                plt.annotate(\n",
        "                    [q[0], q[3]],\n",
        "                    (xi, yi),\n",
        "                    textcoords=\"offset points\",\n",
        "                    xytext=(5, -10),\n",
        "                    ha=\"left\",\n",
        "                    fontsize=8,\n",
        "                )\n",
        "\n",
        "    elif combine_layouts:\n",
        "        all_layouts = {}\n",
        "        all_initial_layout = []\n",
        "        for (\n",
        "            initial_layout,\n",
        "            layouts,\n",
        "        ) in layouts_mse.items():  # puts together all layout information\n",
        "            all_layouts.update(layouts)\n",
        "            all_initial_layout += initial_layout\n",
        "\n",
        "        sorted_layouts = dict(\n",
        "            sorted(all_layouts.items(), key=lambda item: item[1])\n",
        "        )  # sort layouts by mse\n",
        "\n",
        "        # get layouts and mses\n",
        "        layout_list = list(sorted_layouts.keys())\n",
        "        mse_list = np.asarray(list(sorted_layouts.values()))\n",
        "\n",
        "        # convert to numpy\n",
        "        x = np.array(mse_list)\n",
        "        y = np.arange(1, len(x) + 1) / len(x)\n",
        "\n",
        "        # Prepend (x[0], 0) to start CDF at zero\n",
        "        x = np.insert(x, 0, x[0])\n",
        "        y = np.insert(y, 0, 0)\n",
        "\n",
        "        # Create the plot\n",
        "        plt.plot(\n",
        "            x,\n",
        "            y,\n",
        "            marker=\"x\",\n",
        "            linestyle=\"-\",\n",
        "            label=f\"qubits: {sorted(list(set(all_initial_layout)))}\",\n",
        "        )\n",
        "\n",
        "        # add qubit labels for the edge pairs\n",
        "        for xi, yi, q in zip(x[1:], y[1:], layout_list):\n",
        "            plt.annotate(\n",
        "                [q[0], q[3]],\n",
        "                (xi, yi),\n",
        "                textcoords=\"offset points\",\n",
        "                xytext=(5, -10),\n",
        "                ha=\"left\",\n",
        "                fontsize=8,\n",
        "            )\n",
        "\n",
        "    plt.xscale(\"log\")\n",
        "    plt.xlabel(\"Mean squared error of ⟨ZZ⟩ and ⟨XX⟩\")\n",
        "    plt.ylabel(\"Cumulative distribution function\")\n",
        "    plt.title(\"CDF for different initial layouts\")\n",
        "    plt.grid(alpha=0.3)\n",
        "    plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "30e65223",
      "metadata": {},
      "source": [
        "<span id=\"small-scale-simulator-example\" />\n",
        "\n",
        "## Exemplo de simulador em pequena escala\n",
        "\n",
        "`[0, 1, 2, 3]`Antes de executar o código na QPU real, verificamos se o circuito produz um par de Bell, testando-o em um simulador sem ruído com uma cadeia de quatro qubits. Utilizamos o Qiskit Runtime`Sampler` com `AerSimulator` como modo de backend para executar os circuitos.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "2a16f346",
      "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",
        "O primeiro passo é criar um conjunto de circuitos quânticos para avaliar todas as ligações de pares de Bell candidatas, adaptadas à topologia do dispositivo. `[0, 1, 2, 3]`Começamos construindo esses circuitos com uma cadeia de quatro qubits.\n",
        "\n",
        "A `create_bell_stab()` rotina realiza o seguinte para cada cadeia:\n",
        "\n",
        "* Prepare um par Bell médio: aplique um Hadamard no qubit 1 e um CNOT do qubit 1 para o qubit 2. Isso entrelaça os qubits 1 e 2 (criando um estado $|\\Phi^+\\rangle = (|00\\rangle + |11\\rangle)/\\sqrt{2}$ de Bell).\n",
        "* Entrelace os qubits de borda: aplique um CNOT do qubit 0 ao qubit 1 e um CNOT do qubit 2 ao qubit 3. Isso liga os pares inicialmente separados, de modo que os qubits 0 e 3 ficarão entrelaçados após as próximas etapas. Um Hadamard no qubit 2 também é aplicado (isso, combinado com os CNOTs anteriores, faz parte de uma medição Bell nos qubits 1 e 2). Neste ponto, os qubits 0 e 3 ainda não estão entrelaçados, mas os qubits 1 e 2 estão entrelaçados com eles em um estado maior de quatro qubits.\n",
        "* Medições no meio do circuito e feedforward: os qubits 1 e 2 (os qubits do meio) são medidos na base computacional, produzindo dois bits clássicos. Com base nos resultados dessas medições, aplicamos operações condicionais: se a medição do qubit 1 (chamemos esse bit $m_{12}$ de ) for 1, aplicamos um $X$ portão no qubit 3; se a medição do qubit 2 ( $m_{21}$ ) for 1, aplicamos um $Z$ portão no qubit 0. Essas portas condicionais (realizadas usando o Qiskit `if_test`/`if_else` construct) implementam as correções padrão de teletransporte. Eles “desfazem” as inversões aleatórias de Pauli que ocorrem devido à projeção dos qubits 1 e 2, garantindo que os qubits 0 e 3 terminem em um estado Bell conhecido, independentemente dos resultados da medição. Após esta etapa, os qubits 0 e 3 devem, idealmente, estar entrelaçados no estado $|\\Phi^+\\rangle$ de Bell.\n",
        "* Medir os estabilizadores do par de sinos: Em seguida, dividimos o circuito em duas versões. Na primeira versão, medimos o $ZZ$ estabilizador nos qubits 0 e 3. Na segunda versão, medimos o $XX$ estabilizador nesses qubits.\n",
        "\n",
        "Para cada configuração inicial de quatro qubits, a função acima retorna dois circuitos (um para a medição do estabilizad $ZZ$ e outro para a medição do estabilizador $XX$ ). Esses circuitos incluem medições no meio do circuito e operações condicionais (if/else), que são as instruções fundamentais do circuito dinâmico.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 19,
      "id": "160e25c5",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/edc-cut-bell-pair-benchmarking/extracted-outputs/160e25c5-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 19,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "from qiskit_aer import AerSimulator\n",
        "\n",
        "# 4-qubit chain for simulation\n",
        "sim_layout = [[0, 1, 2, 3]]\n",
        "\n",
        "aer_backend = AerSimulator()\n",
        "sim_circuits = create_bell_stab(sim_layout)\n",
        "sim_circuits[1].draw(\"mpl\", fold=-1, idle_wires=False)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "17953786",
      "metadata": {},
      "source": [
        "<span id=\"step-2-optimize-the-problem-for-quantum-hardware-execution\" />\n",
        "\n",
        "### Etapa 2: Otimizar o problema para execução em hardware quântico\n",
        "\n",
        "Antes de executar nossos circuitos, precisamos compilá-los para operações de porta compatíveis com o backend especificado. A transpilação irá mapear o circuito abstrato para os qubits físicos e o conjunto de portas do backend escolhido. Como já escolhemos qubits físicos específicos para cada cadeia (fornecendo um `initial_layout` ao gerador de circuitos), usamos o transpiler `optimization_level=0` com esse layout fixo. Isso indica ao Qiskit para não reatribuir qubits nem realizar otimizações complexas que possam alterar a estrutura do circuito. Queremos manter a sequência de operações (especialmente as portas condicionais) exatamente como especificado.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "61dd47b7",
      "metadata": {},
      "outputs": [],
      "source": [
        "pm_sim = generate_preset_pass_manager(\n",
        "    optimization_level=0, backend=aer_backend, initial_layout=sim_layout[0]\n",
        ")\n",
        "isa_sim_circuits = pm_sim.run(sim_circuits)\n",
        "isa_sim_circuits[1].draw(\"mpl\", fold=-1, idle_wires=False)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "cd161710",
      "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.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 14,
      "id": "71750073",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "layout [0, 1, 2, 3]\n",
            "qubits: [0, 1, 2, 3], mse:, 0.0\n",
            " \n"
          ]
        }
      ],
      "source": [
        "# Run on noiseless simulator\n",
        "sampler_sim = Sampler(mode=aer_backend)\n",
        "sim_job = sampler_sim.run(isa_sim_circuits)\n",
        "sim_mse = get_mse(sim_job.result(), sim_layout)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "4b0b451f",
      "metadata": {},
      "source": [
        "<span id=\"step-4-post-process-and-return-result-in-the-desired-classical-format\" />\n",
        "\n",
        "### Etapa 4: Pós-processamento e retorno do resultado no formato clássico desejado\n",
        "\n",
        "A etapa final consiste em calcular a métrica do erro quadrático médio (MSE) para cada grupo de qubits testado e resumir os resultados. Para cada cadeia, agora temos os valores medidos $\\langle Z_0Z_3\\rangle$ e $\\langle X_0X_3\\rangle$. Se os qubits 0 e 3 estivessem perfeitamente entrelaçados em um estado $|\\Phi^+\\rangle$ de Bell, esperaríamos que ambos fossem +1. Quantificamos o desvio usando o MSE:\n",
        "\n",
        "$\\text{MSE} = \\frac{( \\langle Z_0Z_3\\rangle - 1)^2 + (\\langle X_0X_3\\rangle - 1)^2}{2}.$\n",
        "\n",
        "Esse valor é 0 para um par de Bell perfeito e aumenta à medida que o estado entrelaçado se torna mais ruidoso (com resultados aleatórios que geram uma expectativa em torno de 0, o MSE se aproximaria de 1). O código calcula esse MSE para cada grupo de quatro qubits. Sem ruído, observamos que MSE = 0 neste exemplo de simulador em pequena escala, como esperado.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c8e42569",
      "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 maior escala, que é então executado em hardware quântico real.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "55f49481",
      "metadata": {},
      "outputs": [],
      "source": [
        "service = QiskitRuntimeService()\n",
        "backend = service.least_busy(operational=True)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "27ee6db2",
      "metadata": {},
      "source": [
        "Pesquisamos programaticamente no mapa de acoplamento do dispositivo todas as cadeias de quatro qubits conectadas linearmente. Cada uma dessas cadeias (identificadas pelos índices de qubits $[q0-q1-q2-q3]$ ) serve como um caso de teste para o circuito de troca de entrelaçamento. Ao identificar todos os caminhos possíveis de \" length-4 \", garantimos a cobertura máxima para os possíveis agrupamentos de qubits que poderiam implementar o protocolo.\n",
        "\n",
        "Geramos essas cadeias utilizando uma função auxiliar que realiza uma pesquisa gananciosa no gráfico do dispositivo. Ele retorna \"faixas\" de quatro cadeias de quatro qubits agrupadas em conjuntos de 16 qubits. O agrupamento nos permite realizar várias experiências com quatro qubits em paralelo em partes distintas do chip e utilizar todo o dispositivo de forma eficiente. Cada faixa de 16 qubits contém quatro cadeias independentes, o que significa que nenhum qubit é reutilizado dentro desse grupo. Por exemplo, uma faixa pode consistir nas cadeias $[0-1-2-3]$, $[4-5-6-7]$, $[8-9-10-11]$ e $[12-13-14-15]$, todas agrupadas. Qualquer qubit que não tenha sido incluído em uma faixa é retornado na `leftover` variável.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "0cae5453",
      "metadata": {},
      "outputs": [],
      "source": [
        "from itertools import chain\n",
        "from collections import defaultdict\n",
        "\n",
        "\n",
        "def stripes16_from_backend(backend):\n",
        "    \"\"\"\n",
        "    Creates stripes of 16 qubits, four non-overlapping\n",
        "    four-qubit chains, that cover as much of the coupling\n",
        "    map as possible. Returns any unused qubits as leftovers.\n",
        "    \"\"\"\n",
        "    # get the undirected adjacency list\n",
        "    edges = backend.coupling_map.get_edges()\n",
        "    graph = defaultdict(set)\n",
        "    for u, v in edges:\n",
        "        graph[u].add(v)\n",
        "        graph[v].add(u)\n",
        "\n",
        "    qubits = sorted(graph)  # all qubit indices that appear\n",
        "\n",
        "    # greedy search for 4-long linear chains (blocks) ────────────\n",
        "    used = set()  # qubits already placed in a block\n",
        "    blocks = []  # each block is a four-qubit list\n",
        "\n",
        "    for q in qubits:  # deterministic order for reproducibility\n",
        "        if q in used:\n",
        "            continue  # already consumed by earlier block\n",
        "\n",
        "        # depth-first \"straight\" walk of length 3 without revisiting nodes\n",
        "        def extend(path):\n",
        "            if len(path) == 4:\n",
        "                return path\n",
        "            tip = path[-1]\n",
        "            for nbr in sorted(graph[tip]):  # deterministic\n",
        "                if nbr not in path and nbr not in used:\n",
        "                    maybe = extend(path + [nbr])\n",
        "                    if maybe:\n",
        "                        return maybe\n",
        "            return None\n",
        "\n",
        "        block = extend([q])\n",
        "        if block:  # found a 4-node path\n",
        "            blocks.append(block)\n",
        "            used.update(block)\n",
        "\n",
        "    # bundle four four-qubit blocks into one 16-qubit\n",
        "    # stripe (max number of measurement compatible with if-else)\n",
        "    stripes = [\n",
        "        list(chain.from_iterable(blocks[i : i + 4]))\n",
        "        for i in range(0, len(blocks) // 4 * 4, 4)  # full groups of four\n",
        "    ]\n",
        "\n",
        "    leftovers = set(qubits) - set(chain.from_iterable(stripes))\n",
        "    return stripes, leftovers"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 15,
      "id": "3cf9db43",
      "metadata": {},
      "outputs": [],
      "source": [
        "initial_layouts, leftover = stripes16_from_backend(backend)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "8121aa97",
      "metadata": {},
      "source": [
        "Em seguida, construímos o circuito para cada faixa de 16 qubits utilizando a função `create_bell_stab()`. Ao final desta etapa, temos uma lista de circuitos que abrange todas as cadeias de quatro qubits do dispositivo. Em seguida, compilamos e executamos os circuitos no backend real e processamos os resultados posteriormente.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 17,
      "id": "bd04755f",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "layout [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]\n",
            "qubits: [0, 1, 2, 3], mse:, 0.6302\n",
            "qubits: [4, 5, 6, 7], mse:, 0.0949\n",
            "qubits: [8, 9, 10, 11], mse:, 0.1729\n",
            "qubits: [12, 13, 14, 15], mse:, 0.0473\n",
            " \n",
            "layout [16, 23, 22, 21, 17, 27, 26, 25, 18, 31, 30, 29, 19, 35, 34, 33]\n",
            "qubits: [16, 23, 22, 21], mse:, 0.0533\n",
            "qubits: [17, 27, 26, 25], mse:, 0.2966\n",
            "qubits: [18, 31, 30, 29], mse:, 0.0447\n",
            "qubits: [19, 35, 34, 33], mse:, 0.0392\n",
            " \n",
            "layout [36, 41, 42, 43, 37, 45, 46, 47, 38, 49, 50, 51, 39, 53, 54, 55]\n",
            "qubits: [36, 41, 42, 43], mse:, 0.1577\n",
            "qubits: [37, 45, 46, 47], mse:, 0.0705\n",
            "qubits: [38, 49, 50, 51], mse:, 0.2914\n",
            "qubits: [39, 53, 54, 55], mse:, 0.1711\n",
            " \n",
            "layout [56, 63, 62, 61, 57, 67, 66, 65, 58, 71, 70, 69, 59, 75, 74, 73]\n",
            "qubits: [56, 63, 62, 61], mse:, 0.1236\n",
            "qubits: [57, 67, 66, 65], mse:, 0.9969\n",
            "qubits: [58, 71, 70, 69], mse:, 0.0631\n",
            "qubits: [59, 75, 74, 73], mse:, 0.0301\n",
            " \n",
            "layout [76, 81, 82, 83, 77, 85, 86, 87, 78, 89, 90, 91, 79, 93, 94, 95]\n",
            "qubits: [76, 81, 82, 83], mse:, 0.2787\n",
            "qubits: [77, 85, 86, 87], mse:, 0.0497\n",
            "qubits: [78, 89, 90, 91], mse:, 0.1271\n",
            "qubits: [79, 93, 94, 95], mse:, 0.0468\n",
            " \n",
            "layout [96, 103, 102, 101, 97, 107, 106, 105, 98, 111, 110, 109, 99, 115, 114, 113]\n",
            "qubits: [96, 103, 102, 101], mse:, 0.8657\n",
            "qubits: [97, 107, 106, 105], mse:, 0.0399\n",
            "qubits: [98, 111, 110, 109], mse:, 0.0667\n",
            "qubits: [99, 115, 114, 113], mse:, 0.2444\n",
            " \n",
            "layout [116, 121, 122, 123, 117, 125, 126, 127, 118, 129, 130, 131, 119, 133, 134, 135]\n",
            "qubits: [116, 121, 122, 123], mse:, 0.0429\n",
            "qubits: [117, 125, 126, 127], mse:, 0.0487\n",
            "qubits: [118, 129, 130, 131], mse:, 0.0823\n",
            "qubits: [119, 133, 134, 135], mse:, 0.0583\n",
            " \n",
            "layout [136, 143, 142, 141, 137, 147, 146, 145, 138, 151, 150, 149, 139, 155, 154, 153]\n",
            "qubits: [136, 143, 142, 141], mse:, 0.0209\n",
            "qubits: [137, 147, 146, 145], mse:, 0.0384\n",
            "qubits: [138, 151, 150, 149], mse:, 0.4941\n",
            "qubits: [139, 155, 154, 153], mse:, 0.1062\n",
            " \n"
          ]
        }
      ],
      "source": [
        "# -------------------------Step 1-------------------------\n",
        "circuits = create_bell_stab(initial_layouts)\n",
        "\n",
        "# -------------------------Step 2-------------------------\n",
        "isa_circuits = []\n",
        "for ind, init_layout in enumerate(initial_layouts):\n",
        "    pm = generate_preset_pass_manager(\n",
        "        optimization_level=0, backend=backend, initial_layout=init_layout\n",
        "    )\n",
        "    isa_circ = pm.run(circuits[ind * 2 : ind * 2 + 2])\n",
        "    isa_circuits.extend(isa_circ)\n",
        "isa_circuits[1].draw(\"mpl\", fold=-1, idle_wires=False)\n",
        "\n",
        "# -------------------------Step 3-------------------------\n",
        "sampler = Sampler(mode=backend)\n",
        "sampler.options.environment.job_tags = [\"TUT_BDC\"]\n",
        "job = sampler.run(isa_circuits)\n",
        "\n",
        "# -------------------------Step 4-------------------------\n",
        "layouts_mse = get_mse(job.result(), initial_layouts)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "87484abf",
      "metadata": {},
      "source": [
        "Os resultados revelam uma ampla variação na qualidade do entrelaçamento em todo o dispositivo. Isso confirma a conclusão do artigo de que pode haver uma variação de mais de uma ordem de magnitude na fidelidade do estado de Bell, dependendo de quais qubits físicos são usados. Em termos práticos, isso significa que certas regiões ou ligações no chip são muito melhores em realizar medições no meio do circuito e operações de feedforward do que outras. Fatores como erro de leitura do qubit, tempo de vida do qubit e interferência provavelmente contribuem para essas diferenças. Por exemplo, se uma cadeia incluir um qubit de leitura particularmente ruidoso, a medição no meio do circuito poderá não ser fiável, levando a uma baixa fidelidade para esse par entrelaçado (MSE elevado).\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "5af0facd",
      "metadata": {},
      "source": [
        "Por fim, visualizamos o desempenho geral traçando a função de distribuição cumulativa (CDF) dos valores MSE para todas as cadeias. O gráfico CDF mostra o limiar MSE no eixo x e a fração de pares de qubits que têm, no máximo, esse MSE no eixo y. Essa curva começa em zero e se aproxima de um à medida que o limite cresce para abranger todos os pontos de dados. Um aumento acentuado próximo a um MSE baixo indicaria que muitos pares são de alta fidelidade; um aumento lento significa que muitos pares têm erros maiores. Anotamos o CDF com as identidades dos melhores pares. No gráfico, cada ponto na CDF corresponde ao MSE de uma cadeia de quatro qubits, e rotulamos o ponto com o par de índices de $[q0, q3]$ qubits que estavam entrelaçados nessa experiência. Isso facilita identificar quais pares de qubits físicos têm o melhor desempenho (os pontos mais à esquerda na CDF).\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 18,
      "id": "678ddac9",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/edc-cut-bell-pair-benchmarking/extracted-outputs/678ddac9-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "plot_mse_ecdfs(layouts_mse, combine_layouts=True)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ba738d2b",
      "metadata": {},
      "source": [
        "<span id=\"next-steps\" />\n",
        "\n",
        "## Próximas etapas\n",
        "\n",
        "<Admonition type=\"tip\" title=\"Recomendações\">\n",
        "  Se você achou este trabalho interessante, talvez se interesse pelos seguintes materiais:\n",
        "\n",
        "  * Aprenda a implementar [o entrelaçamento de longo alcance com circuitos dinâmicos](/docs/tutorials/long-range-entanglement)\n",
        "  * Aprenda a simular o [hamiltoniano de Ising com efeito de kick usando circuitos dinâmicos](/docs/tutorials/dc-hex-ising)\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "109cd9c3",
      "metadata": {},
      "source": [
        "<span id=\"references\" />\n",
        "\n",
        "## Referências\n",
        "\n",
        "[\\[1\\] Carrera Vázquez, A., Tornow, C., Ristè, D. et al. Combinando processadores quânticos com comunicação clássica em tempo real. Nature 636, 75-79 (2024)](https://www.nature.com/articles/s41586-024-08178-2).\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"
    },
    "widgets": {
      "application/vnd.jupyter.widget-state+json": {
        "state": {},
        "version_major": 2,
        "version_minor": 0
      }
    },
    "hours": 1,
    "qpuSeconds": 22
  },
  "nbformat": 4,
  "nbformat_minor": 5
}