{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "frontmatter",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Simulation von verrauschten Quantensystemen mit Pauli-Ausbreitung\"\n",
        "description: \"Simulieren Sie verrauschte Quantensysteme mit der Pauli-Ausbreitung – für die neueste Version der Pauli-Ausbreitung\"\n",
        "---\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "91eb1163-e828-4c59-a06d-e4f0030f8071",
      "metadata": {},
      "source": [
        "<span id=\"simulate-noisy-quantum-systems-with-pauli-propagation\" />\n",
        "\n",
        "# Simulation von verrauschten Quantensystemen mit Pauli-Ausbreitung\n",
        "\n",
        "{/* cspell:ignore mathscr, mapsto */}\n",
        "\n",
        "In dieser Anleitung verwenden wir das Paket `pauli-prop` , um die zeitliche Dynamik eines verrauschten Ising-Modells mit neun Qubits und transversalen Feldern (TFIM) auf einem quadratischen Gitter der Art „ 3x3 “ klassisch zu simulieren. Wir verwenden Anweisungen aus „ [PauliLindbladError](https://qiskit.github.io/qiskit-aer/stubs/qiskit_aer.noise.PauliLindbladError.html) “, um einen Rauschkanal zu definieren ( $\\Lambda$ ), der auf eine Reihe von Verschränkungsschichten wirkt ( $\\mathcal{U}$ ). Anschließend leiten wir die Observable ( $O$ ) rückwärts durch den verrauschten Schaltkreis und schätzen die Erwartungswerte für eine Vielzahl von Rauschmodellen sowie für den rauschfreien Fall.\n",
        "\n",
        "![Lautes Elektroauto](https://quantum.cloud.ibm.com/docs/images/addons/pauli-prop/noisy_ev.avif)\n",
        "\n",
        "Während die Beobachtungsgröße rückwärts durch den Schaltkreis weitergeleitet wird, dämpft jeder Rauschkanal $\\Lambda_k$, der mit einer Verschränkungsschicht $\\mathcal{U}_k$ assoziiert ist, die Pauli-Terme in $O$, die mit den zugehörigen Pauli-Lindblad-Generatoren antikommutieren. Konkret gilt: Wenn $G_{k,i}$ ein Pauli-Generator von $\\Lambda_k$ mit der Rate $\\gamma_{k,i}$ ist, dann transformiert sich ein Pauli-Term $P$ in $O$ wie folgt: $c_P \\mapsto c_P e^{-2\\gamma_{k,i}} \\quad \\text{if } \\{P, G_{k,i}\\}=0$, wobei $c_P$ der Koeffizient von $P$ ist. Sobald $O$ bis zum Anfang des Schaltkreises propagiert wurde, lässt sich der Erwartungswert in Bezug auf den Nullzustand $|0\\rangle^{\\otimes N}$ trivial berechnen, indem man die Koeffizienten jedes Diagonalterms in $O$ summiert (Terme, die $Z$ oder $I$ auf allen Qubits enthalten).\n",
        "\n",
        "Workflows:\n",
        "\n",
        "* Legen Sie das TFIM-Gitter fest und verwenden Sie Kantenfärbung, um eine minimale Menge von Verschränkungsschichten zu identifizieren\n",
        "* Erzeuge synthetische Rauschmodelle, $\\Lambda_k$, für jede einzelne Verschränkungsschicht, $U_k$\n",
        "  * Erstellen Sie Lärmmodelle verschiedener Größenordnungen, um die Auswirkungen von Gate-Rauschen auf das System zu untersuchen\n",
        "* Erstellen Sie rauschfreie und rauschbehaftete Quantenschaltungen für die verschiedenen Tiefen und Rauschskalen von Interesse\n",
        "  * In rauschbehafteten Schaltungen werden vor jeder Verschränkungsschicht Befehle `PauliLindbladError` eingefügt\n",
        "* Verwenden Sie die Pauli-Ausbreitung, um die exakten Erwartungswerte des Systems in verschiedenen Tiefen zu simulieren\n",
        "  * Bei neun Qubits geschieht dies, indem man $O$ auf $4^9$ Terme anwachsen lässt, die den gesamten Pauli-Raum abdecken\n",
        "* Verwenden Sie die Pauli-Ausbreitung, um verrauschte Erwartungswerte zu simulieren\n",
        "* Beobachten Sie, wie zunehmendes Gate-Rauschen die Genauigkeit des Quantenmodells beeinträchtigt\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "b70d9f4d-2771-45fb-98ac-3c2c4ce978b5",
      "metadata": {},
      "source": [
        "<span id=\"generate-a-3x3-square-lattice-and-find-a-4-coloring-on-the-edges\" />\n",
        "\n",
        "## Erzeuge ein quadratisches Gitter der Größe 3x3 und finde eine 4-Färbung der Kanten\n",
        "\n",
        "Die Knoten im Graphen stellen Qubits dar, und die Kanten stellen eine Verbindung zwischen zwei Qubits dar. Die Färbung der Kanten entspricht eindeutigen Verschränkungsschichten im Quantenschaltkreis, sodass Gatter auf Verbindungen, die unterschiedlichen Farben zugeordnet sind, nicht gleichzeitig angewendet werden können.\n",
        "\n",
        "Die Ermittlung einer minimalen Anzahl eindeutiger Verschränkungsschichten ist oft wichtig für die Implementierung effizienter Protokolle zum Lernen mit Rauschen, da das Rauschen für jede Schicht unabhängig gelernt werden muss. Je mehr Ebenen wir lernen müssen, desto mehr Aufnahmen müssen wir von der QPU machen. Für diese Demo nutzen wir die Schichteninformationen, um verrauschte Schaltkreise aufzubauen, und fügen vor `qiskit-aer` jeder Verschränkungsschicht „ [PauliLindbladError](https://qiskit.github.io/qiskit-aer/stubs/qiskit_aer.noise.PauliLindbladError.html) “-Befehle ein, um das Gatterrauschen der QPU zu modellieren.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "id": "58bcd725-3447-4bfc-8d63-12fd58082ff7",
      "metadata": {
        "editable": true,
        "slideshow": {
          "slide_type": ""
        },
        "tags": []
      },
      "outputs": [],
      "source": [
        "from collections import defaultdict\n",
        "\n",
        "import numpy as np\n",
        "from qiskit.transpiler import CouplingMap\n",
        "from qiskit_addon_utils.coloring import auto_color_edges\n",
        "\n",
        "# Define rectangular square-lattice on 20 qubits\n",
        "num_rows = 3\n",
        "num_cols = 3\n",
        "num_qubits = num_rows * num_cols\n",
        "\n",
        "coupling_map = CouplingMap.from_grid(\n",
        "    num_rows=num_rows, num_columns=num_cols, bidirectional=False\n",
        ")\n",
        "\n",
        "# Create mapping from color to edge list\n",
        "coloring = auto_color_edges(coupling_map.get_edges())\n",
        "color_to_edge = defaultdict(list)\n",
        "for edge, color in coloring.items():\n",
        "    color_to_edge[color].append(edge)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "adbceb93-6344-465c-9c4a-1c6e98ebc3d6",
      "metadata": {
        "editable": true,
        "slideshow": {
          "slide_type": ""
        },
        "tags": [
          "remove-input"
        ]
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "The circuit will have 9 qubits and 4 unique entangling layers.\n"
          ]
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/addons/pauli-prop/guides/simulate-noisy-expectation-values/extracted-outputs/adbceb93-6344-465c-9c4a-1c6e98ebc3d6-1.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 2,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "from rustworkx import PyDiGraph\n",
        "from rustworkx.visualization import graphviz_draw\n",
        "\n",
        "# Inspect graph coupling and unique entangling layers\n",
        "print(\n",
        "    f\"The circuit will have {num_qubits} qubits and {len(color_to_edge)} unique entangling layers.\"\n",
        ")\n",
        "sq_lattice = PyDiGraph()\n",
        "sq_lattice.extend_from_weighted_edge_list(\n",
        "    [\n",
        "        (source, target, color)\n",
        "        for ((source, target), color) in coloring.items()\n",
        "    ]\n",
        ")\n",
        "\n",
        "\n",
        "def color_edge_4color(edge):\n",
        "    color_dict = {0: \"red\", 1: \"green\", 2: \"blue\", 3: \"orange\"}\n",
        "    return {\"color\": color_dict[edge]}\n",
        "\n",
        "\n",
        "graphviz_draw(sq_lattice, edge_attr_fn=color_edge_4color, method=\"neato\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "538ccd51-897b-4794-aaff-864d0cea02b3",
      "metadata": {},
      "source": [
        "<span id=\"generate-synthetic-noise-models\" />\n",
        "\n",
        "## Synthetische Rauschmodelle generieren\n",
        "\n",
        "Bevor wir die Quantenschaltungen erstellen, generieren wir für jede der Verschränkungsschichten ein Rauschmodell (eine Instanz von [PauliLindbladError](https://qiskit.github.io/qiskit-aer/stubs/qiskit_aer.noise.PauliLindbladError.html) ). Wir werden sie später als Anweisungen in unsere Quantenschaltungen einbetten. Für jede Schicht erzeugen wir Rauschkanäle unterschiedlicher Größenordnung. Konkret erzeugen wir Rauschmodelle mit [einem „Error Per Layered Gate“ (EPLG)](https://www.ibm.com/quantum/blog/quantum-metric-layer-fidelity) von etwa und `.0004, .0008, .0012, .0016,` `.002`.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "id": "3b3bb04e-d8f7-429f-b396-258ccd7197da",
      "metadata": {
        "editable": true,
        "slideshow": {
          "slide_type": ""
        },
        "tags": []
      },
      "outputs": [],
      "source": [
        "from qiskit.quantum_info import SparsePauliOp, pauli_basis\n",
        "from qiskit_aer.noise import PauliLindbladError\n",
        "\n",
        "# Pauli-Lindblad noise parameters\n",
        "seed = 1764\n",
        "target_EPLGs = [0.0004, 0.0008, 0.0012, 0.0016, 0.002]\n",
        "\n",
        "\n",
        "def generate_random_pauli_lindblad_noise(\n",
        "    edges,\n",
        "    num_qubits: int | None = None,\n",
        "    noise_scale: float = 1e-3,\n",
        "    seed: int | None = None,\n",
        ") -> PauliLindbladError:\n",
        "    \"\"\"Generate random Pauli-Lindblad noise over the full Pauli basis.\"\"\"\n",
        "    if num_qubits is None:\n",
        "        num_qubits = np.max(edges)\n",
        "\n",
        "    basis_paulis = [p for p in pauli_basis(2) if np.sum(p.x + p.z)]\n",
        "    basis_paulis = SparsePauliOp.from_sparse_list(\n",
        "        [\n",
        "            (pauli.to_label(), edge, 1)\n",
        "            for pauli in basis_paulis\n",
        "            for edge in edges\n",
        "        ],\n",
        "        num_qubits=num_qubits,\n",
        "    )\n",
        "    basis_paulis = basis_paulis.simplify()\n",
        "    basis_paulis = basis_paulis.paulis\n",
        "\n",
        "    rng = np.random.default_rng(seed=seed)\n",
        "    rates = rng.random(len(basis_paulis)) * noise_scale\n",
        "\n",
        "    return PauliLindbladError(generators=basis_paulis, rates=rates)\n",
        "\n",
        "\n",
        "num_generators = (\n",
        "    (num_rows * num_cols)\n",
        "    + (num_rows - 1) * num_cols\n",
        "    + num_rows * (num_cols - 1)\n",
        ")\n",
        "noise_scales = [\n",
        "    EPLG * (num_rows * num_cols) / num_generators for EPLG in target_EPLGs\n",
        "]\n",
        "noise_models_per_EPLG = [\n",
        "    [\n",
        "        generate_random_pauli_lindblad_noise(\n",
        "            color_to_edge[color],\n",
        "            num_qubits=num_qubits,\n",
        "            noise_scale=noise_scale,\n",
        "            seed=seed,\n",
        "        )\n",
        "        for color in range(len(color_to_edge))\n",
        "    ]\n",
        "    for noise_scale in noise_scales\n",
        "]"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "5e2044c3-d265-4de3-b489-acfb4b4666de",
      "metadata": {},
      "source": [
        "<span id=\"create-the-quantum-circuits\" />\n",
        "\n",
        "## Erstellen Sie die Quantenschaltungen\n",
        "\n",
        "In dieser Demonstration simulieren wir die zeitliche Dynamik eines Ising-Modells mit Querfeld (TFIM) für eine steigende Anzahl von Trotter-Schritten (1–10 Schritte). Für jede der 10 Schaltungstiefen simulieren wir den Einfluss von Gate-Rauschen unter Verwendung von Rauschmodellen unterschiedlicher Größenordnung (`EPLGs = .0004, .0008, .0012, .0016, .002`). Das Rauschen wird als `QuantumCircuit` „ [PauliLindbladError](https://qiskit.github.io/qiskit-aer/stubs/qiskit_aer.noise.PauliLindbladError.html) “-Anweisung aus Qiskit Aer in die Schaltung eingefügt. Der betrachtete Hamilton-Operator lautet:\n",
        "\n",
        "$H = -J\\sum\\limits_{\\langle i,j \\rangle} Z_iZ_j + h\\sum\\limits_iX_i$\n",
        "\n",
        "wobei $J>0$ die Kopplung zwischen den Spins der nächsten Nachbarn beschreibt, $i<j$ und $h$ das globale Transversalfeld ist.\n",
        "\n",
        "Hier implementieren wir den zeitabhängigen Hamilton-Operator über verschiedene Zeit- und Rauschskalen hinweg. Wir erstellen insgesamt 60 Schaltkreise: 10 rauschfreie Schaltkreise mit unterschiedlicher Trotter-Tiefe und 50 rauschbehaftete Schaltkreise für die 10 Trotter-Tiefen über fünf Rauschskalen hinweg. Ausgehend von einem Konnektivitätsgraphen wird das Modell durch einige wenige Variablen parametrisiert:\n",
        "\n",
        "* `num_steps`: Die Anzahl der Trotter-Schritte\n",
        "* `J`: Kopplungsstärke verbundener Standorte\n",
        "* `h`: Stärke des externen Magnetfelds\n",
        "* `dt`: Zeitänderung über einen Trotter-Schritt hinweg\n",
        "* `initial_state_angle`: Eine anfängliche Anregung, $R_y(\\theta)$, die gleichmäßig auf alle Qubits angewendet werden soll\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "id": "91e18295-cd56-4245-a4ae-fa30ffb9cd14",
      "metadata": {
        "editable": true,
        "slideshow": {
          "slide_type": ""
        },
        "tags": []
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "10 noiseless and 50 noisy Trotter circuits generated. 10 different depths across 5 different noise models\n",
            "\n",
            "Below: Initial state and one noisy Trotter step.\n"
          ]
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/addons/pauli-prop/guides/simulate-noisy-expectation-values/extracted-outputs/91e18295-cd56-4245-a4ae-fa30ffb9cd14-1.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 4,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "from typing import Any\n",
        "\n",
        "from qiskit import QuantumCircuit\n",
        "\n",
        "# Ising model parameters\n",
        "num_steps = 10\n",
        "J = -1.0\n",
        "dt = 0.25 / abs(J)\n",
        "h = 2.0 * abs(J)\n",
        "initial_state_angle = np.pi / 18.0\n",
        "rx_angle = 2.0 * h * dt\n",
        "rzz_angle = 2.0 * J * dt\n",
        "\n",
        "\n",
        "def generate_ising_circuit(\n",
        "    num_qubits: int,\n",
        "    num_steps: int,\n",
        "    rx_angle: float,\n",
        "    rzz_angle: float,\n",
        "    coloring: dict[Any, list[tuple[int, int]]],\n",
        "    layer_noise_models: list[PauliLindbladError] | None = None,\n",
        "    initial_state_angle: float | None = None,\n",
        ") -> QuantumCircuit:\n",
        "    \"\"\"Generate a quantum circuit implementing a transverse-field Ising model\"\"\"\n",
        "    qc = QuantumCircuit(num_qubits)\n",
        "    if initial_state_angle:\n",
        "        qc.ry(initial_state_angle, range(num_qubits))\n",
        "    qc.rx(rx_angle / 2, range(num_qubits))\n",
        "    for i in range(num_steps):\n",
        "        for j, layer in enumerate(coloring):\n",
        "            edges = coloring[layer]\n",
        "            if layer_noise_models:\n",
        "                qc.append(layer_noise_models[j], qargs=range(num_qubits))\n",
        "            for edge in edges:\n",
        "                qc.rzz(rzz_angle, *edge)\n",
        "        if i == num_steps - 1:\n",
        "            qc.rx(rx_angle / 2, range(num_qubits))\n",
        "        else:\n",
        "            qc.rx(rx_angle, range(num_qubits))\n",
        "    return qc\n",
        "\n",
        "\n",
        "# Create the noiseless and noisy circuits\n",
        "noiseless_circs = []\n",
        "noisy_circs = []\n",
        "for steps in range(1, num_steps + 1):\n",
        "    noiseless_circs.append(\n",
        "        generate_ising_circuit(\n",
        "            num_qubits,\n",
        "            steps,\n",
        "            rx_angle,\n",
        "            rzz_angle,\n",
        "            color_to_edge,\n",
        "            initial_state_angle=initial_state_angle,\n",
        "        )\n",
        "    )\n",
        "    noisy_circs_per_step = []\n",
        "    for noise_models in noise_models_per_EPLG:\n",
        "        noisy_circs_per_step.append(\n",
        "            generate_ising_circuit(\n",
        "                num_qubits,\n",
        "                steps,\n",
        "                rx_angle,\n",
        "                rzz_angle,\n",
        "                color_to_edge,\n",
        "                layer_noise_models=noise_models,\n",
        "                initial_state_angle=initial_state_angle,\n",
        "            )\n",
        "        )\n",
        "    noisy_circs.append(noisy_circs_per_step)\n",
        "print(\n",
        "    f\"{num_steps} noiseless and {num_steps * len(target_EPLGs)} noisy Trotter circuits generated. {num_steps} different depths across {len(target_EPLGs)} different noise models\"\n",
        ")\n",
        "print(\"\\nBelow: Initial state and one noisy Trotter step.\")\n",
        "noisy_circs[0][0].draw(\"mpl\", fold=-1)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "007a0560-e1bf-4715-a2fd-a8a5f1e5ac24",
      "metadata": {},
      "source": [
        "<span id=\"specify-observable-and-run-simulations\" />\n",
        "\n",
        "## Beobachtungsgrößen festlegen und Simulationen ausführen\n",
        "\n",
        "Für diese Demonstration simulieren wir die Erwartungswerte des durchschnittlichen Zwei-Standort-Korrelators:\n",
        "\n",
        "$\\langle O \\rangle = \\langle Z_{tot}^2(s) \\rangle = \\frac{1}{N^2}\\sum \\langle \\Psi(\\theta)|(\\mathscr{U}^{\\dagger})^sZ_jZ_k(\\mathscr{U})^s|\\Psi(\\theta) \\rangle$\n",
        "\n",
        "wobei $\\Psi(\\theta)$ einer gleichmäßigen Rotation $R_y(\\theta)$ für alle Qubits entspricht, $\\mathscr{U}^s$ $s$ Trotter-Schichten beschreibt und $(j,k)$ alle verbundenen Knotenpaare auf dem Gitter indiziert.\n",
        "\n",
        "Schließlich verwenden wir `pauli_prop` zur Simulation der beobachtbaren Erwartungswerte für jede der Schaltungen. Für diese 9-Qubit-Demonstration führen wir alle Simulationen **exakt** durch. **Es wird keine Pauli-Propagationsabschneidung durchgeführt; daher lassen sich die Unterschiede in den Erwartungswerten zwischen den verschiedenen Rauschmodellen vollständig auf den Gate-Fehler zurückführen**. Der Simulationsprozess erfolgt in vier Schritten:\n",
        "\n",
        "* Verlegen Sie die Clifford-Gatter in der Schaltung an den Anfang der Schaltung, indem Sie `pauli_prop.evolve_through_cliffords`\n",
        "* Führe die Beobachtungsgröße durch den Nicht-Clifford-Teil der Schaltung weiter, indem du `pauli_prop.propagate_through_circuit`\n",
        "  * Wir führen exakte Simulationen durch, indem wir die Observable auf die Größe des gesamten Pauli-Raums anwachsen lassen: $4^9$\n",
        "* Die entwickelte Observable über den Clifford-Teil der Schaltung weiterverarbeiten, unter Verwendung von Qiskits `SparsePauliOp.evolve`\n",
        "* Schätze den Erwartungswert in Bezug auf den Nullzustand, $|0\\rangle^{\\otimes N}$, indem du die Koeffizienten jedes Diagonalterms in $O$ summiert (Terme, die $Z$ oder $I$ auf allen Qubits enthalten)\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 5,
      "id": "7ceb7937-ef7d-483a-b49a-d3aa2a3e6c43",
      "metadata": {
        "editable": true,
        "slideshow": {
          "slide_type": ""
        },
        "tags": []
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Ran 10 noiseless and 50 noisy simulations in 103s.\n"
          ]
        }
      ],
      "source": [
        "import time\n",
        "\n",
        "from pauli_prop import evolve_through_cliffords, propagate_through_circuit\n",
        "from qiskit.quantum_info import Pauli\n",
        "\n",
        "# Average ZZ-correlator observable\n",
        "id_pauli = Pauli(\"I\" * num_qubits)\n",
        "observable = 2 * SparsePauliOp(\n",
        "    [\n",
        "        id_pauli.dot(Pauli(\"ZZ\"), [i, j])\n",
        "        for i in range(num_qubits)\n",
        "        for j in range(i + 1, num_qubits)\n",
        "    ]\n",
        ")\n",
        "observable /= num_qubits**2\n",
        "\n",
        "# Pauli propagation parameters\n",
        "max_terms = 4**num_qubits  # Exact propagation\n",
        "atol = 1e-12\n",
        "\n",
        "# Run simulations\n",
        "exact_evs = []\n",
        "noisy_evs = [[] for _ in range(len(target_EPLGs))]\n",
        "st = time.perf_counter()\n",
        "for i, noiseless_circ in enumerate(noiseless_circs):\n",
        "    cliff, non_cliff = evolve_through_cliffords(noiseless_circ)\n",
        "    evolved_obs = propagate_through_circuit(\n",
        "        observable, non_cliff, max_terms=max_terms, atol=atol, frame=\"h\"\n",
        "    )[0]\n",
        "    evolved_obs.paulis = evolved_obs.paulis.evolve(cliff, frame=\"h\")\n",
        "    exact_evs.append(\n",
        "        float(evolved_obs.coeffs[~evolved_obs.paulis.x.any(axis=1)].sum())\n",
        "    )\n",
        "    for j in range(len(target_EPLGs)):\n",
        "        noisy_circ = noisy_circs[i][j]\n",
        "        cliff, non_cliff = evolve_through_cliffords(noisy_circ)\n",
        "        evolved_obs = propagate_through_circuit(\n",
        "            observable, non_cliff, max_terms=max_terms, atol=1e-12, frame=\"h\"\n",
        "        )[0]\n",
        "        evolved_obs.paulis = evolved_obs.paulis.evolve(cliff, frame=\"h\")\n",
        "        noisy_evs[j].append(\n",
        "            float(evolved_obs.coeffs[~evolved_obs.paulis.x.any(axis=1)].sum())\n",
        "        )\n",
        "print(\n",
        "    f\"Ran {len(noiseless_circs)} noiseless and {len(target_EPLGs) * num_steps} noisy simulations in {int(time.perf_counter() - st)}s.\"\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "d138fc46-5a47-46d8-9074-6aa30b273a86",
      "metadata": {},
      "source": [
        "<span id=\"observe-effect-of-gate-error-on-the-model\" />\n",
        "\n",
        "## Beobachten Sie den Einfluss des Gate-Fehlers auf das Modell\n",
        "\n",
        "Beachten Sie, dass es sich hierbei um ein Experiment mit neun Qubits handelt, sodass die Pauli-Ausbreitungsroutine exakt ist und alle Fehler in den Rauschdiagrammen auf Gatterfehler zurückgeführt werden können.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 6,
      "id": "73b00aa9-ee66-4b65-ad41-f6278555e5c5",
      "metadata": {
        "editable": true,
        "slideshow": {
          "slide_type": ""
        },
        "tags": [
          "remove-input"
        ]
      },
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/addons/pauli-prop/guides/simulate-noisy-expectation-values/extracted-outputs/73b00aa9-ee66-4b65-ad41-f6278555e5c5-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "import matplotlib.pyplot as plt\n",
        "\n",
        "xs = range(1, num_steps + 1)\n",
        "plt.plot(xs, exact_evs, label=\"Noiseless\", color=\"black\", marker=\"o\")\n",
        "colors = [\".3\", \".4\", \".5\", \".6\", \".7\"]\n",
        "for i, evs in enumerate(noisy_evs):\n",
        "    plt.plot(\n",
        "        xs,\n",
        "        evs,\n",
        "        label=f\"{target_EPLGs[i]} EPLG\",\n",
        "        linestyle=\"--\",\n",
        "        color=colors[i],\n",
        "        marker=\"o\",\n",
        "    )\n",
        "plt.xlabel(\"# Trotter steps\")\n",
        "plt.ylabel(r\"$\\langle Z_{tot}^2 \\rangle$\")\n",
        "plt.legend()\n",
        "plt.show()"
      ]
    },
    {
      "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"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 5
}