{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "frontmatter",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Simulazione di sistemi quantistici soggetti a rumore mediante la propagazione di Pauli\"\n",
        "description: \"Simula sistemi quantistici soggetti a rumore con la propagazione di Pauli per l'ultima versione della propagazione di Pauli\"\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",
        "# Simulazione di sistemi quantistici soggetti a rumore mediante la propagazione di Pauli\n",
        "\n",
        "{/* cspell:ignore mathscr, mapsto */}\n",
        "\n",
        "In questa guida utilizziamo il pacchetto `pauli-prop` per simulare in modo classico le dinamiche temporali di un modello di Ising a campo trasversale (TFIM) a nove qubit con rumore su un reticolo quadrato di tipo “ 3x3 ”. Utilizziamo le istruzioni [PauliLindbladError](https://qiskit.github.io/qiskit-aer/stubs/qiskit_aer.noise.PauliLindbladError.html) per definire un canale di rumore, $\\Lambda$, che agisce su un insieme di livelli di entanglement, $\\mathcal{U}$. Successivamente, propaghiamo l’osservabile, $O$, all’indietro attraverso il circuito soggetto a rumore e stimiamo i valori attesi per una varietà di modelli di rumore, oltre che per il caso privo di rumore.\n",
        "\n",
        "![Veicolo elettrico rumoroso](https://quantum.cloud.ibm.com/docs/images/addons/pauli-prop/noisy_ev.avif)\n",
        "\n",
        "Man mano che l’osservabile viene propagato all’indietro attraverso il circuito, ciascun canale di rumore, $\\Lambda_k$, associato a uno strato di entanglement, $\\mathcal{U}_k$, smorza i termini di Pauli in $O$ che anticommutano con i propri generatori di Pauli-Lindblad. In particolare, se $G_{k,i}$ è un generatore di Pauli di $\\Lambda_k$ con rata $\\gamma_{k,i}$, allora un termine di Pauli, $P$, in $O$ si trasforma come segue: $c_P \\mapsto c_P e^{-2\\gamma_{k,i}} \\quad \\text{if } \\{P, G_{k,i}\\}=0$, dove $c_P$ è il coefficiente di $P$. Una volta che $O$ è stato propagato all’inizio del circuito, il valore atteso rispetto allo stato zero, $|0\\rangle^{\\otimes N}$, può essere calcolato banalmente sommando i coefficienti di ciascun termine diagonale in $O$ (termini contenenti $Z$ o $I$ su tutti i qubit).\n",
        "\n",
        "Flusso di lavoro:\n",
        "\n",
        "* Specificare il reticolo TFIM e utilizzare la colorazione dei bordi per identificare un insieme minimo di strati di entanglement\n",
        "* Generare modelli di rumore sintetico, $\\Lambda_k$, per ogni singolo strato di entanglement, $U_k$\n",
        "  * Creare modelli di rumore su varie scale per studiare l'impatto del rumore dei gate sul sistema\n",
        "* Creare circuiti quantistici privi di rumore e con rumore per le varie profondità e scale di rumore di interesse\n",
        "  * Nei circuiti rumorosi, prima di ogni strato di intrecciamento vengono inserite delle istruzioni `PauliLindbladError`\n",
        "* Utilizzare la propagazione di Pauli per simulare i valori esatti delle aspettative del sistema a varie profondità\n",
        "  * Per nove qubit, ciò avviene facendo crescere l' $O$ e fino a comprendere i termini $4^9$, coprendo così l'intero spazio di Pauli\n",
        "* Utilizzare la propagazione di Pauli per simulare valori attesi soggetti a rumore\n",
        "* Si osservi come l'aumento del rumore di gate comprometta l'accuratezza del modello quantistico\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",
        "## Generare un reticolo quadrato di tipo “ 3x3 ” e trovare una colorazione a 4 colori sugli spigoli\n",
        "\n",
        "I vertici del grafo rappresentano i qubit, mentre gli archi rappresentano una connessione tra due qubit. La colorazione dei bordi corrisponde a livelli di entanglement distinti nel circuito quantistico, in modo tale che le porte presenti sulle connessioni associate a colori diversi non possano essere applicate contemporaneamente.\n",
        "\n",
        "L'identificazione di un insieme minimo di livelli di entanglement univoci è spesso importante per l'implementazione di protocolli efficienti di apprendimento del rumore, poiché il rumore di ciascun livello deve essere appreso in modo indipendente. Più livelli dobbiamo imparare, più immagini dobbiamo acquisire dalla QPU. In questa demo, utilizziamo le informazioni relative agli strati per costruire circuiti soggetti a rumore e inseriamo istruzioni di tipo “ [PauliLindbladError](https://qiskit.github.io/qiskit-aer/stubs/qiskit_aer.noise.PauliLindbladError.html) ” prima `qiskit-aer` di ogni strato di entanglement, al fine di modellare il rumore dei gate della QPU.\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",
        "## Generare modelli di rumore sintetico\n",
        "\n",
        "Prima di creare i circuiti quantistici, generiamo un modello di rumore (istanza di [PauliLindbladError](https://qiskit.github.io/qiskit-aer/stubs/qiskit_aer.noise.PauliLindbladError.html) ) per ciascuno degli strati di entanglement. In seguito li inseriremo come istruzioni nei nostri circuiti quantistici. Per ogni livello, generiamo canali di rumore su scale diverse. Nello specifico, generiamo modelli di rumore con un valore di [Error Per Layered Gate (EPLG)](https://www.ibm.com/quantum/blog/quantum-metric-layer-fidelity) pari a circa e `.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",
        "## Creare i circuiti quantistici\n",
        "\n",
        "In questa dimostrazione simuliamo le dinamiche temporali di un modello di Ising a campo trasversale (TFIM) per un numero crescente di passi di Trotter (da 1 a 10 passi). Per ciascuna delle 10 profondità del circuito, simuliamo l’effetto del rumore di gate, sulla base di modelli di rumore su scale diverse (`EPLGs = .0004, .0008, .0012, .0016, .002`). Il rumore viene inserito nel circuito `QuantumCircuit` tramite l’istruzione “ [PauliLindbladError](https://qiskit.github.io/qiskit-aer/stubs/qiskit_aer.noise.PauliLindbladError.html) ” di Qiskit Aer. L'Hamiltoniano considerato è:\n",
        "\n",
        "$H = -J\\sum\\limits_{\\langle i,j \\rangle} Z_iZ_j + h\\sum\\limits_iX_i$\n",
        "\n",
        "dove $J>0$ descrive l'accoppiamento tra gli spin più vicini, $i<j$, e $h$ è il campo trasversale globale.\n",
        "\n",
        "Qui implementiamo l'Hamiltoniano evoluto nel tempo su varie scale temporali e di rumore. Creiamo un totale di 60 circuiti: 10 circuiti privi di rumore con profondità di Trotter variabile e 50 circuiti con rumore per le 10 profondità di Trotter, distribuiti su cinque scale di rumore. Dato un grafo di connettività, il modello è parametrizzato da alcune variabili:\n",
        "\n",
        "* `num_steps`: Il numero di passi di Trotter\n",
        "* `J`: Forza di accoppiamento dei siti collegati\n",
        "* `h`: Intensità del campo magnetico esterno\n",
        "* `dt`: Variazione temporale lungo un passo di Trotter\n",
        "* `initial_state_angle`: Un'eccitazione iniziale, $R_y(\\theta)$, da applicare in modo uniforme a tutti i qubit\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",
        "## Specificare le variabili osservabili ed eseguire le simulazioni\n",
        "\n",
        "Per questa dimostrazione, simuliamo i valori attesi del correlatore medio a due siti:\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",
        "dove $\\Psi(\\theta)$ corrisponde a una rotazione uniforme $R_y(\\theta)$ su tutti i qubit, $\\mathscr{U}^s$ descrive $s$ strati di Trotter e $(j,k)$ indicizza tutte le coppie di vertici connessi sul reticolo.\n",
        "\n",
        "Infine, utilizziamo `pauli_prop` per simulare i valori attesi osservabili per ciascuno dei circuiti. Per questa dimostrazione con nove qubit, eseguiamo tutte le simulazioni **in modo esatto**. **Non verrà effettuato alcun troncamento della propagazione di Pauli; pertanto, le differenze nei valori attesi tra i diversi modelli di rumore possono essere interamente attribuite all'errore del gate**. Il processo di simulazione si articola in quattro fasi:\n",
        "\n",
        "* Spostare i porte Clifford nel circuito verso la parte anteriore del circuito utilizzando `pauli_prop.evolve_through_cliffords`\n",
        "* Propagare l'osservabile attraverso la parte non-Clifford del circuito utilizzando `pauli_prop.propagate_through_circuit`\n",
        "  * Eseguiamo simulazioni esatte lasciando che l’osservabile raggiunga le dimensioni dello spazio di Pauli completo, $4^9$\n",
        "* Propagare l'osservabile evoluto attraverso la parte Clifford del circuito utilizzando Qiskit's `SparsePauliOp.evolve`\n",
        "* Calcolare il valore atteso rispetto allo stato zero, $|0\\rangle^{\\otimes N}$, sommando i coefficienti di ciascun termine diagonale in $O$ (termini contenenti $Z$ o $I$ su tutti i qubit)\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",
        "## Osservare l'effetto dell'errore di gate sul modello\n",
        "\n",
        "Ricordate che, trattandosi di un esperimento a nove qubit, la routine di propagazione di Pauli è esatta e tutto l'errore riscontrabile nei grafici del rumore può essere attribuito all'errore dei gate.\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
}