{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "aff344db",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Simula un campo inclinato Ising 2D e con la funzione QESEM\"\n",
        "description: \"Questo tutorial mostra una simulazione di un modello di Ising a campo trasversale dell' 2d con mitigazione dell'errore QESEM combinata con il modulo di retropropagazione dell'operatore Qiskit.\"\n",
        "---\n",
        "\n",
        "{/* cspell:ignore bppeps */}\n",
        "\n",
        "<span id=\"simulate-2d-tilted-field-ising-with-the-qesem-function\" />\n",
        "\n",
        "# Simula un campo inclinato Ising 2D e con la funzione QESEM\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "edb68a2a9b5ee911",
      "metadata": {},
      "source": [
        "<Admonition type=\"note\">\n",
        "  Qiskit Functions sono una funzione sperimentale disponibile solo per gli utenti di IBM Quantum® Premium Plan, Flex Plan e On-Prem (tramite IBM Quantum Platform API) Plan. Sono in stato di anteprima e sono soggetti a modifiche.\n",
        "</Admonition>\n",
        "\n",
        "*Stima di utilizzo: 20 minuti su un processore Heron r2. (NOTA: questa è solo una stima. Il tempo di esecuzione può variare)*\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "88c617fe",
      "metadata": {},
      "source": [
        "<span id=\"background\" />\n",
        "\n",
        "## Sfondo\n",
        "\n",
        "Questo tutorial mostra come utilizzare [QESEM](/docs/guides/qedma-qesem), la funzione Qiskit di Qedma, per simulare la dinamica di un modello quantistico di spin canonico, il modello 2D tilted-field Ising (TFI) con angoli non-Clifford:\n",
        "\n",
        "$$\n",
        "H = J \\sum_{\\langle i,j \\rangle} Z_i Z_j + g_x \\sum_i X_i + g_z \\sum_i Z_i ,\n",
        "$$\n",
        "\n",
        "dove $\\langle i,j \\rangle$ indica i vicini più prossimi su un reticolo. Simulare l'evoluzione temporale di sistemi quantistici a molti corpi è un compito computazionalmente difficile per i computer classici. I computer quantistici, invece, sono naturalmente progettati per svolgere questo compito in modo efficiente. Il modello TFI, in particolare, è diventato un benchmark popolare sull'hardware quantistico grazie al suo ricco comportamento fisico e alla sua implementazione facile da usare.\n",
        "\n",
        "Invece di simulare dinamiche a tempo continuo, adottiamo il modello di Ising calciato, strettamente correlato. La dinamica può essere espressa esattamente come un circuito quantistico periodico, in cui ogni passo evolutivo consiste in tre strati di porte frazionali a due qubit $R_{ZZ} (\\alpha_{ZZ})$, intervallati da strati di porte a singolo qubit $R_X (\\alpha_X)$ e $R_Z (\\alpha_Z)$.\n",
        "\n",
        "Utilizzeremo angoli generici che sono impegnativi sia per la simulazione classica che per la mitigazione degli errori. In particolare, abbiamo scelto $\\alpha_{ZZ} = 1.0$, $\\alpha_X = 0.53$, e $\\alpha_Z = 0.1$, ponendo il modello lontano da qualsiasi punto integrabile.\n",
        "\n",
        "In questa esercitazione faremo quanto segue:\n",
        "\n",
        "* Stimare il tempo di esecuzione previsto della QPU per l'attenuazione completa degli errori utilizzando le funzioni di stima del tempo analitiche ed empiriche di QESEM.\n",
        "* Costruire e simulare il circuito del modello Ising a campo inclinato 2D utilizzando layout di qubit e strati di gate ispirati all'hardware.\n",
        "* Visualizzare la connettività dei qubit del dispositivo e i sottografi selezionati per l'esperimento.\n",
        "* Dimostrare l'uso della [retropropagazione dell'operatore (OBP)](https://qiskit.github.io/qiskit-addon-obp/) per ridurre la profondità del circuito. Questa tecnica riduce le operazioni dall'estremità del circuito a scapito di un maggior numero di misurazioni da parte dell'operatore.\n",
        "* Eseguire una mitigazione imparziale degli errori (EM) per più osservabili simultaneamente utilizzando QESEM, confrontando i risultati ideali, rumorosi e mitigati.\n",
        "* Analizzare e tracciare l'impatto dell'attenuazione degli errori sulla magnetizzazione a diverse profondità del circuito.\n",
        "\n",
        "Nota: OBP restituisce in genere un insieme di osservabili possibilmente non comunicanti. QESEM ottimizza automaticamente le basi di misura quando le osservabili target contengono termini non comunicanti. Genera insiemi di basi di misura candidate utilizzando diversi algoritmi euristici e seleziona l'insieme che minimizza il numero di basi distinte. Ciò significa che QESEM raggruppa le osservabili compatibili in basi comuni per ridurre il numero totale di configurazioni di misura necessarie, migliorando l'efficienza.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "bb2bd156c9c6db20",
      "metadata": {},
      "source": [
        "<span id=\"about-qesem\" />\n",
        "\n",
        "## Informazioni su QESEM\n",
        "\n",
        "QESEM è un software affidabile, ad alta precisione e basato sulla caratterizzazione che implementa una mitigazione efficiente e imparziale degli errori quasi-probabilistici.\n",
        "È stato progettato per mitigare gli errori nei circuiti quantistici generici ed è indipendente dalle applicazioni. È stato convalidato su diverse piattaforme hardware, tra cui esperimenti su scala industriale su dispositivi IBM® Eagle e Heron. Le fasi del flusso di lavoro QESEM sono le seguenti:\n",
        "\n",
        "1. Caratterizzazione del dispositivo - mappa le fedeltà dei gate e identifica gli errori di coerenza, fornendo dati di calibrazione in tempo reale. Questa fase garantisce che la mitigazione sfrutti le operazioni di massima fedeltà disponibili.\n",
        "2. Traspilazione noise-aware - genera e valuta mappature di qubit, set di operazioni e basi di misura alternative, selezionando la variante che riduce al minimo il tempo di esecuzione stimato della QPU, con parallelizzazione opzionale per accelerare la raccolta dei dati.\n",
        "3. Soppressione degli errori: ridefinisce le porte native, applica il Pauli twirling e ottimizza il controllo del livello degli impulsi (sulle piattaforme supportate) per migliorare la fedeltà.\n",
        "4. Caratterizzazione del circuito - costruisce un modello di errore locale su misura e lo adatta alle misure QPU per quantificare il rumore residuo.\n",
        "5. Attenuazione degli errori - costruisce decomposizioni quasi-probabilistiche di tipo multiplo e le campiona con un processo adattivo che riduce al minimo il tempo di mitigazione della QPU e la sensibilità alle fluttuazioni dell'hardware, ottenendo un'elevata precisione con grandi volumi di circuiti.\n",
        "\n",
        "Per ulteriori informazioni su QESEM e su un esperimento su scala industriale di questo modello condotto su un sottografo ad alta connettività da 103 qubit della geometria nativa \"heavy-hex\" di `ibm_marrakesh`, consultare l'articolo \"[Reliable high-accuracy error mitigation for utility-scale quantum circuits](https://arxiv.org/abs/2508.10997) \".\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "d1ad1f56516888f2",
      "metadata": {},
      "source": [
        "![Flusso di lavoro QESEM.](https://quantum.cloud.ibm.com/docs/images/tutorials/qedma-2d-ising-with-qesem/QESEM_workflow.avif)\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "4a2ede52",
      "metadata": {},
      "source": [
        "<span id=\"requirements\" />\n",
        "\n",
        "## Requisiti\n",
        "\n",
        "Installate i seguenti pacchetti Python prima di eseguire il notebook:\n",
        "\n",
        "* Qiskit SDK v2.0.0 o più tardi (`pip install qiskit`)\n",
        "* Qiskit Runtime v0.40.0 o più tardi (`pip install qiskit-ibm-runtime`)\n",
        "* Qiskit Functions Catalog v0.8.0 o più tardi ( `pip install qiskit-ibm-catalog` )\n",
        "* Operator Backpropagation Qiskit addon v0.3.0 o successivo ( `pip install qiskit-addon-obp` )\n",
        "* Addon Qiskit Utils v0.1.1 o successivo ( `pip install qiskit-addon-utils` )\n",
        "* Simulatore Qiskit Aer v0.17.1 o successivo ( `pip install qiskit-aer` )\n",
        "* Matplotlib v3.10.3 o più tardi ( `pip install matplotlib` )\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a675b3b1",
      "metadata": {},
      "source": [
        "<span id=\"setup\" />\n",
        "\n",
        "## Configura\n",
        "\n",
        "Per prima cosa, importare le librerie pertinenti:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "acea2e46",
      "metadata": {
        "ExecuteTime": {
          "end_time": "2025-11-05T12:50:58.471653Z",
          "start_time": "2025-11-05T12:50:58.467381Z"
        }
      },
      "outputs": [],
      "source": [
        "%matplotlib inline\n",
        "\n",
        "from typing import Sequence\n",
        "\n",
        "import matplotlib.pyplot as plt\n",
        "import numpy as np\n",
        "\n",
        "import qiskit\n",
        "from qiskit_ibm_runtime import EstimatorV2 as Estimator\n",
        "from qiskit_ibm_catalog import QiskitFunctionsCatalog\n",
        "from qiskit_aer import AerSimulator\n",
        "from qiskit_addon_utils.slicing import combine_slices, slice_by_gate_types\n",
        "from qiskit_addon_obp import backpropagate\n",
        "from qiskit_addon_obp.utils.simplify import OperatorBudget\n",
        "from qiskit.visualization import (\n",
        "    plot_gate_map,\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "467f1569",
      "metadata": {},
      "source": [
        "A questo punto, effettua l'autenticazione utilizzando la tua chiave API dalla dashboard di [IBM Quantum Platform](http://quantum.cloud.ibm.com/). Quindi, selezionare la funzione Qiskit come segue. (Si noti che, per motivi di sicurezza, è consigliabile [salvare le credenziali dell'account](/docs/guides/functions-get-started#install-qiskit-functions-catalog-client) sul proprio computer, se si utilizza un dispositivo affidabile, in modo da non dover inserire la chiave API ogni volta che ci si autentica.)\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "41a53d27",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Paste here your instance and token strings\n",
        "\n",
        "instance = \"YOUR_INSTANCE\"\n",
        "token = \"YOUR_TOKEN\"\n",
        "channel = \"ibm_quantum_platform\"\n",
        "\n",
        "catalog = QiskitFunctionsCatalog(\n",
        "    channel=channel, token=token, instance=instance\n",
        ")\n",
        "qesem_function = catalog.load(\"qedma/qesem\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "69eafdcc",
      "metadata": {},
      "source": [
        "<span id=\"step-1-map-classical-inputs-to-a-quantum-problem\" />\n",
        "\n",
        "## Fase 1: mappare gli input classici su un problema quantistico\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ac9dcff0",
      "metadata": {},
      "source": [
        "Iniziamo definendo una funzione che crea il circuito Trotter:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "3842021c",
      "metadata": {
        "ExecuteTime": {
          "end_time": "2025-11-05T12:51:12.446678Z",
          "start_time": "2025-11-05T12:51:12.442978Z"
        }
      },
      "outputs": [],
      "source": [
        "def trotter_circuit_from_layers(\n",
        "    steps: int,\n",
        "    theta_x: float,\n",
        "    theta_z: float,\n",
        "    theta_zz: float,\n",
        "    layers: Sequence[Sequence[tuple[int, int]]],\n",
        "    init_state: str | None = None,\n",
        ") -> qiskit.QuantumCircuit:\n",
        "    \"\"\"\n",
        "    Generates an ising trotter circuit\n",
        "    :param steps: trotter steps\n",
        "    :param theta_x: RX angle\n",
        "    :param theta_z: RZ angle\n",
        "    :param theta_zz: RZZ angle\n",
        "    :param layers: list of layers (can be list of layers in device)\n",
        "    :param init_state: Initial state to prepare.\n",
        "     If None, will not prepare any state. If \"+\", will\n",
        "     add Hadamard gates to all qubits.\n",
        "    :return: QuantumCircuit\n",
        "    \"\"\"\n",
        "    qubits = sorted({i for layer in layers for edge in layer for i in edge})\n",
        "    circ = qiskit.QuantumCircuit(max(qubits) + 1)\n",
        "\n",
        "    if init_state == \"+\":\n",
        "        print(\"init_state = +\")\n",
        "        for q in qubits:\n",
        "            circ.h(q)\n",
        "\n",
        "    for _ in range(steps):\n",
        "        for q in qubits:\n",
        "            circ.rx(theta_x, q)\n",
        "            circ.rz(theta_z, q)\n",
        "\n",
        "        for layer in layers:\n",
        "            for edge in layer:\n",
        "                circ.rzz(theta_zz, *edge)\n",
        "        circ.barrier(qubits)\n",
        "\n",
        "    return circ"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "8b4b76968dc50bb1",
      "metadata": {},
      "source": [
        "Quindi creiamo una funzione per calcolare i valori di aspettativa ideali utilizzando `AerSimulator`.\n",
        "\n",
        "Si noti che, per i circuiti di grandi dimensioni (30 o più qubit), si consiglia di utilizzare i valori precalcolati ottenuti dalle simulazioni PEPS con propagazione delle credenze (BP). Questo codice include, a titolo esemplificativo, valori precalcolati per 35 qubit, basati sull'approccio BP per l'evoluzione di una rete tensoriale PEPS presentato [in questo articolo](https://www.science.org/doi/10.1126/sciadv.adk4321) (che chiamiamo PEPS-BP), utilizzando il pacchetto [quimb](https://joss.theoj.org/papers/10.21105/joss.00819) di Python dedicato alle reti tensoriali.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "12f4f2e334e4268b",
      "metadata": {
        "ExecuteTime": {
          "end_time": "2025-11-06T20:20:32.759885Z",
          "start_time": "2025-11-06T20:20:32.752872Z"
        }
      },
      "outputs": [],
      "source": [
        "def calculate_ideal_evs(circ, obs, num_qubits, step):\n",
        "    # Predefined results for large circuits - calculated using\n",
        "    # bppeps for 3, 5, 7, 9 trotter steps\n",
        "    predefined_35 = [\n",
        "        0.79537,\n",
        "        0.78653,\n",
        "        0.79699,\n",
        "    ]\n",
        "\n",
        "    if num_qubits == 35:\n",
        "        print(\n",
        "            \"Using precalculated ideal values for large circuits calculated \"\n",
        "            \"with belief propagation PEPS. Currently only for 35 qubits.\"\n",
        "        )\n",
        "        return predefined_35[step]\n",
        "\n",
        "    else:\n",
        "        simulator = AerSimulator()\n",
        "\n",
        "        # Use Estimator primitive to get expectation value\n",
        "        estimator = Estimator(simulator)\n",
        "        sim_result = estimator.run([(circ, [obs])], precision=0.0001).result()\n",
        "\n",
        "        # Extracting the result\n",
        "        ideal_values = sim_result[0].data.evs[0]\n",
        "        return ideal_values"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "7d342cd7",
      "metadata": {},
      "source": [
        "Utilizziamo una mappatura dei livelli $R_{ZZ}$ basata sull'hardware e ricavata dal dispositivo Heron, da cui ritagliamo i livelli in base al numero di qubit che vogliamo simulare. Definiamo sottografi per 10, 21, 28 e 35 qubit che mantengono una struttura 2D (sentitevi liberi di cambiare il vostro sottografo preferito):\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "27402210",
      "metadata": {
        "ExecuteTime": {
          "end_time": "2025-11-05T12:51:22.025741Z",
          "start_time": "2025-11-05T12:51:22.018325Z"
        }
      },
      "outputs": [],
      "source": [
        "LAYERS_HERON_R2 = [  # the full set of hardware layers for Heron r2\n",
        "    [\n",
        "        (2, 3),\n",
        "        (6, 7),\n",
        "        (10, 11),\n",
        "        (14, 15),\n",
        "        (20, 21),\n",
        "        (16, 23),\n",
        "        (24, 25),\n",
        "        (17, 27),\n",
        "        (28, 29),\n",
        "        (18, 31),\n",
        "        (32, 33),\n",
        "        (19, 35),\n",
        "        (36, 41),\n",
        "        (42, 43),\n",
        "        (37, 45),\n",
        "        (46, 47),\n",
        "        (38, 49),\n",
        "        (50, 51),\n",
        "        (39, 53),\n",
        "        (60, 61),\n",
        "        (56, 63),\n",
        "        (64, 65),\n",
        "        (57, 67),\n",
        "        (68, 69),\n",
        "        (58, 71),\n",
        "        (72, 73),\n",
        "        (59, 75),\n",
        "        (76, 81),\n",
        "        (82, 83),\n",
        "        (77, 85),\n",
        "        (86, 87),\n",
        "        (78, 89),\n",
        "        (90, 91),\n",
        "        (79, 93),\n",
        "        (94, 95),\n",
        "        (100, 101),\n",
        "        (96, 103),\n",
        "        (104, 105),\n",
        "        (97, 107),\n",
        "        (108, 109),\n",
        "        (98, 111),\n",
        "        (112, 113),\n",
        "        (99, 115),\n",
        "        (116, 121),\n",
        "        (122, 123),\n",
        "        (117, 125),\n",
        "        (126, 127),\n",
        "        (118, 129),\n",
        "        (130, 131),\n",
        "        (119, 133),\n",
        "        (134, 135),\n",
        "        (140, 141),\n",
        "        (136, 143),\n",
        "        (144, 145),\n",
        "        (137, 147),\n",
        "        (148, 149),\n",
        "        (138, 151),\n",
        "        (152, 153),\n",
        "        (139, 155),\n",
        "    ],\n",
        "    [\n",
        "        (1, 2),\n",
        "        (3, 4),\n",
        "        (5, 6),\n",
        "        (7, 8),\n",
        "        (9, 10),\n",
        "        (11, 12),\n",
        "        (13, 14),\n",
        "        (21, 22),\n",
        "        (23, 24),\n",
        "        (25, 26),\n",
        "        (27, 28),\n",
        "        (29, 30),\n",
        "        (31, 32),\n",
        "        (33, 34),\n",
        "        (40, 41),\n",
        "        (43, 44),\n",
        "        (45, 46),\n",
        "        (47, 48),\n",
        "        (49, 50),\n",
        "        (51, 52),\n",
        "        (53, 54),\n",
        "        (55, 59),\n",
        "        (61, 62),\n",
        "        (63, 64),\n",
        "        (65, 66),\n",
        "        (67, 68),\n",
        "        (69, 70),\n",
        "        (71, 72),\n",
        "        (73, 74),\n",
        "        (80, 81),\n",
        "        (83, 84),\n",
        "        (85, 86),\n",
        "        (87, 88),\n",
        "        (89, 90),\n",
        "        (91, 92),\n",
        "        (93, 94),\n",
        "        (95, 99),\n",
        "        (101, 102),\n",
        "        (103, 104),\n",
        "        (105, 106),\n",
        "        (107, 108),\n",
        "        (109, 110),\n",
        "        (111, 112),\n",
        "        (113, 114),\n",
        "        (120, 121),\n",
        "        (123, 124),\n",
        "        (125, 126),\n",
        "        (127, 128),\n",
        "        (129, 130),\n",
        "        (131, 132),\n",
        "        (133, 134),\n",
        "        (135, 139),\n",
        "        (141, 142),\n",
        "        (143, 144),\n",
        "        (145, 146),\n",
        "        (147, 148),\n",
        "        (149, 150),\n",
        "        (151, 152),\n",
        "        (153, 154),\n",
        "    ],\n",
        "    [\n",
        "        (3, 16),\n",
        "        (7, 17),\n",
        "        (11, 18),\n",
        "        (22, 23),\n",
        "        (26, 27),\n",
        "        (30, 31),\n",
        "        (34, 35),\n",
        "        (21, 36),\n",
        "        (25, 37),\n",
        "        (29, 38),\n",
        "        (33, 39),\n",
        "        (41, 42),\n",
        "        (44, 45),\n",
        "        (48, 49),\n",
        "        (52, 53),\n",
        "        (43, 56),\n",
        "        (47, 57),\n",
        "        (51, 58),\n",
        "        (62, 63),\n",
        "        (66, 67),\n",
        "        (70, 71),\n",
        "        (74, 75),\n",
        "        (61, 76),\n",
        "        (65, 77),\n",
        "        (69, 78),\n",
        "        (73, 79),\n",
        "        (81, 82),\n",
        "        (84, 85),\n",
        "        (88, 89),\n",
        "        (92, 93),\n",
        "        (83, 96),\n",
        "        (87, 97),\n",
        "        (91, 98),\n",
        "        (102, 103),\n",
        "        (106, 107),\n",
        "        (110, 111),\n",
        "        (114, 115),\n",
        "        (101, 116),\n",
        "        (105, 117),\n",
        "        (109, 118),\n",
        "        (113, 119),\n",
        "        (121, 122),\n",
        "        (124, 125),\n",
        "        (128, 129),\n",
        "        (132, 133),\n",
        "        (123, 136),\n",
        "        (127, 137),\n",
        "        (131, 138),\n",
        "        (142, 143),\n",
        "        (146, 147),\n",
        "        (150, 151),\n",
        "        (154, 155),\n",
        "        (0, 1),\n",
        "        (4, 5),\n",
        "        (8, 9),\n",
        "        (12, 13),\n",
        "        (54, 55),\n",
        "        (15, 19),\n",
        "    ],\n",
        "]\n",
        "\n",
        "subgraphs = {  # the subgraphs for the different qubit counts such that it's 2D\n",
        "    10: list(range(22, 29)) + [16, 17, 37],\n",
        "    21: list(range(3, 12)) + list(range(23, 32)) + [16, 17, 18],\n",
        "    28: list(range(3, 12))\n",
        "    + list(range(23, 32))\n",
        "    + list(range(45, 50))\n",
        "    + [16, 17, 18, 37, 38],\n",
        "    35: list(range(3, 12))\n",
        "    + list(range(21, 32))\n",
        "    + list(range(41, 50))\n",
        "    + [16, 17, 18, 36, 37, 38],\n",
        "    42: list(range(3, 12))\n",
        "    + list(range(21, 32))\n",
        "    + list(range(41, 50))\n",
        "    + list(range(63, 68))\n",
        "    + [16, 17, 18, 36, 37, 38, 56, 57],\n",
        "}\n",
        "\n",
        "n_qubits = 35  # 21, 28, 35, 42"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 15,
      "id": "c03b7ca9951aba38",
      "metadata": {
        "ExecuteTime": {
          "end_time": "2025-11-05T12:51:25.383040Z",
          "start_time": "2025-11-05T12:51:25.380298Z"
        }
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "[[(6, 7), (10, 11), (16, 23), (24, 25), (17, 27), (28, 29), (18, 31), (36, 41), (42, 43), (37, 45), (46, 47), (38, 49)], [(3, 4), (5, 6), (7, 8), (9, 10), (21, 22), (23, 24), (25, 26), (27, 28), (29, 30), (43, 44), (45, 46), (47, 48)], [(3, 16), (7, 17), (11, 18), (22, 23), (26, 27), (30, 31), (21, 36), (25, 37), (29, 38), (41, 42), (44, 45), (48, 49), (4, 5), (8, 9)]]\n"
          ]
        }
      ],
      "source": [
        "layers = [\n",
        "    [\n",
        "        edge\n",
        "        for edge in layer\n",
        "        if edge[0] in subgraphs[n_qubits] and edge[1] in subgraphs[n_qubits]\n",
        "    ]\n",
        "    for layer in LAYERS_HERON_R2\n",
        "]\n",
        "\n",
        "print(layers)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "46f93e695a2ae23e",
      "metadata": {},
      "source": [
        "Ora visualizziamo la disposizione dei qubit sul dispositivo Heron per il sottografo selezionato:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "4f02995559bb437f",
      "metadata": {
        "ExecuteTime": {
          "end_time": "2025-11-05T12:51:33.346900Z",
          "start_time": "2025-11-05T12:51:29.975427Z"
        }
      },
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/qedma-2d-ising-with-qesem/extracted-outputs/4f02995559bb437f-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "catalog = QiskitFunctionsCatalog(\n",
        "    channel=channel,\n",
        "    token=token,\n",
        "    instance=instance,\n",
        ")\n",
        "backend = catalog.backend(\"ibm_fez\")  # or any available device\n",
        "\n",
        "selected_qubits = subgraphs[n_qubits]\n",
        "num_qubits = backend.configuration().num_qubits\n",
        "qubit_color = [\n",
        "    \"#ff7f0e\" if i in selected_qubits else \"#d3d3d3\"\n",
        "    for i in range(num_qubits)\n",
        "]\n",
        "\n",
        "plot_gate_map(\n",
        "    backend=backend,\n",
        "    figsize=(15, 10),\n",
        "    qubit_color=qubit_color,\n",
        ")\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "7b6ecaa9",
      "metadata": {},
      "source": [
        "Si noti che la connettività della disposizione dei qubit scelta non è necessariamente lineare e può coprire ampie regioni del dispositivo Heron a seconda del numero di qubit selezionato.\n",
        "\n",
        "Ora generiamo il circuito di Trotter e l'osservabile di magnetizzazione media per il numero di qubit e i parametri scelti:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "1d34dd0d",
      "metadata": {
        "ExecuteTime": {
          "end_time": "2025-11-05T12:56:50.447530Z",
          "start_time": "2025-11-05T12:56:47.922845Z"
        }
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Circuit 2q layers: 27\n",
            "\n",
            "Circuit structure:\n"
          ]
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/qedma-2d-ising-with-qesem/extracted-outputs/1d34dd0d-1.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        },
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "SparsePauliOp(['IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII'],\n",
            "              coeffs=[0.02857143+0.j, 0.02857143+0.j, 0.02857143+0.j, 0.02857143+0.j,\n",
            " 0.02857143+0.j, 0.02857143+0.j, 0.02857143+0.j, 0.02857143+0.j,\n",
            " 0.02857143+0.j, 0.02857143+0.j, 0.02857143+0.j, 0.02857143+0.j,\n",
            " 0.02857143+0.j, 0.02857143+0.j, 0.02857143+0.j, 0.02857143+0.j,\n",
            " 0.02857143+0.j, 0.02857143+0.j, 0.02857143+0.j, 0.02857143+0.j,\n",
            " 0.02857143+0.j, 0.02857143+0.j, 0.02857143+0.j, 0.02857143+0.j,\n",
            " 0.02857143+0.j, 0.02857143+0.j, 0.02857143+0.j, 0.02857143+0.j,\n",
            " 0.02857143+0.j, 0.02857143+0.j, 0.02857143+0.j, 0.02857143+0.j,\n",
            " 0.02857143+0.j, 0.02857143+0.j, 0.02857143+0.j])\n"
          ]
        }
      ],
      "source": [
        "# Chosen parameters:\n",
        "theta_x = 0.53\n",
        "theta_z = 0.1\n",
        "theta_zz = 1.0\n",
        "steps = 9\n",
        "\n",
        "circ = trotter_circuit_from_layers(steps, theta_x, theta_z, theta_zz, layers)\n",
        "print(\n",
        "    f\"Circuit 2q layers: \"\n",
        "    f\"{circ.depth(filter_function=lambda instr: len(instr.qubits) == 2)}\"\n",
        ")\n",
        "print(\"\\nCircuit structure:\")\n",
        "\n",
        "circ.draw(\"mpl\", scale=0.8, fold=-1, idle_wires=False)\n",
        "plt.show()\n",
        "\n",
        "observable = qiskit.quantum_info.SparsePauliOp.from_sparse_list(\n",
        "    [(\"Z\", [q], 1 / n_qubits) for q in subgraphs[n_qubits]],\n",
        "    np.max(subgraphs[n_qubits]) + 1,\n",
        ")  # Average magnetization observable\n",
        "\n",
        "print(observable)\n",
        "obs_list = [observable]"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "76923674",
      "metadata": {},
      "source": [
        "<span id=\"step-2-optimize-problem-for-quantum-hardware-execution\" />\n",
        "\n",
        "## Fase 2: Ottimizzazione del problema per l'esecuzione su hardware quantistico\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "7402ea001f0b9f1a",
      "metadata": {},
      "source": [
        "<span id=\"qpu-time-estimation-with-and-without-obp\" />\n",
        "\n",
        "### Stima del tempo QPU con e senza OBP\n",
        "\n",
        "In genere gli utenti vogliono sapere quanto tempo QPU è necessario per il loro esperimento.\n",
        "Tuttavia, questo è considerato un problema difficile per i computer classici.\n",
        "\n",
        "QESEM offre due modalità di stima del tempo per informare gli utenti sulla fattibilità dei loro esperimenti:\n",
        "\n",
        "1. Stima analitica del tempo - fornisce una stima molto approssimativa e non richiede tempo per la QPU. Questo può essere utilizzato per verificare se un passaggio di transpilazione potrebbe ridurre il tempo di QPU.\n",
        "2. Stima empirica del tempo (dimostrata qui): fornisce una stima abbastanza buona e utilizza pochi minuti di tempo della QPU.\n",
        "\n",
        "In entrambi i casi, QESEM fornisce la stima del tempo necessario per raggiungere la precisione richiesta per **tutte le** osservabili.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "478e18ff",
      "metadata": {
        "ExecuteTime": {
          "end_time": "2025-11-05T14:39:16.112626Z",
          "start_time": "2025-11-05T14:39:12.416396Z"
        }
      },
      "outputs": [],
      "source": [
        "run_on_real_hardware = True\n",
        "\n",
        "precision = 0.05\n",
        "if run_on_real_hardware:\n",
        "    backend_name = \"ibm_fez\"\n",
        "else:\n",
        "    backend_name = \"fake_fez\"\n",
        "\n",
        "# Start a job for empirical time estimation\n",
        "estimation_job_wo_obp = qesem_function.run(\n",
        "    pubs=[(circ, obs_list)],\n",
        "    instance=instance,\n",
        "    backend_name=backend_name,  # E.g. \"ibm_brisbane\"\n",
        "    options={\n",
        "        # \"empirical\" - gets actual time estimates without running full mitigation\n",
        "        \"estimate_time_only\": \"empirical\",\n",
        "        \"max_execution_time\": 120,  # Limits the QPU time, specified in seconds.\n",
        "        \"default_precision\": precision,\n",
        "    },\n",
        ")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 31,
      "id": "bd0ff03fe6324ce5",
      "metadata": {
        "ExecuteTime": {
          "end_time": "2025-11-05T15:04:27.695966Z",
          "start_time": "2025-11-05T15:04:25.885170Z"
        }
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "17d3828e-9fdb-482e-8e9b-392f3eefe313\n",
            "DONE\n"
          ]
        }
      ],
      "source": [
        "print(estimation_job_wo_obp.job_id)\n",
        "print(estimation_job_wo_obp.status())"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "6357b2b5",
      "metadata": {
        "ExecuteTime": {
          "end_time": "2025-11-05T15:09:13.194534Z",
          "start_time": "2025-11-05T15:09:05.286214Z"
        }
      },
      "outputs": [],
      "source": [
        "# Get the result object (blocking method).\n",
        "# Use job.status() in a loop for non-blocking.\n",
        "# This takes 1-3 minutes\n",
        "result = estimation_job_wo_obp.result()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 33,
      "id": "1e3eab49",
      "metadata": {
        "ExecuteTime": {
          "end_time": "2025-11-05T15:04:47.335176Z",
          "start_time": "2025-11-05T15:04:47.332618Z"
        }
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Empirical time estimation (sec): 1200\n"
          ]
        }
      ],
      "source": [
        "print(\n",
        "    f\"Empirical time estimation (sec): {result[0].metadata['time_estimation_sec']}\"\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "75dbab74",
      "metadata": {},
      "source": [
        "Ora useremo la retropropagazione dell'operatore (OBP). (Per ulteriori dettagli sull'add-on OBP Qiskit, consultare la documentazione [OBP](https://qiskit.github.io/qiskit-addon-obp/).) Creeremo una funzione che genera le sezioni del circuito per la retropropagazione:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "2ac55a76daddddcf",
      "metadata": {
        "ExecuteTime": {
          "end_time": "2025-11-05T15:08:38.887654Z",
          "start_time": "2025-11-05T15:08:38.878679Z"
        }
      },
      "outputs": [],
      "source": [
        "def run_backpropagation(circ_vec, observable, steps_vec, max_qwc_groups=8):\n",
        "    \"\"\"\n",
        "    Runs backpropagation for a list of circuits and observables.\n",
        "    Returns lists of backpropagated circuits and observables.\n",
        "    \"\"\"\n",
        "    op_budget = OperatorBudget(max_qwc_groups=max_qwc_groups)\n",
        "    bp_circuit_vec = []\n",
        "    bp_observable_vec = []\n",
        "\n",
        "    for i, circ in enumerate(circ_vec):\n",
        "        slices = slice_by_gate_types(circ)\n",
        "        bp_observable, remaining_slices, metadata = backpropagate(\n",
        "            observable,\n",
        "            slices,\n",
        "            operator_budget=op_budget,\n",
        "        )\n",
        "        bp_circuit = combine_slices(remaining_slices, include_barriers=True)\n",
        "        bp_circuit_vec.append(bp_circuit)\n",
        "        bp_observable_vec.append(bp_observable)\n",
        "        print(f\"n.o. steps: {steps_vec[i]}\")\n",
        "        print(f\"Backpropagated {metadata.num_backpropagated_slices} slices.\")\n",
        "        print(\n",
        "            f\"New observable has {len(bp_observable.paulis)} terms, \"\n",
        "            f\"which can be combined into \"\n",
        "            f\"{len(bp_observable.group_commuting(qubit_wise=True))} groups.\\n\"\n",
        "            f\"After truncation, the error in our observable is bounded by \"\n",
        "            f\"{metadata.accumulated_error(0):.3e}\"\n",
        "        )\n",
        "        print(\"-----------------\")\n",
        "    return bp_circuit_vec, bp_observable_vec"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "84135dc1fb687d2d",
      "metadata": {},
      "source": [
        "Chiamiamo la funzione:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 39,
      "id": "1bc2f43bd93b3a92",
      "metadata": {
        "ExecuteTime": {
          "end_time": "2025-11-05T15:15:00.391655Z",
          "start_time": "2025-11-05T15:14:59.993228Z"
        }
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "n.o. steps: 9\n",
            "Backpropagated 11 slices.\n",
            "New observable has 363 terms, which can be combined into 4 groups.\n",
            "After truncation, the error in our observable is bounded by 0.000e+00\n",
            "-----------------\n"
          ]
        }
      ],
      "source": [
        "bp_circ_vec, bp_obs_vec = run_backpropagation([circ], observable, [steps])"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 40,
      "id": "cedb7fa1",
      "metadata": {
        "ExecuteTime": {
          "end_time": "2025-11-05T15:15:53.031008Z",
          "start_time": "2025-11-05T15:15:49.863350Z"
        }
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "The remaining circuit after backpropagation looks as follows:\n"
          ]
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/qedma-2d-ising-with-qesem/extracted-outputs/cedb7fa1-1.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "print(\"The remaining circuit after backpropagation looks as follows:\")\n",
        "bp_circ_vec[-1].draw(\"mpl\", scale=0.8, fold=-1, idle_wires=False)\n",
        "None"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "fc1e532a",
      "metadata": {},
      "source": [
        "Si può notare che la retropropagazione ha ridotto due strati del circuito.\n",
        "Ora che abbiamo il nostro circuito ridotto e le osservabili espanse, facciamo la stima del tempo per il circuito retropropagato:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 42,
      "id": "88160fbc",
      "metadata": {
        "ExecuteTime": {
          "end_time": "2025-11-05T15:27:07.496690Z",
          "start_time": "2025-11-05T15:27:01.891318Z"
        }
      },
      "outputs": [],
      "source": [
        "# Start a job for empirical time estimation\n",
        "estimation_job_obp = qesem_function.run(\n",
        "    pubs=[(bp_circ_vec[-1], [bp_obs_vec[-1]])],\n",
        "    instance=instance,\n",
        "    backend_name=backend_name,\n",
        "    options={\n",
        "        \"estimate_time_only\": \"empirical\",\n",
        "        \"max_execution_time\": 120,\n",
        "        \"default_precision\": precision,\n",
        "    },\n",
        ")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 48,
      "id": "c9ec8a5dcf0bfb21",
      "metadata": {
        "ExecuteTime": {
          "end_time": "2025-11-05T17:15:10.776085Z",
          "start_time": "2025-11-05T17:15:08.740353Z"
        }
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "8bae699d-a16b-4d39-bbd9-d123fbcce55d\n",
            "DONE\n"
          ]
        }
      ],
      "source": [
        "print(estimation_job_obp.job_id)\n",
        "print(estimation_job_obp.status())"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 49,
      "id": "19cd4cc2",
      "metadata": {
        "ExecuteTime": {
          "end_time": "2025-11-05T17:15:19.426157Z",
          "start_time": "2025-11-05T17:15:13.048448Z"
        }
      },
      "outputs": [],
      "source": [
        "result_obp = estimation_job_obp.result()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 50,
      "id": "feca3059",
      "metadata": {
        "ExecuteTime": {
          "end_time": "2025-11-05T17:15:21.205216Z",
          "start_time": "2025-11-05T17:15:21.203160Z"
        }
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Empirical time estimation (sec): 900\n"
          ]
        }
      ],
      "source": [
        "print(\n",
        "    f\"Empirical time estimation (sec): {result_obp[0].metadata['time_estimation_sec']}\"\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "504669f5",
      "metadata": {},
      "source": [
        "Vediamo che l'OBP riduce il costo del tempo per la mitigazione del circuito.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "4d1d5092",
      "metadata": {},
      "source": [
        "<span id=\"step-3-execute-using-qiskit-primitives\" />\n",
        "\n",
        "## Passaggio 3: eseguire utilizzando Qiskit primitives\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "3da535e9",
      "metadata": {},
      "source": [
        "<span id=\"run-with-real-backend\" />\n",
        "\n",
        "### Esegui con backend reale\n",
        "\n",
        "Ora eseguiamo l'esperimento completo su un paio di gradini Trotter. Il numero di qubit, la precisione richiesta e il tempo massimo di QPU possono essere modificati in base alle risorse QPU disponibili. Si noti che la limitazione del tempo massimo di QPU influisce sulla precisione finale, come si vedrà nel grafico finale qui sotto.\n",
        "\n",
        "Analizziamo quattro circuiti con 5, 7 e 9 passi di Trotter a una precisione di 0.05, confrontando i valori di aspettativa ideali, rumorosi e mitigati dagli errori:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 84,
      "id": "7cfb4dbc",
      "metadata": {
        "ExecuteTime": {
          "end_time": "2025-11-06T13:47:04.428822Z",
          "start_time": "2025-11-06T13:47:04.415793Z"
        }
      },
      "outputs": [],
      "source": [
        "steps_vec = [5, 7, 9]\n",
        "\n",
        "circ_vec = []\n",
        "for steps in steps_vec:\n",
        "    circ = trotter_circuit_from_layers(\n",
        "        steps, theta_x, theta_z, theta_zz, layers\n",
        "    )\n",
        "    circ_vec.append(circ)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "d63c1de0",
      "metadata": {},
      "source": [
        "Anche in questo caso, eseguiamo l'OBP su ogni circuito per ridurre i tempi di esecuzione:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 85,
      "id": "267e030f",
      "metadata": {
        "ExecuteTime": {
          "end_time": "2025-11-06T13:47:10.089306Z",
          "start_time": "2025-11-06T13:47:09.003172Z"
        }
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "n.o. steps: 5\n",
            "Backpropagated 11 slices.\n",
            "New observable has 363 terms, which can be combined into 4 groups.\n",
            "After truncation, the error in our observable is bounded by 0.000e+00\n",
            "-----------------\n",
            "n.o. steps: 7\n",
            "Backpropagated 11 slices.\n",
            "New observable has 363 terms, which can be combined into 4 groups.\n",
            "After truncation, the error in our observable is bounded by 0.000e+00\n",
            "-----------------\n",
            "n.o. steps: 9\n",
            "Backpropagated 11 slices.\n",
            "New observable has 363 terms, which can be combined into 4 groups.\n",
            "After truncation, the error in our observable is bounded by 0.000e+00\n",
            "-----------------\n"
          ]
        }
      ],
      "source": [
        "bp_circ_vec_35, bp_obs_vec_35 = run_backpropagation(\n",
        "    circ_vec, observable, steps_vec\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "68bb0915",
      "metadata": {},
      "source": [
        "Ora eseguiamo un lotto di lavori QESEM completi. Limitiamo il tempo di esecuzione massimo della QPU per ciascuno dei punti per controllare meglio il budget della QPU.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 106,
      "id": "c039197fca88d16a",
      "metadata": {
        "ExecuteTime": {
          "end_time": "2025-11-06T16:41:01.087031Z",
          "start_time": "2025-11-06T16:41:01.073625Z"
        }
      },
      "outputs": [],
      "source": [
        "run_on_real_hardware = True\n",
        "\n",
        "precision = 0.05\n",
        "if run_on_real_hardware:\n",
        "    backend_name = \"ibm_marrakesh\"\n",
        "else:\n",
        "    backend_name = \"fake_fez\""
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 54,
      "id": "e60a2fc8",
      "metadata": {
        "ExecuteTime": {
          "end_time": "2025-11-05T17:27:41.525432Z",
          "start_time": "2025-11-05T17:27:41.523337Z"
        }
      },
      "outputs": [],
      "source": [
        "# Running full jobs for:\n",
        "pubs_list = [\n",
        "    [(bp_circ_vec_35[i], bp_obs_vec_35[i])] for i in range(len(bp_obs_vec_35))\n",
        "]"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 55,
      "id": "534ad1d013e4fb2",
      "metadata": {
        "ExecuteTime": {
          "end_time": "2025-11-05T17:29:17.142120Z",
          "start_time": "2025-11-05T17:29:04.155649Z"
        }
      },
      "outputs": [],
      "source": [
        "# Initiating multiple jobs for different lengths\n",
        "job_list = []\n",
        "for pubs in pubs_list:\n",
        "    job_obp = qesem_function.run(\n",
        "        pubs=pubs,\n",
        "        instance=instance,\n",
        "        backend_name=backend_name,  # E.g. \"ibm_brisbane\"\n",
        "        options={\n",
        "            \"max_execution_time\": 300,  # Limits the QPU time, specified in seconds.\n",
        "            \"default_precision\": 0.05,\n",
        "        },\n",
        "    )\n",
        "    job_list.append(job_obp)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "05c75ada",
      "metadata": {},
      "source": [
        "Qui si controlla lo stato di ogni lavoro:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 61,
      "id": "b869fd4f",
      "metadata": {
        "ExecuteTime": {
          "end_time": "2025-11-05T19:44:14.257551Z",
          "start_time": "2025-11-05T19:44:08.130764Z"
        }
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "DONE\n",
            "DONE\n",
            "DONE\n",
            "DONE\n"
          ]
        }
      ],
      "source": [
        "for job in job_list:\n",
        "    print(job.status())"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "68cabcc4",
      "metadata": {},
      "source": [
        "<span id=\"step-4-post-process-and-return-result-in-desired-classical-format\" />\n",
        "\n",
        "## Fase 4: Post-elaborazione e restituzione del risultato nel formato classico desiderato\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "72e511b12012254d",
      "metadata": {},
      "source": [
        "Una volta terminata l'esecuzione di tutti i lavori, possiamo confrontare il loro valore di aspettativa rumorosa e attenuata.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 130,
      "id": "9bc7cce51e0d4b4c",
      "metadata": {
        "ExecuteTime": {
          "end_time": "2025-11-06T20:49:40.252799Z",
          "start_time": "2025-11-06T20:49:14.517682Z"
        }
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Using precalculated ideal values for large circuits calculated with belief propagation PEPS. Currently only for 35 qubits.\n",
            "---------------------------------\n",
            "Ideal: 0.79537\n",
            "Noisy: 0.7039237951821501\n",
            "QESEM: 0.7828018244130982 ± 0.013257266977728376\n",
            "Using precalculated ideal values for large circuits calculated with belief propagation PEPS. Currently only for 35 qubits.\n",
            "---------------------------------\n",
            "Ideal: 0.78653\n",
            "Noisy: 0.6478583812958806\n",
            "QESEM: 0.7875259197423828 ± 0.02703045139248604\n",
            "Using precalculated ideal values for large circuits calculated with belief propagation PEPS. Currently only for 35 qubits.\n",
            "---------------------------------\n",
            "Ideal: 0.79699\n",
            "Noisy: 0.6171787879868142\n",
            "QESEM: 0.6918791909168913 ± 0.0740873782039517\n"
          ]
        }
      ],
      "source": [
        "ideal_values = []\n",
        "noisy_values = []\n",
        "error_mitigated_values = []\n",
        "error_mitigated_stds = []\n",
        "\n",
        "for i in range(len(job_list)):\n",
        "    job = job_list[i]\n",
        "    result = job.result()  # Blocking - takes 3-5 minutes\n",
        "    noisy_results = result[0].metadata[\"noisy_results\"]\n",
        "\n",
        "    ideal_val = calculate_ideal_evs(circ_vec[i], observable, n_qubits, i)\n",
        "    print(\"---------------------------------\")\n",
        "    print(f\"Ideal: {ideal_val}\")\n",
        "    print(f\"Noisy: {noisy_results.evs}\")\n",
        "    print(f\"QESEM: {result[0].data.evs} \\u00b1 {result[0].data.stds}\")\n",
        "\n",
        "    ideal_values.append(ideal_val)\n",
        "    noisy_values.append(noisy_results.evs)\n",
        "    error_mitigated_values.append(result[0].data.evs)\n",
        "    error_mitigated_stds.append(result[0].data.stds)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "52d3b66d2e42b464",
      "metadata": {},
      "source": [
        "Infine, possiamo tracciare il grafico della magnetizzazione in funzione del numero di passi. Questo riassume i vantaggi dell'utilizzo della funzione Qiskit di QESEM per la mitigazione degli errori senza bias su dispositivi quantistici rumorosi.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 131,
      "id": "0f1a44d0",
      "metadata": {
        "ExecuteTime": {
          "end_time": "2025-11-06T20:49:49.067165Z",
          "start_time": "2025-11-06T20:49:48.990714Z"
        }
      },
      "outputs": [
        {
          "data": {
            "text/plain": [
              "Text(0, 0.5, 'Magnetization')"
            ]
          },
          "execution_count": 131,
          "metadata": {},
          "output_type": "execute_result"
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/qedma-2d-ising-with-qesem/extracted-outputs/0f1a44d0-1.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "plt.plot(steps_vec, ideal_values, \"--\", label=\"ideal\")\n",
        "plt.scatter(steps_vec, noisy_values, label=\"noisy\")\n",
        "plt.errorbar(\n",
        "    steps_vec,\n",
        "    error_mitigated_values,\n",
        "    yerr=error_mitigated_stds,\n",
        "    fmt=\"o\",\n",
        "    capsize=5,\n",
        "    label=\"QESEM mitigation\",\n",
        ")\n",
        "plt.legend()\n",
        "plt.xlabel(\"n.o. steps\")\n",
        "plt.ylabel(\"Magnetization\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "e14cb01633a8451a",
      "metadata": {},
      "source": [
        "<Admonition type=\"note\">\n",
        "  Il nono passo presenta un'ampia barra di errore statistico perché abbiamo limitato il tempo di QPU a 5 minuti. Se si esegue questo passaggio per 15 minuti (come suggerisce la stima empirica del tempo), si otterrà una barra di errore più piccola. Di conseguenza, il valore mitigato si avvicinerà al valore ideale.\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": 1,
    "qpuSeconds": 1200
  },
  "nbformat": 4,
  "nbformat_minor": 5
}