{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "slc-title-0000-4000-8000-000000000000",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Probabilistische Fehlerkompensation mit schattierten Lichtkegeln\"\n",
        "description: \"Verwenden Sie die probabilistische Fehlerkompensation (PEC) mit abgeschatteten Lichtkegeln, um den PEC-Abtast-Overhead zu reduzieren und Fehler bei der Schätzung der Erwartungswerte auf der Hardware zu minimieren\"\n",
        "---\n",
        "\n",
        "{/* cspell:ignore samplomatic samplex Trotterized Trotter Lindblad lightcone lightcones Eddins reverser broadcastable postselection postselect antinoise eigval atol starmap unmitigated NoiseLearner PauliLindblad QubitSparsePauli Kingston kicked qubit canonical isa plm qspl Rzz pauli TREX levelname xticks expvals markerfacecolor ncols */}\n",
        "\n",
        "<span id=\"probabilistic-error-cancellation-with-shaded-lightcones\" />\n",
        "\n",
        "# Probabilistische Fehlerkompensation mit schattierten Lichtkegeln\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": "slc-lout-0000-4000-8000-000000000001",
      "metadata": {},
      "source": [
        "<span id=\"learning-outcomes\" />\n",
        "\n",
        "## Lernziele\n",
        "\n",
        "* Was die probabilistische Fehlerkompensation (PEC) ist und warum ihr Abtast-Overhead $\\gamma^2$ exponentiell mit dem auf die Schaltung einwirkenden Gesamtrauschen wächst\n",
        "* Wie „Shaded Lightcones“ (SLC) den Beitrag jedes Rauschterms zur Zielgröße begrenzen, sodass Sie das Budget für die Rauschunterdrückung dort einsetzen können, wo es darauf ankommt – und wie Sie durch die Abschattung – über die bereits durch einen rein geometrischen Lichtkegel erzielte Reduzierung des Overheads ohne Bias hinaus – einen begrenzten Restbias gegen einen noch geringeren Abtast-Overhead eintauschen können\n",
        "* Wie man mit `NoiseLearnerV3` das Layer-Rauschen erlernt und über `samplomatic` und die `Executor` Primitive Anti-Rauschen einspeist\n",
        "* Wie man PEC und PEC+SLC mit TREX und Nachselektion kombiniert, um einen Erwartungswert auf der Hardware zu schätzen\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": "slc-bkgd-0000-4000-8000-000000000002",
      "metadata": {},
      "source": [
        "<span id=\"background\" />\n",
        "\n",
        "## Hintergrund\n",
        "\n",
        "In diesem Tutorial wird gezeigt, wie sich Fehler mithilfe des Add-ons „Shaded Light Cone“ (SLC) minimieren lassen. Dieses Add-on ist eine Weiterentwicklung der [Technik](/docs/guides/error-mitigation-and-suppression-techniques#probabilistic-error-cancellation-pec) der probabilistischen Fehlerkompensation (PEC), bei der ein Benutzer das Rauschen einzelner Schichten in einer Schaltung ermittelt und dieses Rauschen anschließend durch die Anwendung von Einzel-Qubit-Gattern und Nachbearbeitungsverfahren kompensiert. Im Vergleich zu anderen Methoden bietet PEC robustere Schätzgrenzen für den Bias des korrigierten Ergebnisses, ist jedoch in der Regel mit einem höheren Zeitaufwand für die QPU verbunden. Während des PEC-Prozesses wird das durchschnittliche Ergebnis, um die durch Rauschen verursachte Abschwächung des Erwartungswerts auszugleichen, um den Faktor $\\gamma = \\exp(\\sum_{l,\\sigma} 2\\lambda_{l,\\sigma})$ neu skaliert, wobei $\\lambda_{l,\\sigma}$ die gelernte Rauschfehlerrate des Pauli- $\\sigma$ s in der Schicht $l$ des Schaltkreises ist. Diese Skalierung erhöht die Varianz um den Faktor $\\gamma^2$ und vervielfacht somit auch die Anzahl der auf der QPU erforderlichen Schaltungsausführungen um den Faktor $\\gamma^2$, was wir als Abtastkosten oder Abtast-Overhead bezeichnen. Da $\\gamma$ exponentiell wächst, beschränkt sich PEC häufig auf flache Schaltungen oder Schaltungen mit wenigen Qubits. Erfahren Sie mehr über PEC im Artikel „[Probabilistische Fehlerkompensation mit spärlichen Pauli-Lindblad-Modellen auf verrauschten Quantenprozessoren](https://arxiv.org/abs/2201.09866) “.\n",
        "\n",
        "Wenn wir Fehler identifizieren können, die nicht behoben werden müssen, können wir diese Stichprobenkosten exponentiell senken. Ein erster Schritt in diese Richtung ist die Implementierung einer lokal orientierten Fehlerminderung, bei der ein schnell berechenbarer herkömmlicher „Lichtkegel“ verwendet wird, um den PEC-Overhead zu reduzieren, indem die Fehlerempfindlichkeit einer Observable im gesamten Schaltkreis begrenzt wird, wodurch die Anwendbarkeit von PEC bei bestimmten Problemen auf größere Maßstäbe ausgeweitet wird. Fehler außerhalb dieses Lichtkegels können das Messergebnis nicht beeinflussen und können daher aus dem Fehlerausgleichsverfahren ausgeschlossen werden. Dieser Ausschluss verringert den Stichprobenaufwand – in manchen Fällen sogar erheblich –, ohne dass dabei zusätzliche Verzerrungen entstehen. Insbesondere bei der Messung einer lokalen Observable $O$ in einem Schaltkreis mit fester Tiefe erreicht der erforderliche Abtast-Overhead bei einer Skalierung der Anzahl der Qubits im Schaltkreis schließlich ein Plateau (siehe Abb. 2b in „[Locality and error mitigation of quantum circuits](https://arxiv.org/abs/2303.06496) “).\n",
        "\n",
        "„Shaded Lightcones“ (SLC) gehen noch einen Schritt weiter und nutzen klassische Simulationen, um die Fehleranfälligkeit in der gesamten Schaltung genauer einzugrenzen. Dadurch wird ein Teil der QPU-Zeit gegen CPU-Zeit eingetauscht, wodurch sich der für die Renormierung des Bias erforderliche Abtast-Overhead verringert. Anstelle einer starren Grenze wird jedem potenziellen Fehler in der Schaltung ein abgestufter „Grad“ zugewiesen, der die Anfälligkeit des Erwartungswerts der Observable gegenüber diesem Fehler nach oben begrenzt. (Bei den im Folgenden vorgestellten, rückwärts propagierten Grenzwerten hängt diese Anfälligkeit auch vom Anfangszustand der Schaltung ab – in typischen Arbeitsabläufen und in diesem Tutorial ist dies der Zustand, in dem alle Bits auf Null gesetzt sind.) Diese verfeinerte Charakterisierung ermöglicht eine effizientere und gezieltere Anwendung von PEC mit geringerer Varianz und gibt dem Anwender gleichzeitig die Möglichkeit, den Bias bei der Schätzung der Observablen kontrolliert anzupassen. Weitere Einzelheiten finden Sie unter „[Lightcone-Shading für die klassisch beschleunigte Minderung](https://arxiv.org/abs/2409.04401) von Quantenfehlern“.\n",
        "\n",
        "Unser Workflow für das SLC-Add-on nutzt die Bibliothek [`samplomatic`](/docs/addons/samplomatic/index) in Verbindung mit den `Executor` Klassen und `QuantumProgram` , die in v0.47.0`qiskit-ibm-runtime` zu Qiskit Runtime hinzugefügt wurden. Dadurch erhalten Benutzer eine modularere Kontrolle über die Ausführungseinstellungen zur Fehlerunterdrückung und -minderung, ohne dass die Benutzerfreundlichkeit darunter leidet. Weitere Informationen finden Sie im Leitfaden [zum Modell der](/docs/guides/directed-execution-model) gerichteten Ausführung.\n",
        "\n",
        "<span id=\"slc-error-mitigation-workflow-at-a-glance\" />\n",
        "\n",
        "### Der SLC-Workflow zur Fehlerminderung auf einen Blick\n",
        "\n",
        "Zur Modellierung des Rauschens der QPU verwenden wir ein spärliches Pauli-Lindblad-Rauschmodell mit Pauli-Fehlerraten für ein und zwei Qubits, die lokal an jedem Qubit und an jeder Kante des Bausteins erzeugt werden. Mit dieser Auswahl sieht der in diesem Tutorial vorgestellte SLC-Workflow zur Fehlerminderung wie folgt aus:\n",
        "\n",
        "a. CPU — CPU-gebundene Auswirkungen von Ein- und Zwei-Qubit-Pauli-Fehlern pro Fehler\n",
        "\n",
        "1. Vorwärtsausbreitung (gebundener Effekt auf die Observable). Übertrage jeden Fehler bis zum Ende der Schaltung und berechne seinen Kommutator mit der Observable.\n",
        "   * Kürze die Terme des Operators während der Entwicklung, um die Berechnung überschaubar zu halten.\n",
        "   * Diese Grenzen lassen sich durch eine grobe Rückpropagation der Observablen auf der Grundlage quantenphysikalischer Geschwindigkeitsgrenzen weiter eingrenzen.\n",
        "2. Rückwärtsausbreitung (Einfluss der Anfangsbedingungen). Übertrage jeden Fehler bis zum Anfang des Schaltkreises und berechne dessen Kommutator mit dem Anfangszustand.\n",
        "\n",
        "b. QPU – Lärmpegel ermitteln. Verwenden Sie `NoiseLearnerV3` zur Schätzung der Raten des Pauli-Lindblad-Rauschmodells.\n",
        "\n",
        "c. CPU – Maßnahmen zur Risikominderung priorisieren\n",
        "\n",
        "1. Aktualisiere die zusammengeführten Grenzen mit den erlernten Rauschraten. Die zuvor berechneten Vorwärts- und Rückwärtsgrenzen kombinieren und mit den erlernten Rauschraten aktualisieren.\n",
        "2. Ordnen Sie die Rauschkomponenten nach ihrer zu mindernenden Bedeutung, indem Sie die berechneten Grenzen und die erlernten Raten verwenden. Ordnen Sie jeden möglichen Rauschfehler nach seiner geschätzten Auswirkung auf die Verzerrung und den damit verbundenen Korrekturaufwand.\n",
        "\n",
        "d. QPU – Anti-Rauschen einfügen und ausführen. Führen Sie die gewünschte Schaltung mit Anti-Rauschen (inversem Rauschen) aus, das mithilfe von `Box` Anmerkungen festgelegt wurde.\n",
        "\n",
        "e. CPU – Schätzung beobachtbar. Berechnen Sie den Erwartungswert unter Anwendung einer messungsbasierten Nachselektion, um die Auswirkungen des nicht-markovschen Rauschens zu verringern.\n",
        "\n",
        "<span id=\"noise-learning-overview\" />\n",
        "\n",
        "### Überblick über das Lernen mit Rauschen\n",
        "\n",
        "Das Rauschlernen ist ein gängiger Schritt in verschiedenen Methoden zur Fehlerminderung, [der](/docs/guides/noise-learning) vom Rauschlerner durchgeführt wird; es kommt auch im [Tutorial](/docs/tutorials/probabilistic-error-amplification) zur probabilistischen Fehlerverstärkung vor. `NoiseLearnerV3`In können Sie die zu lernenden Rauschschichten gezielt als [`CircuitInstruction`](/docs/api/qiskit/qiskit.circuit.CircuitInstruction) Objekte identifizieren, sodass Sie die gewünschten SLC-Rauschgrenzen für jede Schicht auf die oben beschriebene Weise berechnen können. Das Pauli-Lindblad-Modell liefert Koeffizienten, die bei der PEC+SLC-Priorisierung verwendet werden. `generate_noise_model_paulis`Die Art und Weise, wie die Gates in Schichten zusammengefasst werden, lässt sich mithilfe der `generate_boxing_pass_manager` Hilfsfunktionen und `find_unique_box_instructions` festlegen und anschließend in die SLC-Nutzfunktion einlesen, wie in Schritt 2 unten beschrieben.\n",
        "\n",
        "| **Teil 1**                                                                                                             | **Teil 2**                                                                                                      | **Teil 3**                                                                                                       |\n",
        "| ---------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |\n",
        "| Pauli-Twirl-Schichten mit Zwei-Qubit-Gattern                                                                           | Identitätspaare von Schichten wiederholen und Rauschen lernen                                                   | Berechnung der Genauigkeit (Fehler für jeden Rauschkanal)                                                        |\n",
        "| ![Pauli wirbelt herum](https://quantum.cloud.ibm.com/docs/images/tutorials/pec-with-shaded-lightcones/paulitwirl.avif) | ![Ebene lernen](https://quantum.cloud.ibm.com/docs/images/tutorials/pec-with-shaded-lightcones/learnlayer.avif) | ![Kurvenanpassung](https://quantum.cloud.ibm.com/docs/images/tutorials/pec-with-shaded-lightcones/curvefit.avif) |\n",
        "\n",
        "<span id=\"post-processing-overview\" />\n",
        "\n",
        "### Übersicht über die Nachbearbeitung\n",
        "\n",
        "Nachdem wir die Berechnung auf Quantenhardware unter Verwendung des `samplomatic` und `Executor` Frameworks durchgeführt haben, wandeln wir unsere Bitstring-Messwerte in den gewünschten Observablenwert um. In diesem Tutorial veranschaulichen wir den Arbeitsablauf anhand eines gespiegelten Ising-Schaltkreises (der in Schritt 1 unten aufgebaut wurde), bei dem der ideale Erwartungswert der gemessenen Observable 1 beträgt, da die zweite Hälfte des Schaltkreises die erste aufhebt. Bei der Berechnung des beobachtbaren Werts mit der `executor_expectation_values` Funktion wenden wir einige Nachbearbeitungsverfahren an, die den Einfluss von Rauschen verringern. Dazu gehören das Entfernen von Aufnahmen, die durch nicht-markovisches Rauschen beeinträchtigt sind, die Minderung von Auslesefehlern sowie die Berücksichtigung von Details unserer PEC-Implementierung. Einzelheiten werden im folgenden Schritt 4 erläutert.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "slc-reqs-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",
        "* „Shaded Lightcones“ – Qiskit-Add-on v0.1 oder höher (`pip install qiskit-addon-slc`)\n",
        "* Qiskit-Add-on „utils“ v0.3 oder höher (`pip install qiskit-addon-utils`)\n",
        "* Samplomatic v0.13 oder höher (`pip install samplomatic`)\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "slc-setup-000-4000-8000-000000000004",
      "metadata": {},
      "source": [
        "<span id=\"setup\" />\n",
        "\n",
        "## Einrichtung\n",
        "\n",
        "Importieren Sie zunächst die Pakete und Funktionen, die zur Ausführung dieses Notebooks benötigt werden.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "id": "946458b7-ab59-448c-8268-f62d8f53bc6d",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "env: OMP_NUM_THREADS=1\n"
          ]
        }
      ],
      "source": [
        "from multiprocessing import set_start_method\n",
        "\n",
        "# Setting this value prevents itertools.starmap deadlock on UNIX systems\n",
        "set_start_method(\"spawn\")\n",
        "\n",
        "# Needed to prevent PySCF from parallelizing internally (SLC only)\n",
        "%set_env OMP_NUM_THREADS=1"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "slc-imports-00-4000-8000-000000000005",
      "metadata": {},
      "outputs": [],
      "source": [
        "import numpy as np\n",
        "from matplotlib import pyplot as plt\n",
        "\n",
        "from qiskit import QuantumCircuit\n",
        "from qiskit.quantum_info import SparsePauliOp\n",
        "from qiskit.transpiler import generate_preset_pass_manager, PassManager\n",
        "\n",
        "from qiskit_ibm_runtime import (\n",
        "    QiskitRuntimeService,\n",
        "    QuantumProgram,\n",
        "    Executor,\n",
        "    NoiseLearnerV3,\n",
        ")\n",
        "\n",
        "import samplomatic\n",
        "from samplomatic.utils import find_unique_box_instructions\n",
        "from samplomatic.transpiler import generate_boxing_pass_manager\n",
        "\n",
        "from qiskit_addon_utils.exp_vals.measurement_bases import (\n",
        "    get_measurement_bases,\n",
        ")\n",
        "from qiskit_addon_utils.exp_vals.expectation_values import (\n",
        "    executor_expectation_values,\n",
        ")\n",
        "from qiskit_addon_utils.noise_management import (\n",
        "    gamma_from_noisy_boxes,\n",
        "    trex_factors,\n",
        ")\n",
        "from qiskit_addon_utils.noise_management.post_selection import PostSelector\n",
        "from qiskit_addon_utils.noise_management.post_selection.transpiler.passes import (\n",
        "    AddPostSelectionMeasures,\n",
        "    AddSpectatorMeasures,\n",
        ")\n",
        "\n",
        "from qiskit_addon_slc.bounds import (\n",
        "    compute_backward_bounds,\n",
        "    compute_forward_bounds,\n",
        "    compute_local_scales,\n",
        "    merge_bounds,\n",
        "    tighten_with_speed_limit,\n",
        ")\n",
        "from qiskit_addon_slc.utils import (\n",
        "    generate_noise_model_paulis,\n",
        "    map_modifier_ref_to_ref,\n",
        ")\n",
        "from qiskit_addon_slc.visualization import draw_shaded_lightcone"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "slc-sim-0000-4000-8000-000000000006",
      "metadata": {},
      "source": [
        "<span id=\"small-scale-simulator-example\" />\n",
        "\n",
        "## Beispiel für einen Simulator im kleinen Maßstab\n",
        "\n",
        "Wie andere lernbasierte Methoden zur Fehlerminderung mindert auch PEC mit abgeschatteten Lichtkegeln das *physikalische* Rauschen eines bestimmten Quantenprozessors, sodass es von Hardware-Funktionen abhängt, für die es in einem idealen Simulator keine sinnvolle Entsprechung gibt:\n",
        "\n",
        "* `NoiseLearnerV3` charakterisiert experimentell den spärlichen Pauli-Lindblad-Rauschkanal auf jeder einzelnen Zwei-Qubit-Schicht. Bei einem geräuschlosen Simulator gibt es keine Geräusche, die unterdrückt werden müssten.\n",
        "* Die `Executor` Primitive nimmt die von `samplomatic` auf einem Backend erzeugten, mit Anti-Rauschen versetzten, gedrehten Schaltkreise abtasten.\n",
        "\n",
        "Die Berechnung der Grenze des schattierten Lichtkegels ist klassisch, aber sie ist nur im Zusammenhang mit den erlernten Hardware-Rauschraten aussagekräftig, die das Minderungspotenzial und den Abtast-Overhead bestimmen. Aus diesen Gründen lassen wir das Beispiel mit dem Simulator im kleinen Maßstab außer Acht und demonstrieren den vollständigen PEC+SLC-Workflow direkt auf der Hardware, wobei jeder Schritt des Qiskit-Musters im Folgenden einzeln erläutert wird.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "slc-hw1-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 den gesamten PEC+SLC-Workflow auf einem gespiegelten Ising-Schaltkreis mit 20 Qubits durch, der auf der „ IBM Quantum® “-Hardware ausgeführt wird, und folgen dabei den vier Schritten eines Qiskit-Musters.\n",
        "\n",
        "<span id=\"step-1-map-the-problem\" />\n",
        "\n",
        "### Schritt 1: Das Problem erfassen\n",
        "\n",
        "Unsere Demonstration ist wie folgt aufgebaut:\n",
        "\n",
        "1. Wir entscheiden uns dafür, die Zeitentwicklung unter einem Hamilton-Operator für eine Ising-Kette mit der Form „ 1D “ durchzuführen. Die Ising-Kette „ 1D “ bietet eine schön kompakte Schaltungsstruktur, die sich gut zur Veranschaulichung von PEC-Implementierungen eignet.\n",
        "2. Wir haben uns dafür entschieden, die beobachtbare Größe „ $\\langle X_6 Z_{13}\\rangle$ “ zu messen, die eine „ $X$ “-Komponente und eine „ $Z$ “-Komponente enthält. Dies ist hilfreich, um zu verdeutlichen, wie die SLC-Grenzen verschiedene Arten von Fehlern unterschiedlich behandeln.\n",
        "3. Wir möchten, dass der ideale Erwartungswert dieser Observablen genau 1 beträgt, damit sich die Qualität der gemilderten Schätzungen leicht beurteilen lässt. Durch Spiegeln der Schaltung sind wir schon fast am Ziel: Für jedes Gatter in der zweiten Hälfte der Schaltung gibt es ein inverses Gatter in der ersten Hälfte, sodass die ideale Schaltung als Identitätsschaltung fungiert.\n",
        "4. Schließlich setzt das Erreichen des idealen Erwartungswerts von 1 voraus, dass der Schaltkreis in einem +1-Eigenzustand der Observable beginnt – und somit dank der Spiegelung im Idealfall auch dort endet. Da die Observable eine nicht- $Z$ e Komponente enthält und die `Executor` Primitive am Ende der Schaltung die gewünschte Messbasis bereitstellt, fügen wir die entsprechenden Gatter zur Basisvorbereitung mithilfe einer `prepare_basis` Funktion am Anfang der Spiegel-Schaltung ein. Wir verwenden die `get_measurement_bases` Funktion, um zu ermitteln, welche Gatter benötigt werden und wo sie platziert werden müssen, und behalten dabei die Feinheiten der Qubit-Indizierung im Blick, die sich aus `box` den Annotationskonventionen ergeben, wie im Abschnitt über die Vorbereitung von Messungen in kanonischer Basis erläutert.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "id": "5bfefe99-5958-486e-b8ca-ef2573c7f1e5",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Width of the Ising chain (a free parameter of this demonstration)\n",
        "num_qubits = 20\n",
        "\n",
        "# The observable to measure: one X component (on qubit 6) and one Z component\n",
        "# (on qubit 13), as motivated in \"Step 1: Map the problem\" above\n",
        "target_obs_sparse = [(\"XZ\", [6, 13], 1.0)]"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "id": "e5d4d6d5-9016-4c9a-916b-5075ff459fbf",
      "metadata": {},
      "outputs": [],
      "source": [
        "observable = SparsePauliOp.from_sparse_list(\n",
        "    target_obs_sparse, num_qubits=num_qubits\n",
        ")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 5,
      "id": "bb40bad8-cb18-4542-9e18-edf5fdfb5452",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Determine the measurement basis each qubit requires for this observable.\n",
        "# The reverser is used during post-processing in Step 4.\n",
        "bases_virt, reverser_virt = get_measurement_bases(observable)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 6,
      "id": "5b92fd4e-c559-4c56-8bf6-a6f2e397550c",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Free parameters of the demonstration circuit: the number of Trotter steps\n",
        "# sets the circuit depth, and rx_angle is the transverse-field rotation angle\n",
        "# applied in each step (pi/4 makes the dynamics non-Clifford)\n",
        "num_trotter_steps = 10\n",
        "rx_angle = np.pi / 4"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 7,
      "id": "cc2b001f-c006-45f1-9942-dcbdabefbec4",
      "metadata": {},
      "outputs": [],
      "source": [
        "def construct_ising_circuit(\n",
        "    num_qubits: int,\n",
        "    num_trotter_steps: int,\n",
        "    rx_angle: float,\n",
        ") -> QuantumCircuit:\n",
        "    circuit = QuantumCircuit(num_qubits)\n",
        "\n",
        "    for _ in range(num_trotter_steps):\n",
        "        circuit.rx(rx_angle, range(num_qubits))\n",
        "        for first_qubit in (1, 2):\n",
        "            for idx in range(first_qubit, num_qubits, 2):\n",
        "                # equivalent to Rzz(-pi/2):\n",
        "                circuit.sdg([idx - 1, idx])\n",
        "                circuit.cz(idx - 1, idx)\n",
        "\n",
        "    return circuit\n",
        "\n",
        "\n",
        "def prepare_basis(\n",
        "    circuit: QuantumCircuit, basis: list[int]\n",
        ") -> QuantumCircuit:\n",
        "    # basis is a list of integer values from 0 to 3. These map to the basis measurement as:\n",
        "    # 0 = I; 1 = Z; 2 = X; 3 = Y\n",
        "    assert len(basis) == circuit.num_qubits\n",
        "\n",
        "    out_circ = circuit.copy_empty_like()\n",
        "    for qb, bas in enumerate(basis):\n",
        "        if bas == 2:\n",
        "            out_circ.h(qb)\n",
        "        elif bas == 3:\n",
        "            out_circ.rx(-np.pi / 2, qb)\n",
        "\n",
        "    out_circ.barrier()\n",
        "    out_circ.compose(circuit, inplace=True)\n",
        "    return out_circ\n",
        "\n",
        "\n",
        "def mirror_circuit(circuit: QuantumCircuit) -> QuantumCircuit:\n",
        "    mirror_circ = circuit.copy_empty_like()\n",
        "    mirror_circ.compose(circuit.inverse(), inplace=True)\n",
        "    mirror_circ.barrier()\n",
        "    mirror_circ.compose(circuit, inplace=True)\n",
        "    mirror_circ.measure_active()\n",
        "    return mirror_circ"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 8,
      "id": "67553310-f1f0-4736-85ad-bfebd3c00414",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Instantiate the mirrored circuit and prepend the basis-preparation gates\n",
        "circuit = construct_ising_circuit(num_qubits, num_trotter_steps, rx_angle)\n",
        "mirrored_circuit = mirror_circuit(circuit)\n",
        "mirrored_circuit = prepare_basis(mirrored_circuit, bases_virt[0])"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 9,
      "id": "0dab284e-dbed-4d2f-92aa-c6c9c0e909a6",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/0dab284e-dbed-4d2f-92aa-c6c9c0e909a6-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 9,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "mirrored_circuit.draw(\n",
        "    \"mpl\", fold=-1, scale=0.3, idle_wires=False, measure_arrows=False\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "slc-step2-000-4000-8000-000000000013",
      "metadata": {},
      "source": [
        "<span id=\"step-2-optimize\" />\n",
        "\n",
        "### Schritt 2: Optimieren\n",
        "\n",
        "Wir optimieren die Details in Bezug auf die zu durchlaufende Schaltung, die zu messende Größe und die Parameter für das Rauschlernen. Als Ausgangspunkt wählen wir den am wenigsten ausgelasteten Heron-Prozessor aus und instanziieren ihn mit aktivierten Bruchgates. Diese fraktionierten Gatter ermöglichen eine höhere Empfindlichkeit bei einigen unserer Filtervorgänge nach der Auswahl.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 10,
      "id": "slc-creds-000-4000-8000-000000000014",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Selected backend: ibm_kingston\n"
          ]
        }
      ],
      "source": [
        "# Initialize IBM Quantum Compute Service using your saved credentials\n",
        "service = QiskitRuntimeService()\n",
        "\n",
        "# Select the least busy Heron device. This tutorial targets Heron processors:\n",
        "# the usage estimate and the non-Markovian-noise post-selection discussed in\n",
        "# Step 4 are specific to this processor family.\n",
        "backend = service.least_busy(\n",
        "    operational=True,\n",
        "    simulator=False,\n",
        "    filters=lambda b: b.processor_type[\"family\"] == \"Heron\",\n",
        ")\n",
        "# Re-fetch with fractional gates enabled (least_busy does not forward this)\n",
        "# Fractional gates are enabled so the non-Clifford Rx rotations are supported natively.\n",
        "backend = service.backend(backend.name, use_fractional_gates=True)\n",
        "print(f\"Selected backend: {backend.name}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "81dd2580-d4a8-45bb-bd05-707c64aa4ac7",
      "metadata": {},
      "source": [
        "Zunächst werden wir unsere Schaltung in ISA-Befehle transpilieren, [wie es für die Ausführung auf unseren QPUs erforderlich ist](/docs/guides/transpile#instruction-set-architecture). Die Wahl der physikalischen Qubits, auf denen die Berechnungen ausgeführt werden, hat einen großen Einfluss auf die Qualität der Ergebnisse, da die Kohärenzzeiten und die Gattertreue innerhalb eines Geräts variieren und sich im Laufe der Zeit verändern. Für ein sorgfältiges Experiment kann es sich lohnen, eine Kette von Qubits anhand kurzer Benchmark-Experimente manuell auszuwählen (siehe beispielsweise das Tutorial „[Echtzeit-Benchmarking zur Qubit-Auswahl](/docs/tutorials/real-time-benchmarking-for-qubit-selection) “). Für die Zwecke dieses Tutorials nutzen wir den Qiskit-Transpiler, um ein Layout auf der Grundlage der Kopplungskarte des Backends und dessen aktuellster Kalibrierungsdaten auszuwählen.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 11,
      "id": "a7505de6-70ef-4996-9f6b-0025f267e920",
      "metadata": {
        "scrolled": true
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Selected layout: [71, 58, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 36, 21, 22, 23, 16, 3, 2]\n"
          ]
        }
      ],
      "source": [
        "isa_pm = generate_preset_pass_manager(backend=backend, optimization_level=1)\n",
        "\n",
        "isa_circuit = isa_pm.run(mirrored_circuit)\n",
        "\n",
        "# The chain of physical qubits selected by the transpiler\n",
        "layout = isa_circuit.layout.final_index_layout()\n",
        "print(f\"Selected layout: {layout}\")\n",
        "\n",
        "isa_observable = observable.apply_layout(\n",
        "    layout, num_qubits=isa_circuit.num_qubits\n",
        ")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 12,
      "id": "87f19e08-0cae-4959-9215-354ab099ab02",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/87f19e08-0cae-4959-9215-354ab099ab02-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 12,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "wire_order = layout + [\n",
        "    q for q in range(isa_circuit.num_qubits) if q not in layout\n",
        "]\n",
        "isa_circuit.draw(\n",
        "    \"mpl\",\n",
        "    fold=-1,\n",
        "    scale=0.3,\n",
        "    idle_wires=False,\n",
        "    wire_order=wire_order,\n",
        "    measure_arrows=False,\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "7e7135e7-1e86-4d16-b806-30d4a9225e88",
      "metadata": {},
      "source": [
        "<span id=\"box-the-circuit\" />\n",
        "\n",
        "### Schaltkreis einrahmen\n",
        "\n",
        "Um die Implementierung zu vereinfachen, nutzen wir den `generate_boxing_pass_manager` Transpilation-Durchlauf, bei dem die Schaltungsanweisungen in kommentierte Kästchen eingefügt werden. Diese Kästchen zeigen deutlich, an welchen Stellen im Fall von PEC eine Entstörung in den Schaltkreis eingespeist werden sollte. Einzelheiten zu den Einstellungen finden Sie in der [Samplomatic-Dokumentation](/docs/addons/samplomatic/).\n",
        "\n",
        "Beachten Sie, dass der SLC-Workflow die Verwendung von `inject_noise_strategy=\"individual_modification\"` im weiteren Verlauf des Prozesses erfordert, da wir dadurch die `InjectNoise` Anmerkungen zu den `BoxOp` Anweisungen im Schaltkreis eindeutig identifizieren können. (Kästchen ohne Anmerkung `InjectNoise` lassen sich auf diese Weise nicht identifizieren.)\n",
        "\n",
        "<Admonition type=\"caution\">\n",
        "  Die SLC-gebundenen Berechnungen gehen derzeit davon aus, dass die `InjectNoise` Annotationen mit `site=\"after\"` platziert sind (siehe [qiskit-addon-slc#33](https://github.com/Qiskit/qiskit-addon-slc/issues/33) ). Da sich der Standardwert dieser Einstellung in neueren Versionen geändert hat (siehe [samplomatic#372](https://github.com/Qiskit/samplomatic/pull/372) ), legen `inject_noise_site=\"after\"` wir ihn `samplomatic` im Folgenden explizit fest.\n",
        "</Admonition>\n",
        "\n",
        "Die `find_unique_box_instructions` Funktion durchläuft den angegebenen „boxed circuit“ und identifiziert diejenigen, die einzigartige Zwei-Qubit-Schichten ( 2Q ) oder Messungen aufweisen, zum Zweck des Rauschlernens und der Rauscheinjektion.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 13,
      "id": "440f818c-9417-4855-b31b-db47b8595618",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Box circuit with Twirl and InjectNoise annotations\n",
        "boxes_pm = generate_boxing_pass_manager(\n",
        "    twirling_strategy=\"active\",\n",
        "    inject_noise_strategy=\"individual_modification\",\n",
        "    inject_noise_site=\"after\",\n",
        "    inject_noise_targets=\"gates\",\n",
        "    measure_annotations=\"all\",\n",
        ")\n",
        "\n",
        "\n",
        "boxed_circuit = boxes_pm.run(isa_circuit)\n",
        "\n",
        "# Find the unique instructions (layers) from boxed circuit\n",
        "unique_2q_instructions = find_unique_box_instructions(\n",
        "    boxed_circuit, normalize_annotations=None, undress_boxes=True\n",
        ")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 14,
      "id": "d662b076-005e-46c7-826d-4c29a4937404",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/d662b076-005e-46c7-826d-4c29a4937404-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 14,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "boxed_circuit.draw(\n",
        "    \"mpl\",\n",
        "    fold=-1,\n",
        "    scale=0.3,\n",
        "    idle_wires=False,\n",
        "    wire_order=wire_order,\n",
        "    measure_arrows=False,\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "aa8794ef-7906-4ee3-a9b0-e0399b3e9129",
      "metadata": {},
      "source": [
        "<span id=\"prepare-canonical-bases-measurements\" />\n",
        "\n",
        "### Messungen der kanonischen Basen vorbereiten\n",
        "\n",
        "Beachten Sie, dass wir besonders darauf achten müssen, die Reihenfolge der Qubits im Auge zu behalten. Im Folgenden führen wir den Begriff ein, um `canonical_qubits` die Reihenfolge der Qubits bei der Übergabe an den Executor angemessen anzupassen – dies ergibt sich aus der Art und Weise, wie die Qubit-Reihenfolge beim „Boxen“ von Schaltkreisen und beim Ermitteln eindeutiger Befehle erfasst wird. Weitere Informationen finden Sie in der Dokumentation zur [Qubit-Reihenfolgekonvention](/docs/addons/samplomatic/guides/samplex-io#qubit-ordering-convention).\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 15,
      "id": "e242702b-0274-4ebf-837d-4319a6772339",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Determine the canonical qubits order\n",
        "meas_box = boxed_circuit.data[-1]\n",
        "canonical_qubits = [\n",
        "    idx\n",
        "    for idx, qubit in enumerate(boxed_circuit.qubits)\n",
        "    if qubit in meas_box.qubits\n",
        "]\n",
        "\n",
        "# map canonical qubit to physical (isa) qubit\n",
        "c_2_p = {c: p for c, p in enumerate(canonical_qubits)}\n",
        "# map physical (isa) qubit to virtual qubit (index in original circuit)\n",
        "p_2_v = {p: v for v, p in enumerate(layout)}\n",
        "# compute map between virtual and canonical qubit indices.\n",
        "c_2_v = {c: p_2_v[p] for c, p in c_2_p.items()}\n",
        "\n",
        "assert len(c_2_v) == num_qubits\n",
        "\n",
        "bases_canon = [\n",
        "    np.array([base_i[c_2_v[c]] for c in range(num_qubits)], dtype=np.uint8)\n",
        "    for base_i in bases_virt\n",
        "]"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "61d2762c",
      "metadata": {},
      "source": [
        "<span id=\"workflow-for-lightcone-shading-noise-learning-and-anti-noise-injection\" />\n",
        "\n",
        "### Arbeitsablauf für Lightcone-Schattierung, Rauschlernen und Anti-Rausch-Injektion\n",
        "\n",
        "<Admonition type=\"note\">\n",
        "  In diesem Tutorial führen wir die SLC-gebundenen Berechnungen durch, bevor das Rauschlernen abgeschlossen ist, sodass die zu optimierende Schaltung zeitlich so nah wie möglich am erlernten Rauschmodell ausgeführt wird. Grundsätzlich lässt sich dieser Arbeitsablauf noch weiter parallelisieren: Ein Job zum Lernen des Rauschverhaltens kann ausgeführt werden, während parallel dazu die Rauschgrenzen geschätzt werden. Bei einer beliebigen Quantenschaltung kann die durch Rauschen begrenzte Berechnung schwach exponentiell skalieren, sodass die Parallelisierung der begrenzten Berechnung (beispielsweise über viele CPU-Kerne hinweg) bei einem gegebenen Rechenzeitbudget engere Grenzen ermöglicht, und die QPU-Ausführungen sowie die begrenzten Berechnungen können ihrerseits für einen möglichst effizienten Arbeitsablauf parallelisiert werden.\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "4a2ec5ed-c339-4273-b407-6bb46b9ede19",
      "metadata": {},
      "source": [
        "<span id=\"predict-to-be-learned-noise-model-paulis\" />\n",
        "\n",
        "#### Vorhersage des zu erlernenden Rauschmodells Paulis\n",
        "\n",
        "Bevor wir das Noise-Learning auf der Hardware ausführen, ermitteln wir zunächst, welche Pauli-Fehlerterme unser Rauschmodell enthalten wird. Zur Erinnerung: Wir modellieren das Rauschen jeder einzelnen Schicht als spärliches Pauli-Lindblad-Modell mit einer Fehlerrate für jedes Pauli-Element mit dem Gewicht 1 ( $X$, $Y$ oder $Z$ ) an jedem aktiven Qubit und einer weiteren für jedes Pauli-Element mit dem Gewicht 2 an jedem verbundenen Paar aktiver Qubits. Die `generate_noise_model_paulis` Funktion durchläuft jede einzelne „boxed“ Ebene des angegebenen Schaltkreises und generiert unter Berücksichtigung der Qubit-Verbindungen des Schaltkreises genau diese Liste von Pauli-Termen.\n",
        "\n",
        "Mit anderen Worten: Dieser Schritt prognostiziert die *Struktur* des Rauschmodells, das `NoiseLearnerV3` später mit den gelernten Raten ausgefüllt wird. Da wir diese Struktur bereits im Voraus kennen, können wir für jeden potenziellen Fehler eine Vorwärts- und eine Rückwärtsgrenze berechnen, noch bevor das Rauschlernen überhaupt ausgeführt wurde.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 16,
      "id": "e6783d37-c91f-4c6e-af4e-fb8f37d79e6b",
      "metadata": {},
      "outputs": [],
      "source": [
        "noise_model_paulis = generate_noise_model_paulis(\n",
        "    unique_2q_instructions, backend.coupling_map, boxed_circuit\n",
        ")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 17,
      "id": "f9fd1389-8c61-4585-85a6-ad17ab7a2f1c",
      "metadata": {},
      "outputs": [],
      "source": [
        "noise_model_rates = {ref: None for ref in noise_model_paulis}"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "942dd468-b2c5-49db-ada5-bcc45024df83",
      "metadata": {},
      "source": [
        "<span id=\"a-compute-forward-bounds\" />\n",
        "\n",
        "##### a. Vordere Grenzen berechnen\n",
        "\n",
        "Die `compute_forward_bounds` Funktion wertet die Kommutationsrelationen zwischen den Gattern in jeder Schicht und den zuvor erzeugten Pauli-Termen dahingehend aus, wie sich Fehler bei der Vorwärtsausbreitung auf die gewünschte Observable $A$ auswirken. Bei Gattern, die mit den Pauli-Termen kommutieren, wird nichts unternommen. Clifford-Gatter werden an den Anfang der Schaltung verschoben. Bei Nicht-Clifford-Gattern schätzen wir deren Einfluss auf die Zielobservablen, um diese später (nachdem alle Grenzen zusammengeführt wurden) bei der Rauschunterdrückung vorrangig zu behandeln. Diese Schranke wird erreicht, indem zunächst die „ L2 “-Norm angewendet wird (nämlich die Quadratwurzel aus der Summe der Quadrate der Koeffizienten der relevanten Pauli-Terme). Wenn zu viele Qubit-Terme beteiligt sind, greifen wir auf eine weniger strenge Schranke zurück, die auf der Dreiecksungleichung basiert.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "slc-bparam-00-4000-8000-000000000030",
      "metadata": {},
      "source": [
        "<span id=\"set-the-bound-computation-parameters\" />\n",
        "\n",
        "#### Die Parameter für die Gebietsberechnung festlegen\n",
        "\n",
        "Die folgenden Einstellungen steuern die Berechnungen der klassischen Grenzen:\n",
        "\n",
        "* `slc_atol`: die absolute Toleranz der Berechnungen. Operanden mit Koeffizienten unterhalb dieses Schwellenwerts werden als vernachlässigbar betrachtet und verworfen.\n",
        "* `slc_eigval_max_qubits`: die maximale Anzahl von Qubits, für die die strengere, auf Eigenwerten basierende ( L2-norm ) Schranke berechnet wird. Operatoren, die auf mehr Qubits als dieser Anzahl unterstützt werden, fallen auf die oben beschriebene, lockere Dreiecksungleichungsgrenze zurück.\n",
        "* `slc_evolution_max_terms`: die maximale Anzahl von Pauli-Termen, die bei der Weiterleitung der Operatoren durch den Schaltkreis beibehalten werden. Durch das Weglassen der kleinsten Terme bleibt die Berechnung überschaubar, allerdings auf Kosten etwas ungenauerer Schätzgrenzen.\n",
        "* `slc_num_processes`: die Anzahl der CPU-Prozesse, über die die gebundene Berechnung parallelisiert wird.\n",
        "* `slc_timeout`: das Zeitbudget (in Sekunden) für die Berechnung der Bindung. Allen Ebenen, die innerhalb dieses Budgets nicht erreicht werden, wird die triviale Grenze 2 zugewiesen.\n",
        "\n",
        "<Admonition type=\"note\">\n",
        "  In diesem Tutorial wird das Timeout *absichtlich* erreicht: Wir halten `slc_timeout` die Zeit bewusst kurz, damit das Notebook schnell läuft und um zu zeigen, dass das Erreichen des Timeouts unbedenklich ist. Die berechneten Schattierungen stellen in jeder Stufe Obergrenzen dar; das Erreichen des Zeitlimits führt daher lediglich dazu, dass die Grenzen für die nicht erreichten Schichten weiter gefasst werden (trivial); die Auswirkungen eines Fehlers werden dabei niemals unterschätzt. Dies zeigt sich in den folgenden Visualisierungen darin, dass die Ebenens ganz links vollständig mit der trivialen Grenze ausgefüllt sind. Durch eine Verlängerung des Timeouts (oder eine Erhöhung der Anzahl der Prozesse) werden die Grenzen enger gesetzt und der Overhead bei der Stichprobenentnahme weiter reduziert.\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 18,
      "id": "79fe4a21-b80d-496b-ba20-f16d9b11834d",
      "metadata": {},
      "outputs": [],
      "source": [
        "slc_atol = 1e-8\n",
        "slc_eigval_max_qubits = 18\n",
        "slc_evolution_max_terms = 1000\n",
        "slc_num_processes = 8\n",
        "slc_timeout = 60"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 19,
      "id": "705fcb9f-c753-45a6-97dd-9ff8ecb07018",
      "metadata": {
        "scrolled": true
      },
      "outputs": [
        {
          "name": "stderr",
          "output_type": "stream",
          "text": [
            "Bounds computation timed out.\n"
          ]
        }
      ],
      "source": [
        "forward_bounds = compute_forward_bounds(\n",
        "    boxed_circuit,\n",
        "    noise_model_paulis,\n",
        "    isa_observable,\n",
        "    evolution_max_terms=slc_evolution_max_terms,\n",
        "    eigval_max_qubits=slc_eigval_max_qubits,\n",
        "    atol=slc_atol,\n",
        "    num_processes=slc_num_processes,\n",
        "    timeout=slc_timeout,\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "aaf7c188-c85b-42cf-a096-0a5dc72a68e3",
      "metadata": {},
      "source": [
        "<span id=\"visualize-the-slc-for-manual-inspection\" />\n",
        "\n",
        "#### Visualisieren Sie den SLC zur manuellen Überprüfung\n",
        "\n",
        "Man kann das Verhalten der schraffierten Grenzen interpretieren, indem man untersucht, wie die Messungen und die Pauli-Terme mit den lokalen Fehlern zusammenwirken. Diese Muster sind charakteristisch für dieses zeitliche Evolutionsproblem des „Kicked“-Ising-Hamiltonian und tauchen auch in der Arbeit [„Lightcone Shading for Classically Accelerated Quantum Error Mitigation“](https://arxiv.org/abs/2409.04401) auf, wobei sie mehrere verräterische Merkmale aufweisen:\n",
        "\n",
        "* Wir können die beiden Kegel, die sich aus den beiden nicht identischen Paulis im Observablen ergeben, klar voneinander unterscheiden.\n",
        "* Wir können sehen, dass die X-Messung am Qubit 6 mit dem X-Fehler in der äußersten Schicht kommutiert.\n",
        "* Wir sehen, dass der Z-Pauli-Operator auf Qubit 13 mit dem Z-Fehler in der äußersten Schicht kommutiert.\n",
        "* Wenn wir das oben angegebene Zeitlimit erreichen, werden die verbleibenden Schichten auf der linken Seite vollständig mit trivialen Grenzen von zwei gefüllt.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 20,
      "id": "285edaef-8e79-41eb-a563-4675ea75d5b1",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/285edaef-8e79-41eb-a563-4675ea75d5b1-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/285edaef-8e79-41eb-a563-4675ea75d5b1-1.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/285edaef-8e79-41eb-a563-4675ea75d5b1-2.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "for p in \"XYZ\":\n",
        "    display(\n",
        "        draw_shaded_lightcone(\n",
        "            boxed_circuit,\n",
        "            forward_bounds,\n",
        "            noise_model_paulis,\n",
        "            pauli_filter=p,\n",
        "            scale=0.15,\n",
        "            fold=-1,\n",
        "            idle_wires=False,\n",
        "            wire_order=wire_order,\n",
        "            measure_arrows=False,\n",
        "        )\n",
        "    )"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "1aee6b29-8077-4019-9e37-3e80292aadf6",
      "metadata": {},
      "source": [
        "<span id=\"b-tighten-forward-bounds-with-quantum-speed-limits\" />\n",
        "\n",
        "#### b. Die oberen Schranken mit Hilfe von Quanten-Geschwindigkeitsgrenzen verschärfen\n",
        "\n",
        "Anschließend verschärfen wir die Grenzen mithilfe der `tighten_with_speed_limit` Funktion. Die geometrische Ausdehnung des Lichtkegels der Observablen ist in den oben berechneten Vorwärtsgrenzen bereits vollständig berücksichtigt. Diese Funktion fügt einen Selbstkonsistenz-Durchlauf hinzu, der die begrenzte Geschwindigkeit nutzt, mit der Informationen durch den Schaltkreis fließen können – der Lichtkegel wächst um höchstens ein Qubit pro Schicht aus Zwei-Qubit-Gattern –, um die zuvor berechneten Grenzen untereinander enger anzugleichen. Der Informationsfluss verläuft in beide Richtungen:\n",
        "\n",
        "1. Von den Kommutatorgrenzen zu den Grenzen der rückwärtspropagierten Observable: Wenn die Observable mit einem Fehler vom Typ „ $Z$ “ auf einem Qubit kommutiert, kann sie dort keine nennenswerte Komponente vom Typ „ $X$ “ oder „ $Y$ “ aufweisen.\n",
        "2. Von den Schranken für die rückwärtspropagierte Observable zu den Kommutator-Schranken: Wenn die Observable an einem Qubit nur eine geringe „ $X$ “- und „ $Y$ “-Komponente aufweist, dann muss sie dort nahezu mit $Z$ kommutieren.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 21,
      "id": "e060d547-79da-4f54-bf5e-677f70306040",
      "metadata": {
        "scrolled": true
      },
      "outputs": [],
      "source": [
        "forward_bounds_tighter = tighten_with_speed_limit(\n",
        "    forward_bounds, boxed_circuit, noise_model_paulis, isa_observable\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "85f3c9c2-d60c-4044-a496-dae88c3dd749",
      "metadata": {},
      "source": [
        "<span id=\"visualize-the-slc-for-manual-inspection\" />\n",
        "\n",
        "#### Visualisieren Sie den SLC zur manuellen Überprüfung\n",
        "\n",
        "Grundsätzlich sorgt diese Verschärfung für einen sanfteren Übergang zwischen den berechneten Grenzen und den trivialen Grenzen, die den Schichten jenseits des Timeouts zugewiesen werden. In diesem Beispiel ist der Effekt kaum sichtbar, da sich die Lichtkegel bis zum Erreichen der Zeitüberschreitung bereits bis zu den Rändern der Schaltung ausgebreitet haben.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 22,
      "id": "6ba4b397-e903-4b85-9482-31a96c689dc0",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/6ba4b397-e903-4b85-9482-31a96c689dc0-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/6ba4b397-e903-4b85-9482-31a96c689dc0-1.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/6ba4b397-e903-4b85-9482-31a96c689dc0-2.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "for p in \"XYZ\":\n",
        "    display(\n",
        "        draw_shaded_lightcone(\n",
        "            boxed_circuit,\n",
        "            forward_bounds_tighter,\n",
        "            noise_model_paulis,\n",
        "            pauli_filter=p,\n",
        "            scale=0.15,\n",
        "            fold=-1,\n",
        "            idle_wires=False,\n",
        "            wire_order=wire_order,\n",
        "            measure_arrows=False,\n",
        "        )\n",
        "    )"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ce0f9cf4-565a-4f18-baa5-bec5c7b354a2",
      "metadata": {},
      "source": [
        "<span id=\"c-compute-backward-bounds\" />\n",
        "\n",
        "#### c. Rückwärtsgrenzen berechnen\n",
        "\n",
        "In diesem Teil der Rauschvorhersage wird untersucht, wie sich ein Fehler in einer bestimmten Schicht auf den Eingangs $\\rho$ auswirken kann. Die `compute_backward_bounds` Funktion invertiert zunächst die Schaltung und entfernt die Messgatter, um anschließend eine ähnliche Analyse wie bei den Vorwärtsberechnungen durchzuführen.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 23,
      "id": "ff895191-feda-4958-a58a-3d1fb973cb68",
      "metadata": {
        "scrolled": true
      },
      "outputs": [],
      "source": [
        "backward_bounds = compute_backward_bounds(\n",
        "    boxed_circuit,\n",
        "    noise_model_paulis,\n",
        "    evolution_max_terms=slc_evolution_max_terms,\n",
        "    num_processes=slc_num_processes,\n",
        "    timeout=slc_timeout,\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c707d352-37fe-4c53-82a3-a1207b7a7bfc",
      "metadata": {},
      "source": [
        "<span id=\"visualize-the-slc-for-manual-inspection\" />\n",
        "\n",
        "#### Visualisieren Sie den SLC zur manuellen Überprüfung\n",
        "\n",
        "Anhand der Berechnung der Rückwärtsgrenzen lässt sich erkennen, wie die Struktur des Anfangszustands das frühe Verhalten der Fehlerausbreitung bestimmt:\n",
        "\n",
        "* Wir können deutlich erkennen, wie die Z-Fehler zunächst mit dem Anfangszustand |0⟩ kommutieren.\n",
        "* Nur bei Qubit 6, wo wir den +1-Eigenzustand der X-Basis initialisieren, kommutiert ein Z-Fehler nicht, während ein X-Fehler sehr wohl kommutiert.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 24,
      "id": "40d0eba9-5e0b-4b1d-8fb2-c239e262062f",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/40d0eba9-5e0b-4b1d-8fb2-c239e262062f-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/40d0eba9-5e0b-4b1d-8fb2-c239e262062f-1.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/40d0eba9-5e0b-4b1d-8fb2-c239e262062f-2.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "for p in \"XYZ\":\n",
        "    display(\n",
        "        draw_shaded_lightcone(\n",
        "            boxed_circuit,\n",
        "            backward_bounds,\n",
        "            noise_model_paulis,\n",
        "            pauli_filter=p,\n",
        "            scale=0.15,\n",
        "            fold=-1,\n",
        "            idle_wires=False,\n",
        "            wire_order=wire_order,\n",
        "            measure_arrows=False,\n",
        "        )\n",
        "    )"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c8960e51-733e-4458-b77c-287ba3abdb8d",
      "metadata": {},
      "source": [
        "<span id=\"preview-merged-bounds-without-learned-noise-rates\" />\n",
        "\n",
        "#### Vorschau der zusammengeführten Grenzen ohne erlernte Rauschraten\n",
        "\n",
        "Die `merged_bounds` Funktion bestimmt den Punkt im Schaltkreis, an dem der Wechsel von Rückwärtsgrenzen zu Vorwärtsgrenzen die geschätzte Gesamtverzerrung der gewünschten Observable minimiert. Diese Verzerrung wird als Summe der rückwärtsgerichteten Beiträge für alle Rauschstellen vor diesem Punkt sowie der vorwärtsgerichteten Beiträge für alle Rauschstellen danach berechnet. Derzeit wird dies für alle Qubits einheitlich gehandhabt.\n",
        "\n",
        "<Admonition type=\"note\" title=\"Wichtiger Hinweis\">\n",
        "  Der optimale Zeitpunkt für den Wechsel von rückwärtsgerichteten zu vorwärtsgerichteten Grenzen hängt von den erlernten Rauschraten ab. Bislang haben wir noch keine Raten ermittelt: `noise_model_rates` Für jede Schicht wurde der Wert auf `None` gesetzt, was `merge_bounds` als gleichmäßige Rauschraten behandelt wird. Die hier dargestellten zusammengeführten Grenzen sind daher nur eine Vorschau; wir berechnen sie in Schritt 3 anhand der tatsächlich erlernten Raten neu.\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 25,
      "id": "b5cd8752-3bb5-44ee-97a8-6edaf5946e9d",
      "metadata": {},
      "outputs": [
        {
          "name": "stderr",
          "output_type": "stream",
          "text": [
            "Missing noise rates. Partitioning backward/forward commutator bounds by assuming uniform error rates.\n",
            "Optimal spacetime partitioning not implemented!Just partitioning list of noisy boxes.\n"
          ]
        }
      ],
      "source": [
        "merged_bounds = merge_bounds(\n",
        "    boxed_circuit,\n",
        "    forward_bounds_tighter,\n",
        "    backward_bounds,\n",
        "    noise_model_rates,\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ace27801-b949-44e0-801d-e74ccb2f877a",
      "metadata": {},
      "source": [
        "<span id=\"visualize-the-slc-for-manual-inspection\" />\n",
        "\n",
        "### Visualisieren Sie den SLC zur manuellen Überprüfung\n",
        "\n",
        "Nach der Zusammenführung der hinteren und der verschärften vorderen Grenzen wird das Verhalten der kombinierten SLCs deutlich:\n",
        "\n",
        "* Die obige Funktion gibt an, bei welcher Partition der Übergang von hinteren zu engeren vorderen Schranken stattfindet.\n",
        "* Wie unten zu sehen ist, enthalten die SLCs nun teilweise rückwärtsgerichtete und teilweise verschärfte Vorwärtsgrenzen.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 26,
      "id": "eb2948bc-fa27-4108-b03c-0291d3b2f7c8",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/eb2948bc-fa27-4108-b03c-0291d3b2f7c8-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/eb2948bc-fa27-4108-b03c-0291d3b2f7c8-1.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/eb2948bc-fa27-4108-b03c-0291d3b2f7c8-2.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "for p in \"XYZ\":\n",
        "    display(\n",
        "        draw_shaded_lightcone(\n",
        "            boxed_circuit,\n",
        "            merged_bounds,\n",
        "            noise_model_paulis,\n",
        "            pauli_filter=p,\n",
        "            scale=0.15,\n",
        "            fold=-1,\n",
        "            idle_wires=False,\n",
        "            wire_order=wire_order,\n",
        "            measure_arrows=False,\n",
        "        )\n",
        "    )"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "slc-step3-000-4000-8000-000000000050",
      "metadata": {},
      "source": [
        "<span id=\"step-3-execute\" />\n",
        "\n",
        "### Schritt 3: Ausführen\n",
        "\n",
        "In diesem Abschnitt beginnen wir mit dem Teil des Arbeitsablaufs, bei dem ein echtes Quantenbauelement zum Einsatz kommt. Diese lernbasierte Methode zur Fehlerminderung umfasst zwei Schritte:\n",
        "\n",
        "1. `NoiseLearnerV3`Lerne den Klang kennen, indem du … verwendest.\n",
        "2. Führen Sie eine Fehlerminderungsschaltung mit dem `samplomatic` und `Executor` Framework aus.\n",
        "\n",
        "Anhand der begrenzten Fehler unserer Quantenschaltung ermitteln wir die damit verbundenen Rauschraten, um unser Fehlerbudget zu priorisieren, den Abtast-Overhead zu bestimmen und die Ausführung auf einer QPU durchzuführen.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "slc-nlmd-0000-4000-8000-000000000051",
      "metadata": {},
      "source": [
        "<span id=\"a-learn-the-noise-rates\" />\n",
        "\n",
        "### a. Informieren Sie sich über die Lärmgrenzwerte\n",
        "\n",
        "Der „Noise Learner“ charakterisiert die Rauschprozesse, die die Gatter in einer oder mehreren Schaltungen von Interesse beeinflussen, auf der Grundlage des spärlichen [Pauli-Lindblad-Rauschmodells](https://arxiv.org/abs/2201.09866). Die `run()` Methode startet einen Noise-Learning-Auftrag für die angegebenen eindeutigen Zwei-Qubit-Schichten unter Verwendung der in der Noise-Learner-Konfiguration festgelegten Optionen. Diese Optionen steuern die Pauli-Twirling-Strategie, die Anzahl der Randomisierungen und Versuche, die Lerntiefen sowie die Nachauswahl.\n",
        "\n",
        "Auch die Lerntiefe wählen wir bewusst aus. Eine praktische Erkenntnis im Zusammenhang mit `samplomatic` lernbasierter Mitigation ist, dass es äußerst vorteilhaft ist, wenn die tiefste Lerntiefe mit der Tiefe der Schaltung übereinstimmt, für die eine Mitigation angestrebt wird. Da NLv3`layer_pair_depths` in *Schichtpaaren* gemessen werden (eine Schicht plus ihre Inverse), setzen wir den tiefsten Wert auf die Hälfte der Tiefe der Zwei-Qubit-Schicht der Schaltung.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 27,
      "id": "slc-psflag-00-4000-8000-000000000052",
      "metadata": {},
      "outputs": [],
      "source": [
        "post_selection_enabled = True"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 28,
      "id": "slc-nlopt-00-4000-8000-000000000053",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Match the deepest noise-learning depth to the depth of the circuit being\n",
        "# mitigated. NLv3 ``layer_pair_depths`` are measured in layer pairs (a layer\n",
        "# plus its inverse), so the deepest value is half the circuit's two-qubit-layer\n",
        "# depth. Learning to this depth markedly improves the quality of the mitigation.\n",
        "#\n",
        "# We measure the two-qubit-layer depth on the pre-boxed ISA circuit: after\n",
        "# boxing, every two-qubit gate is hidden inside a full-width ``BoxOp``, so a\n",
        "# ``num_qubits == 2`` filter on ``boxed_circuit`` matches nothing (and\n",
        "# ``QuantumCircuit.depth`` does not recurse into boxes).\n",
        "depth_2q = isa_circuit.depth(lambda instr: instr.operation.num_qubits == 2)\n",
        "max_layer_pair_depth = depth_2q // 2  # dividing by 2 since we want pairs\n",
        "\n",
        "# Use a fixed schedule of learning depths, but drop any that exceed the circuit's\n",
        "# depth and always cap the deepest value at ``max_layer_pair_depth`` so we never\n",
        "# learn deeper than the circuit being mitigated.\n",
        "candidate_depths = [1, 2, 4, 8, 12, 16, 24, 32, 40, 48]\n",
        "layer_pair_depths = sorted(\n",
        "    {d for d in candidate_depths if d < max_layer_pair_depth}\n",
        "    | {max_layer_pair_depth}\n",
        ")\n",
        "\n",
        "noise_learner_options = {\n",
        "    \"num_randomizations\": 64,\n",
        "    \"shots_per_randomization\": 128,\n",
        "    \"layer_pair_depths\": layer_pair_depths,\n",
        "    \"post_selection\": {\n",
        "        \"enable\": post_selection_enabled,\n",
        "        \"strategy\": \"edge\",\n",
        "        \"x_pulse_type\": \"rx\",\n",
        "    },\n",
        "    \"environment\": {\"job_tags\": [\"TUT_SLC\"]},\n",
        "}\n",
        "\n",
        "noise_learner = NoiseLearnerV3(backend, noise_learner_options)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 29,
      "id": "slc-nlrun-00-4000-8000-000000000054",
      "metadata": {},
      "outputs": [],
      "source": [
        "noise_learner_job = noise_learner.run(unique_2q_instructions)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 30,
      "id": "db624045-7abe-41f1-a60c-1ba141aaa6f6",
      "metadata": {},
      "outputs": [],
      "source": [
        "noise_learner_result = noise_learner_job.result()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 31,
      "id": "b08c37a6-5acb-41e9-a76c-abc0195d87f7",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Minimum fraction of shots kept for noise learning experiments: 0.71\n"
          ]
        }
      ],
      "source": [
        "if post_selection_enabled:\n",
        "    print(\n",
        "        \"Minimum fraction of shots kept for noise learning experiments: \",\n",
        "        end=\"\",\n",
        "    )\n",
        "    print(\n",
        "        f\"{min([min(d.values()) for d in [nlr.metadata['post_selection']['fraction_kept'] for nlr in noise_learner_result[:2]]]):.2f}\"\n",
        "    )"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 32,
      "id": "99144d7f-b891-4115-8cb3-ecdbedd10a73",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Get a dict mapping each InjectNoise.ref to its learned PauliLindbladMap\n",
        "refs_2_plm = noise_learner_result.to_dict(\n",
        "    unique_2q_instructions, require_refs=False\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "e6a790a3-bc11-46e2-8cb4-2e43257926c8",
      "metadata": {},
      "source": [
        "<span id=\"bi-update-merged-bounds-with-actual-learned-noise-rates\" />\n",
        "\n",
        "### b.i. Aktualisierung der zusammengeführten Grenzen anhand der tatsächlich erlernten Rauschraten\n",
        "\n",
        "Nachdem das spezifische Rauschmodell nun trainiert wurde, können wir die ermittelten Rauschraten auf die vorhergesagten Rauschgrenzen anwenden und so endgültig feststellen, welche Grenzen den größten Einfluss auf die Minimierung des Bias haben.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 33,
      "id": "bba8fca6-5339-4d4d-be01-45ca8bf94a5a",
      "metadata": {},
      "outputs": [
        {
          "name": "stderr",
          "output_type": "stream",
          "text": [
            "Optimal spacetime partitioning not implemented!Just partitioning list of noisy boxes.\n"
          ]
        }
      ],
      "source": [
        "merged_bounds = merge_bounds(\n",
        "    boxed_circuit,\n",
        "    forward_bounds_tighter,\n",
        "    backward_bounds,\n",
        "    refs_2_plm,\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "649b9640-e89f-4021-9f7d-f1e136c4eb6d",
      "metadata": {},
      "source": [
        "<span id=\"bii-compute-the-local_scales-for-the-hardware-execution\" />\n",
        "\n",
        "#### b.ii. Berechne das `local_scales` für die Hardware-Ausführung\n",
        "\n",
        "`compute_local_scales` Es werden alle möglichen Rauschfehler in der Schaltung untersucht und abgeschätzt, inwieweit dieser Fehler das Endergebnis der Messung verfälschen könnte und wie aufwendig dessen Korrektur wäre. Anschließend werden die Fehler danach gewichtet, inwieweit sich ihre Behebung lohnt, und es wird die Teilmenge ausgewählt, die die Verzerrung so weit wie möglich reduziert, ohne dabei das zulässige Budget für Stichprobenkosten zu überschreiten (bzw. die gewünschte Genauigkeit zu erreichen). Das Ergebnis ist eine Reihe von Skalierungsfaktoren, die angeben, welche Fehler aktiv gemindert werden und welche unberücksichtigt bleiben (`local_scales`), zusammen mit dem prognostizierten Gesamt-Overhead der Stichprobenkosten (`sampling_costs`) und der verbleibenden Verzerrung (`residual_bias_bound`).\n",
        "\n",
        "Die Möglichkeit, die gewünschte Restvorspannung zu steuern, ist ein entscheidendes Merkmal der SLC-Implementierung von PEC. Während bei der [ursprünglichen Implementierung](https://arxiv.org/abs/2201.09866) der Abtast-Overhead stets auf eine Null-Verzerrung ausgerichtet war, können wir den erforderlichen Abtast-Overhead nun durch einen Kompromiss hinsichtlich der erwarteten verbleibenden Verzerrung anpassen. Dies hilft dem Benutzer, ein festgelegtes Stichprobenbudget einzuhalten, was insbesondere bei der ersten Prototypenentwicklung eines Arbeitsablaufs nützlich sein kann.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 34,
      "id": "8914e00e-ba21-41dc-9e64-b942bb23fde1",
      "metadata": {},
      "outputs": [],
      "source": [
        "id_map = map_modifier_ref_to_ref(boxed_circuit)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 35,
      "id": "4c971d31-f2b4-4d17-8a83-4f6d14f81f6f",
      "metadata": {
        "scrolled": true
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Full PEC gamma=12.750876317157042, sampling cost (gamma^2) = 162.58484685543633\n"
          ]
        }
      ],
      "source": [
        "summed_rates = 0.0\n",
        "for box_id, noise_id in id_map.items():\n",
        "    learned_plm = refs_2_plm[noise_id]\n",
        "    summed_rates += np.sum(learned_plm.rates)\n",
        "    # print(f\"{box_id}:\\tgamma = {np.exp(2 * summed_rates):1.6e}\\tsampling cost = {np.exp(4 * summed_rates):1.6e}\")\n",
        "total_gamma = np.exp(2 * summed_rates)\n",
        "print(\n",
        "    f\"Full PEC gamma={total_gamma}, sampling cost (gamma^2) = {total_gamma**2}\"\n",
        ")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 36,
      "id": "fb6f2aa0-8a35-420c-aae3-6fc5e9aa8747",
      "metadata": {},
      "outputs": [],
      "source": [
        "biases = []\n",
        "costs = []\n",
        "for bias in [0.0] + np.arange(0.001, 0.102, 0.01).tolist():\n",
        "    _, cost_, bias_ = compute_local_scales(\n",
        "        boxed_circuit,\n",
        "        merged_bounds,\n",
        "        refs_2_plm,\n",
        "        sampling_cost_budget=np.inf,\n",
        "        bias_tolerance=bias,\n",
        "    )\n",
        "    biases.append(bias_)\n",
        "    costs.append(cost_)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "slc-trade-00-4000-8000-000000000065",
      "metadata": {},
      "source": [
        "<span id=\"trade-off-sampling-overhead-against-residual-bias\" />\n",
        "\n",
        "#### Abwägung zwischen dem Aufwand für die Stichprobenziehung und der verbleibenden Verzerrung\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 37,
      "id": "slc-ovplot-0-4000-8000-000000000066",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "Text(0.5, 0.98, 'PEC sampling overhead reduction due to SLC')"
            ]
          },
          "execution_count": 37,
          "metadata": {},
          "output_type": "execute_result"
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/slc-ovplot-0-4000-8000-000000000066-1.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "xticks = np.arange(0, 11)\n",
        "\n",
        "fig, ax = plt.subplots()\n",
        "ax.scatter(\n",
        "    [0], [total_gamma**2], marker=\"D\", c=\"tab:orange\", label=\"full PEC\"\n",
        ")\n",
        "ax.plot(\n",
        "    100 * np.array(biases),\n",
        "    np.array(costs),\n",
        "    \"o-\",\n",
        "    c=\"tab:blue\",\n",
        "    label=\"PEC+SLC\",\n",
        ")\n",
        "ax.set_yscale(\"log\")\n",
        "ax.set_xticks(xticks, [f\"{x:.1f}\" for x in xticks])\n",
        "\n",
        "ax.set_xlabel(\"Remaining bias [%]\")\n",
        "ax.set_ylabel(r\"Sampling overhead, $\\gamma^2$\")\n",
        "ax.grid()\n",
        "ax.legend()\n",
        "fig.suptitle(\"PEC sampling overhead reduction due to SLC\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 38,
      "id": "1df482f9-c12c-4ea7-a71e-7ffc0837b5a6",
      "metadata": {},
      "outputs": [],
      "source": [
        "chosen_bias_thres = 0.1"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 39,
      "id": "slc-lscale-0-4000-8000-000000000068",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "PEC+SLC sampling cost (gamma^2) = 48.13646509622307 w/ remaining bias = 10.0%\n"
          ]
        }
      ],
      "source": [
        "local_scales, sampling_cost, residual_bias_bound = compute_local_scales(\n",
        "    boxed_circuit,\n",
        "    merged_bounds,\n",
        "    refs_2_plm,\n",
        "    sampling_cost_budget=np.inf,\n",
        "    bias_tolerance=chosen_bias_thres,\n",
        ")\n",
        "print(\n",
        "    f\"PEC+SLC sampling cost (gamma^2) = {sampling_cost} \"\n",
        "    f\"w/ remaining bias = {100 * residual_bias_bound:.1f}%\"\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "8b03c0ed-d026-4b29-bc09-68c4d0c94889",
      "metadata": {},
      "source": [
        "<span id=\"c-execute-the-circuit-of-interest-with-antinoise\" />\n",
        "\n",
        "### c. Führen Sie die gewünschte Schaltung mit Antinoise aus\n",
        "\n",
        "<span id=\"ci-prepare-template-circuit-by-using-samplex\" />\n",
        "\n",
        "#### c.i. Bereiten Sie die Schaltungsvorlage vor, indem Sie `samplex`\n",
        "\n",
        "`template_circuit`Das `samplex` ist eine Ausgabe der `build` Methode von Samplomatic, die alle Informationen enthält, die zur Erzeugung zufälliger Parameter für erforderlich sind. Diese werden dann zum Einrichten der `QuantumProgram` Objekte verwendet, die wiederum auf einer QPU mit der `Executor` Primitive ausgeführt werden. `samplex`Jedes `QuantumProgram` kann mehrere Elemente enthalten, die man sich als ein Paar aus `template` und vorstellen kann.\n",
        "\n",
        "Weitere Informationen findest du im Tutorial „[Hello Samplomatic](https://github.com/qiskit-community/qdc-challenges-2025/blob/main/day3_tutorials/Track_A/hello_samplomatic/Samplomatic%20-%20Hello%20World.ipynb) “.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 40,
      "id": "a68109cc-3b38-40e0-b145-edaa06b9934d",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Build template circuit and samplex for later use with the \"Executor\"\n",
        "template_circuit, samplex = samplomatic.build(boxed_circuit)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 41,
      "id": "0de4922b-b958-48d7-b63a-819edfb354bd",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Set up postselection if it's been enabled\n",
        "if post_selection_enabled:\n",
        "    # Set up post selection PM (to add PS instructions)\n",
        "    post_selection_pm = PassManager(\n",
        "        [\n",
        "            AddSpectatorMeasures(backend.coupling_map),\n",
        "            AddPostSelectionMeasures(x_pulse_type=\"rx\"),\n",
        "        ]\n",
        "    )\n",
        "    final_template_circuit = post_selection_pm.run(template_circuit)\n",
        "else:\n",
        "    final_template_circuit = template_circuit"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "4f00c61a-4c4d-40d9-9a41-11ea8e9a9337",
      "metadata": {},
      "source": [
        "<span id=\"cii-set-up-the-quantumprogram\" />\n",
        "\n",
        "#### c.ii. Richten Sie die `QuantumProgram`\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 42,
      "id": "a63d8881-2bbb-4c99-afd3-43f03ee04e5c",
      "metadata": {},
      "outputs": [],
      "source": [
        "num_randomizations = 4096\n",
        "shots_per_randomization = 64\n",
        "chunk_size = 256"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 43,
      "id": "038a04c9-20ba-432c-bfec-cf69e67ac82f",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Set up QuantumProgram\n",
        "program = QuantumProgram(shots=shots_per_randomization, noise_maps=refs_2_plm)\n",
        "\n",
        "# no EM\n",
        "\n",
        "# Collect up a dict of the other arguments that need to be bound to samplex_inputs\n",
        "samplex_inputs = {\n",
        "    f\"noise_scales.{ref}\": float(0) for ref in local_scales.keys()\n",
        "}\n",
        "samplex_inputs |= {\"basis_changes\": {\"basis0\": bases_canon[0]}}\n",
        "\n",
        "# Convert samplex_inputs into a dict to pass to QuantumProgram\n",
        "samplex_arguments = (\n",
        "    samplex.inputs().bind(**samplex_inputs).make_broadcastable()\n",
        ")\n",
        "\n",
        "program.append_samplex_item(\n",
        "    circuit=final_template_circuit,\n",
        "    samplex=samplex,\n",
        "    samplex_arguments=samplex_arguments,\n",
        "    shape=(num_randomizations,),\n",
        "    chunk_size=chunk_size,\n",
        ")\n",
        "\n",
        "# plain PEC\n",
        "\n",
        "# Collect a dict of the other arguments that need to be bound to samplex_inputs\n",
        "samplex_inputs = {\n",
        "    f\"noise_scales.{ref}\": float(-1) for ref in local_scales.keys()\n",
        "}\n",
        "samplex_inputs |= {\"basis_changes\": {\"basis0\": bases_canon[0]}}\n",
        "\n",
        "# Convert samplex_inputs into a dict to pass to QuantumProgram\n",
        "samplex_arguments = (\n",
        "    samplex.inputs().bind(**samplex_inputs).make_broadcastable()\n",
        ")\n",
        "\n",
        "program.append_samplex_item(\n",
        "    circuit=final_template_circuit,\n",
        "    samplex=samplex,\n",
        "    samplex_arguments=samplex_arguments,\n",
        "    shape=(num_randomizations,),\n",
        "    chunk_size=chunk_size,\n",
        ")\n",
        "\n",
        "# PEC+SLC\n",
        "\n",
        "# Collect a dict of the other arguments that need to be bound to samplex_inputs\n",
        "samplex_inputs = {\n",
        "    f\"noise_scales.{ref}\": float(-1) for ref in local_scales.keys()\n",
        "}\n",
        "samplex_inputs |= {\"basis_changes\": {\"basis0\": bases_canon[0]}}\n",
        "samplex_inputs |= {\"local_scales\": local_scales}\n",
        "\n",
        "# Convert samplex_inputs into a dict to pass to QuantumProgram\n",
        "samplex_arguments = (\n",
        "    samplex.inputs().bind(**samplex_inputs).make_broadcastable()\n",
        ")\n",
        "\n",
        "program.append_samplex_item(\n",
        "    circuit=final_template_circuit,\n",
        "    samplex=samplex,\n",
        "    samplex_arguments=samplex_arguments,\n",
        "    shape=(num_randomizations,),\n",
        "    chunk_size=chunk_size,\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "3100fff6-9fe9-49ad-9662-549cea099e9b",
      "metadata": {},
      "source": [
        "<span id=\"ciii-execute-program-with-the-executor-primitive\" />\n",
        "\n",
        "#### c.iii. Programm mit der `Executor` Primitive ausführen\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 44,
      "id": "2a1c0fe1-8761-462b-a1d9-b951d5224164",
      "metadata": {},
      "outputs": [],
      "source": [
        "executor = Executor(backend)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 45,
      "id": "slc-exec-000-4000-8000-000000000078",
      "metadata": {},
      "outputs": [],
      "source": [
        "job_exec = executor.run(program)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 46,
      "id": "70dd5d10-9cc5-4341-9642-18ba3a0ee58b",
      "metadata": {},
      "outputs": [],
      "source": [
        "results_exec = job_exec.result()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "slc-step4-000-4000-8000-000000000080",
      "metadata": {},
      "source": [
        "<span id=\"step-4-post-process\" />\n",
        "\n",
        "### Schritt 4: Nachbearbeitung\n",
        "\n",
        "`executor_expectation_values`Da wir den endgültigen Erwartungswert, der uns interessiert, mithilfe von berechnen, wenden wir einige Nachbearbeitungsverfahren an, um sicherzustellen, dass wir Ergebnisse von höchstmöglicher Qualität erhalten. Zunächst wenden wir unser [„Twirled Readout Error Extinction“-Verfahren (TREX)](/docs/guides/error-mitigation-and-suppression-techniques#twirled-readout-error-extinction-trex) an, das alle während des Auslesevorgangs auftretenden Fehler berücksichtigt. Anschließend beheben wir Fehler, die durch nicht-markovisches Rauschen in unseren Heron-Backends verursacht werden, mithilfe einer Post-Selektionsmethode. Bei diesem Verfahren werden aktive und passive Qubits gemessen, anschließend wird an jedem Qubit eine langsame Rotation durchgeführt, und danach erfolgt eine erneute Messung. `PostSelector`In Fällen, in denen die beiden Messungen nicht wie erwartet ein umgedrehtes Qubit bestätigen, werden diese Messdurchläufe durch Anwendung einer `mask` aus der verworfen. Im Rahmen der Maskenberechnung kann eine bestimmte Strategie festgelegt werden, um anhand von Einzel-Qubit-Knoten oder benachbarten Zuschauer-Kanten zu filtern, was sich sowohl auf die Anzahl der herausgefilterten Durchläufe als auch auf die Qualität der Ergebnisse auswirken kann.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 47,
      "id": "147bcd34-f79e-4abb-821d-5ea9d8388c9f",
      "metadata": {},
      "outputs": [],
      "source": [
        "measurement_noise_map = noise_learner_result[2].to_pauli_lindblad_map()\n",
        "trex_scale_factors = trex_factors(measurement_noise_map, reverser_virt)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 48,
      "id": "981353e7-95a9-48bb-92c8-87b931e2b295",
      "metadata": {},
      "outputs": [],
      "source": [
        "post_selection_strategy = \"node\""
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 49,
      "id": "826afe02-3308-4d20-a9e1-ac0b2ba25eca",
      "metadata": {},
      "outputs": [],
      "source": [
        "def post_process_conv(datum, steps=16, gamma=None, ps=False, trex=False):\n",
        "    meas = datum[\"meas\"]\n",
        "    flips = datum[\"measurement_flips.meas\"]\n",
        "    signs = datum.get(\"pauli_signs\", None)\n",
        "\n",
        "    meas_basis_axis = None\n",
        "    avg_axis = 0\n",
        "\n",
        "    mask = None\n",
        "    if ps and post_selection_enabled:\n",
        "        # Post-select the results\n",
        "        post_selector = PostSelector.from_circuit(\n",
        "            circuit=final_template_circuit, coupling_map=backend.coupling_map\n",
        "        )\n",
        "\n",
        "        # Compute the ps mask for filtering results\n",
        "        mask = post_selector.compute_mask(\n",
        "            datum, strategy=post_selection_strategy\n",
        "        )\n",
        "\n",
        "        # Compute fraction of shots kept from post selection\n",
        "        total_num_shots = num_randomizations * shots_per_randomization\n",
        "        ps_ratio = np.sum(mask) * 100 / total_num_shots / len(bases_canon)\n",
        "        print(\n",
        "            f\"With {post_selection_strategy}-based post selection ({ps_ratio:.1f}% of shots kept):\"\n",
        "        )\n",
        "\n",
        "    results = []\n",
        "    for i in range(steps, num_randomizations + 1, steps):\n",
        "        # Compute mitigated expvals w/out post-selection\n",
        "        res = executor_expectation_values(\n",
        "            meas[:i],\n",
        "            reverser_virt,\n",
        "            meas_basis_axis,\n",
        "            avg_axis=avg_axis,\n",
        "            measurement_flips=flips[:i],\n",
        "            pauli_signs=signs[:i] if signs is not None else None,\n",
        "            postselect_mask=mask[:i] if mask is not None else None,\n",
        "            rescale_factors=trex_scale_factors if trex else None,\n",
        "            gamma_factor=gamma,\n",
        "        )\n",
        "        results.append(res[0])\n",
        "    return results"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 50,
      "id": "57b68d19-cda9-4688-b07e-0c7f9da5f252",
      "metadata": {},
      "outputs": [],
      "source": [
        "gamma_pec = gamma_from_noisy_boxes(refs_2_plm, id_map)\n",
        "gamma_slc = gamma_from_noisy_boxes(refs_2_plm, id_map, local_scales)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 51,
      "id": "b5244ee6-98b2-43da-8238-3d6fb0f4cdd0",
      "metadata": {},
      "outputs": [],
      "source": [
        "steps = 16"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 52,
      "id": "95bedd10-5ce9-4145-9b20-1459aa8e7db9",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "With node-based post selection (24.1% of shots kept):\n",
            "With node-based post selection (24.2% of shots kept):\n"
          ]
        }
      ],
      "source": [
        "results = {}\n",
        "\n",
        "for label, result_idx, gamma, use_ps, use_trex in [\n",
        "    (\"PEC\", 1, gamma_pec, True, True),\n",
        "    (\"PEC+SLC\", 2, gamma_slc, True, True),\n",
        "    (\"Unmitigated\", 0, None, False, False),\n",
        "]:\n",
        "    res = post_process_conv(\n",
        "        results_exec[result_idx],\n",
        "        steps=steps,\n",
        "        gamma=gamma,\n",
        "        ps=use_ps,\n",
        "        trex=use_trex,\n",
        "    )\n",
        "    results[label] = res"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "1d778145-9ead-4b9f-82a3-cec8aa8c8307",
      "metadata": {},
      "source": [
        "Anhand der Auswertung der Versuchsergebnisse können wir das Verhalten der verschiedenen Ansätze direkt miteinander vergleichen: PEC, PEC in Kombination mit SLC und die unmodifizierte Basislinie. Einige konkrete Punkte, die besonders hervorzuheben sind:\n",
        "\n",
        "* Das unverfälschte Ergebnis liegt weit außerhalb des 10-prozentigen Verzerrungsbandes (siehe 0.35 ) und wird durch die Anzahl der Randomisierungen nicht beeinflusst.\n",
        "* Bei diesem Gerät verursacht die vollständige PEC einen Abtast-Overhead von $\\gamma^2 \\approx 1.6\\times10^2$. Nach anfänglichen starken Schwankungen stellt die einfache PEC-Schätzung das Signal wieder her und pendelt sich bei etwa 1.1 ein, wobei der genaue Wert leicht überschritten wird. Beachten Sie, dass das 10-prozentige Bias-Band nur für den PEC+SLC-Schätzer gilt: Der reine PEC-Schätzer ist grundsätzlich unverzerrt, sodass diese verbleibende Abweichung nicht durch eine Bias-Garantie abgedeckt ist, sondern stattdessen statistische Schwankungen und Unvollkommenheiten im gelernten Rauschmodell widerspiegelt, was bei dieser Problemgröße durchaus nachvollziehbar ist.\n",
        "* SLC reduziert den Overhead um weitere \\~3.4-fold (auf $\\gamma^2 \\approx 48$, was einer Rest-Bias-Grenze von etwa 10 % entspricht). Auch PEC+SLC unterliegt bei wenigen Randomisierungen starken Schwankungen und reicht anfangs sogar bis in den unphysikalischen Bereich hinein, doch seine Schätzung konvergiert stetig gegen den exakten Wert und pendelt sich nahe bei 1.0 deutlich innerhalb des Bandes ein. Es erreicht oder übertrifft die Genauigkeit von einfachem PEC bei etwa einem Drittel der Abtastkosten und verdeutlicht damit den Vorteil der Lichtkegelschattierung.\n",
        "* Die Fehlerbalken beider gemilderter Schätzungen verengen sich mit zunehmender Anzahl von Randomisierungen; bei einer festen Anzahl von Randomisierungen weist der PEC+SLC-Schätzer die geringere statistische Varianz auf, was mit seinem geringeren „ $\\gamma^2$ “ im Einklang steht.\n",
        "\n",
        "Beachten Sie, dass die erlernten Rauschraten (und damit auch die Overheads und die korrigierten Schätzwerte) vom Backend und dessen Kalibrierung zum Zeitpunkt der Ausführung abhängen, sodass eine erneute Ausführung dieses Notebooks zu quantitativ unterschiedlichen Werten führen kann.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 53,
      "id": "8df6612b-d611-4201-b1f4-6d8de4a192ad",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "(-50.0, 4100.0)"
            ]
          },
          "execution_count": 53,
          "metadata": {},
          "output_type": "execute_result"
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/8df6612b-d611-4201-b1f4-6d8de4a192ad-1.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "fig, ax = plt.subplots(1, 1, figsize=(12, 6))\n",
        "\n",
        "ax.axhline(1.0, color=\"black\", label=\"Exact\")\n",
        "ax.fill_between(\n",
        "    [-50, 4100], -10, 0, color=\"grey\", alpha=0.25, label=\"Unphysical\"\n",
        ")\n",
        "ax.fill_between([-50, 4100], 1, 10, color=\"grey\", alpha=0.25)\n",
        "ax.fill_between(\n",
        "    [-50, 4100], 0.9, 1.1, color=\"red\", alpha=0.25, label=\"10% bias\"\n",
        ")\n",
        "\n",
        "for label, res in results.items():\n",
        "    ax.errorbar(\n",
        "        list(range(steps, num_randomizations + 1, steps)),\n",
        "        [r[0] for r in res],\n",
        "        yerr=[r[1] for r in res],\n",
        "        alpha=0.75,\n",
        "        marker=\"o\",\n",
        "        linestyle=\"\",\n",
        "        markerfacecolor=\"none\",\n",
        "        label=label,\n",
        "    )\n",
        "\n",
        "ax.set_ylabel(r\"$\\langle X_{6}Z_{13}\\rangle$\")\n",
        "ax.set_xlabel(\"# randomizations\")\n",
        "ax.grid()\n",
        "\n",
        "ax.legend(ncols=2)\n",
        "ax.set_ylim([-0.1, 2.0])\n",
        "ax.set_xlim([-50, 4100])"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "slc-next-000-4000-8000-000000000099",
      "metadata": {},
      "source": [
        "<span id=\"next-steps\" />\n",
        "\n",
        "## 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",
        "  * Die [`qiskit-addon-slc`](https://github.com/Qiskit/qiskit-addon-slc) und [`samplomatic`](https://github.com/Qiskit/samplomatic) Dokumentation\n",
        "  * [Lightcone-Shading zur klassischen Beschleunigung der Quantenfehlerminderung](https://arxiv.org/abs/2409.04401)\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "id": "a1b8767d",
      "source": "© IBM Corp., 2017-2026"
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3"
    },
    "hours": 3,
    "qpuSeconds": 600
  },
  "nbformat": 4,
  "nbformat_minor": 5
}