{
  "cells": [
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "2a246d14-ed2b-4573-bf97-939c3628b3bb",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Cicli di ottimizzazione\"\n",
        "description: \"Questa lezione illustra l'uso dei classici cicli di ottimizzazione per inserire i parametri nei calcoli quantistici.\"\n",
        "---\n",
        "\n",
        "{/* cspell:ignore nabla */}\n",
        "\n",
        "<span id=\"optimization-loops\" />\n",
        "\n",
        "# Cicli di ottimizzazione\n",
        "\n",
        "In questa lezione impareremo a utilizzare un *ottimizzatore* per esplorare iterativamente gli stati quantistici parametrizzati del nostro ansatz:\n",
        "\n",
        "* Bootstrap di un ciclo di ottimizzazione\n",
        "* Comprendere i compromessi nell'utilizzo di ottimizzatori locali e globali\n",
        "* Esplorare gli altipiani sterili e come evitarli\n",
        "\n",
        "Ad alto livello, gli ottimizzatori sono fondamentali per esplorare il nostro spazio di ricerca. L'ottimizzatore utilizza le valutazioni delle funzioni di costo per selezionare la serie successiva di parametri in un ciclo variazionale e ripete il processo fino a raggiungere uno stato stabile. In questa fase, viene restituito un insieme ottimale di valori dei parametri $\\vec\\theta^*$.\n",
        "\n",
        "![Un diagramma di alcuni fattori importanti nell'ottimizzazione, tra cui i plateau sterili, gli ottimizzatori con o senza gradiente e il bootstrapping.](https://quantum.cloud.ibm.com/learning/images/courses/variational-algorithm-design/optimization-loops/optimization-workflow.svg)\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "110421e7-0d2a-4653-a195-1925c809ca61",
      "metadata": {},
      "source": [
        "<span id=\"local-and-global-optimizers\" />\n",
        "\n",
        "## Ottimizzatori locali e globali\n",
        "\n",
        "Prima di esplorare ogni classe di ottimizzatori, imposteremo il nostro problema. Inizieremo con un circuito contenente otto parametri variazionali:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "id": "15cf7810-1d25-4c54-aff0-91d3a0c51cec",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/courses/variational-algorithm-design/optimization-loops/extracted-outputs/15cf7810-1d25-4c54-aff0-91d3a0c51cec-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 1,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "from qiskit import QuantumCircuit\n",
        "from qiskit.quantum_info import SparsePauliOp\n",
        "from qiskit.circuit.library import TwoLocal\n",
        "import numpy as np\n",
        "\n",
        "theta_list = (2 * np.pi * np.random.rand(1, 8)).tolist()\n",
        "observable = SparsePauliOp.from_list([(\"XX\", 1), (\"YY\", -3)])\n",
        "\n",
        "reference_circuit = QuantumCircuit(2)\n",
        "reference_circuit.x(0)\n",
        "\n",
        "variational_form = TwoLocal(\n",
        "    2,\n",
        "    rotation_blocks=[\"rz\", \"ry\"],\n",
        "    entanglement_blocks=\"cx\",\n",
        "    entanglement=\"linear\",\n",
        "    reps=1,\n",
        ")\n",
        "ansatz = reference_circuit.compose(variational_form)\n",
        "\n",
        "ansatz.decompose().draw(\"mpl\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "950569c0-4e8f-49b5-b118-7cd227bd5ce1",
      "metadata": {},
      "outputs": [],
      "source": [
        "def cost_func_vqe(params, ansatz, hamiltonian, estimator):\n",
        "    \"\"\"Return estimate of energy from estimator\n",
        "\n",
        "    Parameters:\n",
        "        params (ndarray): Array of ansatz parameters\n",
        "        ansatz (QuantumCircuit): Parameterized ansatz circuit\n",
        "        hamiltonian (SparsePauliOp): Operator representation of Hamiltonian\n",
        "        estimator (Estimator): Estimator primitive instance\n",
        "\n",
        "    Returns:\n",
        "        float: Energy estimate\n",
        "    \"\"\"\n",
        "    pub = (ansatz, hamiltonian, params)\n",
        "    cost = estimator.run([pub]).result()[0].data.evs\n",
        "    return cost"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "id": "f536ee50-373f-4873-b258-66c5c07c65c5",
      "metadata": {},
      "outputs": [],
      "source": [
        "from qiskit.primitives import StatevectorEstimator\n",
        "\n",
        "estimator = StatevectorEstimator()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c9dcec4d-5b84-4731-8b5a-cb8a0f7572d5",
      "metadata": {},
      "source": [
        "<span id=\"local-optimizers\" />\n",
        "\n",
        "### Ottimizzatori locali\n",
        "\n",
        "Gli ottimizzatori locali cercano un punto che minimizzi la funzione di costo a partire da uno o più punti iniziali $C(\\vec{\\theta_0})$ e si spostano su punti diversi in base a ciò che osservano nella regione che stanno valutando nelle iterazioni successive. Ciò implica che la convergenza di questi algoritmi sarà solitamente veloce, ma può dipendere fortemente dal punto iniziale. Gli ottimizzatori locali non sono in grado di vedere oltre la regione in cui stanno valutando e possono essere particolarmente vulnerabili ai minimi locali, segnalando la convergenza quando ne trovano uno e ignorando altri stati con valutazioni più favorevoli.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "id": "447df742-13bd-4d4a-a364-41d34380cbc9",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              " message: Optimization terminated successfully\n",
              " success: True\n",
              "  status: 0\n",
              "     fun: -3.9999999964520634\n",
              "       x: [ 1.000e+00  1.000e+00 -1.571e+00 -4.556e-05 -1.207e+00\n",
              "           -1.935e+00  4.079e-01 -4.079e-01]\n",
              "     nit: 12\n",
              "     jac: [ 0.000e+00  0.000e+00 -7.957e-04  2.543e-04  1.381e-03\n",
              "            1.381e-03  5.430e-04  5.431e-04]\n",
              "    nfev: 112\n",
              "    njev: 12"
            ]
          },
          "execution_count": 4,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "# SciPy minimizer routine\n",
        "from scipy.optimize import minimize\n",
        "\n",
        "x0 = np.ones(8)\n",
        "\n",
        "result = minimize(\n",
        "    cost_func_vqe, x0, args=(ansatz, observable, estimator), method=\"SLSQP\"\n",
        ")\n",
        "\n",
        "result"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "25fee3f4-9d3e-45ce-a104-dfde5e132b2b",
      "metadata": {},
      "source": [
        "<span id=\"global-optimizers\" />\n",
        "\n",
        "### Ottimizzatori globali\n",
        "\n",
        "Gli ottimizzatori globali cercano il punto che minimizza la funzione di costo in diverse regioni del suo dominio (cioè non locali), valutandola in modo iterativo (cioè all'iterazione $i$ ) su un insieme di vettori di parametri $\\Theta_i := \\\\{ {\\vec\\theta_{i,j} | j \\in \\mathcal{J}_\\text{opt}^i} \\\\}$ determinati dall'ottimizzatore. Ciò li rende meno suscettibili ai minimi locali e in qualche modo indipendenti dall'inizializzazione, ma anche significativamente più lenti a convergere verso una soluzione proposta.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "8d794038-8e75-4649-a978-a9e832ebed60",
      "metadata": {},
      "source": [
        "<span id=\"bootstrapping-optimization\" />\n",
        "\n",
        "### Ottimizzazione del bootstrapping\n",
        "\n",
        "*Il bootstrapping*, ovvero l'impostazione del valore iniziale dei parametri $\\vec\\theta$ sulla base di un'ottimizzazione precedente, può aiutare il nostro ottimizzatore a convergere più rapidamente verso una soluzione. Si parla di punto iniziale $\\vec\\theta_0$ e di $|\\psi(\\vec\\theta_0)\\rangle = U_V(\\vec\\theta_0)|\\rho\\rangle$ come stato iniziale. Questo stato iniziale differisce dal nostro stato di riferimento $|\\rho\\rangle$, poiché il primo si concentra sui parametri iniziali impostati durante il nostro ciclo di ottimizzazione, mentre il secondo si concentra sull'utilizzo di soluzioni \"di riferimento\" note. Possono coincidere se $U_V(\\vec\\theta_0) \\equiv I$ (cioè l'operazione di identità).\n",
        "\n",
        "Quando gli ottimizzatori locali convergono verso minimi locali non ottimali, possiamo provare a fare il bootstrap dell'ottimizzazione a livello globale e a perfezionare la convergenza a livello locale. Sebbene ciò richieda l'impostazione di due carichi di lavoro variazionali, consente all'ottimizzatore di trovare una soluzione più ottimale rispetto al solo ottimizzatore locale.\n",
        "\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "07ba982e-9280-4e15-9d8c-4b6460b1deea",
      "metadata": {
        "gloss": {
          "hyperparameter": {
            "text": "A hyperparameter is a parameter that we use to control our algorithm. The ‘hyper’ distinguishes it from the parameters (θ) that our algorithm is trying to find.",
            "title": "Hyperparameter"
          },
          "local-minimum": {
            "text": "A local minimum is the lowest point of the function, for a small range of values of theta. In contrast, a global minimum is <i>the</i> lowest point, anywhere in our function (that is, for any value of theta). <a href='https://en.wikipedia.org/wiki/Maxima_and_minima'>Read more</a>.",
            "title": "Local minimum"
          }
        }
      },
      "source": [
        "<span id=\"gradient-based-and-gradient-free-optimizers\" />\n",
        "\n",
        "## Ottimizzatori basati sul gradiente e senza gradiente\n",
        "\n",
        "<span id=\"gradient-based\" />\n",
        "\n",
        "### Basato sul gradiente\n",
        "\n",
        "Per la nostra funzione di costo $C(\\vec\\theta)$, se abbiamo accesso al gradiente della funzione $\\vec{\\nabla} C(\\vec\\theta)$ a partire da un punto iniziale, il modo più semplice per minimizzare la funzione è aggiornare i parametri verso la direzione di massima discesa della funzione. Cioè, aggiorniamo i parametri come $\\vec\\theta_{n+1} = \\vec\\theta_n - \\eta \\vec{\\nabla} C(\\vec\\theta)$, dove $\\eta$ è il tasso di apprendimento - un piccolo <DefinitionTooltip definition=\"Un iperparametro è un parametro che utilizziamo per controllare il nostro algoritmo. Il termine iper lo distingue dai parametri (θ) che il nostro algoritmo cerca di trovare.\">iperparametro</DefinitionTooltip> positivo che controlla la dimensione dell'aggiornamento. Continuiamo a farlo finché non convergiamo a un <DefinitionTooltip definition=\"Un minimo locale è il punto più basso della funzione, per un piccolo intervallo di valori di theta. Al contrario, un minimo globale è il punto più basso, in qualsiasi punto della nostra funzione (cioè per qualsiasi valore di θ).\">minimo locale</DefinitionTooltip> della funzione di costo, $C({\\vec\\theta^*})$.\n",
        "\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "0897260d-f3f8-4caa-8a79-f258e41f8e21",
      "metadata": {},
      "source": [
        "Possiamo utilizzare questa funzione di costo e un ottimizzatore per calcolare i parametri ottimali\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 5,
      "id": "aad81101-3c19-4946-8453-3db9df3369c0",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "  message: Optimization terminated successfully.\n",
              "  success: True\n",
              "   status: 0\n",
              "      fun: -3.9999999999997025\n",
              "        x: [ 1.000e+00  1.000e+00  1.571e+00  3.220e-07  2.009e-01\n",
              "            -2.009e-01  6.342e-01 -6.342e-01]\n",
              "      nit: 14\n",
              "      jac: [-1.192e-07 -2.980e-08  8.345e-07  1.103e-06  5.960e-08\n",
              "             0.000e+00 -5.960e-08  2.980e-08]\n",
              " hess_inv: [[ 1.000e+00  1.872e-10 ...  5.077e-05  3.847e-05]\n",
              "            [ 1.872e-10  1.000e+00 ... -5.208e-05 -4.060e-05]\n",
              "            ...\n",
              "            [ 5.077e-05 -5.208e-05 ...  7.243e-01 -2.604e-01]\n",
              "            [ 3.847e-05 -4.060e-05 ... -2.604e-01  8.179e-01]]\n",
              "     nfev: 144\n",
              "     njev: 16"
            ]
          },
          "execution_count": 5,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "# SciPy minimizer routine\n",
        "from scipy.optimize import minimize\n",
        "\n",
        "x0 = np.ones(8)\n",
        "\n",
        "result = minimize(\n",
        "    cost_func_vqe, x0, args=(ansatz, observable, estimator), method=\"BFGS\"\n",
        ")\n",
        "\n",
        "result"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "0f7d55f8-e3a2-4223-b456-17b39a81f524",
      "metadata": {},
      "source": [
        "I principali svantaggi di questo tipo di ottimizzazione sono la velocità di convergenza, che può essere molto lenta, e la mancanza di garanzia di raggiungere la soluzione ottimale.\n",
        "\n",
        "![grafico di f(theta) contro theta, i punti multipli mostrano diversi stati di un algoritmo di discesa del gradiente che trova il minimo di una curva.](https://quantum.cloud.ibm.com/learning/images/courses/variational-algorithm-design/optimization-loops/optimization-gradient-descent.svg)\n",
        "\n",
        "<span id=\"gradient-free\" />\n",
        "\n",
        "### Senza gradiente\n",
        "\n",
        "Gli algoritmi di ottimizzazione senza gradiente non richiedono informazioni sul gradiente e possono essere utili in situazioni in cui il calcolo del gradiente è difficile, costoso o troppo rumoroso. Inoltre, tendono a essere più robusti nel trovare gli ottimali globali, mentre i metodi basati sul gradiente tendono a convergere verso gli ottimali locali. Esploreremo alcuni casi in cui un ottimizzatore senza gradiente può aiutare a evitare i plateau sterili. Tuttavia, i metodi privi di gradiente richiedono risorse computazionali più elevate, soprattutto per problemi con spazi di ricerca altamente dimensionali.\n",
        "\n",
        "Ecco un esempio che utilizza l'ottimizzatore [`COBYLA`](https://docs.scipy.org/doc/scipy/reference/optimize.minimize-cobyla.html#optimize-minimize-cobyla) invece dell'ottimizzatore:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 6,
      "id": "c749695b-3124-47d8-8a23-10c9d5965a7f",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              " message: Optimization terminated successfully.\n",
              " success: True\n",
              "  status: 1\n",
              "     fun: -3.999999973369678\n",
              "       x: [ 1.631e+00  1.492e+00  1.571e+00  3.142e+00  1.375e+00\n",
              "           -1.767e+00  1.484e+00  1.658e+00]\n",
              "    nfev: 137\n",
              "   maxcv: 0.0"
            ]
          },
          "execution_count": 6,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "# SciPy minimizer routine\n",
        "from scipy.optimize import minimize\n",
        "\n",
        "x0 = np.ones(8)\n",
        "\n",
        "result = minimize(\n",
        "    cost_func_vqe, x0, args=(ansatz, observable, estimator), method=\"COBYLA\"\n",
        ")\n",
        "\n",
        "result"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "396d0617-9f20-48cb-b57c-30dfa54293ff",
      "metadata": {
        "gloss": {
          "barren-plateaus": {
            "text": "When gradients of parametrized quantum circuits become exponentially small with respect to the number of qubits, making optimization difficult and potentially impossible.",
            "title": "Barren Plateaus"
          }
        }
      },
      "source": [
        "<span id=\"barren-plateaus\" />\n",
        "\n",
        "## Altipiani aridi\n",
        "\n",
        "In effetti, il panorama dei costi può essere piuttosto complicato, come dimostrano le colline e le valli dell'esempio seguente. Il metodo di ottimizzazione ci fa navigare nel panorama dei costi, alla ricerca del minimo, come mostrato dai punti e dalle linee nere. Si può notare che due delle tre ricerche finiscono in un minimo locale del paesaggio, piuttosto che in uno globale.\n",
        "\n",
        "![Un complicato collettore curvo con molti picchi e avvallamenti.](https://quantum.cloud.ibm.com/learning/images/courses/variational-algorithm-design/optimization-loops/optimization-loss-landscape.svg)\n",
        "\n",
        "Indipendentemente dal tipo di metodo di ottimizzazione utilizzato, se il panorama dei costi è relativamente piatto, può essere difficile per il metodo determinare la direzione di ricerca appropriata. Questo scenario viene definito <DefinitionTooltip definition=\"Quando i gradienti dei circuiti quantistici parametrizzati diventano esponenzialmente piccoli rispetto al numero di qubit, rendendo l'ottimizzazione difficile e potenzialmente impossibile.\">un altopiano brullo,</DefinitionTooltip>, dove il panorama dei costi diventa progressivamente più piatto (e quindi più difficile da determinare la direzione verso il minimo). Per un'ampia gamma di circuiti quantistici parametrizzati, la probabilità che il gradiente lungo qualsiasi direzione ragionevole sia non nullo con una precisione fissa diminuisce esponenzialmente all'aumentare del numero di qubit.\n",
        "\n",
        "![Un diagramma di un altopiano geografico rispetto al pendio di una montagna, per spiegare perché una pendenza ci aiuta a trovare un minimo e un altopiano ostacola i nostri sforzi.](https://quantum.cloud.ibm.com/learning/images/courses/variational-algorithm-design/optimization-loops/optimization-barren-plateaus.svg)\n",
        "\n",
        "Anche se quest'area è ancora oggetto di ricerca attiva, abbiamo alcune raccomandazioni per migliorare le prestazioni dell'ottimizzazione:\n",
        "\n",
        "* **Il bootstrapping** può aiutare il ciclo di ottimizzazione a non rimanere bloccato in uno spazio di parametri in cui il gradiente è piccolo.\n",
        "* **Sperimentazione di ansatz efficienti dal punto di vista hardware** : poiché stiamo usando un sistema quantistico rumoroso come oracolo black-box, la qualità di queste valutazioni può influenzare le prestazioni dell'ottimizzatore. L'utilizzo di ansatz efficienti dal punto di vista hardware, come ad esempio [`EfficientSU2`](/docs/api/qiskit/qiskit.circuit.library.EfficientSU2)può evitare di produrre gradienti esponenzialmente piccoli.\n",
        "* **Sperimentazione con la soppressione e la mitigazione degli errori** : le primitive di `IBM Quantum` offrono un'interfaccia semplice per sperimentare con vari valori rispettivamente per `optimization_level` e `resilience_setting`. Ciò può ridurre l'impatto del rumore e rendere più efficiente il processo di ottimizzazione.\n",
        "* **Sperimentazione di ottimizzatori senza gradiente** : A differenza degli algoritmi di ottimizzazione basati sul gradiente, gli ottimizzatori come `COBYLA` non si basano sulle informazioni sul gradiente per ottimizzare i parametri e quindi hanno meno probabilità di essere influenzati dal plateau sterile.\n",
        "\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "182110bc-f952-4460-8048-d1df961ba860",
      "metadata": {},
      "source": [
        "<span id=\"summary\" />\n",
        "\n",
        "## Riepilogo\n",
        "\n",
        "In questa lezione avete imparato a definire il vostro ciclo di ottimizzazione:\n",
        "\n",
        "* Bootstrap di un ciclo di ottimizzazione\n",
        "* Comprendere i compromessi nell'utilizzo di ottimizzatori locali e globali\n",
        "* Esplorare gli altipiani sterili e come evitarli\n",
        "\n",
        "Il nostro carico di lavoro variazionale di alto livello è completo:\n",
        "\n",
        "![Un circuito quantistico ora con un'unità per preparare lo stato di riferimento e una seconda unità per variare lo stato usando parametri variazionali.](https://quantum.cloud.ibm.com/learning/images/courses/variational-algorithm-design/optimization-loops/optimization-circuit.svg)\n",
        "\n",
        "Successivamente, esploreremo algoritmi variazionali specifici tenendo conto di questo quadro.\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"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 2
}