{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "44d97249",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Langreichweitige Verschränkung mit dynamischen Schaltkreisen\"\n",
        "description: \"In diesem Tutorial wird ein CNOT-Schaltkreis mit großer Reichweite unter Verwendung dynamischer Schaltungen mit Bell-Paaren, Messungen und Vorwärtskopplung implementiert und mit einem direkten unitären Ansatz verglichen.\"\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",
        "# Langreichweitige Verschränkung mit dynamischen Schaltkreisen\n",
        "\n",
        "*Geschätzte Nutzungsdauer: 4 Minuten auf einem Heron r2 Prozessor. (HINWEIS: Dies ist nur eine Schätzung. Ihre Laufzeit kann variieren.)*\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "57f65bca",
      "metadata": {},
      "source": [
        "<span id=\"learning-outcomes\" />\n",
        "\n",
        "## Lernziele\n",
        "\n",
        "* Wie man ein CNOT-Gatter mit großer Reichweite mithilfe dynamischer Schaltungen mit Messungen während des Schaltvorgangs (MCMs) und klassischer Vorwärtskopplung implementiert;\n",
        "* Wie man das äquivalente Gatter mithilfe eines auf dem unitären SWAP-Operator basierenden Ansatzes implementiert;\n",
        "* Wie lassen sich beide Ansätze vergleichen, indem man die Gate-Genauigkeit als Funktion des Qubit-Abstands misst?\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "748eee3b",
      "metadata": {},
      "source": [
        "<span id=\"prerequisites\" />\n",
        "\n",
        "## Voraussetzungen\n",
        "\n",
        "* [Grundlegende Konzepte der Quanteninformatik](/learning/courses/basics-of-quantum-information), darunter Bell-Zustände, Verschränkung und Quantengatter;\n",
        "* Kenntnisse im Bereich [dynamischer Schaltungen](/docs/guides/classical-feedforward-and-control-flow) (Messungen während des Betriebs und klassische Vorwärtsregelung);\n",
        "* Grundkenntnisse in „ [Qiskit SDK](/docs/guides) “ und „ [IBM Quantum® Compute Service](/docs/guides/compute-services#qiskit-runtime)“ sowie Zugriff auf ein „ [IBM Quantum “-Konto](/docs/guides/cloud-setup).\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "b05b669f",
      "metadata": {},
      "source": [
        "<span id=\"background\" />\n",
        "\n",
        "## Hintergrund\n",
        "\n",
        "Die Verschränkung über große Entfernungen zwischen weit entfernten Qubits ist auf Geräten mit begrenzter Konnektivität eine Herausforderung. Dieses Tutorial zeigt, wie dynamische Schaltkreise eine solche Verschränkung erzeugen können, indem sie ein Long-Range-Controlled-X-Gate (LRCX) mit einem messungsbasierten Protokoll implementieren.\n",
        "\n",
        "In Anlehnung an den Ansatz von Elisa Bäumer et al. ( [1](#ref-1)) nutzt die Methode die Messung in der Mitte des Schaltkreises und die Vorwärtssteuerung, um Gatter mit konstanter Tiefe unabhängig vom Qubitabstand zu erreichen. Es erzeugt dazwischenliegende Bell-Paare, misst ein Qubit von jedem Paar und wendet klassisch konditionierte Gatter an, um die Verschränkung über das Gerät zu übertragen. Dadurch werden lange SWAP-Ketten vermieden und sowohl die Schaltungstiefe als auch die Anfälligkeit für Zwei-Qubit-Gatefehler verringert.\n",
        "\n",
        "In diesem Notizbuch passen wir das Protokoll für die „ IBM Quantum “-Hardware an und messen deren Leistung in Abhängigkeit vom Abstand zwischen Steuerung und Ziel, wobei wir einen Vergleich mit einer auf dem unitaren SWAP-Verfahren basierenden Referenzlösung anstellen.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c6cd3174",
      "metadata": {},
      "source": [
        "<span id=\"requirements\" />\n",
        "\n",
        "## Anforderungen\n",
        "\n",
        "Vergewissern Sie sich vor Beginn dieses Tutorials, dass Sie die folgenden Komponenten installiert haben:\n",
        "\n",
        "* Qiskit SDK v2.0 oder später, mit [Visual](/docs/api/qiskit/visualization) isierungsunterstützung\n",
        "* Qiskit Runtime v0.37 oder später (`pip install qiskit-ibm-runtime`)\n",
        "* Qiskit Aer v0.17 oder höher (`pip install qiskit-aer`)\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "39e4440a",
      "metadata": {},
      "source": [
        "<span id=\"setup\" />\n",
        "\n",
        "## Einrichtung\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",
        "## Beispiel für einen Simulator im kleinen Maßstab\n",
        "\n",
        "Bevor wir den Code auf der echten QPU ausführen, überprüfen wir auf einem rauschfreien Simulator, ob sowohl die dynamischen als auch die unitären Schaltkreise einen idealen Bell-Zustand erzeugen. Wir verwenden den Modus „ IBM Quantum “ `Sampler` mit dem Backend-Modus `AerSimulator` „as“ und einem geringen Abstand von 6.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "28145093",
      "metadata": {},
      "source": [
        "<span id=\"step-1-map-classical-inputs-to-a-quantum-problem\" />\n",
        "\n",
        "### Schritt 1: Klassische Eingaben einem Quantenproblem zuordnen\n",
        "\n",
        "Wir realisieren nun ein CNOT-Gatter mit großer Reichweite zwischen zwei weit voneinander entfernten Qubits gemäß dem unten dargestellten dynamischen Schaltkreis (in Anlehnung an Abb. 1a in Ref. [1](#ref-1) ). Der Kerngedanke besteht darin, einen „Bus“ aus Ancilla-Qubits, die auf den Zustand $|0\\rangle$ initialisiert sind, zur Vermittlung der Fern-Gatter-Teleportation zu nutzen.\n",
        "\n",
        "![CNOT-Schaltung mit großer Reichweite](https://quantum.cloud.ibm.com/docs/images/tutorials/long-range-entanglement/dynamic_vs_unitary_long_range_illustration.avif)\n",
        "\n",
        "Wie in der Abbildung dargestellt, funktioniert der Prozess wie folgt:\n",
        "\n",
        "1. Bereiten Sie eine Kette von Bell-Paaren vor, die die Kontroll- und Ziel-Qubits über dazwischenliegende Ancillas verbinden.\n",
        "2. Führen Sie Bell-Messungen zwischen nicht verschränkten benachbarten Qubits durch und tauschen Sie die Verschränkung schrittweise aus, bis Kontrolle und Ziel ein Bell-Paar haben.\n",
        "3. Verwenden Sie dieses Bell-Paar für die Tor-Teleportation und verwandeln Sie ein lokales CNOT in ein deterministisches CNOT mit großer Reichweite und konstanter Tiefe.\n",
        "\n",
        "Dieser Ansatz ersetzt lange SWAP-Ketten durch ein Protokoll mit konstanter Tiefe, wodurch die Anfälligkeit für Zwei-Qubit-Gate-Fehler verringert wird und die Operation mit der Größe des Bauelements skalierbar wird.\n",
        "\n",
        "Im Folgenden werden wir zunächst die dynamische Schaltungsimplementierung der LRCX-Schaltung erläutern. Am Ende werden wir auch eine unitäre Implementierung zum Vergleich bereitstellen, um die Vorteile dynamischer Schaltungen in dieser Umgebung hervorzuheben.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "89597fcf",
      "metadata": {},
      "source": [
        "<span id=\"initialize-circuit\" />\n",
        "\n",
        "#### Schaltung initialisieren\n",
        "\n",
        "Wir beginnen mit einem einfachen Quantenproblem, das als Vergleichsgrundlage dienen soll. Konkret initialisieren wir einen Schaltkreis mit einem Steuerqubit am Index 0 und wenden darauf ein Hadamard-Gatter an. Dadurch entsteht ein Überlagerungszustand, der bei anschließender Durchführung einer kontrollierten X-Operation einen Bell-Zustand $(|00\\rangle + |11\\rangle)/\\sqrt{2}$ zwischen dem Steuer- und dem Ziel-Qubit erzeugt.\n",
        "\n",
        "Zum gegenwärtigen Zeitpunkt ist die Konstruktion des Langstrecken-Kontrollsystems LRCX noch nicht abgeschlossen. Unser Ziel ist es vielmehr, eine klare und minimale Ausgangsschaltung zu definieren, die die Rolle des LRCX hervorhebt. In Schritt 2 zeigen wir, wie LRCX als Optimierung mit dynamischen Schaltungen implementiert werden kann, und vergleichen seine Leistung mit der eines unitären Äquivalents. Wichtig ist, dass das LRCX-Protokoll auf jede Ausgangsschaltung angewendet werden kann. Zur Veranschaulichung verwenden wir hier diesen einfachen Hadamard-Aufbau.\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",
        "### Schritt 2: Optimierung des Problems für die Ausführung auf Quantenhardware\n",
        "\n",
        "In diesem Schritt zeigen wir, wie die LRCX-Schaltung mit Hilfe dynamischer Schaltungen aufgebaut wird. Ziel ist es, die Schaltung für die Ausführung auf Hardware zu optimieren, indem die Tiefe im Vergleich zu einer rein unitären Implementierung verringert wird. Zur Veranschaulichung der Vorteile werden wir sowohl die dynamische LRCX-Konstruktion als auch ihr unitäres Äquivalent darstellen und später ihre Leistung nach der Transpilierung vergleichen. Wichtig ist, dass wir hier das LRCX auf ein einfaches Hadamard-initialisiertes Problem anwenden, aber das Protokoll kann auf jede Schaltung angewendet werden, für die ein CNOT mit großer Reichweite erforderlich ist.\n",
        "\n",
        "<span id=\"prepare-bell-pairs\" />\n",
        "\n",
        "#### Glockenpaare vorbereiten\n",
        "\n",
        "Wir beginnen damit, eine Kette von Bell-Paaren entlang des Pfades zwischen dem Kontroll- und dem Ziel-Qubit zu erstellen. Wenn der Abstand ungerade ist, wird zunächst ein CNOT von der Kontrolle zu ihrem Nachbarn angewendet, der der CNOT ist, der teleportiert werden soll. Bei einem geraden Abstand wird dieser CNOT nach dem Schritt der Vorbereitung des Glockenpaars angewendet. Die Bell-Paar-Kette verschränkt dann aufeinanderfolgende Qubit-Paare und stellt so die Ressource bereit, die für die Übertragung der Steuerinformationen über das Gerät erforderlich ist.\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",
        "#### Messe benachbarte Qubit-Paare in der Bell-Basis\n",
        "\n",
        "Als nächstes messen wir *unverschränkte* benachbarte Qubits in der Bell-Basis (Zwei-Qubit-Messungen von $XX$ und $ZZ$ ). Dadurch entsteht ein weitreichendes Bell-Paar zwischen dem Ziel-Qubit und dem zur Kontrolle benachbarten Qubit (bis zu Pauli-Korrekturen, die im nächsten Schritt per Feedforward implementiert werden). Parallel dazu implementieren wir die Verschränkungsmessung, die das CNOT-Gate auf das beabsichtigte Ziel-Qubit teleportiert.\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",
        "#### Wende Feedforward-Korrekturen an, um die Pauli-Nebenproduktoperatoren zu korrigieren\n",
        "\n",
        "Die Messungen auf Bell-Basis führen Pauli-Nebenprodukte ein, die anhand der aufgezeichneten Ergebnisse korrigiert werden müssen. Dies geschieht in zwei Schritten. Zunächst müssen wir die Parität aller $ZZ$ Messungen berechnen, die dann verwendet wird, um ein $X$ Gatter bedingt auf das Ziel-Qubit anzuwenden. Ebenso wird die Parität der $XX$ Messungen berechnet und verwendet, um ein $Z$ Gate auf das Kontroll-Qubit anzuwenden.\n",
        "\n",
        "Mit dem neuen Framework für klassische Ausdrücke in Qiskit können diese Paritäten direkt in der klassischen Verarbeitungsschicht des Schaltkreises berechnet werden. Anstatt für jedes Messbit eine Folge von einzelnen bedingten Gattern anzuwenden, können wir einen einzigen klassischen Ausdruck bilden, der das XOR (Parität) aller relevanten Messergebnisse darstellt. Dieser Ausdruck wird dann als Bedingung in einem einzigen `if_test` -Block verwendet, so dass die Korrekturgatter in konstanter Tiefe angewendet werden können. Dieser Ansatz vereinfacht die Schaltung und stellt sicher, dass die Vorwärtskorrekturen keine unnötige zusätzliche Latenzzeit verursachen.\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",
        "#### Mess- und Ziel-Qubits\n",
        "\n",
        "Wir definieren eine Hilfsfunktion, die die Messung der Kontroll- und Zielqubits in den Basen $XX$, $YY$ oder $ZZ$ ermöglicht. Um den Bell-Zustand $(|00\\rangle + |11\\rangle)/\\sqrt{2}$ zu überprüfen, sollten die Erwartungswerte von $XX$ und $ZZ$ beide $+1$ sein, da sie Stabilisatoren des Zustands sind. Die Messung $YY$ wird auch hier unterstützt und wird im Folgenden zur Berechnung der Wiedergabetreue verwendet.\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",
        "#### Alles zusammenfügen\n",
        "\n",
        "Wir kombinieren die oben definierten Schritte, um an beiden Enden einer eindimensionalen ( 1D ) Linie ein Langreichweiten-CX-Gate zu erzeugen. Die Schritte umfassen Folgendes:\n",
        "\n",
        "* Initialisierung des Steuerqubits in „ $|+\\rangle$ “\n",
        "* Vorbereiten von Glockenpaaren\n",
        "* Messung benachbarter Qubit-Paare\n",
        "* Anwendung von Feedforward-Korrekturen in Abhängigkeit von den 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",
        "#### Einheitliche Implementierung, bei der die Qubits in die Mitte verschoben werden\n",
        "\n",
        "Zum Vergleich betrachten wir zunächst den Fall, in dem ein CNOT-Gatter mit großer Reichweite mithilfe von Verbindungen zwischen den nächsten Nachbarn und unitären Gattern realisiert wird. In der folgenden Abbildung ist links eine Schaltung für ein CNOT-Gatter mit großer Reichweite dargestellt, das eine „ 1D “-Kette aus n-Qubits überspannt, bei der ausschließlich Verbindungen zwischen den nächsten Nachbarn bestehen. In der Mitte steht eine äquivalente unitäre Zerlegung, die mit lokalen CNOT-Gattern implementierbar ist, Schaltungstiefe $O(n)$.\n",
        "\n",
        "![CNOT-Schaltung mit großer Reichweite](https://quantum.cloud.ibm.com/docs/images/tutorials/long-range-entanglement/dynamic_vs_unitary_long_range_illustration.avif)\n",
        "\n",
        "Die Schaltung in der Mitte lässt sich wie folgt realisieren:\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": [
        "Bauen Sie nun die Schaltungen auf, die im Basisbereich von $XX$, $YY$ und $ZZ$ messen, genau wie wir es oben bei den dynamischen Schaltungen getan haben.\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": [
        "`distance=6`Nachdem wir nun sowohl die dynamischen als auch die unitären Schaltkreise für ein kleines Beispiel mit erstellt haben, transpilieren wir sie, um sie zunächst auf einem rauschfreien Simulator auszuführen.\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",
        "### Schritt 3: Mit „ Qiskit primitives “ ausführen\n",
        "\n",
        "Wir können das Experiment nun auf dem rauschfreien Simulator-Backend ausführen. Wir verwenden den „ Qiskit Runtime “-Sampler mit dem Modus „ AerSimulator “ als Backend, um die Schaltungen auszuführen.\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",
        "### Schritt 4: Nachbearbeitung und Rückgabe des Ergebnisses im gewünschten klassischen Format\n",
        "\n",
        "Nachdem die Experimente erfolgreich durchgeführt wurden, werten wir nun die Messwerte aus, um aussagekräftige Kennzahlen zu ermitteln.\n",
        "In diesem Schritt gehen wir wie folgt vor:\n",
        "\n",
        "* Festlegung von Qualitätsmaßstäben für die Bewertung der Leistung der weitreichenden CX.\n",
        "* Berechnung von Erwartungswerten von Pauli-Operatoren aus rohen Messergebnissen.\n",
        "* Verwenden Sie diese, um die Treue des erzeugten Bell-Zustands zu berechnen.\n",
        "\n",
        "In einer geräuschlosen Simulation werden wir überprüfen, ob die Genauigkeitsmetrik für die konstruierten Schaltungen den Wert $1$ annimmt. In den Experimenten mit den realen QPUs wird diese Analyse ein klares Bild davon liefern, wie gut die dynamischen Schaltungen im Vergleich zur unitären Basisimplementierung abschneiden.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "4ba303da",
      "metadata": {},
      "source": [
        "<span id=\"quality-metrics\" />\n",
        "\n",
        "#### Qualitätsmetriken\n",
        "\n",
        "Um den Erfolg des Langreichweiten-CX-Protokolls zu bewerten, messen wir, wie nah der Ausgangszustand am idealen Bell-Zustand liegt. Eine bequeme Möglichkeit, dies zu quantifizieren, besteht darin, die Zustandsgenauigkeit anhand der Erwartungswerte der Pauli-Operatoren zu berechnen. Wir können die Treue für einen Bell-Zustand in Bezug auf den Steuer- und den Zielzustand berechnen, sobald uns die Werte von $\\braket{XX}$, $\\braket{YY}$ und $\\braket{ZZ}$ bekannt sind. Insbesondere gilt:\n",
        "\n",
        "$ F = \\frac{1}{4} (1 + \\braket{XX} - \\braket{YY} + \\braket{ZZ})$\n",
        "\n",
        "Um diese Erwartungswerte aus den Rohmessdaten zu berechnen, definieren wir eine Reihe von Hilfsfunktionen:\n",
        "\n",
        "* **`compute_ZZ_expectation`**: Berechnet bei gegebenen Messwerten den Erwartungswert eines Zwei-Qubit-Pauli-Operators auf der Basis $Z$.\n",
        "* **`compute_fidelity`**: Kombiniert die Erwartungswerte von $XX$, $YY$ und $ZZ$ in den obigen Treueausdruck.\n",
        "* **`get_counts_from_bitarray`**: Dienstprogramm zum Extrahieren von Zählungen aus Backend-Ergebnisobjekten.\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": [
        "Wie bei einer rauschfreien Simulation zu erwarten, betragen die Genauigkeiten sowohl in dynamischen als auch in unitären Schaltungen $1$.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "9ac23eb2",
      "metadata": {},
      "source": [
        "<span id=\"large-scale-hardware-example\" />\n",
        "\n",
        "## Beispiel für groß angelegte Hardware\n",
        "\n",
        "Hier führen wir nun all diese Details zu einem einzigen, groß angelegten Arbeitsablauf zusammen, der anschließend auf echter Quantenhardware ausgeführt wird.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ad4224f2",
      "metadata": {},
      "source": [
        "<span id=\"generate-circuits-for-different-distances\" />\n",
        "\n",
        "### Schaltungen für verschiedene Entfernungen generieren\n",
        "\n",
        "Wir generieren nun CX-Schaltungen mit großer Reichweite für eine Reihe von Qubit-Abständen von bis zu 60 Qubits. Für jede Distanz erstellen wir Kreise, die in den Basen $XX$, $YY$ und $ZZ$ gemessen werden und später zur Berechnung der Treuewerte herangezogen werden.\n",
        "\n",
        "Die Liste der Entfernungen umfasst sowohl Kurz- als auch Langstreckenabstände, wobei `distance = 0` einem CX-Nachbarn entspricht. Dieselben Abstände werden später zum Vergleich auch für die Erstellung der entsprechenden Einheitsschaltungen verwendet.\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": [
        "Nun, da wir sowohl dynamische als auch unitäre Schaltungen für eine Reihe von Entfernungen haben, sind wir bereit für die Transpilation. Zunächst müssen wir ein Backend-Gerät auswählen.\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": [
        "Der folgende Schritt stellt sicher, dass das Backend die Anweisung `if_else` unterstützt, die für die neuere Version der dynamischen Schaltungen erforderlich ist. Da sich diese Funktion noch im Anfangsstadium befindet, fügen wir die `IfElseOp` explizit zum Backend-Ziel hinzu, wenn sie nicht bereits verfügbar ist.\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",
        "#### Verwenden Sie die Zeichenfolge „Layer Fidelity“, um die „ 1D “-Kette auszuwählen\n",
        "\n",
        "Da wir die Leistung von dynamischen und unitären Schaltungen auf einer 1D Kette vergleichen wollen, verwenden wir den Layer Fidelity String, um eine lineare Topologie der besten Kette von Qubits aus dem Gerät auszuwählen. Dadurch wird sichergestellt, dass beide Arten von Schaltungen unter denselben Konnektivitätsbedingungen umgesetzt werden, was einen fairen Vergleich ihrer Leistung ermöglicht.\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",
        "### Visualisieren Sie die für den LRCX-Schaltkreis verwendeten Qubits\n",
        "\n",
        "In diesem Abschnitt wird untersucht, wie die LRCX-Schaltung auf die Hardware abgebildet wird. Wir beginnen mit der Visualisierung der physikalischen Qubits, die in der Schaltung verwendet werden, und untersuchen dann, wie sich der Abstand zwischen Kontrolle und Ziel im Layout auf die Anzahl der Operationen auswirkt.\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": [
        "Als Nächstes führen wir das Experiment auf dem eigentlichen Backend durch. Außerdem nutzen wir Batching, um das Experiment über mehrere Durchläufe hinweg effizient durchzuführen. Durch die Durchführung wiederholter Versuche können wir Durchschnittswerte berechnen, um einen genaueren Vergleich zwischen der unitären und der dynamischen Methode anzustellen, und zudem deren Variabilität quantifizieren, indem wir die Abweichungen zwischen den einzelnen Durchläufen vergleichen.\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": [
        "Wir berechnen die Genauigkeit für die dynamischen CX-Schaltungen mit großer Reichweite. Für jede Distanz extrahieren wir die Messergebnisse aus den Datenbanken $\\braket{XX}$, $\\braket{YY}$ und $\\braket{ZZ}$. Diese Ergebnisse werden mithilfe der zuvor definierten Hilfsfunktionen kombiniert, um die Treue gemäß $F = \\tfrac{1}{4} \\big( 1 + \\langle XX \\rangle - \\langle YY \\rangle + \\langle ZZ \\rangle \\big)$ zu berechnen. Dies liefert die beobachtete Treue des dynamisch ausgeführten Protokolls bei jedem Abstand.\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": [
        "Jetzt berechnen wir die Treue für die unitäre CX-Schaltung mit großer Reichweite, und zwar auf dieselbe Weise wie für die dynamischen Schaltungen oben.\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",
        "### Zeichnen Sie die Ergebnisse auf\n",
        "\n",
        "Um die Ergebnisse visuell zu veranschaulichen, sind in der folgenden Grafik die geschätzten Gate-Treffgenauigkeiten dargestellt, die bei unterschiedlichen Abständen zwischen verschränkten Qubits für jede Methode gemessen wurden.\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": [
        "Aus dem obigen Diagramm geht hervor, dass LRCX nicht durchgängig besser abschneidet als die direkte unitäre Implementierung. Bei kurzen Abständen zwischen Steuerung und Ziel erreicht die unitäre Schaltung sogar eine höhere Genauigkeit. Bei größeren Abständen beginnt die dynamische Schaltung jedoch, eine bessere Genauigkeit zu erreichen als die unitäre Implementierung. Dieses Verhalten ist bei aktueller Hardware nicht unerwartet: Dynamische Schaltungen verringern zwar die Schaltungstiefe, indem sie lange SWAP-Ketten vermeiden, führen aber durch Messungen in der Mitte der Schaltung, klassische Feedforward- und Steuerpfad-Verzögerungen zu zusätzlicher Schaltungszeit. Die zusätzliche Latenzzeit erhöht die Dekohärenz und die Auslesefehler, was bei kurzen Entfernungen die Tiefeneinsparungen aufwiegen kann.\n",
        "\n",
        "Dennoch beobachten wir einen Übergangspunkt, an dem der dynamische Ansatz den einheitlichen Ansatz übertrifft. Dies ist eine direkte Folge der unterschiedlichen Skalierung: die Tiefe der unitären Schaltung wächst linear mit dem Abstand zwischen den Qubits, während die Tiefe der dynamischen Schaltung konstant bleibt.\n",
        "\n",
        "**Wichtigste Punkte:**\n",
        "\n",
        "* **Unmittelbarer Nutzen von dynamischen Schaltungen:** Die Hauptmotivation ist heute die Verringerung der *Zwei-Qubit-Tiefe*, nicht unbedingt die Verbesserung der Wiedergabetreue.\n",
        "* **Warum die Genauigkeit heute schlechter sein kann:** Erhöhte Schaltzeiten durch Messungen und klassische Operationen dominieren oft, insbesondere wenn der Abstand zwischen Steuerung und Ziel klein ist.\n",
        "* **Ein Blick in die Zukunft:** Mit der Verbesserung der Hardware, insbesondere durch schnelleres Auslesen, kürzere Latenzzeiten bei der klassischen Steuerung und geringeren Overhead in der Mitte des Schaltkreises, sollten wir erwarten, dass sich diese Verringerung der Tiefe und Dauer in messbarem Zuwachs an Wiedergabetreue niederschlägt.\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": [
        "Diese Darstellung der Zwei-Qubit-Tiefe verdeutlicht den Hauptvorteil des mit dynamischen Schaltungen implementierten LRCX: Die Leistung bleibt im Wesentlichen konstant, wenn der Abstand zwischen Kontroll- und Ziel-Qubit zunimmt. Im Gegensatz dazu wächst die unitäre Implementierung aufgrund der erforderlichen SWAP-Ketten linear mit der Entfernung. Die Tiefe erfasst die logische Skalierung von Zwei-Qubit-Operationen, während die Anzahl der Messungen den zusätzlichen Overhead für dynamische Schaltungen widerspiegelt. Diese Messungen sind effizient, da sie parallel durchgeführt werden, aber sie verursachen auf der heutigen Hardware immer noch feste Kosten.\n",
        "\n",
        "Warum die Genauigkeit heute schlechter sein kann: Erhöhte Schaltzeiten durch Messungen und klassische Operationen dominieren oft, insbesondere wenn der Abstand zwischen Steuerung und Ziel klein ist. So beträgt beispielsweise die durchschnittliche Ausleselänge eines Heron r2 Prozessors 2.280 ns, während seine 2Q Gatterlänge nur 68 ns beträgt.\n",
        "\n",
        "Da sich die Mess- und klassischen Latenzzeiten verbessern, erwarten wir, dass die Skalierung dynamischer Schaltungen mit konstanter Tiefe und konstanter Messung bei größeren Schaltungen deutliche Vorteile in Bezug auf Genauigkeit und Laufzeit bringt.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "dae8ad4a",
      "metadata": {},
      "source": [
        "<span id=\"next-steps\" />\n",
        "\n",
        "## Nächste Schritte\n",
        "\n",
        "Wenn Sie diese Arbeit interessant fanden, könnten Sie auch an folgenden Materialien interessiert sein:\n",
        "\n",
        "<Admonition type=\"tip\" title=\"Empfehlungen\">\n",
        "  * [Vergleich dynamischer Schaltungen mit abgeschnittenen Bell-Paaren](/docs/tutorials/edc-cut-bell-pair-benchmarking)\n",
        "  * [Simulation des „Kicked“-Ising-Hamiltonian mit dynamischen Schaltkreisen](/docs/tutorials/dc-hex-ising)\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "cc5af2f9",
      "metadata": {},
      "source": [
        "<span id=\"references\" />\n",
        "\n",
        "## Referenzen\n",
        "\n",
        "<a id=\"ref-1\" />\n",
        "\n",
        "\\[1] Efficient Long-Range Entanglement using Dynamic Circuits, von *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
}