{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "eb419bc5-908c-4f0d-a4ff-c4d13f04e332",
      "metadata": {
        "tags": []
      },
      "source": [
        "---\n",
        "title: \"Stima dell'energia dello stato fondamentale della catena di Heisenberg con VQE\"\n",
        "description: \"Creare, implementare ed eseguire un modello Qiskit per simulare una catena di Heisenberg e stimarne l'energia dello stato fondamentale.\"\n",
        "---\n",
        "\n",
        "{/* cspell:ignore hyperparameters, forall, nabla, nparams */}\n",
        "\n",
        "<span id=\"ground-state-energy-estimation-of-the-heisenberg-chain-with-vqe\" />\n",
        "\n",
        "# Stima dell'energia dello stato fondamentale della catena di Heisenberg con VQE\n",
        "\n",
        "*Stima dell'utilizzo: 37 minuti su un processore Heron (NOTA: si tratta solo di una stima. (La durata potrebbe variare.)*\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "e28ad544",
      "metadata": {},
      "source": [
        "<span id=\"learning-outcomes\" />\n",
        "\n",
        "## Risultati di apprendimento\n",
        "\n",
        "Una volta completato questo tutorial, avrai acquisito le seguenti conoscenze:\n",
        "\n",
        "* Come modellare una catena di spin di Heisenberg come hamiltoniano quantistico utilizzando Qiskit\n",
        "* Come utilizzare l'ottimizzatore SPSA per stimare l'energia dello stato fondamentale di un sistema quantistico\n",
        "* Come eseguire flussi di lavoro variazionali su hardware quantistico IBM® utilizzando primitive e sessioni IBM Quantum\n",
        "\n",
        "<span id=\"prerequisites\" />\n",
        "\n",
        "## Prerequisiti\n",
        "\n",
        "Si consiglia di approfondire i seguenti argomenti:\n",
        "\n",
        "* [Fondamenti dell'informazione quantistica](/learning/courses/basics-of-quantum-information)\n",
        "* [Introduzione ai modelli di Qiskit](/docs/guides/intro-to-patterns)\n",
        "* [Progettazione di algoritmi variazionali](/learning/courses/variational-algorithm-design)\n",
        "\n",
        "<span id=\"background\" />\n",
        "\n",
        "## Sfondo\n",
        "\n",
        "La catena di spin di Heisenberg è uno dei modelli più studiati nella fisica della materia condensata e nel magnetismo quantistico. Descrive un reticolo unidimensionale di spin quantistici interagenti, in cui gli spin più vicini sono accoppiati tramite interazioni di scambio. L'hamiltoniano del modello di Heisenberg isotropo con un campo magnetico esterno è dato da:\n",
        "\n",
        "$H = \\sum_{\\langle i,j \\rangle} \\left( J_x X_i X_j + J_y Y_i Y_j + J_z Z_i Z_j \\right) + \\sum_{i} h_i Z_i,$\n",
        "\n",
        "dove $X_i$, $Y_i$ e $Z_i$ sono gli operatori di Pauli che agiscono sul sito $i$, la somma $\\langle i,j \\rangle$ si estende sulle coppie di vicini più prossimi, $J_x = J_y = J_z = 0.5$ sono le costanti di accoppiamento di scambio (isotropiche in questo tutorial) e $h_i$ rappresenta un campo magnetico esterno dipendente dal sito. In questo tutorial, i valori del campo magnetico vengono campionati in modo casuale nell'intervallo $[-1, 1]$. Si noti che nell'implementazione riportata di seguito, l'insieme delle coppie \"di vicini più prossimi\" è determinato dall'accoppiamento nativo del backend hardware tra i primi $N$ qubit, che potrebbero non formare una catena lineare rigorosa a seconda della topologia del dispositivo.\n",
        "\n",
        "Comprendere l'energia dello stato fondamentale di questo hamiltoniano riveste un'importanza fondamentale in fisica. Lo stato fondamentale codifica informazioni relative alle transizioni di fase quantistiche, alla struttura dell'entanglement e all'ordinamento magnetico. In linea di massima, il calcolo dell’energia esatta dello stato fondamentale diventa impraticabile all’aumentare del numero di spin, poiché la dimensione dello spazio di Hilbert cresce in modo esponenziale secondo la formula $2^N$ per $N$ spin. Questo lo rende un candidato naturale per la simulazione quantistica.\n",
        "\n",
        "Il Variational Quantum Eigensolver (VQE) è un algoritmo ibrido quantistico-classico progettato per stimare l'energia dello stato fondamentale di un hamiltoniano. Il metodo consiste nel preparare uno stato quantistico parametrizzato $|\\psi(\\theta)\\rangle$ (detto \"ansatz\") su un computer quantistico e nel misurare il valore atteso $\\langle \\psi(\\theta) | H | \\psi(\\theta) \\rangle$. Un ottimizzatore classico regola poi iterativamente i parametri $\\theta$ per minimizzare tale energia, sfruttando il principio variazionale che garantisce che l'energia misurata sia sempre un limite superiore dell'energia effettiva dello stato fondamentale.\n",
        "\n",
        "In questo tutorial utilizziamo l'ansatz `efficient_su2` della libreria circuit di Qiskit, che costruisce livelli di rotazioni di singoli qubit e porte di entanglement. L'ottimizzazione viene eseguita utilizzando l'algoritmo SPSA (Simultaneous Perturbation Stochastic Approximation), particolarmente adatto all'hardware quantistico soggetto a rumore poiché stima i gradienti utilizzando solo due valutazioni della funzione per ogni iterazione, indipendentemente dal numero di parametri.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "bc52f763",
      "metadata": {},
      "source": [
        "<span id=\"requirements\" />\n",
        "\n",
        "## Requisiti\n",
        "\n",
        "Prima di iniziare questa esercitazione, assicuratevi di aver installato quanto segue:\n",
        "\n",
        "* Qiskit SDK v2.0 o versioni successive, con supporto [alla visualizzazione](/docs/api/qiskit/visualization)\n",
        "* Qiskit Runtime v0.44 o versioni successive (`pip install qiskit-ibm-runtime`)\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a46e9e3e",
      "metadata": {},
      "source": [
        "<span id=\"setup\" />\n",
        "\n",
        "## Configura\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "id": "e7754922",
      "metadata": {},
      "outputs": [],
      "source": [
        "import numpy as np\n",
        "import matplotlib.pyplot as plt\n",
        "from typing import Sequence\n",
        "\n",
        "from qiskit import QuantumCircuit\n",
        "from qiskit.quantum_info import SparsePauliOp\n",
        "from qiskit.primitives import BaseEstimatorV2\n",
        "from qiskit.circuit.library import XGate\n",
        "from qiskit.circuit.library import efficient_su2\n",
        "from qiskit.transpiler import PassManager\n",
        "from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager\n",
        "from qiskit.transpiler.passes.scheduling import (\n",
        "    ALAPScheduleAnalysis,\n",
        "    PadDynamicalDecoupling,\n",
        ")\n",
        "from qiskit_ibm_runtime import QiskitRuntimeService, Session, EstimatorV2\n",
        "\n",
        "\n",
        "def visualize_results(results):\n",
        "    plt.plot(results[\"cost_history\"], lw=2)\n",
        "    plt.xlabel(\"Number of function evaluations\")\n",
        "    plt.ylabel(\"Energy\")\n",
        "    plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "890d9c81",
      "metadata": {},
      "source": [
        "<span id=\"small-scale-example\" />\n",
        "\n",
        "## Esempio su piccola scala\n",
        "\n",
        "In questa sezione, esamineremo passo dopo passo il modello Qiskit su piccola scala, illustrando gli elementi chiave man mano che sviluppiamo il flusso di lavoro.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "132fb15f-10b4-4d7e-83d8-f512a6f675d1",
      "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",
        "* Ingresso: Numero di rotazioni\n",
        "* Uscita: Ansatz e Hamiltoniana che modella la catena di Heisenberg\n",
        "\n",
        "Costruire un ansatz e un hamiltoniano che descrivano una catena di Heisenberg a 10 spin. In questa fase, costruiremo un hamiltoniano di Heisenberg a 10 spin sulla mappa di accoppiamento del backend meno carico e prepareremo l'ansatz `efficient_su2` .\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "7e8d2f10-f1d6-4ec2-bac9-9db23499c9e1",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/spin-chain-vqe/extracted-outputs/7e8d2f10-f1d6-4ec2-bac9-9db23499c9e1-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 2,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "num_spins = 10\n",
        "ansatz = efficient_su2(num_qubits=num_spins, reps=2)\n",
        "\n",
        "service = QiskitRuntimeService()\n",
        "backend = service.least_busy(\n",
        "    operational=True, min_num_qubits=num_spins, simulator=False\n",
        ")\n",
        "\n",
        "coupling = backend.target.build_coupling_map()\n",
        "reduced_coupling = coupling.reduce(list(range(num_spins)))\n",
        "\n",
        "edge_list = reduced_coupling.graph.edge_list()\n",
        "ham_list = []\n",
        "\n",
        "for edge in edge_list:\n",
        "    ham_list.append((\"ZZ\", edge, 0.5))\n",
        "    ham_list.append((\"YY\", edge, 0.5))\n",
        "    ham_list.append((\"XX\", edge, 0.5))\n",
        "\n",
        "for qubit in reduced_coupling.physical_qubits:\n",
        "    ham_list.append((\"Z\", [qubit], np.random.random() * 2 - 1))\n",
        "\n",
        "hamiltonian = SparsePauliOp.from_sparse_list(ham_list, num_qubits=num_spins)\n",
        "\n",
        "ansatz.draw(\"mpl\", style=\"iqp\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ab79119b-5e56-49d8-a20e-1c8e665baec0",
      "metadata": {},
      "source": [
        "<span id=\"step-2-optimize-problem-for-quantum-hardware-execution\" />\n",
        "\n",
        "### Fase 2: Ottimizzazione del problema per l'esecuzione su hardware quantistico\n",
        "\n",
        "* Ingresso: Circuito astratto, osservabile\n",
        "* Uscita: Circuito di destinazione e osservabile, ottimizzato per la QPU selezionata\n",
        "\n",
        "Utilizzare la funzione `generate_preset_pass_manager` di Qiskit per generare automaticamente una routine di ottimizzazione per il nostro circuito rispetto alla QPU selezionata. Abbiamo scelto `optimization_level=3`, che offre il massimo livello di ottimizzazione dei gestori di passaggi preimpostati. Includiamo anche passaggi di programmazione `ALAPScheduleAnalysis` e `PadDynamicalDecoupling` per sopprimere gli errori di decoerenza.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "id": "a0a5f1c8-5c31-4d9f-ae81-37bd67271d44",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/spin-chain-vqe/extracted-outputs/a0a5f1c8-5c31-4d9f-ae81-37bd67271d44-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 3,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "target = backend.target\n",
        "pm = generate_preset_pass_manager(optimization_level=3, target=target)\n",
        "pm.scheduling = PassManager(\n",
        "    [\n",
        "        ALAPScheduleAnalysis(durations=target.durations()),\n",
        "        PadDynamicalDecoupling(\n",
        "            durations=target.durations(),\n",
        "            dd_sequence=[XGate(), XGate()],\n",
        "            pulse_alignment=target.pulse_alignment,\n",
        "        ),\n",
        "    ]\n",
        ")\n",
        "isa_ansatz = pm.run(ansatz)\n",
        "isa_observable = hamiltonian.apply_layout(isa_ansatz.layout)\n",
        "isa_ansatz.draw(\"mpl\", scale=0.6, style=\"iqp\", fold=-1, idle_wires=False)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "9e889d0b-30b5-4e6b-84c9-d1f096abf132",
      "metadata": {},
      "source": [
        "<span id=\"step-3-execute-using-qiskit-primitives\" />\n",
        "\n",
        "### Passaggio 3: eseguire utilizzando Qiskit primitives\n",
        "\n",
        "* Ingresso: Circuito target e osservabile\n",
        "* Output: Risultati dell'ottimizzazione\n",
        "\n",
        "Ridurre al minimo l'energia stimata dello stato fondamentale del sistema ottimizzando i parametri del circuito. Utilizza la primitiva `Estimator` `IBM Quantum` per calcolare la funzione di costo durante l'ottimizzazione.\n",
        "\n",
        "Poiché abbiamo ottimizzato il circuito per il backend nel Passo 2, possiamo evitare di eseguire la transpilazione sul server Quantum Compute impostando e `skip_transpilation=True` passando il circuito ottimizzato. Per questa demo, eseguiremo il codice su una QPU utilizzando le primitive \" IBM Quantum \". Per eseguire il codice con primitive basate `qiskit` su vettori di stato, sostituire il blocco di codice che utilizza primitive di tipo “ IBM Quantum ” con il blocco commentato.\n",
        "\n",
        "In questo tutorial utilizziamo l'SPSA (Simultaneous Perturbation Stochastic Approximation), un ottimizzatore basato sul gradiente. Di seguito ne forniremo una breve introduzione e il codice per implementare l'SPSA utilizzando Qiskit v2.0.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "64f97e6f",
      "metadata": {},
      "source": [
        "<span id=\"introducing-spsa\" />\n",
        "\n",
        "### Vi presentiamo SPSA\n",
        "\n",
        "L'approssimazione stocastica a perturbazione simultanea (SPSA) [\\[1\\]](#references) è un algoritmo di ottimizzazione che approssima l'intero vettore del gradiente utilizzando solo due chiamate di funzione ad ogni iterazione. Sia $f:\\mathbb{R}^p\\rightarrow \\mathbb{R}$ la funzione di costo con $p$ i parametri da ottimizzare, e sia $x_i\\in \\mathbb{R}^p$ il vettore dei parametri al $i^{th}$ passo dell'iterazione. Per calcolare il gradiente, viene creato un vettore casuale $\\Delta_i$ di dimensione $p$, in cui ciascun elemento $\\Delta_{ij}$, $\\forall$ $j\\in \\{1,2,...,p\\}$ è campionato uniformemente da $\\{-1, 1\\}$. Successivamente, ciascun elemento del vettore casuale $\\Delta_i$ viene moltiplicato per un valore piccolo $c_i$ per generare una perturbazione casuale. Il gradiente viene quindi stimato come\n",
        "\n",
        "$[\\nabla f(x_i)]_j \\approx \\frac{f(x_i + c_i \\Delta_i) - f(x_i - c_i \\Delta_i)}{2c_i\\Delta_{ij}}.$\n",
        "\n",
        "Intuitivamente, dato che durante la stima del gradiente viene applicata una perturbazione casuale, ci si aspetta che piccole deviazioni nei valori esatti dell' $f$ e, dovute al rumore, possano essere tollerate e prese in considerazione. Infatti, l'SPSA è noto soprattutto per la sua resistenza al rumore e richiede solo due operazioni hardware per ogni iterazione. Si tratta, quindi, di uno degli ottimizzatori più apprezzati per l'implementazione di algoritmi variazionali.\n",
        "\n",
        "In questo tutorial, gli iperparametri per l'iterazione dell' $i^{th}$, $a_i$ e $c_i$, vengono calcolati come\n",
        "\n",
        "$a_i = \\frac{a}{(A + i + 1)^\\alpha} \\quad \\text{and} \\quad c_i = \\frac{c}{(i+1)^\\gamma},$\n",
        "\n",
        "dove i valori costanti sono fissati rispettivamente a $A = 30$, $\\alpha = 0.9$, $a = 0.3$, $c = 0.1$ e $\\gamma = 0.4$. Questi valori sono stati ricavati da [\\[2\\]](#references). Per ottenere buone prestazioni dall'SPSA è necessario un adeguato aggiustamento degli iperparametri.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "id": "73a9352c",
      "metadata": {},
      "outputs": [],
      "source": [
        "def spsa(\n",
        "    fun, x0, args=(), A=30, alpha=0.9, a=0.3, c=0.1, gamma=0.4, maxiter=100\n",
        "):\n",
        "    nparams = len(x0)\n",
        "    x = np.copy(x0)\n",
        "\n",
        "    for i in range(maxiter):\n",
        "        a_i = a / (A + i + 1) ** alpha\n",
        "        c_i = c / (i + 1) ** gamma\n",
        "        delta_i = np.random.choice([-1, 1], nparams)\n",
        "\n",
        "        # two hardware calls\n",
        "        eval_1 = fun(x + c_i * delta_i, *args)\n",
        "        eval_2 = fun(x - c_i * delta_i, *args)\n",
        "\n",
        "        # compute the gradient and update the parameters\n",
        "        grad = (eval_1 - eval_2) / (2 * c_i) * np.reciprocal(delta_i)\n",
        "        x = x - a_i * grad\n",
        "\n",
        "    return x"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 8,
      "id": "32ca3b6a",
      "metadata": {},
      "outputs": [],
      "source": [
        "def cost_func(\n",
        "    params: Sequence,\n",
        "    ansatz: QuantumCircuit,\n",
        "    hamiltonian: SparsePauliOp,\n",
        "    estimator: BaseEstimatorV2,\n",
        "    cost_history_dict: dict,\n",
        ") -> float:\n",
        "    \"\"\"Ground state energy evaluation.\"\"\"\n",
        "    energy = (\n",
        "        estimator.run([(ansatz, hamiltonian, [params])]).result()[0].data.evs\n",
        "    )\n",
        "\n",
        "    cost_history_dict[\"iters\"] += 1\n",
        "    cost_history_dict[\"prev_vector\"] = list(params)\n",
        "    cost_history_dict[\"cost_history\"].append(float(energy[0]))\n",
        "\n",
        "    print(\n",
        "        f\"Fx Iters. done: {cost_history_dict['iters']} [Current cost: {round(energy[0], 5)}]\",\n",
        "        end=\"\\r\",\n",
        "    )\n",
        "\n",
        "    return energy\n",
        "\n",
        "\n",
        "def solve(x0, isa_ansatz, isa_observable, maxiter=150):\n",
        "    cost_history_dict = {\n",
        "        \"prev_vector\": None,\n",
        "        \"iters\": 0,\n",
        "        \"cost_history\": [],\n",
        "        \"y_min\": None,\n",
        "    }\n",
        "\n",
        "    # Evaluate the problem using a QPU via `qiskit-ibm-runtime`\n",
        "    with Session(backend=backend) as session:\n",
        "        estimator = EstimatorV2(mode=session)\n",
        "        estimator.skip_transpilation = True\n",
        "        estimator.options.environment.job_tags = [\"TUT_HSVQE\"]\n",
        "        x_opt = spsa(\n",
        "            cost_func,\n",
        "            x0=x0,\n",
        "            args=(isa_ansatz, isa_observable, estimator, cost_history_dict),\n",
        "            maxiter=maxiter,\n",
        "        )\n",
        "\n",
        "        y_min = cost_func(\n",
        "            x_opt, isa_ansatz, isa_observable, estimator, cost_history_dict\n",
        "        )\n",
        "\n",
        "    return y_min, cost_history_dict"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 9,
      "id": "f418b372",
      "metadata": {},
      "outputs": [],
      "source": [
        "np.random.seed(42)\n",
        "num_params = ansatz.num_parameters\n",
        "params = 2 * np.pi * np.random.random(num_params)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "3bf42923",
      "metadata": {},
      "source": [
        "Qui impostiamo il `maxiter = 50`. Si noti che, poiché ogni iterazione richiede due chiamate alla funzione per calcolare il gradiente, il numero totale di chiamate alla funzione sarà pari a $2 \\times \\text{maxiter}$. Il valore `maxiter` può essere aumentato a un valore qualsiasi superiore per ottenere una stima più accurata dell'energia.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 10,
      "id": "1732ce37",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Fx Iters. done: 101 [Current cost: -3.03843]"
          ]
        }
      ],
      "source": [
        "maxiter = 50\n",
        "spsa_min, spsa_history = solve(\n",
        "    params, isa_ansatz, isa_observable, maxiter=maxiter\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "33abbb3f-6245-4610-a05d-e2bc4cc551f0",
      "metadata": {},
      "source": [
        "<span id=\"step-4-post-process-and-return-result-in-desired-classical-format\" />\n",
        "\n",
        "### Fase 4: Post-elaborazione e restituzione del risultato nel formato classico desiderato\n",
        "\n",
        "* Dati di input: stime dell'energia dello stato fondamentale durante l'ottimizzazione\n",
        "* Risultato: Energia stimata dello stato fondamentale\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 11,
      "id": "e5b58771-d543-4e75-9746-fbc7b28e4360",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Estimated ground state energy: [-3.03842968]\n"
          ]
        }
      ],
      "source": [
        "print(f\"Estimated ground state energy: {spsa_min}\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 12,
      "id": "ecd7762a",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/spin-chain-vqe/extracted-outputs/ecd7762a-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "results = {\n",
        "    \"spsa\": spsa_history,\n",
        "}\n",
        "\n",
        "visualize_results(spsa_history)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a3f4171f",
      "metadata": {},
      "source": [
        "<span id=\"large-scale-hardware-example\" />\n",
        "\n",
        "## Esempio di hardware su larga scala\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "746e6133",
      "metadata": {},
      "source": [
        "In questo tutorial non è incluso alcun esempio di hardware su larga scala. Con l'aumentare del numero di qubit, il VQE deve affrontare sfide significative a causa del fenomeno [del \"plateau sterile](/learning/courses/variational-algorithm-design/optimization-loops#barren-plateaus) \": il gradiente della funzione di costo svanisce in modo esponenziale con l'aumentare delle dimensioni del sistema, rendendo l'ottimizzazione praticamente impossibile per i circuiti di grandi dimensioni. Se a ciò si aggiunge il rumore dell'hardware, ciò significa che l'estensione del VQE a catene di spin più lunghe non produce risultati affidabili e riproducibili. Per gli approcci che superano tali limiti, consultare la sezione \"Prossimi passi\" qui di seguito.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "8f14492c",
      "metadata": {},
      "source": [
        "<span id=\"challenge\" />\n",
        "\n",
        "## Sfida\n",
        "\n",
        "Ora che disponi di un'implementazione funzionante di VQE per la catena di Heisenberg, prova a fare quanto segue:\n",
        "\n",
        "1. **Prova a variare la profondità dell'ansatz:** modifica il `reps` parametro in `efficient_su2` (ad esempio, prova `reps=1` con e `reps=3`). In che modo la profondità dell'ansatz influisce sull'energia stimata dello stato fondamentale e sulla velocità di convergenza? A che punto si osservano rendimenti decrescenti o instabilità?\n",
        "2. **Ottimizzare gli iperparametri di SPSA:** regolare i parametri della curva di apprendimento (`a`, `c`, `alpha`, `gamma`, `A`) e osservare come influiscono sulla convergenza. Riesci a trovare una configurazione che converga più rapidamente rispetto alle impostazioni predefinite utilizzate qui?\n",
        "3. **Confronta le topologie di accoppiamento:** invece di utilizzare la mappa di accoppiamento nativa del backend, prova a costruire una semplice catena lineare dei vicini più prossimi e confronta i risultati. In che modo la connettività dell'hardware fisico influisce sulla profondità del circuito transpilato e sulla stima finale del consumo energetico?\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "217a9379",
      "metadata": {},
      "source": [
        "<span id=\"references\" />\n",
        "\n",
        "## Riferimenti\n",
        "\n",
        "\\[1] Spall, J. C. (2002). Implementazione dell'algoritmo di perturbazione simultanea per l'ottimizzazione stocastica.\n",
        "IEEE Transactions on Aerospace and Electronic Systems, 34(3), 817-823.\n",
        "\n",
        "\\[2] Sahin, M. Emre, et al. (2025). Qiskit Machine Learning : una libreria open source per attività di machine learning quantistico su larga scala su hardware quantistico e simulatori classici. arXiv:2505.17756.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "8aefe54c",
      "metadata": {},
      "source": [
        "<span id=\"next-steps\" />\n",
        "\n",
        "## Passi successivi\n",
        "\n",
        "<Admonition type=\"tip\" title=\"Suggerimenti\">\n",
        "  Se questo lavoro ti è sembrato interessante, potrebbero interessarti anche i seguenti contenuti:\n",
        "\n",
        "  * **Prova la diagonalizzazione quantistica basata su campioni (SQD):** come illustrato in questo tutorial, il VQE incontra delle difficoltà su larga scala a causa dei plateau sterili e dell’elevato overhead di misurazione. IBM ha sviluppato [la diagonalizzazione quantistica basata su campioni (SQD)](/docs/addons/qiskit-addon-sqd) come alternativa più scalabile. A differenza del VQE, l’SQD evita del tutto l’ottimizzazione variazionale; al suo posto, un computer quantistico genera dei campioni e un computer classico proietta l’Hamiltoniano su un sottospazio generato da tali campioni e lo diagonalizza. Ciò fornisce un limite superiore all'energia dello stato fondamentale con un numero significativamente inferiore di misurazioni e senza il rischio di incorrere in plateau sterili. Segui il [tutorial SQD](/docs/tutorials/sample-based-quantum-diagonalization) per vedere questo approccio in azione.\n",
        "  * **Scopri il corso sugli algoritmi di diagonalizzazione quantistica:** approfondisci la tua conoscenza sia del VQE che dell'SQD, compresi i relativi compromessi, nel corso [sugli algoritmi di diagonalizzazione quantistica](/learning/courses/quantum-diagonalization-algorithms) su IBM Quantum Learning.\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "id": "a1b8767d",
      "source": "© IBM Corp., 2017-2026"
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3"
    },
    "hours": 1.5,
    "qpuSeconds": 2220
  },
  "nbformat": 4,
  "nbformat_minor": 5
}