{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "slc-title-0000-4000-8000-000000000000",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Cancellazione probabilistica dell'errore con coni di luce ombreggiati\"\n",
        "description: \"Utilizzare la cancellazione probabilistica degli errori (PEC) con coni di luce ombreggiati per ridurre il sovraccarico di campionamento della PEC e mitigare gli errori nella stima del valore atteso a livello hardware\"\n",
        "---\n",
        "\n",
        "{/* cspell:ignore samplomatic samplex Trotterized Trotter Lindblad lightcone lightcones Eddins reverser broadcastable postselection postselect antinoise eigval atol starmap unmitigated NoiseLearner PauliLindblad QubitSparsePauli Kingston kicked qubit canonical isa plm qspl Rzz pauli TREX levelname xticks expvals markerfacecolor ncols */}\n",
        "\n",
        "<span id=\"probabilistic-error-cancellation-with-shaded-lightcones\" />\n",
        "\n",
        "# Cancellazione probabilistica dell'errore con coni di luce ombreggiati\n",
        "\n",
        "*Stima del tempo di esecuzione: 10 minuti su un processore Heron (NOTA: si tratta solo di una stima. (La durata potrebbe variare.)*\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "slc-lout-0000-4000-8000-000000000001",
      "metadata": {},
      "source": [
        "<span id=\"learning-outcomes\" />\n",
        "\n",
        "## Risultati di apprendimento\n",
        "\n",
        "Dopo aver seguito questo tutorial, gli utenti dovrebbero aver compreso:\n",
        "\n",
        "* Che cos’è la cancellazione probabilistica dell’errore (PEC) e perché il suo overhead di campionamento $\\gamma^2$ cresce in modo esponenziale con il rumore totale che agisce sul circuito\n",
        "* In che modo i coni di luce ombreggiati (SLC) limitano il contributo di ciascun termine di rumore all’osservabile di riferimento, consentendo di impiegare il budget di mitigazione dove è più necessario — e in che modo, al di là della riduzione dell’overhead dovuta al bias zero già garantita da un cono di luce puramente geometrico, l’ombreggiatura permette di scambiare un bias residuo limitato con un overhead di campionamento ancora più basso\n",
        "* Come rilevare il rumore di livello tramite `NoiseLearnerV3` e inserire l'anti-rumore tramite `samplomatic` e la `Executor` primitiva\n",
        "* Come combinare PEC e PEC+SLC con TREX e la post-selezione per stimare un valore atteso su hardware\n",
        "\n",
        "<span id=\"prerequisites\" />\n",
        "\n",
        "## Prerequisiti\n",
        "\n",
        "Consigliamo agli utenti di acquisire familiarità con i seguenti argomenti prima di seguire questo tutorial:\n",
        "\n",
        "* Il flusso di lavoro [dei modelli di](/docs/guides/intro-to-patterns) Qiskit\n",
        "* Utilizzo della primitiva [\"Estimator\"](/docs/guides/get-started-with-estimator) per calcolare i valori attesi di una grandezza osservabile\n",
        "* Tecniche di mitigazione degli errori quali il “Pauli twirling” e il TREX, illustrate nella sezione [“Combinare le opzioni di mitigazione degli errori con la primitiva Estimator”](/docs/tutorials/combine-error-mitigation-techniques)\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "slc-bkgd-0000-4000-8000-000000000002",
      "metadata": {},
      "source": [
        "<span id=\"background\" />\n",
        "\n",
        "## Sfondo\n",
        "\n",
        "Questo tutorial illustra come ridurre gli errori utilizzando l'add-on Shaded Light Cone (SLC). Questo add-on rappresenta un'evoluzione della [tecnica di cancellazione probabilistica dell'errore (PEC)](/docs/guides/error-mitigation-and-suppression-techniques#probabilistic-error-cancellation-pec), in cui l'utente acquisisce informazioni sul rumore presente nei singoli livelli di un circuito e successivamente lo cancella applicando porte a singolo qubit e tecniche di post-elaborazione. Rispetto ad altri metodi, il PEC offre limiti più solidi sul bias del risultato mitigato, ma tende a comportare un maggiore sovraccarico in termini di tempo di elaborazione della QPU. Durante il PEC, per compensare l’attenuazione del valore atteso causata dal rumore, il risultato medio viene riscalato di un fattore pari a $\\gamma = \\exp(\\sum_{l,\\sigma} 2\\lambda_{l,\\sigma})$, dove $\\lambda_{l,\\sigma}$ è il tasso di errore di Pauli $\\sigma$ appreso al livello $l$ nel circuito. Questo ridimensionamento aumenta la varianza di un fattore pari a $\\gamma^2$ e, di conseguenza, moltiplica anche il numero di esecuzioni del circuito necessarie sulla QPU per $\\gamma^2$, che chiamiamo “costo di campionamento” o “overhead di campionamento”. Poiché l' $\\gamma$ a cresce in modo esponenziale, il PEC è spesso limitato a circuiti poco profondi o con pochi qubit. Per saperne di più sul PEC, consulta l’articolo “[Cancellazione probabilistica degli errori con modelli di Pauli-Lindblad sparsi su processori quantistici soggetti a rumore](https://arxiv.org/abs/2201.09866) ”.\n",
        "\n",
        "Se riusciamo a individuare gli errori che non richiedono alcuna mitigazione, possiamo ridurre in modo esponenziale questo costo di campionamento. Un primo passo in questa direzione consiste nell’implementare una mitigazione degli errori sensibile al contesto locale, che utilizza un “cono di luce” convenzionale, rapidamente calcolabile, per ridurre il sovraccarico del PEC limitando la sensibilità di un osservabile agli errori in tutto il circuito, estendendo così la fattibilità del PEC a scale più ampie per alcuni problemi. Gli errori al di fuori di questo cono di luce non possono influire sul risultato misurato e possono quindi essere esclusi dalla procedura di compensazione degli errori. Questa esclusione riduce il carico di campionamento, in alcuni casi in modo sostanziale, senza introdurre ulteriori distorsioni. In particolare, per misurare un osservabile locale $O$ in un circuito a profondità fissa, il sovraccarico di campionamento richiesto alla fine si stabilizza all’aumentare del numero di qubit nel circuito (cfr. la fig. 2b in “[Locality and error mitigation of quantum circuits](https://arxiv.org/abs/2303.06496) ”).\n",
        "\n",
        "I coni di luce ombreggiati (SLC) vanno oltre, utilizzando simulazioni classiche per limitare in modo più rigoroso la sensibilità agli errori in tutto il circuito. In questo modo si scambia una parte del tempo di elaborazione della QPU con quello della CPU, riducendo il sovraccarico di campionamento necessario per rinormalizzare il bias. Anziché un limite netto, a ciascun potenziale errore nel circuito viene assegnata una \"sfumatura\" graduata che definisce il limite superiore della suscettibilità del valore atteso dell'osservabile a tale errore. (Per i limiti propagati a ritroso introdotti di seguito, questa suscettibilità dipende anche dallo stato iniziale del circuito — lo stato tutto a zero nei flussi di lavoro tipici e in questo tutorial.) Questa caratterizzazione più accurata consente applicazioni più efficienti e mirate della PEC con una varianza ridotta, offrendo al contempo all’utente la possibilità di regolare in modo controllato la distorsione nella stima dell’osservabile. Per ulteriori dettagli, consultare [la sezione “Ombreggiamento Lightcone per la mitigazione degli errori quantistici con accelerazione classica](https://arxiv.org/abs/2409.04401) ”.\n",
        "\n",
        "Il nostro flusso di lavoro per l'add-on SLC sfrutta la [`samplomatic`](https://qiskit.github.io/samplomatic/guides/index.html) libreria insieme alle classi `QuantumProgram` e `Executor` aggiunte a Qiskit Runtime nel post `qiskit-ibm-runtime`v0.47.0, consentendo agli utenti di avere un controllo più modulare delle impostazioni di esecuzione per la soppressione e la mitigazione degli errori, pur mantenendo la facilità d'uso. Per ulteriori informazioni, consulta la guida sul [modello di esecuzione diretta](/docs/guides/directed-execution-model).\n",
        "\n",
        "<span id=\"slc-error-mitigation-workflow-at-a-glance\" />\n",
        "\n",
        "### Panoramica sul flusso di lavoro per la mitigazione degli errori SLC\n",
        "\n",
        "Per modellare il rumore della QPU, utilizziamo un modello di rumore di Pauli-Lindblad sparsa con tassi di errore di Pauli a uno e due qubit, generati localmente su ciascun qubit e su ciascun bordo del dispositivo. Con questa scelta, il flusso di lavoro per la mitigazione degli errori SLC illustrato in questo tutorial è il seguente:\n",
        "\n",
        "a. CPU — Impatto per errore legato alla CPU degli errori di Pauli su uno e due qubit\n",
        "\n",
        "1. Propagazione in avanti (effetto legato sull'osservabile). Propagare ciascun errore fino alla fine del circuito e calcolare il suo commutatore con l'osservabile.\n",
        "   * Troncare i termini dell'operatore durante l'evoluzione per mantenere il calcolo gestibile.\n",
        "   * Restringere ulteriormente questi limiti mediante una retropropagazione approssimativa dell’osservabile basata sui limiti di velocità quantistici.\n",
        "2. Propagazione all'indietro (effetto del vincolo sullo stato iniziale). Riportare ogni errore all'inizio del circuito e calcolare il suo commutatore rispetto allo stato iniziale.\n",
        "\n",
        "b. QPU — Informati sui livelli di rumore. Utilizzare `NoiseLearnerV3` per stimare i tassi del modello di rumore di Pauli-Lindblad.\n",
        "\n",
        "c. CPU — Dare priorità alle misure di mitigazione\n",
        "\n",
        "1. Aggiornare i limiti combinati con i tassi di rumore appresi. Combina i limiti di avanzamento e di arretramento calcolati in precedenza e aggiorna tali valori con i tassi di rumore appresi.\n",
        "2. Classificare le componenti di rumore da mitigare utilizzando i limiti calcolati e i tassi appresi. Assegnare una priorità a ciascun possibile errore di rumore in base al suo impatto stimato sulla distorsione e al costo associato alla sua correzione.\n",
        "\n",
        "d. QPU — Inserisci l'anti-rumore e avvia. Eseguire il circuito di interesse con l'anti-rumore (rumore inverso) specificato tramite `Box` annotazioni.\n",
        "\n",
        "e. CPU — Stima osservabile. Calcolare il valore atteso, applicando una post-selezione basata sulla misurazione per ridurre l'impatto del rumore non markoviano.\n",
        "\n",
        "<span id=\"noise-learning-overview\" />\n",
        "\n",
        "### Panoramica sull'apprendimento del rumore\n",
        "\n",
        "L'apprendimento del rumore è una fase comune a diversi metodi di mitigazione dell'errore, eseguita dal \" [noise learner](/docs/guides/noise-learning) \"; compare anche nel [tutorial sull'amplificazione probabilistica dell'errore](/docs/tutorials/probabilistic-error-amplification). In `NoiseLearnerV3`, è possibile identificare in modo specifico i livelli di rumore da apprendere come [`CircuitInstruction`](/docs/api/qiskit/qiskit.circuit.CircuitInstruction) oggetti, in modo da poter calcolare i limiti di rumore SLC desiderati per ciascun livello secondo la procedura descritta sopra. Il modello teorico di Pauli-Lindblad fornisce i coefficienti da utilizzare nella definizione delle priorità PEC+SLC. `generate_noise_model_paulis`Il modo in cui i gate vengono raggruppati in livelli può essere determinato utilizzando le `generate_boxing_pass_manager` funzioni di convenienza e `find_unique_box_instructions` , per poi essere inseriti nella funzione di utilità SLC, come descritto nel Passo 2 riportato di seguito.\n",
        "\n",
        "| **Parte 1**                                                                                                         | **Parte 2**                                                                                                         | **Parte 3**                                                                                                             |\n",
        "| ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |\n",
        "| Strati di porte a due qubit con rotazione di Pauli                                                                  | Ripetere coppie di livelli identiche e apprendere il rumore                                                         | Calcolare la fedeltà (errore per ciascun canale di rumore)                                                              |\n",
        "| ![Il giro di Pauli](https://quantum.cloud.ibm.com/docs/images/tutorials/pec-with-shaded-lightcones/paulitwirl.avif) | ![Scopri i livelli](https://quantum.cloud.ibm.com/docs/images/tutorials/pec-with-shaded-lightcones/learnlayer.avif) | ![Adattamento alla curva](https://quantum.cloud.ibm.com/docs/images/tutorials/pec-with-shaded-lightcones/curvefit.avif) |\n",
        "\n",
        "<span id=\"post-processing-overview\" />\n",
        "\n",
        "### Panoramica sulla post-elaborazione\n",
        "\n",
        "Dopo aver eseguito il codice sull'hardware quantistico utilizzando i `samplomatic` framework e `Executor` , convertiamo le misurazioni della nostra stringa di bit nel valore osservabile desiderato. In questo tutorial illustriamo il flusso di lavoro su un circuito di Ising speculare (costruito nel Passo 1 riportato di seguito), per il quale il valore atteso ideale dell'osservabile misurato è 1, poiché la seconda metà del circuito annulla l'effetto della prima. Nel calcolare il valore osservabile con la `executor_expectation_values` funzione, applichiamo alcune tecniche di post-elaborazione che riducono l'impatto del rumore. Tra questi figurano l'eliminazione delle immagini affette da rumore non markoviano, la mitigazione degli errori di lettura e la considerazione dei dettagli della nostra implementazione del PEC. I dettagli sono illustrati nel punto 4 riportato di seguito.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "slc-reqs-0000-4000-8000-000000000003",
      "metadata": {},
      "source": [
        "<span id=\"requirements\" />\n",
        "\n",
        "## Requisiti\n",
        "\n",
        "Prima di iniziare questo tutorial, assicurati di avere installato quanto segue:\n",
        "\n",
        "* Qiskit SDK v2.2 o versioni successive, con supporto [alla visualizzazione](/docs/api/qiskit/visualization)\n",
        "* Qiskit Runtime v0.47 o versioni successive (`pip install qiskit-ibm-runtime`)\n",
        "* Add-on Qiskit \"Shaded lightcones\" v0.1 o versioni successive (`pip install qiskit-addon-slc`)\n",
        "* Qiskit addon utils v0.3 o versioni successive (`pip install qiskit-addon-utils`)\n",
        "* Samplomatic v0.13 o o versioni successive (`pip install samplomatic`)\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "slc-setup-000-4000-8000-000000000004",
      "metadata": {},
      "source": [
        "<span id=\"setup\" />\n",
        "\n",
        "## Configura\n",
        "\n",
        "Per prima cosa, importa i pacchetti e le funzioni necessari per eseguire questo notebook.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "id": "946458b7-ab59-448c-8268-f62d8f53bc6d",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "env: OMP_NUM_THREADS=1\n"
          ]
        }
      ],
      "source": [
        "from multiprocessing import set_start_method\n",
        "\n",
        "# Setting this value prevents itertools.starmap deadlock on UNIX systems\n",
        "set_start_method(\"spawn\")\n",
        "\n",
        "# Needed to prevent PySCF from parallelizing internally (SLC only)\n",
        "%set_env OMP_NUM_THREADS=1"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "slc-imports-00-4000-8000-000000000005",
      "metadata": {},
      "outputs": [],
      "source": [
        "import numpy as np\n",
        "from matplotlib import pyplot as plt\n",
        "\n",
        "from qiskit import QuantumCircuit\n",
        "from qiskit.quantum_info import SparsePauliOp\n",
        "from qiskit.transpiler import generate_preset_pass_manager, PassManager\n",
        "\n",
        "from qiskit_ibm_runtime import (\n",
        "    QiskitRuntimeService,\n",
        "    QuantumProgram,\n",
        "    Executor,\n",
        "    NoiseLearnerV3,\n",
        ")\n",
        "\n",
        "import samplomatic\n",
        "from samplomatic.utils import find_unique_box_instructions\n",
        "from samplomatic.transpiler import generate_boxing_pass_manager\n",
        "\n",
        "from qiskit_addon_utils.exp_vals.measurement_bases import (\n",
        "    get_measurement_bases,\n",
        ")\n",
        "from qiskit_addon_utils.exp_vals.expectation_values import (\n",
        "    executor_expectation_values,\n",
        ")\n",
        "from qiskit_addon_utils.noise_management import (\n",
        "    gamma_from_noisy_boxes,\n",
        "    trex_factors,\n",
        ")\n",
        "from qiskit_addon_utils.noise_management.post_selection import PostSelector\n",
        "from qiskit_addon_utils.noise_management.post_selection.transpiler.passes import (\n",
        "    AddPostSelectionMeasures,\n",
        "    AddSpectatorMeasures,\n",
        ")\n",
        "\n",
        "from qiskit_addon_slc.bounds import (\n",
        "    compute_backward_bounds,\n",
        "    compute_forward_bounds,\n",
        "    compute_local_scales,\n",
        "    merge_bounds,\n",
        "    tighten_with_speed_limit,\n",
        ")\n",
        "from qiskit_addon_slc.utils import (\n",
        "    generate_noise_model_paulis,\n",
        "    map_modifier_ref_to_ref,\n",
        ")\n",
        "from qiskit_addon_slc.visualization import draw_shaded_lightcone"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "slc-sim-0000-4000-8000-000000000006",
      "metadata": {},
      "source": [
        "<span id=\"small-scale-simulator-example\" />\n",
        "\n",
        "## Esempio di simulatore su piccola scala\n",
        "\n",
        "Come altri metodi di mitigazione degli errori basati sull'apprendimento, il PEC con coni di luce ombreggiati mitiga il rumore *fisico* di uno specifico processore quantistico, pertanto dipende da servizi hardware che non trovano un equivalente significativo in un simulatore ideale:\n",
        "\n",
        "* `NoiseLearnerV3` caratterizza sperimentalmente il canale di rumore sparso di Pauli-Lindblad su ciascun singolo strato a due qubit. Su un simulatore silenzioso non c'è alcun rumore da eliminare.\n",
        "* Il `Executor` \"primitive\" campiona i circuiti a rotazione con iniezione di anti-rumore generati da `samplomatic` su un backend.\n",
        "\n",
        "Il calcolo del limite del cono di luce ombreggiato è di tipo classico, ma ha senso solo in relazione ai tassi di rumore hardware appresi, che determinano il budget di mitigazione e il sovraccarico di campionamento. Per questi motivi tralasciamo l'esempio del simulatore su piccola scala e illustriamo il flusso di lavoro completo PEC+SLC direttamente sull'hardware, con ogni fase del modello Qiskit descritta in dettaglio di seguito.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "slc-hw1-0000-4000-8000-000000000007",
      "metadata": {},
      "source": [
        "<span id=\"large-scale-hardware-example\" />\n",
        "\n",
        "## Esempio di hardware su larga scala\n",
        "\n",
        "Eseguiamo l'intero flusso di lavoro PEC+SLC su un circuito di Ising speculare da 20 qubit, eseguito su un hardware d IBM Quantum®, seguendo le quattro fasi di un modello Qiskit.\n",
        "\n",
        "<span id=\"step-1-map-the-problem\" />\n",
        "\n",
        "### Fase 1: Analizzare il problema\n",
        "\n",
        "La nostra dimostrazione è strutturata come segue:\n",
        "\n",
        "1. Abbiamo scelto di effettuare l'evoluzione temporale nell'ambito di un hamiltoniano a catena di Ising di tipo “ 1D ”. La catena di Ising \" 1D \" offre una struttura circuitale ben densa, ideale per illustrare le implementazioni del PEC.\n",
        "2. Abbiamo scelto di misurare l' $\\langle X_6 Z_{13}\\rangle$ osservabile, che contiene una componente $X$ e una componente $Z$. Questo è utile per evidenziare come i limiti SLC trattino in modo diverso i vari tipi di errore.\n",
        "3. Vogliamo che il valore atteso ideale di questa grandezza osservabile sia esattamente 1, in modo che sia facile valutare la qualità delle stime mitigate. Riflettendo il circuito otteniamo quasi il risultato desiderato: per ogni porta nella seconda metà del circuito esiste una porta inversa nella prima metà, cosicché il circuito ideale agisce come l'identità.\n",
        "4. Infine, affinché il valore atteso sia pari a 1, è necessario che il circuito inizi — e quindi, grazie alla simmetria speculare, idealmente anche finisca — in uno stato proprio +1 dell’osservabile. Poiché l'osservabile contiene una componente non $Z$ e e la `Executor` primitiva definisce la base di misura desiderata alla fine del circuito, inseriamo i corrispondenti gate di preparazione della base all'inizio del circuito a specchio tramite una `prepare_basis` funzione. Utilizziamo la `get_measurement_bases` funzione per identificare quali porte sono necessarie e dove posizionarle, tenendo conto al contempo delle sottigliezze relative all'indice dei qubit derivanti dalle `box` convenzioni di annotazione, come illustrato nella sezione dedicata alla preparazione delle misurazioni in base canonica.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "id": "5bfefe99-5958-486e-b8ca-ef2573c7f1e5",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Width of the Ising chain (a free parameter of this demonstration)\n",
        "num_qubits = 20\n",
        "\n",
        "# The observable to measure: one X component (on qubit 6) and one Z component\n",
        "# (on qubit 13), as motivated in \"Step 1: Map the problem\" above\n",
        "target_obs_sparse = [(\"XZ\", [6, 13], 1.0)]"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "id": "e5d4d6d5-9016-4c9a-916b-5075ff459fbf",
      "metadata": {},
      "outputs": [],
      "source": [
        "observable = SparsePauliOp.from_sparse_list(\n",
        "    target_obs_sparse, num_qubits=num_qubits\n",
        ")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 5,
      "id": "bb40bad8-cb18-4542-9e18-edf5fdfb5452",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Determine the measurement basis each qubit requires for this observable.\n",
        "# The reverser is used during post-processing in Step 4.\n",
        "bases_virt, reverser_virt = get_measurement_bases(observable)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 6,
      "id": "5b92fd4e-c559-4c56-8bf6-a6f2e397550c",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Free parameters of the demonstration circuit: the number of Trotter steps\n",
        "# sets the circuit depth, and rx_angle is the transverse-field rotation angle\n",
        "# applied in each step (pi/4 makes the dynamics non-Clifford)\n",
        "num_trotter_steps = 10\n",
        "rx_angle = np.pi / 4"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 7,
      "id": "cc2b001f-c006-45f1-9942-dcbdabefbec4",
      "metadata": {},
      "outputs": [],
      "source": [
        "def construct_ising_circuit(\n",
        "    num_qubits: int,\n",
        "    num_trotter_steps: int,\n",
        "    rx_angle: float,\n",
        ") -> QuantumCircuit:\n",
        "    circuit = QuantumCircuit(num_qubits)\n",
        "\n",
        "    for _ in range(num_trotter_steps):\n",
        "        circuit.rx(rx_angle, range(num_qubits))\n",
        "        for first_qubit in (1, 2):\n",
        "            for idx in range(first_qubit, num_qubits, 2):\n",
        "                # equivalent to Rzz(-pi/2):\n",
        "                circuit.sdg([idx - 1, idx])\n",
        "                circuit.cz(idx - 1, idx)\n",
        "\n",
        "    return circuit\n",
        "\n",
        "\n",
        "def prepare_basis(\n",
        "    circuit: QuantumCircuit, basis: list[int]\n",
        ") -> QuantumCircuit:\n",
        "    # basis is a list of integer values from 0 to 3. These map to the basis measurement as:\n",
        "    # 0 = I; 1 = Z; 2 = X; 3 = Y\n",
        "    assert len(basis) == circuit.num_qubits\n",
        "\n",
        "    out_circ = circuit.copy_empty_like()\n",
        "    for qb, bas in enumerate(basis):\n",
        "        if bas == 2:\n",
        "            out_circ.h(qb)\n",
        "        elif bas == 3:\n",
        "            out_circ.rx(-np.pi / 2, qb)\n",
        "\n",
        "    out_circ.barrier()\n",
        "    out_circ.compose(circuit, inplace=True)\n",
        "    return out_circ\n",
        "\n",
        "\n",
        "def mirror_circuit(circuit: QuantumCircuit) -> QuantumCircuit:\n",
        "    mirror_circ = circuit.copy_empty_like()\n",
        "    mirror_circ.compose(circuit.inverse(), inplace=True)\n",
        "    mirror_circ.barrier()\n",
        "    mirror_circ.compose(circuit, inplace=True)\n",
        "    mirror_circ.measure_active()\n",
        "    return mirror_circ"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 8,
      "id": "67553310-f1f0-4736-85ad-bfebd3c00414",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Instantiate the mirrored circuit and prepend the basis-preparation gates\n",
        "circuit = construct_ising_circuit(num_qubits, num_trotter_steps, rx_angle)\n",
        "mirrored_circuit = mirror_circuit(circuit)\n",
        "mirrored_circuit = prepare_basis(mirrored_circuit, bases_virt[0])"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 9,
      "id": "0dab284e-dbed-4d2f-92aa-c6c9c0e909a6",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/0dab284e-dbed-4d2f-92aa-c6c9c0e909a6-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 9,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "mirrored_circuit.draw(\n",
        "    \"mpl\", fold=-1, scale=0.3, idle_wires=False, measure_arrows=False\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "slc-step2-000-4000-8000-000000000013",
      "metadata": {},
      "source": [
        "<span id=\"step-2-optimize\" />\n",
        "\n",
        "### Fase 2: Ottimizzare\n",
        "\n",
        "Ottimizziamo i dettagli relativi al circuito da eseguire, alla grandezza osservabile da misurare e ai parametri di apprendimento del rumore. Come punto di partenza, selezioniamo il processore Heron meno carico e lo istanziamo con i gate frazionari attivati. Questi gate frazionari consentono una maggiore sensibilità in alcune delle nostre operazioni di filtraggio post-selezione.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 10,
      "id": "slc-creds-000-4000-8000-000000000014",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Selected backend: ibm_kingston\n"
          ]
        }
      ],
      "source": [
        "# Initialize IBM Quantum Compute Service using your saved credentials\n",
        "service = QiskitRuntimeService()\n",
        "\n",
        "# Select the least busy Heron device. This tutorial targets Heron processors:\n",
        "# the usage estimate and the non-Markovian-noise post-selection discussed in\n",
        "# Step 4 are specific to this processor family.\n",
        "backend = service.least_busy(\n",
        "    operational=True,\n",
        "    simulator=False,\n",
        "    filters=lambda b: b.processor_type[\"family\"] == \"Heron\",\n",
        ")\n",
        "# Re-fetch with fractional gates enabled (least_busy does not forward this)\n",
        "# Fractional gates are enabled so the non-Clifford Rx rotations are supported natively.\n",
        "backend = service.backend(backend.name, use_fractional_gates=True)\n",
        "print(f\"Selected backend: {backend.name}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "81dd2580-d4a8-45bb-bd05-707c64aa4ac7",
      "metadata": {},
      "source": [
        "Per prima cosa, traspileremo il nostro circuito in istruzioni ISA, [come richiesto per l'esecuzione sulle nostre QPU](/docs/guides/transpile#instruction-set-architecture). La scelta dei qubit fisici su cui eseguire le operazioni ha un forte impatto sulla qualità dei risultati, poiché i tempi di coerenza e le fedeltà dei gate variano all’interno di un dispositivo e subiscono una deriva nel tempo. Per un esperimento accurato, può rivelarsi utile selezionare manualmente una catena di qubit sulla base di rapidi esperimenti di benchmarking (si veda, ad esempio, il tutorial “[Benchmarking in tempo reale per la selezione dei qubit](/docs/tutorials/real-time-benchmarking-for-qubit-selection) ”). Ai fini di questo tutorial, ci affidiamo al transpiler di Qiskit per selezionare un layout basato sulla mappa di accoppiamento del backend e sui suoi dati di calibrazione più recenti.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 11,
      "id": "a7505de6-70ef-4996-9f6b-0025f267e920",
      "metadata": {
        "scrolled": true
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Selected layout: [71, 58, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 36, 21, 22, 23, 16, 3, 2]\n"
          ]
        }
      ],
      "source": [
        "isa_pm = generate_preset_pass_manager(backend=backend, optimization_level=1)\n",
        "\n",
        "isa_circuit = isa_pm.run(mirrored_circuit)\n",
        "\n",
        "# The chain of physical qubits selected by the transpiler\n",
        "layout = isa_circuit.layout.final_index_layout()\n",
        "print(f\"Selected layout: {layout}\")\n",
        "\n",
        "isa_observable = observable.apply_layout(\n",
        "    layout, num_qubits=isa_circuit.num_qubits\n",
        ")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 12,
      "id": "87f19e08-0cae-4959-9215-354ab099ab02",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/87f19e08-0cae-4959-9215-354ab099ab02-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 12,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "wire_order = layout + [\n",
        "    q for q in range(isa_circuit.num_qubits) if q not in layout\n",
        "]\n",
        "isa_circuit.draw(\n",
        "    \"mpl\",\n",
        "    fold=-1,\n",
        "    scale=0.3,\n",
        "    idle_wires=False,\n",
        "    wire_order=wire_order,\n",
        "    measure_arrows=False,\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "7e7135e7-1e86-4d16-b806-30d4a9225e88",
      "metadata": {},
      "source": [
        "<span id=\"box-the-circuit\" />\n",
        "\n",
        "### Inquadrare il circuito\n",
        "\n",
        "Per facilitare l'implementazione, utilizziamo il `generate_boxing_pass_manager` passaggio di transpilazione, che inserisce le istruzioni del circuito in riquadri annotati. Questi riquadri indicano chiaramente dove, nel caso del PEC, occorre inserire il segnale antirumore nel circuito. Per ulteriori dettagli sulle impostazioni, consultare la [documentazione](https://qiskit.github.io/samplomatic/) di Samplomatic.\n",
        "\n",
        "Si noti che il flusso di lavoro SLC richiede l'uso di `inject_noise_strategy=\"individual_modification\"` in una fase successiva del processo, poiché ciò ci consente di identificare in modo univoco le `InjectNoise` annotazioni delle `BoxOp` istruzioni presenti nel circuito. (Le caselle prive di `InjectNoise` annotazione non possono essere identificate in questo modo.)\n",
        "\n",
        "<Admonition type=\"caution\">\n",
        "  I calcoli relativi ai legami SLC attualmente presuppongono che le `InjectNoise` annotazioni siano posizionate con `site=\"after\"` (vedi [qiskit-addon-slc#33](https://github.com/Qiskit/qiskit-addon-slc/issues/33) ). Poiché il valore predefinito di questa impostazione è cambiato nelle versioni più recenti di `samplomatic` (vedi [samplomatic#372](https://github.com/Qiskit/samplomatic/pull/372) ), lo impostiamo `inject_noise_site=\"after\"` esplicitamente qui di seguito.\n",
        "</Admonition>\n",
        "\n",
        "La `find_unique_box_instructions` funzione esegue un'iterazione sul circuito incapsulato fornito e identifica quelli che presentano livelli o misurazioni a due qubit ( 2Q ) unici, ai fini dell'apprendimento del rumore e dell'iniezione di rumore.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 13,
      "id": "440f818c-9417-4855-b31b-db47b8595618",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Box circuit with Twirl and InjectNoise annotations\n",
        "boxes_pm = generate_boxing_pass_manager(\n",
        "    twirling_strategy=\"active\",\n",
        "    inject_noise_strategy=\"individual_modification\",\n",
        "    inject_noise_site=\"after\",\n",
        "    inject_noise_targets=\"gates\",\n",
        "    measure_annotations=\"all\",\n",
        ")\n",
        "\n",
        "\n",
        "boxed_circuit = boxes_pm.run(isa_circuit)\n",
        "\n",
        "# Find the unique instructions (layers) from boxed circuit\n",
        "unique_2q_instructions = find_unique_box_instructions(\n",
        "    boxed_circuit, normalize_annotations=None, undress_boxes=True\n",
        ")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 14,
      "id": "d662b076-005e-46c7-826d-4c29a4937404",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/d662b076-005e-46c7-826d-4c29a4937404-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 14,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "boxed_circuit.draw(\n",
        "    \"mpl\",\n",
        "    fold=-1,\n",
        "    scale=0.3,\n",
        "    idle_wires=False,\n",
        "    wire_order=wire_order,\n",
        "    measure_arrows=False,\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "aa8794ef-7906-4ee3-a9b0-e0399b3e9129",
      "metadata": {},
      "source": [
        "<span id=\"prepare-canonical-bases-measurements\" />\n",
        "\n",
        "### Preparare le misurazioni delle basi canoniche\n",
        "\n",
        "Si noti che occorre prestare particolare attenzione a tenere traccia dell'ordine dei qubit. Di seguito introduciamo il concetto di `canonical_qubits` come strumento per aggiornare correttamente l'ordine dei qubit al momento di trasmetterlo all'Executor, in base al modo in cui tale ordine viene acquisito durante il \"boxing\" dei circuiti e l'individuazione delle istruzioni univoche. Per ulteriori dettagli, consultare la documentazione [relativa alle convenzioni di denominazione dei qubit](https://qiskit.github.io/samplomatic/guides/samplex_io.html#qubit-ordering-convention).\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 15,
      "id": "e242702b-0274-4ebf-837d-4319a6772339",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Determine the canonical qubits order\n",
        "meas_box = boxed_circuit.data[-1]\n",
        "canonical_qubits = [\n",
        "    idx\n",
        "    for idx, qubit in enumerate(boxed_circuit.qubits)\n",
        "    if qubit in meas_box.qubits\n",
        "]\n",
        "\n",
        "# map canonical qubit to physical (isa) qubit\n",
        "c_2_p = {c: p for c, p in enumerate(canonical_qubits)}\n",
        "# map physical (isa) qubit to virtual qubit (index in original circuit)\n",
        "p_2_v = {p: v for v, p in enumerate(layout)}\n",
        "# compute map between virtual and canonical qubit indices.\n",
        "c_2_v = {c: p_2_v[p] for c, p in c_2_p.items()}\n",
        "\n",
        "assert len(c_2_v) == num_qubits\n",
        "\n",
        "bases_canon = [\n",
        "    np.array([base_i[c_2_v[c]] for c in range(num_qubits)], dtype=np.uint8)\n",
        "    for base_i in bases_virt\n",
        "]"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "61d2762c",
      "metadata": {},
      "source": [
        "<span id=\"workflow-for-lightcone-shading-noise-learning-and-anti-noise-injection\" />\n",
        "\n",
        "### Flusso di lavoro per l'ombreggiatura con cono di luce, l'apprendimento del rumore e l'iniezione anti-rumore\n",
        "\n",
        "<Admonition type=\"note\">\n",
        "  In questo tutorial, eseguiamo i calcoli relativi al limite SLC prima che l'apprendimento del rumore sia completato, in modo che il circuito da mitigare venga eseguito il più vicino possibile nel tempo al modello di rumore appreso. In linea di principio, questo flusso di lavoro può essere ulteriormente parallelizzato: un’operazione di apprendimento del rumore può essere eseguita mentre, in parallelo, vengono stimati i limiti del rumore. Per un circuito quantistico arbitrario, il calcolo soggetto al rumore può scalare con una dipendenza debolmente esponenziale; pertanto, la parallelizzazione di tale calcolo (ad esempio, su più core della CPU) consente di ottenere limiti più stretti per un dato budget di tempo di calcolo, e le esecuzioni della QPU e i calcoli soggetti al rumore possono a loro volta essere parallelizzati per ottenere un flusso di lavoro più efficiente.\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "4a2ec5ed-c339-4273-b407-6bb46b9ede19",
      "metadata": {},
      "source": [
        "<span id=\"predict-to-be-learned-noise-model-paulis\" />\n",
        "\n",
        "#### Previsione dei modelli di rumore Paulis da apprendere\n",
        "\n",
        "Prima di eseguire qualsiasi processo di apprendimento del rumore sull'hardware, elenchiamo innanzitutto quali termini di errore di Pauli saranno presenti nel nostro modello di rumore. Ricordiamo che modelliamo il rumore di ogni singolo strato come un modello di Pauli-Lindblad sparso, con un tasso di errore per ogni operatore di Pauli di peso uno ( $X$, $Y$ o $Z$ ) su ciascun qubit attivo, e uno per ogni operatore di Pauli di peso due su ciascuna coppia collegata di qubit attivi. La `generate_noise_model_paulis` funzione esamina ogni singolo strato racchiuso nel circuito fornito e genera esattamente questo elenco di termini di Pauli, tenendo conto della connettività dei qubit del circuito.\n",
        "\n",
        "In altre parole, questa fase prevede la *struttura* del modello di rumore che `NoiseLearnerV3` verrà successivamente popolato con i tassi appresi. Conoscere questa struttura in anticipo è ciò che ci permette di calcolare un limite superiore e uno inferiore per ogni potenziale errore prima ancora che il processo di apprendimento del rumore sia stato eseguito.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 16,
      "id": "e6783d37-c91f-4c6e-af4e-fb8f37d79e6b",
      "metadata": {},
      "outputs": [],
      "source": [
        "noise_model_paulis = generate_noise_model_paulis(\n",
        "    unique_2q_instructions, backend.coupling_map, boxed_circuit\n",
        ")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 17,
      "id": "f9fd1389-8c61-4585-85a6-ad17ab7a2f1c",
      "metadata": {},
      "outputs": [],
      "source": [
        "noise_model_rates = {ref: None for ref in noise_model_paulis}"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "942dd468-b2c5-49db-ada5-bcc45024df83",
      "metadata": {},
      "source": [
        "<span id=\"a-compute-forward-bounds\" />\n",
        "\n",
        "##### a. Calcolare i limiti a valle\n",
        "\n",
        "La `compute_forward_bounds` funzione valuta le relazioni di commutazione tra i gate di ciascun strato e i termini di Pauli generati in precedenza, in base al modo in cui gli errori di propagazione in avanti influenzano l'osservabile desiderato $A$. Per i gate che commutano con i termini di Pauli, non viene eseguita alcuna operazione. Per quanto riguarda le porte di Clifford, vengono posizionate all’inizio del circuito. Per le porte non Clifford, ne approssimiamo l’influenza sulle osservabili di destinazione, che verranno successivamente considerate prioritarie ai fini della cancellazione del rumore (dopo che tutti i limiti saranno stati unificati). Questo limite si ottiene applicando innanzitutto la norma di L2 (ovvero la radice quadrata della somma dei quadrati dei coefficienti dei termini di Pauli pertinenti). Quando i termini relativi ai qubit sono troppo numerosi, ricorriamo a un limite meno rigoroso che si basa sulla disuguaglianza triangolare.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "slc-bparam-00-4000-8000-000000000030",
      "metadata": {},
      "source": [
        "<span id=\"set-the-bound-computation-parameters\" />\n",
        "\n",
        "#### Impostare i parametri di calcolo dei limiti\n",
        "\n",
        "Le seguenti impostazioni regolano i calcoli dei limiti classici:\n",
        "\n",
        "* `slc_atol`: la tolleranza assoluta dei calcoli. I termini dell’operatore con coefficienti inferiori a tale soglia vengono considerati trascurabili e scartati.\n",
        "* `slc_eigval_max_qubits`: il numero massimo di qubit per i quali viene calcolato il limite più restrittivo, basato sugli autovalori ( L2-norm ). Gli operatori supportati su un numero di qubit superiore a questo ricorrono al limite più ampio della disuguaglianza triangolare descritto sopra.\n",
        "* `slc_evolution_max_terms`: il numero massimo di termini di Pauli conservati durante la propagazione degli operatori attraverso il circuito. Troncando i termini più piccoli si mantiene il calcolo gestibile, a costo di ottenere intervalli di valore leggermente meno precisi.\n",
        "* `slc_num_processes`: il numero di processi della CPU tra i quali viene parallelizzato il calcolo associato.\n",
        "* `slc_timeout`: il tempo a disposizione (in secondi) per il calcolo del bound. A tutti gli strati non raggiunti entro tale limite viene assegnato il valore banale pari a 2.\n",
        "\n",
        "<Admonition type=\"note\">\n",
        "  In questo tutorial, il timeout viene raggiunto *intenzionalmente* : lo manteniamo `slc_timeout` deliberatamente basso affinché il notebook funzioni rapidamente e per dimostrare che il raggiungimento del timeout è sicuro. I valori calcolati rappresentano i limiti superiori in ogni fase, quindi il raggiungimento del timeout non fa altro che allargare i limiti (in modo banale) per gli strati che non sono stati raggiunti; non sottostima mai l'effetto di un errore. Ciò risulta evidente nelle visualizzazioni riportate di seguito, dove i livelli più a sinistra sono interamente riempiti con il limite banale. Aumentando il timeout (o il numero di processi) si restringono i limiti e si riduce ulteriormente il sovraccarico di campionamento.\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 18,
      "id": "79fe4a21-b80d-496b-ba20-f16d9b11834d",
      "metadata": {},
      "outputs": [],
      "source": [
        "slc_atol = 1e-8\n",
        "slc_eigval_max_qubits = 18\n",
        "slc_evolution_max_terms = 1000\n",
        "slc_num_processes = 8\n",
        "slc_timeout = 60"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 19,
      "id": "705fcb9f-c753-45a6-97dd-9ff8ecb07018",
      "metadata": {
        "scrolled": true
      },
      "outputs": [
        {
          "name": "stderr",
          "output_type": "stream",
          "text": [
            "Bounds computation timed out.\n"
          ]
        }
      ],
      "source": [
        "forward_bounds = compute_forward_bounds(\n",
        "    boxed_circuit,\n",
        "    noise_model_paulis,\n",
        "    isa_observable,\n",
        "    evolution_max_terms=slc_evolution_max_terms,\n",
        "    eigval_max_qubits=slc_eigval_max_qubits,\n",
        "    atol=slc_atol,\n",
        "    num_processes=slc_num_processes,\n",
        "    timeout=slc_timeout,\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "aaf7c188-c85b-42cf-a096-0a5dc72a68e3",
      "metadata": {},
      "source": [
        "<span id=\"visualize-the-slc-for-manual-inspection\" />\n",
        "\n",
        "#### Visualizzare l'SLC per l'ispezione manuale\n",
        "\n",
        "È possibile interpretare il comportamento degli intervalli ombreggiati esaminando il modo in cui le misurazioni e i termini di Pauli interagiscono con gli errori locali. Questi modelli sono caratteristici di questo problema di evoluzione temporale dell’Hamiltoniano di Ising con “kick” e compaiono anche nell’articolo [“Lightcone Shading for Classically Accelerated Quantum Error Mitigation”](https://arxiv.org/abs/2409.04401), con diverse caratteristiche distintive:\n",
        "\n",
        "* Possiamo distinguere chiaramente i due coni derivanti dai due Pauli non identici presenti nell’osservabile.\n",
        "* Possiamo notare che la misura X sul qubit 6 commuta con l'errore X nello strato più a destra.\n",
        "* Possiamo notare che l'operatore Z di Pauli sul qubit 13 commuta con l'errore Z nello strato più a destra.\n",
        "* Quando si raggiunge il timeout specificato sopra, gli strati rimanenti a sinistra vengono riempiti interamente con valori banali pari a due.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 20,
      "id": "285edaef-8e79-41eb-a563-4675ea75d5b1",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/285edaef-8e79-41eb-a563-4675ea75d5b1-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/285edaef-8e79-41eb-a563-4675ea75d5b1-1.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/285edaef-8e79-41eb-a563-4675ea75d5b1-2.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "for p in \"XYZ\":\n",
        "    display(\n",
        "        draw_shaded_lightcone(\n",
        "            boxed_circuit,\n",
        "            forward_bounds,\n",
        "            noise_model_paulis,\n",
        "            pauli_filter=p,\n",
        "            scale=0.15,\n",
        "            fold=-1,\n",
        "            idle_wires=False,\n",
        "            wire_order=wire_order,\n",
        "            measure_arrows=False,\n",
        "        )\n",
        "    )"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "1aee6b29-8077-4019-9e37-3e80292aadf6",
      "metadata": {},
      "source": [
        "<span id=\"b-tighten-forward-bounds-with-quantum-speed-limits\" />\n",
        "\n",
        "#### b. Restringere i limiti superiori con i limiti di velocità quantistici\n",
        "\n",
        "Successivamente restringiamo gli intervalli utilizzando la `tighten_with_speed_limit` funzione. La diffusione geometrica del cono di luce dell’osservabile è già stata pienamente presa in considerazione nei limiti in avanti calcolati in precedenza. Ciò che questa funzione aggiunge è un ciclo di auto-coerenza che sfrutta la velocità limitata con cui le informazioni possono fluire attraverso il circuito — il cono di luce cresce al massimo di un qubit per ogni strato di porte a due qubit — per restringere i limiti calcolati in precedenza l'uno rispetto all'altro. Le informazioni circolano in entrambe le direzioni:\n",
        "\n",
        "1. Dai limiti del commutatore ai limiti dell'osservabile retropropagato: se l'osservabile commuta con un errore di tipo \" $Z$ \" su un qubit, allora non può avere una componente significativa di tipo \" $X$ \" o \" $Y$ \" in quel punto.\n",
        "2. Dai limiti dell’osservabile retropropagato ai limiti del commutatore: se l’osservabile presenta una componente $X$ e una $Y$ minime su un qubit, allora in quel punto deve quasi commutare con $Z$.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 21,
      "id": "e060d547-79da-4f54-bf5e-677f70306040",
      "metadata": {
        "scrolled": true
      },
      "outputs": [],
      "source": [
        "forward_bounds_tighter = tighten_with_speed_limit(\n",
        "    forward_bounds, boxed_circuit, noise_model_paulis, isa_observable\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "85f3c9c2-d60c-4044-a496-dae88c3dd749",
      "metadata": {},
      "source": [
        "<span id=\"visualize-the-slc-for-manual-inspection\" />\n",
        "\n",
        "#### Visualizzare l'SLC per l'ispezione manuale\n",
        "\n",
        "In linea di principio, questa restrizione garantisce una transizione più graduale tra i limiti calcolati e i limiti banali assegnati agli strati oltre il timeout. In questo esempio l'effetto è appena percettibile, poiché i coni di luce si sono già diffusi fino ai bordi del circuito nel momento in cui viene raggiunto il timeout.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 22,
      "id": "6ba4b397-e903-4b85-9482-31a96c689dc0",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/6ba4b397-e903-4b85-9482-31a96c689dc0-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/6ba4b397-e903-4b85-9482-31a96c689dc0-1.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/6ba4b397-e903-4b85-9482-31a96c689dc0-2.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "for p in \"XYZ\":\n",
        "    display(\n",
        "        draw_shaded_lightcone(\n",
        "            boxed_circuit,\n",
        "            forward_bounds_tighter,\n",
        "            noise_model_paulis,\n",
        "            pauli_filter=p,\n",
        "            scale=0.15,\n",
        "            fold=-1,\n",
        "            idle_wires=False,\n",
        "            wire_order=wire_order,\n",
        "            measure_arrows=False,\n",
        "        )\n",
        "    )"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ce0f9cf4-565a-4f18-baa5-bec5c7b354a2",
      "metadata": {},
      "source": [
        "<span id=\"c-compute-backward-bounds\" />\n",
        "\n",
        "#### c. Calcolare i limiti a ritroso\n",
        "\n",
        "Questa parte della previsione del rumore valuta in che modo un errore in un determinato strato possa influenzare lo stato di ingresso $\\rho$. La `compute_backward_bounds` funzione, innanzitutto, inverte il circuito e rimuove i gate di misurazione, per poi procedere con un’analisi simile a quella effettuata per i calcoli in avanti.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 23,
      "id": "ff895191-feda-4958-a58a-3d1fb973cb68",
      "metadata": {
        "scrolled": true
      },
      "outputs": [],
      "source": [
        "backward_bounds = compute_backward_bounds(\n",
        "    boxed_circuit,\n",
        "    noise_model_paulis,\n",
        "    evolution_max_terms=slc_evolution_max_terms,\n",
        "    num_processes=slc_num_processes,\n",
        "    timeout=slc_timeout,\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c707d352-37fe-4c53-82a3-a1207b7a7bfc",
      "metadata": {},
      "source": [
        "<span id=\"visualize-the-slc-for-manual-inspection\" />\n",
        "\n",
        "#### Visualizzare l'SLC per l'ispezione manuale\n",
        "\n",
        "Dal calcolo dei limiti a ritroso, possiamo osservare come la struttura dello stato iniziale determini il comportamento iniziale della propagazione dell'errore:\n",
        "\n",
        "* Possiamo vedere chiaramente come gli errori Z inizialmente commutino con lo stato iniziale |0⟩.\n",
        "* Solo sul qubit 6, dove inizializziamo lo stato proprio +1 della base X, un errore Z non commuta, mentre un errore X commuta.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 24,
      "id": "40d0eba9-5e0b-4b1d-8fb2-c239e262062f",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/40d0eba9-5e0b-4b1d-8fb2-c239e262062f-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/40d0eba9-5e0b-4b1d-8fb2-c239e262062f-1.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/40d0eba9-5e0b-4b1d-8fb2-c239e262062f-2.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "for p in \"XYZ\":\n",
        "    display(\n",
        "        draw_shaded_lightcone(\n",
        "            boxed_circuit,\n",
        "            backward_bounds,\n",
        "            noise_model_paulis,\n",
        "            pauli_filter=p,\n",
        "            scale=0.15,\n",
        "            fold=-1,\n",
        "            idle_wires=False,\n",
        "            wire_order=wire_order,\n",
        "            measure_arrows=False,\n",
        "        )\n",
        "    )"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c8960e51-733e-4458-b77c-287ba3abdb8d",
      "metadata": {},
      "source": [
        "<span id=\"preview-merged-bounds-without-learned-noise-rates\" />\n",
        "\n",
        "#### Anteprima dei confini unificati senza tassi di rumore appresi\n",
        "\n",
        "La `merged_bounds` funzione determina il punto del circuito in cui il passaggio dai limiti posteriori a quelli anteriori riduce al minimo la distorsione totale stimata sull'osservabile desiderato. Questo bias viene calcolato come somma dei contributi in senso retrogrado per tutte le posizioni di rumore precedenti a quel punto, più i contributi in senso progressivo per tutte le posizioni di rumore successive ad esso. Attualmente, questa operazione viene eseguita in modo uniforme per tutti i qubit.\n",
        "\n",
        "<Admonition type=\"note\" title=\"Nota importante\">\n",
        "  Il punto ottimale in cui passare dal limite posteriore a quello anteriore dipende dai tassi di rumore appresi. A questo punto non abbiamo ancora determinato alcuna frequenza: `noise_model_rates` per ogni strato è stato inizializzato a `None` , che `merge_bounds` viene considerato come una frequenza di rumore uniforme. I limiti combinati qui riportati sono quindi solo un'anteprima; li ricalcoleremo con i tassi effettivi appresi nella Fase 3.\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 25,
      "id": "b5cd8752-3bb5-44ee-97a8-6edaf5946e9d",
      "metadata": {},
      "outputs": [
        {
          "name": "stderr",
          "output_type": "stream",
          "text": [
            "Missing noise rates. Partitioning backward/forward commutator bounds by assuming uniform error rates.\n",
            "Optimal spacetime partitioning not implemented!Just partitioning list of noisy boxes.\n"
          ]
        }
      ],
      "source": [
        "merged_bounds = merge_bounds(\n",
        "    boxed_circuit,\n",
        "    forward_bounds_tighter,\n",
        "    backward_bounds,\n",
        "    noise_model_rates,\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ace27801-b949-44e0-801d-e74ccb2f877a",
      "metadata": {},
      "source": [
        "<span id=\"visualize-the-slc-for-manual-inspection\" />\n",
        "\n",
        "### Visualizzare l'SLC per l'ispezione manuale\n",
        "\n",
        "Dopo aver unito i limiti posteriori e quelli anteriori ristretti, il comportamento degli SLC combinati risulta chiaro:\n",
        "\n",
        "* La funzione sopra riportata ci indica che viene scelta una partizione in cui avviene il passaggio dai limiti posteriori a quelli anteriori più restrittivi.\n",
        "* Come si può vedere di seguito, gli SLC contengono ora limiti superiori parziali e limiti inferiori parziali.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 26,
      "id": "eb2948bc-fa27-4108-b03c-0291d3b2f7c8",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/eb2948bc-fa27-4108-b03c-0291d3b2f7c8-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/eb2948bc-fa27-4108-b03c-0291d3b2f7c8-1.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/eb2948bc-fa27-4108-b03c-0291d3b2f7c8-2.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "for p in \"XYZ\":\n",
        "    display(\n",
        "        draw_shaded_lightcone(\n",
        "            boxed_circuit,\n",
        "            merged_bounds,\n",
        "            noise_model_paulis,\n",
        "            pauli_filter=p,\n",
        "            scale=0.15,\n",
        "            fold=-1,\n",
        "            idle_wires=False,\n",
        "            wire_order=wire_order,\n",
        "            measure_arrows=False,\n",
        "        )\n",
        "    )"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "slc-step3-000-4000-8000-000000000050",
      "metadata": {},
      "source": [
        "<span id=\"step-3-execute\" />\n",
        "\n",
        "### Fase 3: Esecuzione\n",
        "\n",
        "In questa sezione iniziamo la parte del flusso di lavoro che prevede l'utilizzo di un dispositivo quantistico reale. Questo metodo di mitigazione degli errori basato sull'apprendimento prevede due fasi:\n",
        "\n",
        "1. Impara a riconoscere il rumore utilizzando `NoiseLearnerV3`.\n",
        "2. Eseguire un circuito di mitigazione degli errori utilizzando i `samplomatic` framework e `Executor` .\n",
        "\n",
        "Grazie agli errori limitati del nostro circuito quantistico, riusciamo a determinare i tassi di rumore associati per stabilire le priorità nel nostro budget di errore, calcolare il sovraccarico di campionamento ed eseguire il codice su una QPU.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "slc-nlmd-0000-4000-8000-000000000051",
      "metadata": {},
      "source": [
        "<span id=\"a-learn-the-noise-rates\" />\n",
        "\n",
        "### a. Scopri i livelli di rumore\n",
        "\n",
        "Il modello di apprendimento del rumore caratterizza i processi di rumore che influenzano i gate in uno o più circuiti di interesse, sulla base del [modello di rumore di Pauli-Lindblad](https://arxiv.org/abs/2201.09866) a densità ridotta. Il `run()` metodo avvia un processo di apprendimento al rumore per gli strati a due qubit univoci forniti, utilizzando le opzioni specificate nella configurazione del noise-learner. Queste opzioni controllano la strategia di rotazione di Pauli, il numero di randomizzazioni e di tentativi, le profondità di apprendimento e la post-selezione.\n",
        "\n",
        "Scegliamo inoltre con attenzione il livello di approfondimento dell'apprendimento. Un risultato pratico emerso nell’ambito della mitigazione basata sull’apprendimento `samplomatic` è che risulta estremamente vantaggioso che la profondità di apprendimento massima corrisponda alla profondità del circuito che si desidera mitigare. Poiché le funzioni di NLv3`layer_pair_depths` vengono misurate in *coppie* di livelli (un livello più il suo inverso), impostiamo il valore più profondo a metà della profondità del livello a due qubit del circuito.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 27,
      "id": "slc-psflag-00-4000-8000-000000000052",
      "metadata": {},
      "outputs": [],
      "source": [
        "post_selection_enabled = True"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 28,
      "id": "slc-nlopt-00-4000-8000-000000000053",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Match the deepest noise-learning depth to the depth of the circuit being\n",
        "# mitigated. NLv3 ``layer_pair_depths`` are measured in layer pairs (a layer\n",
        "# plus its inverse), so the deepest value is half the circuit's two-qubit-layer\n",
        "# depth. Learning to this depth markedly improves the quality of the mitigation.\n",
        "#\n",
        "# We measure the two-qubit-layer depth on the pre-boxed ISA circuit: after\n",
        "# boxing, every two-qubit gate is hidden inside a full-width ``BoxOp``, so a\n",
        "# ``num_qubits == 2`` filter on ``boxed_circuit`` matches nothing (and\n",
        "# ``QuantumCircuit.depth`` does not recurse into boxes).\n",
        "depth_2q = isa_circuit.depth(lambda instr: instr.operation.num_qubits == 2)\n",
        "max_layer_pair_depth = depth_2q // 2  # dividing by 2 since we want pairs\n",
        "\n",
        "# Use a fixed schedule of learning depths, but drop any that exceed the circuit's\n",
        "# depth and always cap the deepest value at ``max_layer_pair_depth`` so we never\n",
        "# learn deeper than the circuit being mitigated.\n",
        "candidate_depths = [1, 2, 4, 8, 12, 16, 24, 32, 40, 48]\n",
        "layer_pair_depths = sorted(\n",
        "    {d for d in candidate_depths if d < max_layer_pair_depth}\n",
        "    | {max_layer_pair_depth}\n",
        ")\n",
        "\n",
        "noise_learner_options = {\n",
        "    \"num_randomizations\": 64,\n",
        "    \"shots_per_randomization\": 128,\n",
        "    \"layer_pair_depths\": layer_pair_depths,\n",
        "    \"post_selection\": {\n",
        "        \"enable\": post_selection_enabled,\n",
        "        \"strategy\": \"edge\",\n",
        "        \"x_pulse_type\": \"rx\",\n",
        "    },\n",
        "    \"environment\": {\"job_tags\": [\"TUT_SLC\"]},\n",
        "}\n",
        "\n",
        "noise_learner = NoiseLearnerV3(backend, noise_learner_options)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 29,
      "id": "slc-nlrun-00-4000-8000-000000000054",
      "metadata": {},
      "outputs": [],
      "source": [
        "noise_learner_job = noise_learner.run(unique_2q_instructions)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 30,
      "id": "db624045-7abe-41f1-a60c-1ba141aaa6f6",
      "metadata": {},
      "outputs": [],
      "source": [
        "noise_learner_result = noise_learner_job.result()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 31,
      "id": "b08c37a6-5acb-41e9-a76c-abc0195d87f7",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Minimum fraction of shots kept for noise learning experiments: 0.71\n"
          ]
        }
      ],
      "source": [
        "if post_selection_enabled:\n",
        "    print(\n",
        "        \"Minimum fraction of shots kept for noise learning experiments: \",\n",
        "        end=\"\",\n",
        "    )\n",
        "    print(\n",
        "        f\"{min([min(d.values()) for d in [nlr.metadata['post_selection']['fraction_kept'] for nlr in noise_learner_result[:2]]]):.2f}\"\n",
        "    )"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 32,
      "id": "99144d7f-b891-4115-8cb3-ecdbedd10a73",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Get a dict mapping each InjectNoise.ref to its learned PauliLindbladMap\n",
        "refs_2_plm = noise_learner_result.to_dict(\n",
        "    unique_2q_instructions, require_refs=False\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "e6a790a3-bc11-46e2-8cb4-2e43257926c8",
      "metadata": {},
      "source": [
        "<span id=\"bi-update-merged-bounds-with-actual-learned-noise-rates\" />\n",
        "\n",
        "### b.i. Aggiornare i limiti combinati con i tassi di rumore effettivi appresi\n",
        "\n",
        "Ora che il modello di rumore specifico è stato appreso, possiamo applicare i tassi di rumore appresi ai limiti di rumore previsti e ottenere una valutazione definitiva di quali limiti abbiano il maggiore impatto sulla minimizzazione della distorsione.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 33,
      "id": "bba8fca6-5339-4d4d-be01-45ca8bf94a5a",
      "metadata": {},
      "outputs": [
        {
          "name": "stderr",
          "output_type": "stream",
          "text": [
            "Optimal spacetime partitioning not implemented!Just partitioning list of noisy boxes.\n"
          ]
        }
      ],
      "source": [
        "merged_bounds = merge_bounds(\n",
        "    boxed_circuit,\n",
        "    forward_bounds_tighter,\n",
        "    backward_bounds,\n",
        "    refs_2_plm,\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "649b9640-e89f-4021-9f7d-f1e136c4eb6d",
      "metadata": {},
      "source": [
        "<span id=\"bii-compute-the-local_scales-for-the-hardware-execution\" />\n",
        "\n",
        "#### b.ii. Calcolare il `local_scales` per l'esecuzione hardware\n",
        "\n",
        "`compute_local_scales` esamina ogni possibile errore dovuto al rumore nel circuito e valuta in che misura tale errore potrebbe influenzare la misurazione finale, nonché quanto sarebbe costoso correggerlo. Successivamente, classifica gli errori in base alla loro rilevanza ai fini della mitigazione e seleziona il sottoinsieme che riduce il più possibile la distorsione, pur rimanendo entro il budget consentito per i costi di campionamento (o raggiungendo l'accuratezza desiderata). Il risultato è un insieme di fattori di scala che indicano quali errori saranno attivamente mitigati e quali invece non lo saranno (`local_scales`), insieme al sovraccarico previsto del costo totale di campionamento (`sampling_costs`) e alla distorsione residua (`residual_bias_bound`).\n",
        "\n",
        "La possibilità di controllare il bias residuo desiderato è una caratteristica fondamentale dell'implementazione SLC del PEC. Mentre [nell'implementazione](https://arxiv.org/abs/2201.09866) originale il sovraccarico di campionamento era sempre orientato a un bias pari a zero, possiamo ora regolare il sovraccarico di campionamento richiesto, accettando un compromesso in termini di bias residuo atteso. Ciò consente all'utente di rimanere entro un budget di campionamento prestabilito, il che può rivelarsi particolarmente utile nella fase iniziale di prototipazione di un flusso di lavoro.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 34,
      "id": "8914e00e-ba21-41dc-9e64-b942bb23fde1",
      "metadata": {},
      "outputs": [],
      "source": [
        "id_map = map_modifier_ref_to_ref(boxed_circuit)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 35,
      "id": "4c971d31-f2b4-4d17-8a83-4f6d14f81f6f",
      "metadata": {
        "scrolled": true
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Full PEC gamma=12.750876317157042, sampling cost (gamma^2) = 162.58484685543633\n"
          ]
        }
      ],
      "source": [
        "summed_rates = 0.0\n",
        "for box_id, noise_id in id_map.items():\n",
        "    learned_plm = refs_2_plm[noise_id]\n",
        "    summed_rates += np.sum(learned_plm.rates)\n",
        "    # print(f\"{box_id}:\\tgamma = {np.exp(2 * summed_rates):1.6e}\\tsampling cost = {np.exp(4 * summed_rates):1.6e}\")\n",
        "total_gamma = np.exp(2 * summed_rates)\n",
        "print(\n",
        "    f\"Full PEC gamma={total_gamma}, sampling cost (gamma^2) = {total_gamma**2}\"\n",
        ")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 36,
      "id": "fb6f2aa0-8a35-420c-aae3-6fc5e9aa8747",
      "metadata": {},
      "outputs": [],
      "source": [
        "biases = []\n",
        "costs = []\n",
        "for bias in [0.0] + np.arange(0.001, 0.102, 0.01).tolist():\n",
        "    _, cost_, bias_ = compute_local_scales(\n",
        "        boxed_circuit,\n",
        "        merged_bounds,\n",
        "        refs_2_plm,\n",
        "        sampling_cost_budget=np.inf,\n",
        "        bias_tolerance=bias,\n",
        "    )\n",
        "    biases.append(bias_)\n",
        "    costs.append(cost_)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "slc-trade-00-4000-8000-000000000065",
      "metadata": {},
      "source": [
        "<span id=\"trade-off-sampling-overhead-against-residual-bias\" />\n",
        "\n",
        "#### Trovare un compromesso tra il sovraccarico di campionamento e la distorsione residua\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 37,
      "id": "slc-ovplot-0-4000-8000-000000000066",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "Text(0.5, 0.98, 'PEC sampling overhead reduction due to SLC')"
            ]
          },
          "execution_count": 37,
          "metadata": {},
          "output_type": "execute_result"
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/slc-ovplot-0-4000-8000-000000000066-1.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "xticks = np.arange(0, 11)\n",
        "\n",
        "fig, ax = plt.subplots()\n",
        "ax.scatter(\n",
        "    [0], [total_gamma**2], marker=\"D\", c=\"tab:orange\", label=\"full PEC\"\n",
        ")\n",
        "ax.plot(\n",
        "    100 * np.array(biases),\n",
        "    np.array(costs),\n",
        "    \"o-\",\n",
        "    c=\"tab:blue\",\n",
        "    label=\"PEC+SLC\",\n",
        ")\n",
        "ax.set_yscale(\"log\")\n",
        "ax.set_xticks(xticks, [f\"{x:.1f}\" for x in xticks])\n",
        "\n",
        "ax.set_xlabel(\"Remaining bias [%]\")\n",
        "ax.set_ylabel(r\"Sampling overhead, $\\gamma^2$\")\n",
        "ax.grid()\n",
        "ax.legend()\n",
        "fig.suptitle(\"PEC sampling overhead reduction due to SLC\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 38,
      "id": "1df482f9-c12c-4ea7-a71e-7ffc0837b5a6",
      "metadata": {},
      "outputs": [],
      "source": [
        "chosen_bias_thres = 0.1"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 39,
      "id": "slc-lscale-0-4000-8000-000000000068",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "PEC+SLC sampling cost (gamma^2) = 48.13646509622307 w/ remaining bias = 10.0%\n"
          ]
        }
      ],
      "source": [
        "local_scales, sampling_cost, residual_bias_bound = compute_local_scales(\n",
        "    boxed_circuit,\n",
        "    merged_bounds,\n",
        "    refs_2_plm,\n",
        "    sampling_cost_budget=np.inf,\n",
        "    bias_tolerance=chosen_bias_thres,\n",
        ")\n",
        "print(\n",
        "    f\"PEC+SLC sampling cost (gamma^2) = {sampling_cost} \"\n",
        "    f\"w/ remaining bias = {100 * residual_bias_bound:.1f}%\"\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "8b03c0ed-d026-4b29-bc09-68c4d0c94889",
      "metadata": {},
      "source": [
        "<span id=\"c-execute-the-circuit-of-interest-with-antinoise\" />\n",
        "\n",
        "### c. Esegui il circuito di interesse con antinoise\n",
        "\n",
        "<span id=\"ci-prepare-template-circuit-by-using-samplex\" />\n",
        "\n",
        "#### c.i. Preparare il circuito modello utilizzando `samplex`\n",
        "\n",
        "Il `samplex` è un output del `build` metodo di Samplomatic, che codifica tutte le informazioni necessarie per generare parametri casuali per `template_circuit`. Questi vengono poi utilizzati per configurare gli `QuantumProgram` oggetti, che a loro volta vengono eseguiti su una QPU con la `Executor` primitiva. Ciascuno di essi `QuantumProgram` può contenere diversi elementi, che si possono considerare come una coppia di `template` e `samplex`.\n",
        "\n",
        "Per ulteriori dettagli, consulta il tutorial “[Hello samplomatic](https://github.com/qiskit-community/qdc-challenges-2025/blob/main/day3_tutorials/Track_A/hello_samplomatic/Samplomatic%20-%20Hello%20World.ipynb) ”.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 40,
      "id": "a68109cc-3b38-40e0-b145-edaa06b9934d",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Build template circuit and samplex for later use with the \"Executor\"\n",
        "template_circuit, samplex = samplomatic.build(boxed_circuit)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 41,
      "id": "0de4922b-b958-48d7-b63a-819edfb354bd",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Set up postselection if it's been enabled\n",
        "if post_selection_enabled:\n",
        "    # Set up post selection PM (to add PS instructions)\n",
        "    post_selection_pm = PassManager(\n",
        "        [\n",
        "            AddSpectatorMeasures(backend.coupling_map),\n",
        "            AddPostSelectionMeasures(x_pulse_type=\"rx\"),\n",
        "        ]\n",
        "    )\n",
        "    final_template_circuit = post_selection_pm.run(template_circuit)\n",
        "else:\n",
        "    final_template_circuit = template_circuit"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "4f00c61a-4c4d-40d9-9a41-11ea8e9a9337",
      "metadata": {},
      "source": [
        "<span id=\"cii-set-up-the-quantumprogram\" />\n",
        "\n",
        "#### c.ii. Configurare il `QuantumProgram`\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 42,
      "id": "a63d8881-2bbb-4c99-afd3-43f03ee04e5c",
      "metadata": {},
      "outputs": [],
      "source": [
        "num_randomizations = 4096\n",
        "shots_per_randomization = 64\n",
        "chunk_size = 256"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 43,
      "id": "038a04c9-20ba-432c-bfec-cf69e67ac82f",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Set up QuantumProgram\n",
        "program = QuantumProgram(shots=shots_per_randomization, noise_maps=refs_2_plm)\n",
        "\n",
        "# no EM\n",
        "\n",
        "# Collect up a dict of the other arguments that need to be bound to samplex_inputs\n",
        "samplex_inputs = {\n",
        "    f\"noise_scales.{ref}\": float(0) for ref in local_scales.keys()\n",
        "}\n",
        "samplex_inputs |= {\"basis_changes\": {\"basis0\": bases_canon[0]}}\n",
        "\n",
        "# Convert samplex_inputs into a dict to pass to QuantumProgram\n",
        "samplex_arguments = (\n",
        "    samplex.inputs().bind(**samplex_inputs).make_broadcastable()\n",
        ")\n",
        "\n",
        "program.append_samplex_item(\n",
        "    circuit=final_template_circuit,\n",
        "    samplex=samplex,\n",
        "    samplex_arguments=samplex_arguments,\n",
        "    shape=(num_randomizations,),\n",
        "    chunk_size=chunk_size,\n",
        ")\n",
        "\n",
        "# plain PEC\n",
        "\n",
        "# Collect a dict of the other arguments that need to be bound to samplex_inputs\n",
        "samplex_inputs = {\n",
        "    f\"noise_scales.{ref}\": float(-1) for ref in local_scales.keys()\n",
        "}\n",
        "samplex_inputs |= {\"basis_changes\": {\"basis0\": bases_canon[0]}}\n",
        "\n",
        "# Convert samplex_inputs into a dict to pass to QuantumProgram\n",
        "samplex_arguments = (\n",
        "    samplex.inputs().bind(**samplex_inputs).make_broadcastable()\n",
        ")\n",
        "\n",
        "program.append_samplex_item(\n",
        "    circuit=final_template_circuit,\n",
        "    samplex=samplex,\n",
        "    samplex_arguments=samplex_arguments,\n",
        "    shape=(num_randomizations,),\n",
        "    chunk_size=chunk_size,\n",
        ")\n",
        "\n",
        "# PEC+SLC\n",
        "\n",
        "# Collect a dict of the other arguments that need to be bound to samplex_inputs\n",
        "samplex_inputs = {\n",
        "    f\"noise_scales.{ref}\": float(-1) for ref in local_scales.keys()\n",
        "}\n",
        "samplex_inputs |= {\"basis_changes\": {\"basis0\": bases_canon[0]}}\n",
        "samplex_inputs |= {\"local_scales\": local_scales}\n",
        "\n",
        "# Convert samplex_inputs into a dict to pass to QuantumProgram\n",
        "samplex_arguments = (\n",
        "    samplex.inputs().bind(**samplex_inputs).make_broadcastable()\n",
        ")\n",
        "\n",
        "program.append_samplex_item(\n",
        "    circuit=final_template_circuit,\n",
        "    samplex=samplex,\n",
        "    samplex_arguments=samplex_arguments,\n",
        "    shape=(num_randomizations,),\n",
        "    chunk_size=chunk_size,\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "3100fff6-9fe9-49ad-9662-549cea099e9b",
      "metadata": {},
      "source": [
        "<span id=\"ciii-execute-program-with-the-executor-primitive\" />\n",
        "\n",
        "#### c.iii. Eseguire il programma con la `Executor` primitiva\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 44,
      "id": "2a1c0fe1-8761-462b-a1d9-b951d5224164",
      "metadata": {},
      "outputs": [],
      "source": [
        "executor = Executor(backend)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 45,
      "id": "slc-exec-000-4000-8000-000000000078",
      "metadata": {},
      "outputs": [],
      "source": [
        "job_exec = executor.run(program)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 46,
      "id": "70dd5d10-9cc5-4341-9642-18ba3a0ee58b",
      "metadata": {},
      "outputs": [],
      "source": [
        "results_exec = job_exec.result()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "slc-step4-000-4000-8000-000000000080",
      "metadata": {},
      "source": [
        "<span id=\"step-4-post-process\" />\n",
        "\n",
        "### Fase 4: Post-elaborazione\n",
        "\n",
        "Nel calcolare il valore atteso finale di interesse utilizzando `executor_expectation_values`, implementiamo alcune tecniche di post-elaborazione per garantire di ottenere risultati della massima qualità possibile. In primo luogo, applichiamo il nostro [metodo TREX (Twirled Readout Error Extinction)](/docs/guides/error-mitigation-and-suppression-techniques#twirled-readout-error-extinction-trex), che tiene conto di eventuali errori che si verificano durante il processo di lettura. Successivamente, correggiamo gli errori dovuti al rumore non markoviano nei nostri backend Heron utilizzando un metodo di post-selezione. Questo metodo misura i qubit attivi e quelli spettatori, quindi applica una rotazione lenta a ciascun qubit e infine effettua una nuova misurazione. `PostSelector`Nei casi in cui le due misurazioni non confermino, come previsto, la presenza di un qubit capovolto, tali misurazioni vengono scartate applicando un `mask` dal. Nell'ambito del calcolo della maschera, è possibile impostare una strategia specifica per filtrare in base ai nodi a singolo qubit o agli spigoli spettatori adiacenti, il che può influire sia sul numero di shot filtrati sia sulla qualità dei risultati.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 47,
      "id": "147bcd34-f79e-4abb-821d-5ea9d8388c9f",
      "metadata": {},
      "outputs": [],
      "source": [
        "measurement_noise_map = noise_learner_result[2].to_pauli_lindblad_map()\n",
        "trex_scale_factors = trex_factors(measurement_noise_map, reverser_virt)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 48,
      "id": "981353e7-95a9-48bb-92c8-87b931e2b295",
      "metadata": {},
      "outputs": [],
      "source": [
        "post_selection_strategy = \"node\""
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 49,
      "id": "826afe02-3308-4d20-a9e1-ac0b2ba25eca",
      "metadata": {},
      "outputs": [],
      "source": [
        "def post_process_conv(datum, steps=16, gamma=None, ps=False, trex=False):\n",
        "    meas = datum[\"meas\"]\n",
        "    flips = datum[\"measurement_flips.meas\"]\n",
        "    signs = datum.get(\"pauli_signs\", None)\n",
        "\n",
        "    meas_basis_axis = None\n",
        "    avg_axis = 0\n",
        "\n",
        "    mask = None\n",
        "    if ps and post_selection_enabled:\n",
        "        # Post-select the results\n",
        "        post_selector = PostSelector.from_circuit(\n",
        "            circuit=final_template_circuit, coupling_map=backend.coupling_map\n",
        "        )\n",
        "\n",
        "        # Compute the ps mask for filtering results\n",
        "        mask = post_selector.compute_mask(\n",
        "            datum, strategy=post_selection_strategy\n",
        "        )\n",
        "\n",
        "        # Compute fraction of shots kept from post selection\n",
        "        total_num_shots = num_randomizations * shots_per_randomization\n",
        "        ps_ratio = np.sum(mask) * 100 / total_num_shots / len(bases_canon)\n",
        "        print(\n",
        "            f\"With {post_selection_strategy}-based post selection ({ps_ratio:.1f}% of shots kept):\"\n",
        "        )\n",
        "\n",
        "    results = []\n",
        "    for i in range(steps, num_randomizations + 1, steps):\n",
        "        # Compute mitigated expvals w/out post-selection\n",
        "        res = executor_expectation_values(\n",
        "            meas[:i],\n",
        "            reverser_virt,\n",
        "            meas_basis_axis,\n",
        "            avg_axis=avg_axis,\n",
        "            measurement_flips=flips[:i],\n",
        "            pauli_signs=signs[:i] if signs is not None else None,\n",
        "            postselect_mask=mask[:i] if mask is not None else None,\n",
        "            rescale_factors=trex_scale_factors if trex else None,\n",
        "            gamma_factor=gamma,\n",
        "        )\n",
        "        results.append(res[0])\n",
        "    return results"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 50,
      "id": "57b68d19-cda9-4688-b07e-0c7f9da5f252",
      "metadata": {},
      "outputs": [],
      "source": [
        "gamma_pec = gamma_from_noisy_boxes(refs_2_plm, id_map)\n",
        "gamma_slc = gamma_from_noisy_boxes(refs_2_plm, id_map, local_scales)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 51,
      "id": "b5244ee6-98b2-43da-8238-3d6fb0f4cdd0",
      "metadata": {},
      "outputs": [],
      "source": [
        "steps = 16"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 52,
      "id": "95bedd10-5ce9-4145-9b20-1459aa8e7db9",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "With node-based post selection (24.1% of shots kept):\n",
            "With node-based post selection (24.2% of shots kept):\n"
          ]
        }
      ],
      "source": [
        "results = {}\n",
        "\n",
        "for label, result_idx, gamma, use_ps, use_trex in [\n",
        "    (\"PEC\", 1, gamma_pec, True, True),\n",
        "    (\"PEC+SLC\", 2, gamma_slc, True, True),\n",
        "    (\"Unmitigated\", 0, None, False, False),\n",
        "]:\n",
        "    res = post_process_conv(\n",
        "        results_exec[result_idx],\n",
        "        steps=steps,\n",
        "        gamma=gamma,\n",
        "        ps=use_ps,\n",
        "        trex=use_trex,\n",
        "    )\n",
        "    results[label] = res"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "1d778145-9ead-4b9f-82a3-cec8aa8c8307",
      "metadata": {},
      "source": [
        "Dall'analisi dei risultati sperimentali è possibile confrontare direttamente il comportamento dei diversi approcci: PEC, PEC combinato con SLC e lo scenario di riferimento senza misure di mitigazione. Alcuni dettagli specifici da sottolineare:\n",
        "\n",
        "* Il risultato non corretto si colloca ben al di fuori della fascia di errore del 10% (vedi 0.35 ) e non è influenzato dal numero di randomizzazioni.\n",
        "* Su questo dispositivo, il PEC completo comporta un overhead di campionamento pari a $\\gamma^2 \\approx 1.6\\times10^2$. Dopo ampie oscillazioni iniziali, la stima PEC semplice recupera il segnale e si assesta intorno a 1.1, superando leggermente il valore esatto. Si noti che la banda di bias del 10% si applica solo allo stimatore PEC+SLC: il PEC semplice è, in linea di principio, insuperato, quindi questa deviazione residua non è coperta da una garanzia di bias, ma riflette invece fluttuazioni statistiche e imperfezioni nel modello di rumore appreso, il che è ragionevole date le dimensioni del problema.\n",
        "* L'SLC riduce ulteriormente l'overhead di un \\~3.4-fold (a $\\gamma^2 \\approx 48$, con un limite di bias residuo pari a circa il 10%). Anche il PEC+SLC subisce forti fluttuazioni con un numero ridotto di randomizzazioni, arrivando inizialmente persino a raggiungere la regione non fisica, ma la sua stima converge costantemente verso il valore esatto, stabilizzandosi in prossimità di 1.0 ben all’interno della banda. Eguaglia o supera la precisione del PEC standard a circa un terzo del costo di campionamento, dimostrando i vantaggi dell’ombreggiatura del cono di luce.\n",
        "* Le barre di errore di entrambe le stime mitigate si restringono con l’aumentare del numero di randomizzazioni; a parità di numero di randomizzazioni, lo stimatore PEC+SLC presenta una varianza statistica minore, in linea con il suo valore di “ $\\gamma^2$ ” più basso.\n",
        "\n",
        "Si noti che i tassi di rumore appresi (e quindi gli overhead e le stime mitigate) dipendono dal backend e dalla sua calibrazione al momento dell'esecuzione, pertanto l'esecuzione di questo notebook può produrre valori quantitativamente diversi.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 53,
      "id": "8df6612b-d611-4201-b1f4-6d8de4a192ad",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "(-50.0, 4100.0)"
            ]
          },
          "execution_count": 53,
          "metadata": {},
          "output_type": "execute_result"
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/8df6612b-d611-4201-b1f4-6d8de4a192ad-1.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "fig, ax = plt.subplots(1, 1, figsize=(12, 6))\n",
        "\n",
        "ax.axhline(1.0, color=\"black\", label=\"Exact\")\n",
        "ax.fill_between(\n",
        "    [-50, 4100], -10, 0, color=\"grey\", alpha=0.25, label=\"Unphysical\"\n",
        ")\n",
        "ax.fill_between([-50, 4100], 1, 10, color=\"grey\", alpha=0.25)\n",
        "ax.fill_between(\n",
        "    [-50, 4100], 0.9, 1.1, color=\"red\", alpha=0.25, label=\"10% bias\"\n",
        ")\n",
        "\n",
        "for label, res in results.items():\n",
        "    ax.errorbar(\n",
        "        list(range(steps, num_randomizations + 1, steps)),\n",
        "        [r[0] for r in res],\n",
        "        yerr=[r[1] for r in res],\n",
        "        alpha=0.75,\n",
        "        marker=\"o\",\n",
        "        linestyle=\"\",\n",
        "        markerfacecolor=\"none\",\n",
        "        label=label,\n",
        "    )\n",
        "\n",
        "ax.set_ylabel(r\"$\\langle X_{6}Z_{13}\\rangle$\")\n",
        "ax.set_xlabel(\"# randomizations\")\n",
        "ax.grid()\n",
        "\n",
        "ax.legend(ncols=2)\n",
        "ax.set_ylim([-0.1, 2.0])\n",
        "ax.set_xlim([-50, 4100])"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "slc-next-000-4000-8000-000000000099",
      "metadata": {},
      "source": [
        "<span id=\"next-steps\" />\n",
        "\n",
        "## Passi successivi\n",
        "\n",
        "Se questo lavoro ti è sembrato interessante, potrebbero interessarti anche i seguenti contenuti:\n",
        "\n",
        "<Admonition type=\"tip\" title=\"Suggerimenti\">\n",
        "  * [Combinare le opzioni di mitigazione degli errori con la primitiva Stimatore](/docs/tutorials/combine-error-mitigation-techniques)\n",
        "  * [Attenuazione degli errori su scala industriale mediante amplificazione probabilistica degli errori](/docs/tutorials/probabilistic-error-amplification)\n",
        "  * La [`qiskit-addon-slc`](https://github.com/Qiskit/qiskit-addon-slc) documentazione e [`samplomatic`](https://github.com/Qiskit/samplomatic) e\n",
        "  * [Ombreggiamento del cono di luce per la mitigazione degli errori quantistici con accelerazione classica](https://arxiv.org/abs/2409.04401)\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "id": "a1b8767d",
      "source": "© IBM Corp., 2017-2026"
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3"
    },
    "hours": 3,
    "qpuSeconds": 600
  },
  "nbformat": 4,
  "nbformat_minor": 5
}