{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "727f8133-a32e-4bd9-a6c3-1a1c580e06bd",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Verbesserung der Erwartungswerte durch propagierte Lärmabsorption (PNA)\"\n",
        "description: \"Verwenden Sie die propagierte Rauschabsorption (PNA) mit Samplomatic und dem Executor-Primitiv, um das Rauschen von Zwei-Qubit-Gattern bei der Erwartungswertschätzung zu mindern\"\n",
        "---\n",
        "\n",
        "{/* cspell:ignore pstr expvals postsel broadcastable fontsize borderaxespad reweighting */}\n",
        "\n",
        "<span id=\"improve-expectation-values-with-propagated-noise-absorption-pna\" />\n",
        "\n",
        "# Verbesserung der Erwartungswerte durch propagierte Lärmabsorption (PNA)\n",
        "\n",
        "*Geschätzte Laufzeit: 10 Minuten auf einem Heron-Prozessor (HINWEIS: Dies ist lediglich eine Schätzung. (Die Laufzeit kann variieren.)*\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a1b2c3d4-0001-4000-8000-000000000001",
      "metadata": {},
      "source": [
        "<span id=\"learning-outcomes\" />\n",
        "\n",
        "## Lernziele\n",
        "\n",
        "* Was die propagierte Rauschabsorption (PNA) ist und wie sie das Rauschen von Zwei-Qubit-Gattern mindert, indem sie erlernte inverse Rauschkanäle in die gemessene Observable absorbiert\n",
        "* So verwenden Sie [`samplomatic`](/docs/guides/directed-execution-model#samplomatic) zum Einrahmen und Kommentieren von Schaltungsebenen für das Drehen, Basisänderungen und die Rauscheinjektion\n",
        "* Wie man mit `[NoiseLearnerV3`]\\( /docs/guides/directed-execution-model#noiselearnerv3 ) Schichtenrauschen lernt und es in eine rauschmindernde Observable überführt mit [`qiskit-addon-pna`](https://github.com/Qiskit/qiskit-addon-pna)\n",
        "* Wie man mit den Klassen [`Executor`](/docs/guides/get-started-with-executor) und `QuantumProgram` in randomisierte Schaltungen abtastet `qiskit-ibm-runtime`und PNA mit TREX und einer Nachselektion kombiniert\n",
        "\n",
        "<span id=\"prerequisites\" />\n",
        "\n",
        "## Voraussetzungen\n",
        "\n",
        "* Der [Qiskit-Patterns](/docs/guides/intro-to-patterns) -Workflow\n",
        "* Berechnung der Erwartungswerte einer beobachtbaren Größe mithilfe der [Estimator](/docs/guides/get-started-with-estimator) -Primitive\n",
        "* Fehlerminderungstechniken wie „Pauli Twirling“ und „TREX“, die im Abschnitt [„Kombination von Fehlerminderungsoptionen mit der Estimator-Primitive“](/docs/tutorials/combine-error-mitigation-techniques) behandelt werden\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a1b2c3d4-0002-4000-8000-000000000002",
      "metadata": {},
      "source": [
        "<span id=\"background\" />\n",
        "\n",
        "## Hintergrund\n",
        "\n",
        "In diesem Tutorial zeigen wir, wie man erweiterte Tools zur Fehlerminderung in Qiskit nutzen kann, um die Schätzung des Erwartungswerts bei verrauschten Quantenexperimenten zu verbessern.\n",
        "\n",
        "<span id=\"what-is-propagated-noise-absorption-pna\" />\n",
        "\n",
        "### Was ist propagierte Schallabsorption (PNA)?\n",
        "\n",
        "*Die propagierte Rauschabsorption ist eine Technik zur Minderung von Gatterfehlern, bei der die Observable durch den inversen Rauschkanal geleitet wird, der Zwei-Qubit-Gatter beeinflusst, wodurch eine rauschmindernde Observable entsteht.*\n",
        "\n",
        "Mithilfe der Quantenfehlerminderung können wir aus verrauschter Quantenhardware nützliche Erwartungswerte gewinnen, ohne dass eine vollständige Fehlertoleranz erforderlich ist. PNA konzentriert sich speziell darauf, Störeffekte in die Messgröße selbst zu integrieren, anstatt die Funktionsweise der Schaltung zu verändern.\n",
        "\n",
        "Jedes rauschbehaftete Gatter in einer Quantenschaltung lässt sich als ideales Gatter modellieren, auf das ein Rauschkanal folgt. PNA erlernt oder charakterisiert diese Rauschkanäle und definiert deren Inverse. Anstatt die inversen Operationen in die Hardware-Ausführung einzubinden (was in der Regel nicht machbar ist), leitet PNA die inversen Rauschkanäle durch die Schaltung weiter und wendet sie auf die Observable an. Dieser Prozess wandelt den Observablen $O$ in einen neuen Operator $\\tilde{O}$ um, sodass die Messung von $\\tilde{O}$ an einer verrauschten Schaltung denselben Erwartungswert ergibt wie die Messung von $O$ an einer idealen, rauschfreien Schaltung.\n",
        "\n",
        "Dies lässt sich wie folgt zusammenfassen:\n",
        "\n",
        "1. Modellieren Sie jedes verrauschte Gatter $u_i$ als eine ideale Operation, auf die ein Rauschkanal $\\Lambda_i$ folgt.\n",
        "2. Ermitteln oder schätzen Sie die einzelnen „ $\\Lambda_i$ “ mithilfe von Tools zur Rauschcharakterisierung.\n",
        "3. Definieren und übertragen Sie die inversen Rauschkarten $\\Lambda_i^{-1}$ mithilfe von Pauli-Transfer-Verfahren vorwärts durch die Schaltung.\n",
        "4. Integrieren Sie diese Inversen in die Observable, was zu einem rauschreduzierten Operator $\\tilde{O}$ führt.\n",
        "\n",
        "Wird das Rauschen als Pauli-Kanal (oder allgemeiner als spärlicher Pauli-Lindblad-Kanal) beschrieben, lässt sich diese Ausbreitung mithilfe der Pauli-Ausbreitung effizient durchführen. Die Pauli-Ausbreitung bietet einen Rahmen, um zu approximieren, wie sich inverse Rauschkanäle beim Durchlaufen von Schichten aus Clifford- und Nicht-Clifford-Operationen transformieren, und gleichzeitig die Rechenkomplexität zu kontrollieren.\n",
        "\n",
        "Indem die Kompensation in den beobachtbaren Bereich verlagert wird, vermeidet PNA den hohen Abtast-Overhead, der andernfalls durch das Einfügen physikalischer Korrekturoperationen in die Schaltung entstehen würde. Stattdessen wird die ursprüngliche, rauschbehaftete Schaltung ausgeführt, während die Observable in einen neuen Operator $\\tilde{O}$ umgewandelt wird, dessen Erwartungswert die Auswirkungen des Rauschens aufhebt.\n",
        "\n",
        "<span id=\"the-pna-workflow\" />\n",
        "\n",
        "### Der PNA-Workflow\n",
        "\n",
        "Der Prozess lässt sich anhand der folgenden konzeptionellen Phasen nachvollziehen. Die erste schematische Darstellung veranschaulicht ein typisches Experiment mit Störsignalen.\n",
        "\n",
        "![Lautes Experiment](https://quantum.cloud.ibm.com/docs/images/tutorials/propagated-noise-absorption/noisy_expt.avif)\n",
        "\n",
        "Wenn wir das Rauschmodell trainieren, können wir dessen Inverse anwenden und das Rauschen auslöschen.\n",
        "\n",
        "![Experiment zur Rauschunterdrückung](https://quantum.cloud.ibm.com/docs/images/tutorials/propagated-noise-absorption/noise_mitigated_expt.avif)\n",
        "\n",
        "Anstatt den inversen Rauschkanal durch Abtastung auf der QPU zu implementieren, wie dies bei der probabilistischen Fehlerkorrektur (PEC) der Fall ist, wenden wir ihn klassisch unter Verwendung der Pauli-Propagation auf die gemessene Observable an. Die daraus resultierende Observable mildert das gelernte Gate-Rauschen bei der Messung effektiv ab.\n",
        "\n",
        "![PNA-Übersicht](https://quantum.cloud.ibm.com/docs/images/tutorials/propagated-noise-absorption/pna_overview.avif)\n",
        "\n",
        "<span id=\"modular-error-mitigation-with-samplomatic-and-executor\" />\n",
        "\n",
        "### Modulare Fehlerbehebung mit Samplomatic und Executor\n",
        "\n",
        "Der PNA-Ansatz baut auf einer modularen Architektur zur Fehlerminderung in Qiskit auf. Diese Architektur nutzt die [`samplomatic`](https://github.com/Qiskit/samplomatic) Bibliothek in Verbindung mit den `QuantumProgram` Klassen und `Executor` (die unter `qiskit-ibm-runtime`v0.47.0 zu Qiskit Runtime hinzugefügt wurden), um Techniken wie die propagierte Rauschabsorption und das Pauli-Twirling über verschiedene Experimente hinweg kombinierbar und wiederverwendbar zu machen.\n",
        "\n",
        "`Executor`Anstatt die Logik zur Risikominderung in die Schaltungsdefinition selbst einzubetten, wird die Risikominderung deklarativ durch `samplomatic` Annotationen ausgedrückt und programmgesteuert über verwaltet, wodurch gesteuert wird, wie randomisierte Schaltungen generiert, ausgeführt und nachbearbeitet werden.\n",
        "\n",
        "In diesem Tutorial implementieren wir ein [Qiskit-Muster,](/docs/guides/intro-to-patterns) um zu veranschaulichen, wie PNA inverse Pauli-Rauschkanäle weiterleiten und die Observable entsprechend anpassen kann, um die Schätzung von Erwartungswerten auf verrauschten QPUs zu verbessern.\n",
        "\n",
        "<span id=\"workflow-overview\" />\n",
        "\n",
        "### Workflow-Übersicht\n",
        "\n",
        "* **Schritt 1: Abbildung auf das Quantenproblem**\n",
        "  * Konstruieren Sie ein gespiegeltes, „Trotterisiertes“ Kicked-Ising-Modell und eine Zielgröße.\n",
        "* **Schritt 2: Rauschen charakterisieren und verbreiten**\n",
        "  * Verwenden `samplomatic` Sie, um eindeutige Zwei-Qubit-Schichten und Messungen im Schaltkreis zu identifizieren und zu kennzeichnen.\n",
        "  * Erfahren Sie mithilfe von `NoiseLearnerV3`…, welche Art von Rauschen die einzelnen Ebenen jeweils beeinflusst.\n",
        "  * Ordne jede `InjectNoise` Anmerkung dem entsprechenden trainierten Rauschmodell zu.\n",
        "  * Verwenden `qiskit-addon-pna` Sie, um die inversen Rauschkanäle durch die Schaltung weiterzuleiten und sie in die Zielobservable zu integrieren.\n",
        "* **Schritt 3: Durchführung von Quantenexperimenten**\n",
        "  * `Executor`Definieren Sie ein `QuantumProgram` , um eine zufällige Stichprobenziehung über `samplex` festzulegen, und führen Sie die Experimente im Backend mithilfe von durch.\n",
        "* **Schritt 4: Rekonstruktion und Analyse der Ergebnisse**\n",
        "  * Vergleichen Sie die Strategien zur Risikominderung (PNA, PNA+TREX, PNA+PS, PNA+PS+TREX) und stellen Sie die Verbesserung gegenüber den Ergebnissen ohne Risikominderungsmaßnahmen dar.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "3b64ad83-0000-4000-8000-000000000003",
      "metadata": {},
      "source": [
        "<span id=\"requirements\" />\n",
        "\n",
        "## Anforderungen\n",
        "\n",
        "Bevor Sie mit dieser Anleitung beginnen, stellen Sie bitte sicher, dass Folgendes installiert ist:\n",
        "\n",
        "* Qiskit SDK v2.2 oder höher, mit [Visual](/docs/api/qiskit/visualization) isierungsunterstützung\n",
        "* Qiskit Runtime v0.47 oder später (`pip install qiskit-ibm-runtime`)\n",
        "* Samplomatic v0.13 oder höher (`pip install samplomatic`)\n",
        "* PNA-Qiskit-Add-on (`pip install qiskit-addon-pna`)\n",
        "* Qiskit-Add-on „utils“ (`pip install qiskit-addon-utils`)\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "28ad8a65-0000-4000-8000-000000000004",
      "metadata": {},
      "source": [
        "<span id=\"setup\" />\n",
        "\n",
        "## Einrichtung\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "id": "3a851a58-0000-4000-8000-000000000005",
      "metadata": {},
      "outputs": [],
      "source": [
        "from qiskit import QuantumCircuit\n",
        "from qiskit.quantum_info import Pauli, SparsePauliOp\n",
        "from qiskit.transpiler import generate_preset_pass_manager, PassManager\n",
        "from qiskit_ibm_runtime import (\n",
        "    QiskitRuntimeService,\n",
        "    QuantumProgram,\n",
        "    Executor,\n",
        "    NoiseLearnerV3,\n",
        ")\n",
        "from qiskit_addon_utils.exp_vals.measurement_bases import (\n",
        "    get_measurement_bases,\n",
        ")\n",
        "from qiskit_addon_utils.exp_vals.expectation_values import (\n",
        "    executor_expectation_values,\n",
        ")\n",
        "from qiskit_addon_utils.noise_management import trex_factors\n",
        "from qiskit_addon_utils.noise_management.post_selection import PostSelector\n",
        "from qiskit_addon_utils.noise_management.post_selection.transpiler.passes import (\n",
        "    AddPostSelectionMeasures,\n",
        "    AddSpectatorMeasures,\n",
        ")\n",
        "from qiskit_addon_pna import generate_noise_mitigating_observable\n",
        "import samplomatic\n",
        "from samplomatic.transpiler import generate_boxing_pass_manager\n",
        "from samplomatic.annotations import InjectNoise\n",
        "from samplomatic.utils import get_annotation, find_unique_box_instructions\n",
        "import numpy as np\n",
        "import matplotlib.pyplot as plt\n",
        "\n",
        "\n",
        "# Selects a connected chain of low-error qubits on the target backend.\n",
        "# The mirrored kicked-Ising circuit is a 1D chain, so we only need a line\n",
        "# of connected physical qubits; the helper walks the backend's coupling map\n",
        "# and grows a chain along the lowest-error two-qubit edges, so it works for\n",
        "# any backend rather than relying on a hardcoded layout.\n",
        "def find_qubit_chain(backend, length):\n",
        "    \"\"\"Find a connected chain of ``length`` physical qubits on ``backend``.\n",
        "\n",
        "    The chain is grown greedily along the lowest-error two-qubit edges, so it\n",
        "    favors better-performing qubits. Because the mirrored kicked-Ising circuit\n",
        "    is a 1D chain, a connected line is all we need.\n",
        "    \"\"\"\n",
        "    target = backend.target\n",
        "\n",
        "    # Identify the native two-qubit gate and build a per-edge error lookup.\n",
        "    two_qubit_gate = next(\n",
        "        name\n",
        "        for name in target.operation_names\n",
        "        if target[name]\n",
        "        and all(q is not None and len(q) == 2 for q in target[name])\n",
        "    )\n",
        "    edge_error = {\n",
        "        frozenset(qargs): (\n",
        "            1.0 if props is None or props.error is None else props.error\n",
        "        )\n",
        "        for qargs, props in target[two_qubit_gate].items()\n",
        "    }\n",
        "\n",
        "    graph = backend.coupling_map.graph.to_undirected(multigraph=False)\n",
        "    neighbors = {n: list(graph.neighbors(n)) for n in graph.node_indices()}\n",
        "\n",
        "    def first_chain_from(start):\n",
        "        path, visited = [start], {start}\n",
        "\n",
        "        def grow():\n",
        "            if len(path) == length:\n",
        "                return True\n",
        "            node = path[-1]\n",
        "            order = sorted(\n",
        "                neighbors[node],\n",
        "                key=lambda m: edge_error.get(frozenset((node, m)), 1.0),\n",
        "            )\n",
        "            for nxt in order:\n",
        "                if nxt not in visited:\n",
        "                    visited.add(nxt)\n",
        "                    path.append(nxt)\n",
        "                    if grow():\n",
        "                        return True\n",
        "                    path.pop()\n",
        "                    visited.remove(nxt)\n",
        "            return False\n",
        "\n",
        "        return path if grow() else None\n",
        "\n",
        "    def chain_cost(path):\n",
        "        return sum(\n",
        "            edge_error.get(frozenset((path[i], path[i + 1])), 1.0)\n",
        "            for i in range(len(path) - 1)\n",
        "        )\n",
        "\n",
        "    # Try low-degree qubits first (the natural ends of long chains) and keep\n",
        "    # the lowest-error chain found.\n",
        "    best_path, best_cost = None, float(\"inf\")\n",
        "    for start in sorted(neighbors, key=lambda n: len(neighbors[n])):\n",
        "        chain = first_chain_from(start)\n",
        "        if chain is not None and (cost := chain_cost(chain)) < best_cost:\n",
        "            best_path, best_cost = chain, cost\n",
        "\n",
        "    if best_path is None:\n",
        "        raise ValueError(\n",
        "            f\"Could not find a connected chain of {length} qubits \"\n",
        "            f\"on '{backend.name}'.\"\n",
        "        )\n",
        "    return best_path"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a1b2c3d4-0006-4000-8000-000000000006",
      "metadata": {},
      "source": [
        "<span id=\"small-scale-simulator-example\" />\n",
        "\n",
        "## Beispiel für einen Simulator im kleinen Maßstab\n",
        "\n",
        "PNA mindert das *physikalische* Rauschen des Zwei-Qubit-Gates eines bestimmten Quantenprozessors. Der Arbeitsablauf hängt von zwei Hardware-Diensten ab, für die es in einem idealen Simulator keine sinnvolle Entsprechung gibt:\n",
        "\n",
        "* `NoiseLearnerV3` charakterisiert experimentell den spärlichen Pauli-Lindblad-Rauschkanal, der mit jeder einzelnen Zwei-Qubit-Schicht der transpilierten Schaltung verbunden ist. Bei einem rauschfreien Simulator gibt es kein Rauschen, das man lernen könnte, und der propagierte inverse Kanal wäre die Identitätsfunktion.\n",
        "* Die `Executor` Primitive nimmt Proben der verdrehten, zufällig angeordneten Schaltkreise, die von `samplomatic` auf einem Backend generiert wurden.\n",
        "\n",
        "Grundsätzlich könnte man ein synthetisches Rauschmodell verwenden. `generate_noise_mitigating_observable`Sie könnten beispielsweise Anweisungen mit [Qiskit Aer](https://github.com/Qiskit/qiskit-aer) verknüpfen `PauliLindbladError` und die daraus resultierende Schaltung mit Rauschen direkt an weiterleiten. Dies dient jedoch lediglich dazu, die klassische Buchführung anhand des von Ihnen selbst eingefügten Rauschens zu validieren, und verschleiert den Sinn dieser Technik. Aus diesem Grund überspringen wir das Simulator-Beispiel und demonstrieren den vollständigen PNA-Workflow direkt auf der Hardware, wobei jeder Schritt des Qiskit-Musters im Folgenden einzeln erläutert wird.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "306f6858-0000-4000-8000-000000000007",
      "metadata": {},
      "source": [
        "<span id=\"large-scale-hardware-example\" />\n",
        "\n",
        "## Beispiel für groß angelegte Hardware\n",
        "\n",
        "Wir führen nun den gesamten PNA-Workflow auf einem Ising-Modell mit 30 Knoten und „Kick“-Mechanismus durch, das auf einer „ IBM Quantum® “-Hardware ausgeführt wird, wobei wir die vier Schritte eines Qiskit-Musters befolgen.\n",
        "\n",
        "<span id=\"step-1-map-to-a-quantum-problem\" />\n",
        "\n",
        "### Schritt 1: Abbildung auf ein Quantenproblem\n",
        "\n",
        "**Erzeuge die gespiegelte Trotter-Schaltung und die Observable**\n",
        "\n",
        "In diesem Experiment werden wir die zeitliche Dynamik eines Ising-Modells mit 30 Knoten und „Kick“-Effekt auf einer Spin-Kette vom Typ „ 1D “ untersuchen. Der betrachtete Hamilton-Operator lautet:\n",
        "\n",
        "$H = -J\\sum\\limits_{\\langle i,j \\rangle} Z_iZ_j + h\\sum\\limits_iX_i$,\n",
        "\n",
        "wobei $J>0$ die Kopplung der Spins der nächsten Nachbarn beschreibt, $i<j$, und das globale Transversalfeld, $h$, auf $\\frac{\\pi}{8}$ festgelegt ist. Je weiter $h$ von einem Clifford-Winkel entfernt ist (d. h. $\\theta=n\\frac{\\pi}{2}, n \\in \\mathbb{Z}$ ), desto schwieriger wird es, die Anti-Rausch-Generatoren durch die Schaltung zu propagieren.\n",
        "\n",
        "Als Observable wählen wir die durchschnittliche Magnetisierung an einem einzelnen Gitterpunkt, $\\frac{1}{N} \\sum_{i=1}^{N} \\langle z_i \\rangle$, wobei $N$ die Anzahl der Gitterpunkte ist.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "8fcb5af8-64f0-498a-821f-11bcd7aea203",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/propagated-noise-absorption/extracted-outputs/8fcb5af8-64f0-498a-821f-11bcd7aea203-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 2,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "num_qubits = 30\n",
        "num_trotter_steps = 10\n",
        "rx_angle = np.pi / 8\n",
        "\n",
        "# Avg single-site magnetization\n",
        "id_pauli = Pauli(\"I\" * num_qubits)\n",
        "observable = (\n",
        "    SparsePauliOp([id_pauli.dot(Pauli(\"Z\"), [i]) for i in range(num_qubits)])\n",
        "    / num_qubits\n",
        ")\n",
        "\n",
        "# Implement Trotterized kicked-Ising model\n",
        "circuit = QuantumCircuit(num_qubits)\n",
        "for _step in range(num_trotter_steps):\n",
        "    circuit.rx(rx_angle, range(num_qubits))\n",
        "    for first_qubit in (1, 2):\n",
        "        for idx in range(first_qubit, num_qubits, 2):\n",
        "            # equivalent to Rzz(-pi/2):\n",
        "            circuit.sdg([idx - 1, idx])\n",
        "            circuit.cz(idx - 1, idx)\n",
        "# Append the inverse circuit to complete the mirroring\n",
        "circuit.compose(circuit.inverse(), inplace=True)\n",
        "circuit.measure_active()\n",
        "circuit.draw(\"mpl\", fold=-1)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c4216992-0000-4000-8000-000000000008",
      "metadata": {},
      "source": [
        "<span id=\"step-2-optimize-the-problem-for-hardware-execution\" />\n",
        "\n",
        "### Schritt 2: Optimierung des Problems für die Ausführung auf der Hardware\n",
        "\n",
        "Der nächste Schritt besteht darin, unsere gespiegelte Trotter-Schaltung für die Ausführung auf einer „Real- IBM Quantum “-Hardware vorzubereiten. Die Ausführung auf einer QPU erfordert mehr als nur den Aufbau der abstrakten Schaltung, da wir diese so optimieren müssen, dass:\n",
        "\n",
        "1. **Es berücksichtigt die nativen Gate-Einstellungen und die Konnektivität des Backends.**\n",
        "   Bei der Transpilation wird die logische Schaltung in eine ISA-Schaltung abgebildet, die vom Ziel-Backend unterstützt wird. Dadurch wird sichergestellt, dass jede Interaktion zwischen einem Gatter und einem Qubit physikalisch realisierbar ist.\n",
        "\n",
        "2. **Wir können das Rauschen auf der Ebene der Schaltungsschichten charakterisieren.**\n",
        "   PNA basiert auf dem Erlernen und der Weiterverbreitung inverser Rauschkanäle. Um dies effizient zu bewerkstelligen, zerlegen wir den transpilierten Schaltkreis in einzelne „boxed“-Schichten mit jeweils zwei Qubits. Mit diesen Feldern können wir jede Schicht des Netzwerks mit einem eigenen, trainierten Rauschmodell verknüpfen.\n",
        "\n",
        "3. **Wir können realistische Rauschmodelle in PNA einspeisen.**\n",
        "   Sobald die Schaltung in eine Box verpackt wurde, nutzen wir den `NoiseLearnerV3` Dienst, um experimentell die Pauli-Rauschkanäle zu ermitteln, die jede einzelne Zwei-Qubit-Schicht beeinflussen. Diese trainierten Modelle werden dann über die Annotationen von Samplomatic wieder mit der Schaltung verknüpft.\n",
        "\n",
        "Insgesamt schließt dieser Schritt die Lücke zwischen einer idealisierten Spiegelungsschaltung und einer hardwarefähigen Schaltung mit erlernten Rauschmodellen. Mit dieser Konfiguration kann PNA inverse Rauschkanäle durch die Schaltung leiten und die Observable entsprechend anpassen.\n",
        "\n",
        "<span id=\"connect-to-the-backend-and-transpile-to-an-isa-circuit\" />\n",
        "\n",
        "#### Mit dem Backend verbinden und in eine ISA-Schaltung transpilieren\n",
        "\n",
        "Zunächst initialisieren wir den „ IBM Quantum “-Compute-Service und wählen ein Backend aus. Melden Sie sich mit Ihrem eigenen Konto an, indem Sie den [Anweisungen zum Speichern Ihrer Anmeldedaten](/docs/guides/cloud-setup) folgen. Danach `QiskitRuntimeService()` werden diese automatisch abgerufen.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "5c6ee966-0000-4000-8000-000000000009",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Selected backend: ibm_fez\n"
          ]
        }
      ],
      "source": [
        "# Initialize IBM Quantum Compute Service using your saved credentials\n",
        "service = QiskitRuntimeService()\n",
        "\n",
        "backend = service.least_busy(\n",
        "    operational=True, simulator=False, min_num_qubits=num_qubits\n",
        ")\n",
        "# Re-fetch with fractional gates enabled (least_busy does not forward this)\n",
        "# Fractional gates are enabled so the non-Clifford Rx rotations are supported natively.\n",
        "backend = service.backend(backend.name, use_fractional_gates=True)\n",
        "print(f\"Selected backend: {backend.name}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "bd2cea49-0000-4000-8000-000000000010",
      "metadata": {},
      "source": [
        "Als Nächstes wählen wir im Backend eine zusammenhängende Kette von Qubits aus und transpilieren die Schaltung darauf.\n",
        "\n",
        "`num_qubits` Mithilfe des `find_qubit_chain` im Abschnitt „Einrichtung“ definierten Hilfsmittels wählen wir eine Reihe zusammenhängender physikalischer Qubits aus. `initial_layout`Anschließend führen wir eine Transpilation mit `optimization_level=0` durch und legen diese Kette als fest, wodurch die Struktur der Zwei-Qubit-Gatter-Ebene der gespiegelten Schaltung exakt erhalten bleibt. Auf dieser Struktur basieren die Schritte zum Boxen und zum Lärmlernen, daher muss hier eine höhere Optimierungsstufe (die die gespiegelten Gatter aufheben würde) vermieden werden.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "id": "8c9afe5e-6fa3-427d-a672-becdb14490b8",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/propagated-noise-absorption/extracted-outputs/8c9afe5e-6fa3-427d-a672-becdb14490b8-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 4,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "# Find a connected, low-error chain of qubits on the chosen backend\n",
        "layout = find_qubit_chain(backend, num_qubits)\n",
        "\n",
        "# Transpile the circuit for the target backend, pinning the chain as the layout.\n",
        "# optimization_level=0 preserves the mirrored two-qubit-gate layers that the\n",
        "# boxing and noise-learning steps rely on.\n",
        "pm = generate_preset_pass_manager(\n",
        "    backend=backend, optimization_level=0, initial_layout=layout\n",
        ")\n",
        "isa_circuit = pm.run(circuit)\n",
        "isa_observable = observable.apply_layout(isa_circuit.layout)\n",
        "isa_circuit.draw(\"mpl\", fold=-1)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "5d7f16f6-0000-4000-8000-000000000011",
      "metadata": {},
      "source": [
        "<span id=\"twirl-the-two-qubit-gate-layers-and-measurements-and-find-unique-layers\" />\n",
        "\n",
        "### Drehe die Zwei-Qubit-Gatter-Schichten und die Messungen und finde eindeutige Schichten\n",
        "\n",
        "Wir verwenden `samplomatic` zur Einrahmung der Schaltung und zur Identifizierung eindeutiger Zwei-Qubit-Schichten. Eine Box ist ein Konstrukt, das Anweisungen zusammenfasst, sodass bestimmte Absichten oder Anmerkungen später einheitlich auf alle Gatter innerhalb derselben Box angewendet werden können.\n",
        "\n",
        "`generate_boxing_pass_manager`Hier wenden wir die Methode an, die über die bloße Identifizierung von Zwei-Qubit-Schichten hinausgeht. Es erfüllt mehrere wichtige Aufgaben:\n",
        "\n",
        "* Gruppiert alle Zwei-Qubit-Schichten im Schaltkreis,\n",
        "* Wendet die `Twirl` Anmerkungen und `ChangeBasis` auf diese Ebenen an,\n",
        "* Gruppiert Messvorgänge in eigenen, eingerahmten Abschnitten und\n",
        "* Wendet die `InjectNoise` Annotation auf jede Zwei-Qubit-Schicht an.\n",
        "\n",
        "Diese Anmerkungen legen fest, wie Rauschen, Basiswechsel und Twirling in der gesamten Schaltung behandelt werden. Außerdem legen sie die Struktur fest, die später zum Erlernen und zur Geräuschunterdrückung verwendet wird.\n",
        "\n",
        "Die wichtigsten Konfigurationsoptionen sind:\n",
        "\n",
        "* `enable_gates`/`enable_measures: True`: Alle Zwei-Qubit-Gatter-Schichten und Endmessungen in Kästchen einfügen. Einzel-Qubit-Gatter sind innerhalb der Kästchen linksbündig ausgerichtet.\n",
        "* `measure_annotations: all`: Fügen Sie `Twirl` und `ChangeBasis` Anmerkungen auf dem Messrahmen ein.\n",
        "* `twirling_strategy: active`: Alle aktiven Qubits in jedem Feld, das Verschränkungsgatter enthält, drehen.\n",
        "* `inject_noise_targets: gates`: Füge `InjectNoise` allen `Twirl`mit „-“ gekennzeichneten Feldern, die Verflechtungsgatter enthalten, Anmerkungen hinzu.\n",
        "* `inject_noise_strategy: uniform_modification`: Alle Rauschschichten im gesamten Schaltkreis gleichmäßig skalieren.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 5,
      "id": "d4364c0b-2ddc-473d-8171-fd39b30a1e2d",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Box up circuit with Twirl and InjectNoise annotations\n",
        "pm = generate_boxing_pass_manager(\n",
        "    enable_gates=True,\n",
        "    enable_measures=True,\n",
        "    measure_annotations=\"all\",\n",
        "    twirling_strategy=\"active\",\n",
        "    inject_noise_targets=\"gates\",\n",
        "    inject_noise_strategy=\"uniform_modification\",\n",
        ")\n",
        "boxed_circuit = pm.run(isa_circuit)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 6,
      "id": "9c911a29-553c-4ffe-94de-1442f25e3116",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/propagated-noise-absorption/extracted-outputs/9c911a29-553c-4ffe-94de-1442f25e3116-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 6,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "draw_circ = QuantumCircuit(boxed_circuit.num_qubits)\n",
        "draw_circ.append(boxed_circuit.data[0], qargs=boxed_circuit.data[0].qubits)\n",
        "draw_circ.append(boxed_circuit.data[1], qargs=boxed_circuit.data[1].qubits)\n",
        "draw_circ.draw(\"mpl\", fold=-1, scale=0.3, idle_wires=False)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "cc283451-0000-4000-8000-000000000014",
      "metadata": {},
      "source": [
        "Generieren Sie die Vorlagenschaltung und `samplex`, die festlegen, wie die Schaltung abgetastet wird.\n",
        "\n",
        "`Executor`Hier fügen wir außerdem Zuschauer- und Nachauswahlmessungen hinzu, die erforderlich sind, um eine Nachauswahl an den vom … ausgegebenen Stichproben durchzuführen.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 7,
      "id": "a0ca9fbb-c572-4d49-bdef-e056c1bc82e3",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Build template circuit and samplex for later use with the \"Executor\"\n",
        "template_circuit, samplex = samplomatic.build(boxed_circuit)\n",
        "\n",
        "# Add post-selection instructions to the template circuit\n",
        "post_selection_pm = PassManager(\n",
        "    [\n",
        "        AddSpectatorMeasures(backend.coupling_map),\n",
        "        AddPostSelectionMeasures(x_pulse_type=\"rx\"),\n",
        "    ]\n",
        ")\n",
        "template_circuit = post_selection_pm.run(template_circuit)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 8,
      "id": "8d46f7b1-f0e9-4bc3-ba0f-5cc96090a945",
      "metadata": {
        "jupyter": {
          "source_hidden": true
        }
      },
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/propagated-noise-absorption/extracted-outputs/8d46f7b1-f0e9-4bc3-ba0f-5cc96090a945-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 8,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "draw_circ = template_circuit.copy_empty_like()\n",
        "draw_circ.data = template_circuit.data[:324]\n",
        "draw_circ.draw(\"mpl\", fold=-1, scale=0.3, idle_wires=False)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a45820c6-0000-4000-8000-000000000015",
      "metadata": {},
      "source": [
        "<span id=\"learn-the-noise-using-noiselearnerv3\" />\n",
        "\n",
        "### Lerne den Klang mithilfe von `NoiseLearnerV3`\n",
        "\n",
        "Bevor wir PNA zur Fehlerminderung einsetzen können, müssen wir zunächst das Rauschen charakterisieren, das auf jede einzelne Zwei-Qubit-Schicht und die Messschicht in unserer Schaltung einwirkt. Dazu verwenden wir das `NoiseLearnerV3` Programm, um für jede der zuvor identifizierten Schichten Rauschmodelle experimentell zu erlernen. Der Lernende führt Benchmarking-ähnliche Experimente durch, mit denen der Rauschkanal für jede Schicht geschätzt wird, und gibt ein Ergebnisobjekt zurück, das das gelernte Modell enthält.\n",
        "\n",
        "Zunächst identifizieren wir die einzelnen Schichten in unserer Schaltung mithilfe von `find_unique_box_instructions` aus `samplomatic`. Dadurch wird sichergestellt, dass wir das Rauschen pro Schichttyp nur einmal trainieren, wodurch die Anzahl der Experimente und die Gesamtkosten minimiert werden. Die daraus resultierende Liste der Schichten wird an den Noise-Learner übergeben.\n",
        "\n",
        "Es gibt einige wichtige Parameter, die bestimmen, wie das Rauschen gelernt wird:\n",
        "\n",
        "* `num_randomizations`: Anzahl der pro Lernkonfiguration verwendeten Zufallskreisläufe.\n",
        "* `shots_per_randomization`: Anzahl der Schüsse pro zufälligem Lernzyklus.\n",
        "* `layer_pair_depths`: Die Schaltungstiefen (gemessen in der Anzahl der Paare), die in den Lernexperimenten verwendet werden sollen.\n",
        "* `post_selection`: Ermöglicht eine flankenbasierte Nachauswahl mithilfe von `rx` Gatter, um Nachmessimpulse anzulegen.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 9,
      "id": "24670819-0000-4000-8000-000000000016",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Noise learning parameters\n",
        "num_randomizations_nl = 64\n",
        "shots_per_randomization_nl = 128\n",
        "\n",
        "# Match the deepest noise-learning depth to the depth of the circuit being\n",
        "# mitigated. ``layer_pair_depths`` are measured in layer pairs (a layer + its\n",
        "# inverse), so the deepest value is half the circuits's two-qubit-layer depth.\n",
        "# Learning to this depth improves the quality of the mitigation.\n",
        "\n",
        "depth_2q = isa_circuit.depth(lambda instr: instr.operation.num_qubits == 2)\n",
        "max_layer_pair_depth = depth_2q // 2\n",
        "\n",
        "# Use a fixed schedule of learning depths, but drop any that exceed the circuit's\n",
        "# depth and always cap the deepest value at ``max_layer_pair_depth`` so we never\n",
        "# learn deeper than the circuit being mitigated\n",
        "candidate_depths = [1, 2, 4, 8, 12, 16, 24, 32, 40, 48]\n",
        "layer_pair_depths = sorted(\n",
        "    {d for d in candidate_depths if d < max_layer_pair_depth}\n",
        "    | {max_layer_pair_depth}\n",
        ")\n",
        "\n",
        "# Find the unique instructions (layers) from the boxed-up circuit\n",
        "unique_2q_layers_and_meas = find_unique_box_instructions(\n",
        "    boxed_circuit, normalize_annotations=None, undress_boxes=True\n",
        ")\n",
        "\n",
        "# Configure and run the noise learner on the unique layers.\n",
        "# Options can be passed directly as a dictionary.\n",
        "noise_learner_options = {\n",
        "    \"num_randomizations\": num_randomizations_nl,\n",
        "    \"shots_per_randomization\": shots_per_randomization_nl,\n",
        "    \"layer_pair_depths\": layer_pair_depths,\n",
        "    \"post_selection\": {\n",
        "        \"enable\": True,\n",
        "        \"strategy\": \"edge\",\n",
        "        \"x_pulse_type\": \"rx\",\n",
        "    },\n",
        "    \"environment\": {\"job_tags\": [\"TUT_PNA\"]},\n",
        "}\n",
        "\n",
        "noise_learner = NoiseLearnerV3(backend, noise_learner_options)\n",
        "noise_learner_job = noise_learner.run(unique_2q_layers_and_meas)\n",
        "noise_learner_result = noise_learner_job.result()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "aa248719-0000-4000-8000-000000000017",
      "metadata": {},
      "source": [
        "<span id=\"visualize-the-learned-noise-rates\" />\n",
        "\n",
        "### Visualisierung der gelernten Rauschraten\n",
        "\n",
        "Nachdem wir die Rauschmodelle trainiert haben, können wir die Verteilung der geschätzten Fehlerraten sowohl für Ein-Qubit- als auch für Zwei-Qubit-Operationen untersuchen. Der folgende Code extrahiert die Pauli-Lindblad-Darstellungen aus den erlernten Rauschergebnissen und erfasst die entsprechenden Rauschraten.\n",
        "\n",
        "Für jede trainierte Schicht:\n",
        "\n",
        "* Wir wandeln das Rauschmodell in eine spärliche Liste von `(pstr, qubits, rate)` Tupeln um, wobei `pstr` die auf die gegebenen Qubits wirkende Pauli-Kette und `rate` die zugehörige Fehlerrate ist.\n",
        "* Wir unterteilen die Raten in Ein-Qubit-Terme (`len(pstr) == 1`) und Zwei-Qubit-Terme (`len(pstr) == 2`).\n",
        "* Anschließend werden die Ratenlisten sortiert und ihre Medianwerte berechnet.\n",
        "\n",
        "Wir stellen die Rauschratenverteilungen für ein Qubit (rot) und zwei Qubits (blau) auf einer logarithmischen Skala dar, wobei ihre Medianwerte durch vertikale Linien markiert sind, sodass wir die relativen Größenordnungen der gelernten Pauli-Lindblad-Generatoren vergleichen können. Die Reihenfolge der Raten für Ein- und Zwei-Qubit-Zustände hängt vom jeweiligen Gerät und von den konkret untersuchten Schichten ab; in diesem Durchlauf weisen die Generatoren für Ein-Qubit-Zustände ( weight-1 ) die höhere Medianrate auf.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 10,
      "id": "b1a8ad48-2875-41e0-92c4-c28b34083e6b",
      "metadata": {
        "jupyter": {
          "source_hidden": true
        }
      },
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<matplotlib.legend.Legend at 0x125336120>"
            ]
          },
          "execution_count": 10,
          "metadata": {},
          "output_type": "execute_result"
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/propagated-noise-absorption/extracted-outputs/b1a8ad48-2875-41e0-92c4-c28b34083e6b-1.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "hw_rates_1q = []\n",
        "hw_rates_2q = []\n",
        "for nlr in noise_learner_result[:2]:\n",
        "    plm_list = nlr.to_pauli_lindblad_map().to_sparse_list()\n",
        "    hw_rates_1q += [\n",
        "        rate for (pstr, qubits, rate) in plm_list if len(pstr) == 1\n",
        "    ]\n",
        "    hw_rates_2q += [\n",
        "        rate for (pstr, qubits, rate) in plm_list if len(pstr) == 2\n",
        "    ]\n",
        "hw_rates_1q = sorted(hw_rates_1q)\n",
        "hw_rates_2q = sorted(hw_rates_2q)\n",
        "median_1q = hw_rates_1q[len(hw_rates_1q) // 2]\n",
        "median_2q = hw_rates_2q[len(hw_rates_2q) // 2]\n",
        "fig, ax = plt.subplots(1, 1, figsize=(14, 5))\n",
        "ax.scatter(\n",
        "    (hw_rates_1q),\n",
        "    [(i) / (len(hw_rates_1q) - 1) for i in range(len(hw_rates_1q))],\n",
        "    color=\"red\",\n",
        "    label=\"1q rates\",\n",
        ")\n",
        "ax.set_xscale(\"log\")\n",
        "ax.set_ylim(0, 1.1)\n",
        "ax.vlines(median_1q, 0, 1, color=\"red\")\n",
        "ax.text(median_1q * 1.1, 0.1, f\"{median_1q:.2e}\")\n",
        "ax.scatter(\n",
        "    (hw_rates_2q),\n",
        "    [(i) / (len(hw_rates_2q) - 1) for i in range(len(hw_rates_2q))],\n",
        "    color=\"blue\",\n",
        "    label=\"2q rates\",\n",
        ")\n",
        "ax.set_xscale(\"log\")\n",
        "ax.set_ylim(0, 1.1)\n",
        "ax.vlines(median_2q, 0, 1, color=\"blue\")\n",
        "ax.text(median_2q * 1.1, 0.2, f\"{median_2q:.2e}\")\n",
        "ax.set_title(\"Learned noise rates\")\n",
        "ax.set_xlabel(\"Noise rate\")\n",
        "ax.set_yticks([])\n",
        "plt.legend()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "f8a91705-0000-4000-8000-000000000018",
      "metadata": {},
      "source": [
        "<span id=\"associate-circuit-boxes-with-learned-noise\" />\n",
        "\n",
        "### Schaltkästen mit erlerntem Rauschen verknüpfen\n",
        "\n",
        "Sobald wir für jede einzelne Zwei-Qubit-Schicht Rauschmodelle erhalten haben, müssen wir diese mit den entsprechenden `InjectNoise` Anmerkungen innerhalb des eingerahmten Schaltkreises verknüpfen.\n",
        "\n",
        "Die `InjectNoise` Anweisung ist eine `samplomatic` Anmerkung, die die Einzel-Qubit-Dresser nutzt, um auf kontrollierte und konfigurierbare Weise Rauschen in die Schaltung einzuspielen. Es ermöglicht eine modulare Rauschmodellierung über verschiedene Ebenen hinweg.\n",
        "\n",
        "Jede `InjectNoise` Anmerkung enthält:\n",
        "\n",
        "* `InjectNoise.ref` - eine eindeutige Kennung für die Anmerkung. Dies wird vom Objekt `samplex` verwendet, um das entsprechende Rauschmodell korrekt zuzuweisen.\n",
        "* `InjectNoise.modifier_ref` *(optional)* – eine sekundäre Referenz, die es ermöglicht, das zugewiesene Rauschmodell mit einem multiplikativen Faktor zu skalieren.\n",
        "\n",
        "In diesem Schritt erstellen wir eine Zuordnung von jedem `InjectNoise.ref` zu seinem entsprechenden erlernten Rauschmodell (`PauliLindbladMap`). Diese Zuordnung stellt sicher, dass jede Verschränkungsgatter-Schicht im Schaltkreis mit dem passenden Rauschmodell gepaart wird, sodass Rauscheffekte während der Stichprobenentnahme und der anschließenden Rauschunterdrückungsschritte präzise angewendet werden.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 11,
      "id": "e172d227-7a04-48ba-8566-d5f3007ef7bc",
      "metadata": {},
      "outputs": [],
      "source": [
        "# map inject noise refs to pauli lindblad maps\n",
        "refs_to_noise_models = {}\n",
        "for instruction, result in zip(\n",
        "    unique_2q_layers_and_meas, noise_learner_result, strict=False\n",
        "):\n",
        "    if inject_noise_annot := get_annotation(\n",
        "        instruction.operation, InjectNoise\n",
        "    ):\n",
        "        refs_to_noise_models[inject_noise_annot.ref] = (\n",
        "            result.to_pauli_lindblad_map()\n",
        "        )"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "7ab22f68-0000-4000-8000-000000000019",
      "metadata": {},
      "source": [
        "<span id=\"propagate-the-observable-through-the-learned-anti-noise\" />\n",
        "\n",
        "### Das Observable durch das gelernte Anti-Rauschen propagieren\n",
        "\n",
        "Wie oben bereits erläutert, erfolgt dies in zwei Schritten. Zunächst leiten wir einen Anti-Rausch-Generator bis zum Ende der Schaltung weiter. Anschließend wenden wir diesen weiterentwickelten Generator auf die Observable an. Dieser Vorgang wird für jeden Rauschunterdrückungsgenerator in der Schaltung wiederholt. Bei dieser Implementierung werden alle Generatoren einer bestimmten Schicht parallel bis zum Ende der Schaltung weitergeleitet. Zudem wird Multiprocessing nach dem „ Python “-Prinzip eingesetzt, um sowohl die Vorwärtsausbreitung des Anti-Rauschens als auch die Rückwärtsausbreitung der Observable parallel durchzuführen. Dadurch wird eine Anhäufung von weiterentwickelten Generatoren im Speicher verhindert und gleichzeitig die Rechenressourcen optimal genutzt.\n",
        "\n",
        "Bei der Ausführung von PNA müssen Sie stets eine verrauschte Schaltung und eine Beobachtable angeben. Wenn es sich bei Ihrer „noisy circuit“-Schaltung um eine in einem Kasten dargestellte Schaltung mit `InjectNoise` Anmerkungen handelt, müssen Sie die Zuordnung angeben, die wir im obigen Schritt erstellt haben. `qiskit-aer`Man kann auch einen nicht-boxed Schaltkreis übergeben, der Befehle aus enthält `PauliLindbladError` . In diesem Fall muss nicht angegeben werden. `refs_to_noise_models` Berücksichtigen Sie zusätzlich zu den primären Eingaben auch Folgendes:\n",
        "\n",
        "* `max_err_terms`: Die Anzahl der Terme, die in jedem Anti-Rausch-Generator beibehalten werden sollen, während die Vorwärtsberechnung durchgeführt wird. Wenn man diesen Wert größer wählt, erhöht sich in der Regel die Genauigkeit, allerdings ist nicht garantiert, dass dieses Verhalten monoton verläuft.\n",
        "* `max_obs_terms`: Die Anzahl der Terme, die in der rauschmindernden Observable „ $\\tilde{O}$ “ beibehalten werden sollen, während diese durch das entwickelte Antirauschen rückwärtspropagiert wird. Höhere Werte erhöhen in der Regel die Genauigkeit, allerdings ist nicht garantiert, dass dies monoton verläuft.\n",
        "* `num_processes`: Die Anzahl der Kerne, die für den Prozess reserviert werden sollen. Denken Sie daran, dass die Generatoren vorwärts propagiert und parallel auf die Observable angewendet werden.\n",
        "* `search_step`: Beim Backpropagation-Schritt wird ein „greedy“-Verfahren verwendet, um zwei Operatoren in der Pauli-Basis näherungsweise zu konjugieren. `search_step`Diese Methode lässt sich durch eine Erhöhung von beschleunigen. Weitere Informationen finden Sie unter [`pauli-prop` Dokumente](https://qiskit.github.io/pauli-prop/) .\n",
        "* `num_to_measure`: Diese Variable ist zwar keine Eingabe für `generate_noise_mitigating_observable`, wir verwenden sie jedoch, um zu steuern, wie viele Terme aus $\\tilde{O}$ wir tatsächlich messen möchten. Hier erfassen wir nur die 30 wichtigsten Begriffe, bei denen es sich um die ursprünglichen Begriffe in unserer Beobachtungsgröße handelt. Die Terme wurden nun so neu skaliert, dass ihre Messung zu einer Abschwächung des erlernten Gate-Rauschens führt. Obwohl wir nur 30 Terme aus $\\tilde{O}$ messen, ist es oft dennoch sinnvoll, die Anzahl der Terme groß werden zu lassen, da dies die Genauigkeit der Skalierungsfaktoren der führenden Terme erhöht.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 12,
      "id": "19407dc3-eec0-4323-9a8a-47e949fb6ae2",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Finished! 13740 / 13740 generators propagated.                       "
          ]
        }
      ],
      "source": [
        "# PNA parameters\n",
        "num_processes = 8\n",
        "max_err_terms = 10_000\n",
        "max_obs_terms = 10_000\n",
        "num_to_measure = num_qubits\n",
        "\n",
        "obs_tilde_isa = generate_noise_mitigating_observable(\n",
        "    boxed_circuit,\n",
        "    isa_observable,\n",
        "    refs_to_noise_models,\n",
        "    max_err_terms=max_err_terms,\n",
        "    max_obs_terms=max_obs_terms,\n",
        "    num_processes=num_processes,\n",
        "    print_progress=True,\n",
        "    search_step=8,\n",
        ")\n",
        "p_2_v = {p: v for v, p in enumerate(layout)}\n",
        "obs_tilde_virtual = SparsePauliOp.from_sparse_list(\n",
        "    [\n",
        "        (pstr, [p_2_v[p] for p in p_qubits], coeff)\n",
        "        for (pstr, p_qubits, coeff) in obs_tilde_isa.to_sparse_list()\n",
        "    ],\n",
        "    num_qubits=num_qubits,\n",
        ")\n",
        "obs_tilde_virtual = obs_tilde_virtual[\n",
        "    np.argsort(np.abs(obs_tilde_virtual.coeffs))[::-1]\n",
        "][:num_to_measure]"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 13,
      "id": "77246aef-1892-4a27-bbf0-128b12206581",
      "metadata": {
        "jupyter": {
          "source_hidden": true
        }
      },
      "outputs": [
        {
          "data": {
            "text/plain": [
              "[<matplotlib.lines.Line2D at 0x124b0a120>]"
            ]
          },
          "execution_count": 13,
          "metadata": {},
          "output_type": "execute_result"
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/propagated-noise-absorption/extracted-outputs/77246aef-1892-4a27-bbf0-128b12206581-1.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "obs_tilde_isa = obs_tilde_isa[np.argsort(np.abs(obs_tilde_isa.coeffs))][::-1]\n",
        "plt.xscale(\"log\")\n",
        "plt.yscale(\"log\")\n",
        "plt.title(r\"$\\tilde{O}$ coeff magnitudes\")\n",
        "plt.ylabel(\"Magnitude\")\n",
        "plt.xlabel(\"Pauli term index\")\n",
        "plt.plot(np.abs(obs_tilde_isa.coeffs), \".\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "e02700bb-0000-4000-8000-000000000020",
      "metadata": {},
      "source": [
        "<span id=\"transform-the-measurement-bases-to-canonical-form\" />\n",
        "\n",
        "### Bringe die Maßbasen in die kanonische Form über\n",
        "\n",
        "Als Nächstes suchen wir eine minimale Menge von Basen für die Messung, sodass wir jeden Pauli-Term in der gemessenen Observable vollständig abdecken können ( *es können viele Observablen gleichzeitig gemessen werden, sofern sie qubitweise kommutieren* ). Da wir lediglich die Terme in unserer ursprünglichen Observablen messen, die die Summe aller Einzel-Paulis`Z` ist, wird nur eine einzige Basis benötigt: die All-Basis`Z` .\n",
        "\n",
        "Neben der Bestimmung eines Satzes von Pauli-Messbasen müssen wir diese Pauli-Terme auf die kanonische Form abbilden, die von der … erwartet wird `Executor`. Weitere Informationen zur kanonischen Qubit-Anordnung finden Sie in der [Samplomatic-Dokumentation](/docs/addons/samplomatic/guides/samplex-io#qubit-ordering-convention).\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 14,
      "id": "a0ff4edd-434a-4aa5-8175-075e1472c81e",
      "metadata": {},
      "outputs": [],
      "source": [
        "meas_box = boxed_circuit.data[-1]\n",
        "canonical_qubits = [\n",
        "    idx\n",
        "    for idx, qubit in enumerate(boxed_circuit.qubits)\n",
        "    if qubit in meas_box.qubits\n",
        "]\n",
        "c_2_p = {\n",
        "    c: p for c, p in enumerate(canonical_qubits)\n",
        "}  # canonical -> physical\n",
        "p_2_v = {p: v for v, p in enumerate(layout)}  # physical -> virtual\n",
        "c_2_v = {c: p_2_v[p] for c, p in c_2_p.items()}  # canonical -> virtual\n",
        "meas_bases, bases_reverser = get_measurement_bases(obs_tilde_virtual)\n",
        "meas_bases_canonical = [\n",
        "    np.array([base[c_2_v[c]] for c in range(num_qubits)], dtype=np.uint8)\n",
        "    for base in meas_bases\n",
        "]"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "24c95aca-0000-4000-8000-000000000021",
      "metadata": {},
      "source": [
        "<span id=\"step-3-execute-quantum-experiments\" />\n",
        "\n",
        "### Schritt 3: Durchführung von Quantenexperimenten\n",
        "\n",
        "<span id=\"specify-how-to-sample-in-the-quantumprogram\" />\n",
        "\n",
        "#### Legen Sie fest, wie die Stichprobenahme im `QuantumProgram`\n",
        "\n",
        "`Executor``QuantumProgram`Wir richten nun den ein, der als zentraler Container für alle Schaltungen und Abtastkonfigurationen dient, die vom ausgeführt werden. Dieses Objekt legt fest, wie zufällig generierte Schaltungsinstanzen erzeugt, gebündelt und ausgeführt werden, um die in PNA verwendeten Messergebnisse zu erzeugen.\n",
        "\n",
        "Ein `QuantumProgram` kann mehrere Elemente enthalten, die jeweils aus einer Vorlagenschaltung und einem entsprechenden `samplex` Objekt bestehen, das festlegt, wie die Randomisierungen angewendet werden. Diese Abstraktion ermöglicht es `Executor` , den gesamten Arbeitsablauf als ein einziges, modulares Programm zu handhaben – von der zufälligen Schaltkreiserzeugung bis hin zur Erfassung und Zusammenführung der Aufnahmen.\n",
        "\n",
        "In diesem Schritt erstellen wir ein `QuantumProgram` Programm, das unser PNA-Experiment unter Verwendung der Schaltungsvorlage und `samplex` der zuvor von uns erstellten Schaltung ausführt. Die Konfiguration umfasst folgende Elemente:\n",
        "\n",
        "* `template_circuit`: Die Schaltung, die alle Logikgatter enthält, die zur Umsetzung aller gewünschten Randomisierungen erforderlich sind (einschließlich Twirling-Randomisierungen, Parameter usw.).\n",
        "* `samplex`: Ein Objekt, das eine Wahrscheinlichkeitsverteilung über alle möglichen Schaltungsrandomisierungen definiert, aus denen Stichproben entnommen werden sollen.\n",
        "* `samplex_arguments`: Die Bindungen, die erforderlich sind, um die `samplex`\n",
        "  * `basis_changes`: Hier legen wir einen Satz von Basen fest, die gemessen werden sollen und die alle Pauli-Terme in der gemessenen Observable abdecken.\n",
        "  * `noise_scales.ref`: Wir haben die Skalierung jeder Rauschschicht auf `0.0` eingestellt, um zu verhindern, dass zusätzliches Rauschen in unsere Samples gelangt.\n",
        "  * `pauli_lindblad_maps`: Erforderlich, wenn `noise_scales` übergeben werden. Hierbei werden lediglich die Lärmschichten dem zugehörigen Lärmmodell zugeordnet.\n",
        "* `samplex_arguments``shape`: Ein Form-Tupel zur Erweiterung der durch … definierten impliziten Form. Die durch diese Erweiterung eingeführten nichttrivialen Achsen zählen Randomisierungen auf.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 15,
      "id": "7958cdd5-37b9-48ab-a1b1-5bf2a6bcf13e",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Control the # of shots during execution\n",
        "shots_per_randomization_exec = 64\n",
        "num_randomizations_exec = 6144\n",
        "\n",
        "# Zero out the noise to prevent noise from being injected during execution.\n",
        "# We only added InjectNoise annotations so PNA could associate the noise\n",
        "# to layers in the circuit\n",
        "samplex_inputs = {f\"noise_scales.{ref}\": 0.0 for ref in refs_to_noise_models}\n",
        "samplex_inputs |= {\"pauli_lindblad_maps\": refs_to_noise_models}\n",
        "\n",
        "# Specify the bases to measure. The samplex exposes one basis-change input per\n",
        "# ChangeBasis-annotated box; here a single all-Z basis covers every term. We\n",
        "# look up the basis-change interface name rather than hardcoding an index, since\n",
        "# the name depends on the circuit's box structure.\n",
        "bases_broadcastable = np.expand_dims(np.array(meas_bases_canonical), axis=1)\n",
        "samplex_inputs |= {\n",
        "    spec.name: bases_broadcastable\n",
        "    for spec in samplex.inputs().get_specs(r\"^basis_changes\\.\")\n",
        "}\n",
        "\n",
        "# Convert samplex_inputs into a dict to pass to QuantumProgram\n",
        "samplex_arguments = (\n",
        "    samplex.inputs().make_broadcastable().bind(**samplex_inputs)\n",
        ")\n",
        "\n",
        "# Instantiate the QuantumProgram with the specified parameters\n",
        "program = QuantumProgram(shots=shots_per_randomization_exec)\n",
        "program.append_samplex_item(\n",
        "    circuit=template_circuit,\n",
        "    samplex=samplex,\n",
        "    samplex_arguments=samplex_arguments,\n",
        "    shape=(num_randomizations_exec,),\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "b7caba08-0000-4000-8000-000000000022",
      "metadata": {},
      "source": [
        "<span id=\"sample-the-circuit-using-the-executor\" />\n",
        "\n",
        "#### Lesen Sie die Schaltung mit dem `Executor`\n",
        "\n",
        "`QuantumProgram`Nachdem wir nun unsere definiert haben, ist die Durchführung des Experiments unkompliziert. Wir instanziieren einfach das `Executor` Objekt, weisen ihm das Backend zu und führen das Programm aus.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 16,
      "id": "44202d02-0000-4000-8000-000000000023",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Execute (sample) the circuit\n",
        "executor = Executor(backend)\n",
        "job_exec = executor.run(program)\n",
        "exec_results = job_exec.result()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "bb61dcec-0000-4000-8000-000000000024",
      "metadata": {},
      "source": [
        "<span id=\"step-4-reconstruct-and-analyze-results\" />\n",
        "\n",
        "### Schritt 4: Rekonstruktion und Analyse der Ergebnisse\n",
        "\n",
        "Um einen fehlerkorrigierten Erwartungswert zu berechnen, gehen wir wie folgt vor:\n",
        "\n",
        "* Berechnen Sie die TREX-Skalierungsfaktoren auf der Grundlage des ermittelten Rauschens, das die Messungen beeinflusst,\n",
        "* Erzeuge eine Maske, um nur die nachträglich ausgewählten Samples beizubehalten, und\n",
        "* Verwenden Sie die `executor_expectation_values` Funktion aus `qiskit-addon-utils` , um alle Daten zu einem fehlerbereinigten Erwartungswert zusammenzufassen.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 17,
      "id": "1ba3b057-a809-402d-8fd6-4314956dc98e",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Computing the TREX factors\n",
        "measurement_noise_map = noise_learner_result[2].to_pauli_lindblad_map()\n",
        "trex_rescale_factors = trex_factors(measurement_noise_map, bases_reverser)\n",
        "\n",
        "# Post-select the results\n",
        "post_selector = PostSelector.from_circuit(\n",
        "    circuit=template_circuit, coupling_map=backend.coupling_map\n",
        ")\n",
        "\n",
        "# Compute the ps mask for filtering results\n",
        "mask = post_selector.compute_mask(exec_results[0], strategy=\"edge\")\n",
        "\n",
        "# Compute expvals using post selected results\n",
        "results = executor_expectation_values(\n",
        "    exec_results[0][\"meas\"],\n",
        "    bases_reverser,\n",
        "    meas_basis_axis=0,\n",
        "    avg_axis=1,\n",
        "    measurement_flips=exec_results[0][\"measurement_flips.meas\"],\n",
        "    pauli_signs=exec_results[0].get(\"pauli_signs\", None),\n",
        "    postselect_mask=mask,\n",
        "    rescale_factors=trex_rescale_factors,\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "aed4c6f9-0000-4000-8000-000000000025",
      "metadata": {},
      "source": [
        "<span id=\"compare-mitigation-strategies-pna-pna+trex-pna+ps-pna+ps+trex\" />\n",
        "\n",
        "#### Vergleich der Minderungsstrategien: PNA, PNA+TREX, PNA+PS, PNA+PS+TREX\n",
        "\n",
        "Anhand der `Executor` Ergebnisse berechnen und visualisieren wir die Erwartungswerte für verschiedene Varianten der Risikominderung.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 18,
      "id": "8df9366c-d456-416b-80ab-3b941a07a935",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/propagated-noise-absorption/extracted-outputs/8df9366c-d456-416b-80ab-3b941a07a935-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "bases_reverser_unmit = {Pauli(\"Z\" * num_qubits): [observable]}\n",
        "args = [\n",
        "    (bases_reverser_unmit, None, None),\n",
        "    (bases_reverser, None, None),\n",
        "    (bases_reverser, None, trex_rescale_factors),\n",
        "    (bases_reverser, mask, None),\n",
        "    (bases_reverser, mask, trex_rescale_factors),\n",
        "]\n",
        "\n",
        "evs = []\n",
        "for reverser, postsel_mask, factors in args:\n",
        "    # Compute expvals using post selected results\n",
        "    res_ps = executor_expectation_values(\n",
        "        exec_results[0][\"meas\"],\n",
        "        reverser,\n",
        "        meas_basis_axis=0,\n",
        "        avg_axis=1,\n",
        "        measurement_flips=exec_results[0][\"measurement_flips.meas\"],\n",
        "        pauli_signs=exec_results[0].get(\"pauli_signs\", None),\n",
        "        postselect_mask=postsel_mask,\n",
        "        rescale_factors=factors,\n",
        "    )\n",
        "    res_ps = np.array(res_ps)\n",
        "    evs.append(res_ps[:, 0][0])\n",
        "\n",
        "experiments = [\"PNA\", \"PNA+TREX\", \"PNA+PS\", \"PNA+PS+TREX\"]\n",
        "colors = [\"#d9d9d9\", \"#b0b0b0\", \"#7f7f7f\", \"#4c4c4c\"]\n",
        "plt.bar(experiments, evs[1:], color=colors)\n",
        "plt.axhline(y=1, color=\"green\", linestyle=\"--\", linewidth=2, label=\"Ideal\")\n",
        "plt.axhline(\n",
        "    y=evs[0], color=\"red\", linestyle=\"--\", linewidth=2, label=\"Unmitigated\"\n",
        ")\n",
        "plt.ylabel(\"Expectation value\", fontsize=14)\n",
        "\n",
        "plt.title(\n",
        "    r\"30q Mirrored Ising, 10 Trotter steps, $\\theta_{rx}=\\frac{\\pi}{8}$\",\n",
        "    fontsize=14,\n",
        ")\n",
        "plt.legend(loc=\"upper left\", bbox_to_anchor=(1.05, 1), borderaxespad=0.0)\n",
        "plt.xticks(rotation=45)\n",
        "plt.tight_layout()\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "9589eae8-0000-4000-8000-000000000026",
      "metadata": {},
      "source": [
        "Die Ergebnisse verdeutlichen die kumulativen Vorteile einer Kombination verschiedener Techniken zur Fehlerminderung. Bereits der einfache PNA-Ansatz bringt den Erwartungswert wieder nahe an den idealen Referenzwert heran, was darauf hindeutet, dass die Einbeziehung inverser Rauschkanäle in die Observable Fehler bei Zwei-Qubit-Gattern wirksam kompensiert.\n",
        "\n",
        "* Durch Hinzufügen der TREX-Gewichtungsanpassung (PNA+TREX) lässt sich die Schätzung leicht verbessern, indem das Ungleichgewicht bei der Stichprobenauswahl in den randomisierten Schaltkreisen korrigiert wird.\n",
        "* Die Nachselektion (PNA+PS) sorgt für eine deutlichere Verbesserung, indem sie inkonsistente Messergebnisse herausfiltert, die wahrscheinlich auf Restfehler zurückzuführen sind.\n",
        "* Schließlich liefert die Kombination aller drei Methoden (PNA+PS+TREX) das genaueste Ergebnis, das nahezu dem Idealwert entspricht, was zeigt, wie sich diese Strategien zur Fehlerminderung gegenseitig verstärken.\n",
        "\n",
        "Insgesamt zeigt der Vergleich, dass PNA eine solide Grundlage für die rauschbewusste Erwartungswertschätzung bildet, während TREX und die Nachselektion ergänzende Verfeinerungen für weitere Genauigkeitsgewinne bieten.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a1b2c3d4-9999-4000-8000-000000000099",
      "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",
        "  * [Fehlerreduktionsoptionen mit dem Schätz-Basiselement kombinieren](/docs/tutorials/combine-error-mitigation-techniques)\n",
        "  * [Versorgungsmaßstab-Risikominderung mit probabilistischer Fehlerverstärkung](/docs/tutorials/probabilistic-error-amplification)\n",
        "  * [Operator-Backpropagation (OBP) zur Schätzung von Erwartungswerten](/docs/tutorials/operator-back-propagation)\n",
        "  * Die [`samplomatic`](https://github.com/Qiskit/samplomatic) und [`pauli-prop`](https://github.com/Qiskit/pauli-prop) Dokumentation\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "id": "a1b8767d",
      "source": "© IBM Corp., 2017-2026"
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3"
    },
    "hours": 3,
    "qpuSeconds": 600
  },
  "nbformat": 4,
  "nbformat_minor": 5
}