{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "frontmatter",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Simulazione della diffusione dei neutroni con un flusso di lavoro serverless basato su AQC e dinamica di Trotter\"\n",
        "description: \"Calcolare il fattore di struttura dinamico S(q, w) del magnete quantistico KCuF3 eseguendo un flusso di lavoro Trotter con la compressione AQC come modello di funzione implementato.\"\n",
        "---\n",
        "\n",
        "{/* cspell:ignore Trotter Trotterization spinon spinons KCuF quimb DMRG magnon antiferromagnetic Suzuki fidelities isa COBYQA Gjjc qpoints viridis fontsize vmax vmin */}\n",
        "\n",
        "<span id=\"simulate-neutron-scattering-with-an-aqc-+-trotter-dynamics-serverless-workflow\" />\n",
        "\n",
        "# Simulazione della diffusione dei neutroni con un flusso di lavoro serverless basato su AQC e dinamica di Trotter\n",
        "\n",
        "Stima del tempo di *esecuzione: 18 minuti su un processore Heron r3 (NOTA: si tratta solo di una stima. (La durata potrebbe variare.)*\n",
        "\n",
        "<Admonition type=\"note\" title=\"Quale tutorial dovrei seguire?\">\n",
        "  Utilizza questo tutorial per eseguire l’esperimento di diffusione neutronica tramite una funzione implementata in Qiskit Serverless che riunisce la creazione del circuito, la compressione e l’esecuzione in un’unica chiamata. La compressione utilizza le risorse di calcolo e di memoria del worker serverless e può proseguire anche dopo la chiusura del notebook; la preparazione dello stato fondamentale e la post-elaborazione continuano comunque a essere eseguite localmente. È necessario innanzitutto [distribuire il modello di funzione](/docs/guides/function-template-aqc-trotter). Per imparare passo dopo passo come implementarlo, consulta il [tutorial originale](/docs/tutorials/simulate-neutron-scattering).\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "learning-outcomes",
      "metadata": {},
      "source": [
        "<span id=\"learning-outcomes\" />\n",
        "\n",
        "## Risultati di apprendimento\n",
        "\n",
        "* In che modo lo spettro di diffusione inelastica dei neutroni si ricollega al fattore di struttura dinamico $S(q, \\omega)$ di un magnete quantistico di tipo “ 1D ”.\n",
        "* Come preparare lo stato fondamentale KCuF$_3$ (Heisenberg isotropico) utilizzando il gruppo di rinormalizzazione della matrice di densità (DMRG) e la massimizzazione della fedeltà dello stato di prodotto matriciale (MPS).\n",
        "* Come eseguire l'evoluzione temporale di Trotter, la compressione dei circuiti tramite compilazione quantistica approssimativa (AQC) e l'esecuzione mitigata con una singola chiamata di funzione.\n",
        "* Come elaborare le serie temporali “ $\\langle \\sigma_z \\rangle(t)$ ” per ciascun sito in “ $S(q, \\omega)$ ” e identificare il continuum a due spinoni.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "prerequisites",
      "metadata": {},
      "source": [
        "<span id=\"prerequisites\" />\n",
        "\n",
        "## Prerequisiti\n",
        "\n",
        "* Conoscenza dei [modelli di Qiskit](/docs/guides/intro-to-patterns), [`SparsePauliOp`](/docs/api/qiskit/qiskit.quantum_info.SparsePauliOp), e [dell’evoluzione temporale di Trotter](/learning/courses/utility-scale-quantum-computing/quantum-simulation).\n",
        "* È utile, ma non indispensabile, avere una conoscenza di base dei metodi basati sulle reti di tensori (DMRG e MPS), così come è utile avere familiarità con la libreria [`qiskit-addon-aqc-tensor`](https://github.com/Qiskit/qiskit-addon-aqc-tensor) utilizzata dalla funzione per comprimere i circuiti di Trotter.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "background",
      "metadata": {},
      "source": [
        "<span id=\"background\" />\n",
        "\n",
        "## Sfondo\n",
        "\n",
        "La diffusione anelastica dei neutroni misura il fattore di struttura dinamico $S(q, \\omega)$, ovvero la trasformata di Fourier spazio-temporale della funzione di correlazione spin-spin; pertanto, la riproduzione dell’ $S(q, \\omega)$ e a partire da un modello di spin microscopico costituisce un test diretto e falsificabile di una simulazione quantistica. Questo tutorial analizz KCuF$_3$, una catena di Heisenberg antiferromagnetica spin- $\\frac{1}{2}$, le cui eccitazioni non sono singoli ribaltamenti di spin ma coppie di spinoni frazionati: invece di una dispersione netta dei magnoni, $S(q, \\omega)$ mostra un ampio *continuum a due spinoni*, limitato inferiormente da $\\tfrac{\\pi}{2}|\\sin q|$ e superiormente da $\\pi|\\sin(q/2)|$. Si tratta delle curve tratteggiate nei grafici che seguono. Gli aspetti fisici nel loro complesso e il confronto con i dati misurati relativi ai neutroni sono trattati nel [tutorial](/docs/tutorials/simulate-neutron-scattering) originale e in Lee et al., [arXiv:2603.15608](https://arxiv.org/abs/2603.15608).\n",
        "\n",
        "Il flusso di lavoro quantistico rispecchia l'esperimento di diffusione:\n",
        "\n",
        "1. Preparare lo stato fondamentale della catena $|\\psi_0\\rangle$.\n",
        "2. Si inizia con una perturbazione locale nel sito centrale, una rotazione di tipo “ $\\pi/2$ ” $Z$, che riproduce il trasferimento di quantità di moto ed energia da parte del neutrone.\n",
        "3. Evoluzione temporale sotto l’Hamiltoniano di Heisenberg, $e^{-iHt}$, utilizzando una formula di prodotto di Trotter.\n",
        "4. Misurare la magnetizzazione per sito $\\langle \\sigma_z^j \\rangle(t)$. In funzione del sito $j$ e del tempo $t$, questa è esattamente la funzione di Green ritardata $G^R(j, j_c, t)$, quindi non è necessaria alcuna conversione prima della trasformata di Fourier nel passaggio 5.\n",
        "5. Trasformata di Fourier: $G^R$ in $S(q, \\omega)$.\n",
        "\n",
        "Possono sorgere problemi nella fase 3, quando i circuiti di Trotter esatti per evoluzioni lunghe diventano troppo complessi per l'hardware. L'AQC con reti tensoriali risolve questo problema comprimendo un blocco di passi di Trotter in un ansatz parametrizzato, fisso e poco profondo, la cui fedeltà dello stato rispetto all'evoluzione esatta viene massimizzata in ambito classico con un simulatore MPS ( [arXiv:2301.08609](https://arxiv.org/abs/2301.08609) ). Il [template AQC Dynamics](/docs/guides/function-template-aqc-trotter) racchiude l'intero nucleo quantistico (sintesi di Trotter, compressione AQC ed esecuzione mitigata) in un'unica chiamata:\n",
        "\n",
        "| PRE (questo quaderno)                                                                                                                         | FUNZIONE (`aqc-dynamics-function`)                                                                                                                              | PUBBLICA (questo taccuino)                         |\n",
        "| --------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------- |\n",
        "| Stato fondamentale ottenuto tramite DMRG e massimizzazione della fedeltà MPS, con l’effetto kick dei neutroni integrato nello stesso circuito | Sintesi Trotter → compressione AQC → esecuzione su `statevector`, `fake`, o `runtime`, con restituzione di un “ $\\langle \\sigma_z^j \\rangle(t)$ ” per ogni sito | $S(q, \\omega)$ e, il fattore di struttura dinamico |\n",
        "\n",
        "Le attività specifiche dell'esperimento rimangono qui nel quaderno: preparazione dello stato fondamentale (PRE) e post-elaborazione dell' $S(q, \\omega)$ e (POST). Le due fasi ad alta intensità quantistica, la compressione e l'esecuzione, vengono eseguite all'interno della funzione.\n",
        "\n",
        "Questo tutorial è un complemento al [tutorial](/docs/tutorials/simulate-neutron-scattering) “Simulazione della diffusione neutronica nei materiali quantistici con circuiti quantistici”, che riproduce lo stesso esperimento passo dopo passo: lo stesso modello KCuF$_3$, la preparazione dello stato fondamentale, il “kick” neutronico e la post-elaborazione, con la sintesi di Trotter, la compressione AQC e l’esecuzione mitigata descritte passo dopo passo. Leggi quel tutorial per scoprire come funziona la compressione AQC. Leggi questo articolo per eseguire lo stesso esperimento tramite un modello di funzione distribuito: il nucleo quantistico diventa una singola chiamata di funzione e la compressione AQC, che richiede diverse ore, viene eseguita all’interno del worker serverless anziché sul tuo computer, quindi non è necessario disporre di un sistema HPC né di un kernel aperto durante l’esecuzione. La stessa chiamata guida anche altri esperimenti sulle dinamiche di 1D.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "requirements",
      "metadata": {},
      "source": [
        "<span id=\"requirements\" />\n",
        "\n",
        "## Requisiti\n",
        "\n",
        "Prima di iniziare questo tutorial, assicurati di disporre di quanto segue:\n",
        "\n",
        "* La funzione è stata implementata nel tuo account Qiskit Serverless. Esegui prima il modello di funzione di accompagnamento: [distribuisci ed esegui il modello di funzione AQC + dinamica di Trotter](/docs/guides/function-template-aqc-trotter). Questa guida illustra come procurarsi i file sorgente e caricare la funzione sul proprio account. Questo tutorial si limita a richiamare la funzione distribuita.\n",
        "\n",
        "* IBM Quantum® credenziali salvate per `QiskitServerless` (vedere il modello di funzione). Entrambi gli esempi presenti in questo tutorial richiamano la funzione distribuita, quindi entrambi ne hanno bisogno.\n",
        "\n",
        "* Qiskit SDK v2.0 o versioni successive (`pip install qiskit`).\n",
        "\n",
        "* Il client del catalogo \" IBM \" di Qiskit (`pip install qiskit-ibm-catalog`).\n",
        "\n",
        "* NumPy, SciPy, e Matplotlib (`pip install numpy scipy matplotlib`). Per l’ottimizzatore COBYQA utilizzato nella preparazione dello stato fondamentale è necessaria la versione 1.14 o successiva di SciPy.\n",
        "\n",
        "* Lo stack della rete tensoriale AQC, poiché la preparazione dello stato fondamentale nella Fase 1 viene eseguita localmente in questo notebook: `pip install 'qiskit-addon-aqc-tensor[quimb-jax]==0.3.1'`.\n",
        "\n",
        "La prima chiamata a una funzione appena distribuita rimane in attesa mentre il worker serverless installa le sue dipendenze, quindi è possibile che si verifichi una latenza aggiuntiva durante quell'esecuzione.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "setup-md",
      "metadata": {},
      "source": [
        "<span id=\"setup\" />\n",
        "\n",
        "## Configura\n",
        "\n",
        "Importare le librerie e definire gli helper specifici dell’esperimento che verranno utilizzati in seguito: ( `build_gs_ansatz` l’approccio variazionale hamiltoniano, o HVA, per la preparazione dello stato fondamentale), `prepare_ground_state` (DMRG con massimizzazione della fedeltà MPS), e `get_spectrum`, `plot_green`, e `plot_spectrum` (la post-elaborazione “ $S(q, \\omega)$ ”). Questi contenuti sono tratti dal [tutorial](/docs/tutorials/simulate-neutron-scattering) originale sulla diffusione dei neutroni.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "id": "setup-imports",
      "metadata": {},
      "outputs": [],
      "source": [
        "from functools import partial\n",
        "\n",
        "import matplotlib.pyplot as plt\n",
        "import numpy as np\n",
        "import scipy.optimize\n",
        "\n",
        "import quimb.tensor as qtn\n",
        "from qiskit import QuantumCircuit\n",
        "from qiskit.quantum_info import SparsePauliOp\n",
        "from qiskit_addon_aqc_tensor.simulation import tensornetwork_from_circuit\n",
        "from qiskit_addon_aqc_tensor.simulation.quimb import QuimbSimulator\n",
        "from qiskit_ibm_catalog import QiskitServerless"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "setup-helpers",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Setup complete - helpers defined.\n"
          ]
        }
      ],
      "source": [
        "#  Dynamical structure factor via discrete Fourier transform\n",
        "\n",
        "\n",
        "def get_spectrum(n, Gjjc, dt, time_steps, q_steps, w_steps):\n",
        "    \"\"\"Compute the dynamical structure factor from the retarded Green's function.\n",
        "\n",
        "    Uses the center-site approximation and a discrete Fourier transform.\n",
        "    \"\"\"\n",
        "    green = Gjjc / 4  # sigma -> S=1/2\n",
        "    omega_max = np.pi / dt\n",
        "    qpoints = np.arange(0, 2 * np.pi, 2 * np.pi / q_steps)\n",
        "    omegas = np.arange(0, omega_max, omega_max / w_steps)\n",
        "    green_map = np.zeros((omegas.shape[0], qpoints.shape[0]))\n",
        "    center = n // 2 - 1\n",
        "    for iw, w in enumerate(omegas):\n",
        "        exponent = np.exp(1j * w * dt * np.arange(1, time_steps + 1))\n",
        "        S_w = np.dot(green.T, exponent) * dt\n",
        "        for iq, q in enumerate(qpoints):\n",
        "            q_matrix = np.exp(-1j * q * np.arange(-center, center + 2, 1))\n",
        "            green_map[iw, iq] = np.imag(np.dot(S_w, q_matrix))\n",
        "    return green_map\n",
        "\n",
        "\n",
        "#  Plotting helpers\n",
        "\n",
        "\n",
        "def plot_spectrum(\n",
        "    dsf,\n",
        "    dt,\n",
        "    q_steps,\n",
        "    w_steps,\n",
        "    lower_bound=False,\n",
        "    upper_bound=False,\n",
        "    title=None,\n",
        "):\n",
        "    \"\"\"Heat-map of the dynamical structure factor.\"\"\"\n",
        "    omega_max = np.pi / dt\n",
        "    qpoints = np.arange(0, 2 * np.pi, 2 * np.pi / q_steps)\n",
        "    omegas = np.arange(0, omega_max, omega_max / w_steps)\n",
        "    x, y = np.meshgrid(qpoints, omegas)\n",
        "    fig, ax = plt.subplots(figsize=(8, 5))\n",
        "    c = ax.pcolormesh(x, y, dsf / np.max(dsf), cmap=\"viridis\", shading=\"auto\")\n",
        "    fig.colorbar(c, ax=ax, label=\"Normalized intensity\")\n",
        "    if lower_bound:\n",
        "        ax.plot(\n",
        "            qpoints,\n",
        "            np.pi * np.abs(np.sin(qpoints)) / 2,\n",
        "            \"--\",\n",
        "            color=\"white\",\n",
        "            lw=1.5,\n",
        "            label=\"Lower bound\",\n",
        "        )\n",
        "    if upper_bound:\n",
        "        ax.plot(\n",
        "            qpoints,\n",
        "            np.pi * np.abs(np.sin(qpoints / 2)),\n",
        "            \"--\",\n",
        "            color=\"red\",\n",
        "            lw=1.5,\n",
        "            label=\"Upper bound\",\n",
        "        )\n",
        "    ax.set_ylim(0, 3.6)\n",
        "    ax.set_xlim(0, 2 * np.pi - 2 * np.pi / q_steps)\n",
        "    ax.set_xlabel(r\"$q$\", fontsize=16)\n",
        "    ax.set_ylabel(r\"$\\tilde{\\omega} = \\omega / J$\", fontsize=16)\n",
        "    ax.set_xticks([0, np.pi / 2, np.pi, 3 * np.pi / 2, 2 * np.pi])\n",
        "    ax.set_xticklabels([\"0\", r\"$\\pi/2$\", r\"$\\pi$\", r\"$3\\pi/2$\", r\"$2\\pi$\"])\n",
        "    if lower_bound or upper_bound:\n",
        "        ax.legend(loc=\"upper right\", fontsize=11)\n",
        "    if title:\n",
        "        ax.set_title(title, fontsize=14)\n",
        "    plt.tight_layout()\n",
        "    plt.show()\n",
        "\n",
        "\n",
        "def plot_green(n, Gjjc, time_steps, dt, title=None):\n",
        "    \"\"\"Heat-map of the retarded Green's function in real space and time.\"\"\"\n",
        "    fig, ax = plt.subplots(figsize=(8, 6))\n",
        "    t_axis = np.arange(1, time_steps + 1) * dt\n",
        "    site_axis = np.arange(n)\n",
        "    x, y = np.meshgrid(t_axis, site_axis)\n",
        "    c = ax.pcolormesh(\n",
        "        x,\n",
        "        y,\n",
        "        np.real(Gjjc).T,\n",
        "        cmap=\"RdBu\",\n",
        "        vmax=0.5,\n",
        "        vmin=-0.5,\n",
        "        shading=\"auto\",\n",
        "    )\n",
        "    fig.colorbar(c, ax=ax, label=r\"Re $G^R(j, j_c, t)$\")\n",
        "    ax.set_xlabel(r\"Time  ($t / J^{-1}$)\", fontsize=16)\n",
        "    ax.set_ylabel(\"Site index $j$\", fontsize=16)\n",
        "    if title:\n",
        "        ax.set_title(title, fontsize=14)\n",
        "    plt.tight_layout()\n",
        "    plt.show()\n",
        "\n",
        "\n",
        "#  Variational ground-state ansatz (HVA)\n",
        "\n",
        "\n",
        "def _apply_xxz_pair_gate(qc, q0, q1, theta):\n",
        "    \"\"\"Apply the parameterized XXZ-type two-qubit gate used in the HVA.\"\"\"\n",
        "    qc.cx(q0, q1)\n",
        "    qc.rz(theta, q1)\n",
        "    qc.h(q0)\n",
        "    qc.rz(theta + np.pi / 2, q0)\n",
        "    qc.cx(q0, q1)\n",
        "    qc.rz(-theta, q1)\n",
        "    qc.h(q1)\n",
        "    qc.cx(q1, q0)\n",
        "    qc.rz(np.pi / 2, q1)\n",
        "    qc.rz(-np.pi / 2, q0)\n",
        "    qc.h(q1)\n",
        "    qc.h(q0)\n",
        "\n",
        "\n",
        "def build_gs_ansatz(n, params, layers):\n",
        "    \"\"\"Build the Hamiltonian variational ansatz (HVA) circuit for\n",
        "    ground-state preparation of the 1D Heisenberg model.\n",
        "\n",
        "    Starts from a product of singlet pairs and applies alternating\n",
        "    odd/even layers of parameterized XXZ gates. For layer r,\n",
        "    params[2 * r] is the odd-layer (inter-pair) angle and\n",
        "    params[2 * r + 1] is the even-layer (intra-pair) angle.\n",
        "    \"\"\"\n",
        "    qc = QuantumCircuit(n)\n",
        "    # Initial singlet product state\n",
        "    for i in range(n // 2):\n",
        "        qc.x(2 * i)\n",
        "        qc.x(2 * i + 1)\n",
        "        qc.h(2 * i + 1)\n",
        "        qc.cx(2 * i + 1, 2 * i)\n",
        "    # Variational layers\n",
        "    for r in range(layers):\n",
        "        for i in range(1, (n + 1) // 2):  # odd layer\n",
        "            _apply_xxz_pair_gate(qc, 2 * i - 1, 2 * i, params[2 * r])\n",
        "        for i in range(n // 2):  # even layer\n",
        "            _apply_xxz_pair_gate(qc, 2 * i, 2 * i + 1, params[2 * r + 1])\n",
        "    return qc\n",
        "\n",
        "\n",
        "def prepare_ground_state(n, gs_layers=5, max_bond=128, cutoff=1e-8):\n",
        "    \"\"\"Prepare the KCuF3 (isotropic Heisenberg) ground state as a QuantumCircuit.\n",
        "\n",
        "    Runs DMRG (quimb MPO + DMRG2) to get the chain's ground state, then optimizes\n",
        "    the HVA angles to maximize the MPS overlap |<psi_ansatz|psi_DMRG>|^2. No exact\n",
        "    diagonalization, so it scales to larger n.\n",
        "    \"\"\"\n",
        "    J = Jz = 1.0\n",
        "    builder = qtn.SpinHam1D(S=1 / 2)\n",
        "    builder += J * 0.5, \"+\", \"-\"\n",
        "    builder += J * 0.5, \"-\", \"+\"\n",
        "    builder += Jz, \"Z\", \"Z\"\n",
        "    H_mpo = builder.build_mpo(L=n)\n",
        "    dmrg = qtn.DMRG2(H_mpo)\n",
        "    dmrg.solve(tol=1e-8, verbosity=0)\n",
        "\n",
        "    gs_sim = QuimbSimulator(\n",
        "        quimb_circuit_factory=partial(\n",
        "            qtn.CircuitMPS, gate_opts=dict(cutoff=cutoff, max_bond=max_bond)\n",
        "        ),\n",
        "        autodiff_backend=\"jax\",\n",
        "    )\n",
        "\n",
        "    def gs_infidelity(params):\n",
        "        psi = tensornetwork_from_circuit(\n",
        "            build_gs_ansatz(n, params, gs_layers), gs_sim\n",
        "        ).psi\n",
        "        return 1 - abs(psi.H @ dmrg.state) ** 2\n",
        "\n",
        "    # Seed and optimizer match the original tutorial. Each layer starts at\n",
        "    # [0, pi/2]: an odd-layer angle of 0 makes the inter-pair gate the identity,\n",
        "    # and an even-layer angle of pi/2 makes the intra-pair gate a SWAP (since\n",
        "    # 0.5 * (XX + YY + ZZ) = SWAP - I/2). That puts the seed at the singlet-pair\n",
        "    # product limit, which is already a decent approximation to the Heisenberg\n",
        "    # ground state, so the optimizer only has to refine it. The small jitter\n",
        "    # (fixed RNG seed, so runs are reproducible) breaks the exact symmetry\n",
        "    # between layers; COBYQA then runs for up to 100 iterations.\n",
        "    rng = np.random.default_rng(12345)\n",
        "    x0 = np.tile([0.0, np.pi / 2], gs_layers) + rng.normal(\n",
        "        scale=0.1, size=2 * gs_layers\n",
        "    )\n",
        "    result_gs = scipy.optimize.minimize(\n",
        "        gs_infidelity, x0, method=\"COBYQA\", options={\"maxiter\": 100}\n",
        "    )\n",
        "    print(f\"DMRG ground-state energy: {dmrg.energy:.6f}\")\n",
        "    print(f\"GS fidelity: {1 - result_gs.fun:.4f}\")\n",
        "    return build_gs_ansatz(n, result_gs.x, gs_layers)\n",
        "\n",
        "\n",
        "print(\"Setup complete - helpers defined.\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "setup-load-md",
      "metadata": {},
      "source": [
        "<span id=\"load-the-function-template\" />\n",
        "\n",
        "### Carica il modello di funzione\n",
        "\n",
        "Accedi a Qiskit Serverless e carica il file `aqc-dynamics-function`. Entrambi gli esempi presenti in questo tutorial richiamano lo stesso `fn` handle, quindi la funzione viene caricata una sola volta, in questo caso.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "id": "setup-connect",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Credentials are read from the account saved once via QiskitServerless.save_account(...)\n",
        "serverless = QiskitServerless()\n",
        "fn = serverless.load(\"aqc-dynamics-function\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "small-md",
      "metadata": {},
      "source": [
        "<span id=\"small-scale-simulator-example\" />\n",
        "\n",
        "## Esempio di simulatore su piccola scala\n",
        "\n",
        "Per prima cosa eseguiamo l’intero flusso di lavoro su una piccola catena di 10 sedi utilizzando lo stesso `statevector` backend. Questo permette di verificare la correttezza della pipeline PRE → FUNCTION → POST prima di impiegare risorse della QPU.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "small-s1-md",
      "metadata": {},
      "source": [
        "<span id=\"step-1-map-classical-inputs-to-a-quantum-problem\" />\n",
        "\n",
        "### Fase 1: Mappare gli input classici su un problema quantistico\n",
        "\n",
        "Costruire l' KCuF$_3$ come un Hamiltoniano ( `SparsePauliOp` Heisenberg isotropico: $XX + YY + ZZ$ con accoppiamento $\\tfrac14$ su ciascun legame tra vicini più prossimi; le stringhe sono operatori di Pauli, quindi $\\tfrac14$ fornisce l'accoppiamento di spin $\\frac{1}{2}$ ). Preparare lo stato fondamentale con il metodo DMRG e la massimizzazione della fedeltà MPS, quindi integrare l’effetto del neutrone: una rotazione di tipo $\\pi/2$ $Z$ nel sito centrale. Il circuito preparato è quello che passiamo alla funzione come `initial_state`. Lasciamo `observables` il valore predefinito ( $Z$ per sito), che corrisponde esattamente al valore $\\langle \\sigma_z^j \\rangle(t)$ richiesto dal flusso di lavoro dei neutroni.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "id": "small-s1-code",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "DMRG ground-state energy: -4.258035\n",
            "GS fidelity: 0.9841\n",
            "Prepared 10-qubit ground state with the neutron kick at site 4.\n"
          ]
        }
      ],
      "source": [
        "n = 10\n",
        "dt = 0.6  # physical time per Trotter step (also the omega-axis unit in POST)\n",
        "time_steps = 10\n",
        "center = n // 2 - 1\n",
        "\n",
        "# MPS-simulator settings, shared by the ground-state prep here and the AQC\n",
        "# compression inside the function (matches the original tutorial).\n",
        "mps_max_bond = 32\n",
        "mps_cutoff = 1e-8\n",
        "\n",
        "# 1D isotropic Heisenberg (KCuF3) Hamiltonian on n qubits\n",
        "H = SparsePauliOp.from_sparse_list(\n",
        "    [(p, [i, i + 1], 0.25) for i in range(n - 1) for p in (\"XX\", \"YY\", \"ZZ\")],\n",
        "    num_qubits=n,\n",
        ")\n",
        "\n",
        "# Ground state (DMRG + fidelity max) + neutron kick baked into the same circuit\n",
        "gs_circuit = prepare_ground_state(\n",
        "    n, gs_layers=3, max_bond=mps_max_bond, cutoff=mps_cutoff\n",
        ")\n",
        "gs_circuit.rz(\n",
        "    np.pi / 2, center\n",
        ")  # exp(-i (pi/2)/2 Z_center): the neutron perturbation\n",
        "print(\n",
        "    f\"Prepared {n}-qubit ground state with the neutron kick at site {center}.\"\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "small-s3-md",
      "metadata": {},
      "source": [
        "<span id=\"steps-2-and-3-compress-and-execute-with-the-function-template\" />\n",
        "\n",
        "### Passaggi 2 e 3: Comprimere ed eseguire con il modello di funzione\n",
        "\n",
        "In un flusso di lavoro gestito manualmente, si tratta di due fasi distinte: l'ottimizzazione dei circuiti per l'hardware (Fase 2) e la loro esecuzione (Fase 3). Il modello di funzione raggruppa entrambe le operazioni in un’unica chiamata. Esegue la sintesi di Trotter, la compressione AQC e la transpilazione hardware, quindi esegue i circuiti (in questa fase sul simulatore esatto, successivamente con mitigazione degli errori integrata sull’hardware). I due parametri di configurazione sono `aqc_segments` (il piano di compressione) e `aqc_options` (le impostazioni dell'MPS e dell'ottimizzatore). Ogni segmento `{\"n_steps\": k, \"ansatz_steps\": m}` comprime i passi consecutivi `k` di Trotter in un ansatz costruito a partire da un obiettivo di Trotter di `m`-passi, mentre eventuali passi successivi vengono `sum(n_steps)` eseguiti come metodo di Trotter standard. I primi passi, caratterizzati da un basso livello di entanglement, si prestano bene alla compressione in un ansatz poco profondo (`ansatz_steps=1`), pertanto in questo caso comprimiamo i primi tre passi in un ansatz a singolo strato e i due successivi in un ansatz più profondo a due strati; i restanti cinque dei 10 passi di Trotter vengono eseguiti come Trotter standard. Infatti, `aqc_options` seguiamo alla lettera il tutorial originale: dimensione del legame MPS `max_bond=32`, `cutoff=1e-8`, e un ottimizzatore L-BFGS-B con un limite massimo di 100 iterazioni.\n",
        "\n",
        "Chiama la funzione caricata in Setup. `backend=\"statevector\"` esegue il percorso di riferimento esatto: nessun tempo QPU, con i circuiti in esecuzione su un simulatore di vettori di stato esatto all’interno del worker serverless (per richiamarlo è comunque necessario un account Qiskit Serverless salvato). Il contiene `initial_state` lo stato fondamentale preparato (compreso il kick); `observables` viene omesso in modo che la funzione misuri l' $Z$ predefinito per sito.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "small-s3-run",
      "metadata": {},
      "outputs": [],
      "source": [
        "job = fn.run(\n",
        "    t_steps=time_steps,\n",
        "    aqc_segments=[\n",
        "        {\n",
        "            \"n_steps\": 3,\n",
        "            \"ansatz_steps\": 1,\n",
        "        },  # early steps -> shallow 1-layer ansatz\n",
        "        {\n",
        "            \"n_steps\": 2,\n",
        "            \"ansatz_steps\": 2,\n",
        "        },  # later steps -> deeper 2-layer ansatz\n",
        "    ],\n",
        "    aqc_options={\n",
        "        \"max_bond\": mps_max_bond,  # MPS bond dimension for AQC compression\n",
        "        \"cutoff\": mps_cutoff,\n",
        "        \"optimizer_settings\": {\n",
        "            \"method\": \"L-BFGS-B\",\n",
        "            \"jac\": True,\n",
        "            \"options\": {\"maxiter\": 100},\n",
        "        },\n",
        "    },\n",
        "    dt=dt,\n",
        "    hamiltonian=H,\n",
        "    initial_state=gs_circuit,  # prepared ground state including the neutron kick\n",
        "    # observables omitted -> default per-site Z (the neutron sigma_z readout)\n",
        "    backend=\"statevector\",\n",
        ")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 20,
      "id": "small-status",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "DONE\n"
          ]
        }
      ],
      "source": [
        "print(job.status())  # rerun this cell until status says DONE"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 21,
      "id": "small-s3-result",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "AQC fidelities: {'1': 1.0, '2': 0.9999, '3': 0.9992, '4': 0.9998, '5': 0.9995}\n",
            "Green's function shape: (10, 10)\n"
          ]
        }
      ],
      "source": [
        "# The per-site <sigma_z>(t) the function returns is the retarded Green's function\n",
        "# G(j, j_c, t). The workflow samples t = 1..time_steps, so drop the t = 0 row (the\n",
        "# prepared+kicked state before any evolution) before post-processing.\n",
        "result = job.result()\n",
        "print(\n",
        "    \"AQC fidelities:\",\n",
        "    {k: round(v, 4) for k, v in result[\"metadata\"][\"aqc_fidelities\"].items()},\n",
        ")\n",
        "\n",
        "ev = np.array(result[\"expectation_values\"])\n",
        "Gjjc = ev[1:]  # shape (time_steps, n)\n",
        "print(\"Green's function shape:\", Gjjc.shape)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "small-s4-md",
      "metadata": {},
      "source": [
        "<span id=\"step-4-post-process-and-return-result-in-desired-classical-format\" />\n",
        "\n",
        "### Fase 4: Elaborazione successiva e restituzione del risultato nel formato classico desiderato\n",
        "\n",
        "Applicare la trasformata di Fourier alla funzione di Green per ottenere l' $S(q, \\omega)$ e, applicare la simmetria speculare ed eliminare i valori negativi: la post-elaborazione standard dei dati neutronici. Il mirroring è esatto poiché $S(q, \\omega) = S(-q, \\omega)$ per questo modello, e i valori negativi che permangono sono artefatti derivanti dalla trasformata di Fourier di una serie temporale finita e campionata in modo discreto, pertanto vengono troncati a zero. In questa piccola simulazione esatta, il continuum a due spinoni è risolto solo in modo approssimativo, ma il meccanismo è identico a quello della simulazione hardware che segue.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 22,
      "id": "small-s4-code",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/simulate-neutron-scattering-with-a-serverless-workflow/extracted-outputs/small-s4-code-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/simulate-neutron-scattering-with-a-serverless-workflow/extracted-outputs/small-s4-code-1.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "q_res, w_res = 100, 100\n",
        "spectrum = get_spectrum(n, Gjjc, dt, time_steps, q_res, w_res)\n",
        "spectrum = -(spectrum + spectrum[:, ::-1]) / 2  # mirror symmetry\n",
        "spectrum = np.clip(spectrum, a_min=0, a_max=None)  # clip negatives\n",
        "\n",
        "plot_green(\n",
        "    n,\n",
        "    Gjjc,\n",
        "    time_steps,\n",
        "    dt,\n",
        "    title=f\"Retarded Green's function - {n} qubits (AQC, statevector)\",\n",
        ")\n",
        "plot_spectrum(\n",
        "    spectrum,\n",
        "    dt,\n",
        "    q_res,\n",
        "    w_res,\n",
        "    lower_bound=True,\n",
        "    upper_bound=True,\n",
        "    title=f\"Dynamical structure factor - {n} qubits (AQC, statevector)\",\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "large-md",
      "metadata": {},
      "source": [
        "<span id=\"large-scale-hardware-example\" />\n",
        "\n",
        "## Esempio di hardware su larga scala\n",
        "\n",
        "Lo stesso flusso di lavoro è scalabile senza modificare il codice scientifico: una catena di 30 siti, il doppio della profondità di Trotter (20 passi), un piano di compressione che varia la profondità dell’ansatz (un ansatz più profondo per i passi successivi, caratterizzati da un maggiore entanglement) e l’esecuzione su un processore IBM Quantum con le tecniche di mitigazione degli errori integrate nella funzione (disaccoppiamento dinamico, rotazione di Pauli ed estinzione degli errori di lettura con rotazione (TREX)). Seguiamo gli stessi quattro passaggi dell'esempio del simulatore, riutilizzando l'handle `fn` creato in \"Setup\".\n",
        "\n",
        "|                                                  | Su piccola scala | BILANCIA                          |\n",
        "| ------------------------------------------------ | ---------------- | --------------------------------- |\n",
        "| Qubit                                            | 10               | 30                                |\n",
        "| Passi di trotto                                  | 10               | 20                                |\n",
        "| Passaggi compressi con AQC (1 strato + 2 strati) | 3 + 2 = 5        | 6 + 4 = 10                        |\n",
        "| Strati di ansatz dello stato fondamentale        | 3                | 5                                 |\n",
        "| Dimensione massima del collegamento MPS          | 32               | 128                               |\n",
        "| Backend                                          | `statevector`    | QPU con DD, Pauli twirling e TREX |\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "large-s1-md",
      "metadata": {},
      "source": [
        "<span id=\"step-1-map-classical-inputs-to-a-quantum-problem\" />\n",
        "\n",
        "### Fase 1: Mappare gli input classici su un problema quantistico\n",
        "\n",
        "Costruisci lo stesso KCuF$_3$ Heisenberg e `SparsePauliOp` prepara lo stato fondamentale, questa volta con un `gs_layers=5` ansatz più profondo per la catena più lunga, quindi applica il kick neutronico $\\pi/2$ $Z$ nel sito centrale. È identico alla mappatura su piccola scala, ma all'indirizzo $n = 30$.\n",
        "\n",
        "Ci si deve aspettare una fedeltà dello stato fondamentale inferiore rispetto alla simulazione a 10 siti: qui circa 0.82 contro 0.98 per la catena più piccola, poiché cinque strati HVA non sono in grado di rappresentare appieno uno stato fondamentale a 30 siti. Si tratta di un comportamento previsto, non di un errore, e il tutorial originale accetta circa 0.65 in 50 siti proprio per lo stesso motivo. Aumentare il limite di iterazioni del `gs_layers` COBYQA ne migliora le prestazioni, a un costo classico aggiuntivo.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 34,
      "id": "large-code",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "DMRG ground-state energy: -13.111355\n",
            "GS fidelity: 0.8201\n",
            "Prepared 30-qubit ground state with the neutron kick at site 14.\n"
          ]
        }
      ],
      "source": [
        "n = 30\n",
        "dt = 0.6\n",
        "time_steps = 20\n",
        "center = n // 2 - 1\n",
        "\n",
        "# Same MPS settings as the original large-scale run: a larger bond for the\n",
        "# longer, more-entangled chain (shared by GS prep and AQC compression).\n",
        "mps_max_bond = 128\n",
        "mps_cutoff = 1e-8\n",
        "\n",
        "# Same KCuF3 Hamiltonian and ground-state prep, on a larger chain\n",
        "H = SparsePauliOp.from_sparse_list(\n",
        "    [(p, [i, i + 1], 0.25) for i in range(n - 1) for p in (\"XX\", \"YY\", \"ZZ\")],\n",
        "    num_qubits=n,\n",
        ")\n",
        "gs_circuit = prepare_ground_state(\n",
        "    n, gs_layers=5, max_bond=mps_max_bond, cutoff=mps_cutoff\n",
        ")\n",
        "gs_circuit.rz(np.pi / 2, center)  # neutron kick at the center site\n",
        "print(\n",
        "    f\"Prepared {n}-qubit ground state with the neutron kick at site {center}.\"\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "large-s3-md",
      "metadata": {},
      "source": [
        "<span id=\"steps-2-and-3-compress-and-execute-with-the-function-template\" />\n",
        "\n",
        "### Passaggi 2 e 3: Comprimere ed eseguire con il modello di funzione\n",
        "\n",
        "La stessa singola chiamata dell'esempio del simulatore, ora con che `backend_name` punta a un processore IBM Quantum, in modo che la funzione venga transpilata ed eseguita lì. Il piano di compressione varia la profondità dell'ansatz: i primi sei passi di Trotter (a basso entanglement) vengono compressi in un ansatz a singolo strato poco profondo, i quattro successivi in un ansatz a due strati più profondo, mentre i restanti 10 dei 20 passi vengono eseguiti come Trotter standard. `aqc_options` aumenta la dimensione del legame MPS a `max_bond=128` per la catena più lunga e più intrecciata (in linea con l'originale), mantenendo lo stesso ottimizzatore L-BFGS-B con un limite massimo di 100 iterazioni. `estimator_options` Attivare le funzionalità integrate di mitigazione degli errori: disaccoppiamento dinamico ( XY4 ), “gate twirling” e mitigazione delle misurazioni TREX. I valori predefiniti della funzione corrispondono già a quelli del tutorial originale per tutti questi elementi, ad eccezione del budget di apprendimento TREX (`measure_noise_learning`). L’intero blocco viene comunque riportato per intero poiché un oggetto fornito dal chiamante `estimator_options` sostituisce integralmente i valori predefiniti della funzione invece di fondersi con essi; pertanto, l’omissione di una chiave comporterebbe il ricorso al valore predefinito di IBM Quantum Compute anziché a quello della funzione.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "large-s3-run",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "job ID (save this to reconnect later): 43ed8d07-6d7d-4f33-b70a-7f31b765b310\n"
          ]
        }
      ],
      "source": [
        "# Steps 2 + 3: the function compresses (varied ansatz) and executes on hardware.\n",
        "job = fn.run(\n",
        "    t_steps=time_steps,\n",
        "    aqc_segments=[\n",
        "        {\n",
        "            \"n_steps\": 6,\n",
        "            \"ansatz_steps\": 1,\n",
        "        },  # early steps -> shallow 1-layer ansatz\n",
        "        {\n",
        "            \"n_steps\": 4,\n",
        "            \"ansatz_steps\": 2,\n",
        "        },  # later steps -> deeper 2-layer ansatz\n",
        "    ],\n",
        "    aqc_options={\n",
        "        \"max_bond\": mps_max_bond,  # 128 for the longer chain\n",
        "        \"cutoff\": mps_cutoff,\n",
        "        \"optimizer_settings\": {\n",
        "            \"method\": \"L-BFGS-B\",\n",
        "            \"jac\": True,\n",
        "            \"options\": {\"maxiter\": 100},\n",
        "        },\n",
        "    },\n",
        "    dt=dt,\n",
        "    hamiltonian=H,\n",
        "    initial_state=gs_circuit,\n",
        "    backend_name=\"ibm_pittsburgh\",\n",
        "    # Mitigation settings from the original tutorial. Only the two\n",
        "    # measure_noise_learning values differ from the function's defaults; the rest\n",
        "    # restates them, because a caller-supplied estimator_options dict replaces the\n",
        "    # function's defaults wholesale rather than merging into them.\n",
        "    estimator_options={\n",
        "        \"environment\": {\"job_tags\": [\"TUT_SNSSW\"]},\n",
        "        \"dynamical_decoupling\": {\"enable\": True, \"sequence_type\": \"XY4\"},\n",
        "        \"twirling\": {\n",
        "            \"enable_gates\": True,\n",
        "            \"num_randomizations\": 1000,\n",
        "            \"shots_per_randomization\": 128,\n",
        "        },\n",
        "        \"resilience\": {\n",
        "            \"measure_mitigation\": True,\n",
        "            \"measure_noise_learning\": {\n",
        "                \"num_randomizations\": 32,\n",
        "                \"shots_per_randomization\": 100,\n",
        "            },\n",
        "        },\n",
        "    },\n",
        ")\n",
        "print(\"job ID (save this to reconnect later):\", job.job_id)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "large-reconnect-md",
      "metadata": {},
      "source": [
        "<Admonition type=\"note\" title=\"Ripristinare la connessione a un processo in esecuzione da molto tempo\">\n",
        "  L'esecuzione su larga scala non è veloce e, nella maggior parte dei casi, avviene in modalità classica anziché sulla QPU. La compressione AQC viene eseguita all'interno della funzione prima che qualsiasi dato raggiunga la QPU: su 30 siti, `max_bond=128` l'operazione ha richiesto quasi quattro ore nella nostra esecuzione, contro i circa 18 minuti di tempo di elaborazione della QPU indicati nella *stima* dell'utilizzo riportata all'inizio di questo tutorial. A ciò si aggiunge il tempo di attesa in coda. Non è necessario tenere aperto questo notebook o questo kernel mentre è in esecuzione.\n",
        "\n",
        "  Copia l'ID del lavoro visualizzato nella cella precedente e salvalo. Le tre caselle successive ti consentono di riprendere la sessione in un secondo momento:\n",
        "\n",
        "  1. Riconnettere, operazione necessaria solo in una nuova sessione del kernel: rieseguire le celle [di configurazione](#setup) per ricrearle `serverless`, quindi ricostruire l'handle `job` a partire dall'ID salvato. Salta questa cella se sei ancora nella sessione in cui hai effettuato l'invio, poiché l'handle è già attivo.\n",
        "  2. Verifica lo stato: ripeti l'operazione finché non viene segnalato `DONE`.\n",
        "  3. Recupera il risultato: esegui l'operazione solo quando lo stato è `DONE`.\n",
        "\n",
        "  La cella di ricollegamento seguente contiene un segnaposto. Sostituiscilo con il tuo `job_id`:\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "large-reconnect",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Reconnect to a previously submitted job by its ID. Only needed in a NEW kernel\n",
        "# session; if you are still in the session where you submitted, the `job` handle\n",
        "# from the preceding cell is already live, so skip this cell. Replace the ID that follows with your own.\n",
        "job = serverless.get_job_by_id(\"<your job ID>\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 27,
      "id": "large-status",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "DONE\n"
          ]
        }
      ],
      "source": [
        "# Check where the job is. Re-run this until it reports DONE before fetching the\n",
        "# result in the following cell: QUEUED -> INITIALIZING -> RUNNING: OPTIMIZING_FOR_HARDWARE ->\n",
        "# RUNNING: WAITING_FOR_QPU -> RUNNING: EXECUTING_QPU -> RUNNING: POST_PROCESSING\n",
        "# -> DONE.\n",
        "print(job.status())"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "large-s3-result",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "AQC fidelities: {'1': 1.0, '2': 0.9994, '3': 0.9944, '4': 0.9853, '5': 0.9747, '6': 0.959, '7': 0.9495, '8': 0.9542, '9': 0.9533, '10': 0.9451}\n"
          ]
        }
      ],
      "source": [
        "# Run this only once the preceding status cell reports DONE. result() blocks until\n",
        "# the job finishes, so calling it earlier just waits (possibly for hours).\n",
        "result = job.result()\n",
        "print(\n",
        "    \"AQC fidelities:\",\n",
        "    {k: round(v, 4) for k, v in result[\"metadata\"][\"aqc_fidelities\"].items()},\n",
        ")\n",
        "\n",
        "ev = np.array(result[\"expectation_values\"])\n",
        "Gjjc = ev[1:]  # drop the t = 0 row -> shape (time_steps, n)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "large-s4-md",
      "metadata": {},
      "source": [
        "<span id=\"step-4-post-process-and-return-result-in-desired-classical-format\" />\n",
        "\n",
        "### Fase 4: Elaborazione successiva e restituzione del risultato nel formato classico desiderato\n",
        "\n",
        "Post-elaborazione identica a quella della simulazione: applicare la trasformata di Fourier alla funzione di Green per ottenere un $S(q, \\omega)$ e, applicare la simmetria speculare e tagliare i valori negativi. Grazie alla catena più lunga e all’evoluzione, il continuum a due spinoni risulta molto meglio risolto. Dovrebbe riempire la fascia compresa tra i limiti tratteggiati, con la massima luminosità in prossimità di $q = \\pi$.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 33,
      "id": "large-result",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/simulate-neutron-scattering-with-a-serverless-workflow/extracted-outputs/large-result-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/simulate-neutron-scattering-with-a-serverless-workflow/extracted-outputs/large-result-1.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "n = result[\"metadata\"][\"n\"]\n",
        "q_res, w_res = 100, 100\n",
        "spectrum = get_spectrum(n, Gjjc, dt, time_steps, q_res, w_res)\n",
        "spectrum = -(spectrum + spectrum[:, ::-1]) / 2  # mirror symmetry\n",
        "spectrum = np.clip(spectrum, a_min=0, a_max=None)  # clip negatives\n",
        "\n",
        "plot_green(\n",
        "    n,\n",
        "    Gjjc,\n",
        "    time_steps,\n",
        "    dt,\n",
        "    title=f\"Retarded Green's function - {n} qubits (AQC, hardware)\",\n",
        ")\n",
        "plot_spectrum(\n",
        "    spectrum,\n",
        "    dt,\n",
        "    q_res,\n",
        "    w_res,\n",
        "    lower_bound=True,\n",
        "    upper_bound=True,\n",
        "    title=f\"Dynamical structure factor - {n} qubits (AQC, hardware)\",\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "appendix-md",
      "metadata": {},
      "source": [
        "<span id=\"appendix\" />\n",
        "\n",
        "## Appendice\n",
        "\n",
        "L'esempio hardware precedente utilizza una singola lunghezza di catena. I tre spettri riportati di seguito provengono da precedenti esecuzioni hardware dello stesso flusso di lavoro `ibm_pittsburgh` su 10, 20 e 30 siti, con tutte le altre variabili di input mantenute fisse: 20 passi di Trotter a `dt = 0.6`, il piano di compressione composto da sei passi a un livello più quattro passi a due livelli compressi con AQC, e `max_bond = 128`. Si tratta di risultati registrati, non dei valori delle celle precedenti.\n",
        "\n",
        "Poiché per tutte e tre le dimensioni vengono utilizzate le stesse impostazioni, gli spettri sono direttamente comparabili. Regolarli in base alla lunghezza della catena, ad esempio aumentando il numero di livelli dell’ansatz dello stato fondamentale o utilizzando un valore maggiore `max_bond`, può fornire risultati migliori rispetto a quelli qui riportati.\n",
        "\n",
        "![Fattore di struttura dinamico in 10 siti, un unico picco nitido e brillante a q = π vicino al limite](https://quantum.cloud.ibm.com/docs/images/tutorials/simulate-neutron-scattering-with-a-serverless-workflow/appendix-dsf-10.avif \" inferiore di 10 qubit\")\n",
        "\n",
        "![Fattore di struttura dinamico in 20 siti, con peso spettrale che riempie la banda compresa tra i due ](https://quantum.cloud.ibm.com/docs/images/tutorials/simulate-neutron-scattering-with-a-serverless-workflow/appendix-dsf-20.avif \"limiti tratteggiati a due spinoni – 20 qubit\")\n",
        "\n",
        "![Fattore di struttura dinamico a 30 siti; il continuum è risolto in modo più dettagliato, con un contrasto più debole e una certa ponderazione al di fuori dei limiti](https://quantum.cloud.ibm.com/docs/images/tutorials/simulate-neutron-scattering-with-a-serverless-workflow/appendix-dsf-30.avif \" dei 30 qubit\")\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "nextsteps",
      "metadata": {},
      "source": [
        "<span id=\"next-steps\" />\n",
        "\n",
        "## Passi successivi\n",
        "\n",
        "<Admonition type=\"tip\" title=\"Suggerimenti\">\n",
        "  * Adatta questo flusso di lavoro al tuo sistema: la funzione accetta qualsiasi 1D vicino più prossimo `SparsePauliOp`, quindi anche con un diverso Hamiltoniano a catena, uno stato iniziale diverso o un diverso insieme di osservabili, la pipeline PRE → FUNZIONE → POST funziona allo stesso modo. Il contratto completo relativo agli input e agli output è disponibile nel [modello AQC Dynamics su GitHub](https://github.com/qiskit-community/qiskit-function-templates/tree/main/physics/aqc_trotter).\n",
        "  * Leggi l'articolo da cui è tratto questo benchmark: Lee et al., [*Analisi comparativa della simulazione quantistica con esperimenti di diffusione dei neutroni*](https://arxiv.org/abs/2603.15608) ( arXiv:2603.15608 ).\n",
        "  * Rispetto al [tutorial](/docs/tutorials/simulate-neutron-scattering) originale “Simulazione della diffusione dei neutroni”, il flusso di lavoro integrato in questo tutorial viene trasferito su un modello di funzione già implementato.\n",
        "  * Approfondisci le [tecniche](/docs/guides/error-mitigation-and-suppression-techniques) di mitigazione e soppressione degli errori applicate durante l'esecuzione su hardware: disaccoppiamento dinamico, Pauli twirling e TREX.\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "id": "a1b8767d",
      "source": "© IBM Corp., 2017-2026"
    }
  ],
  "metadata": {
    "hours": 4,
    "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"
    },
    "qpuSeconds": 1080
  },
  "nbformat": 4,
  "nbformat_minor": 5
}