{
  "cells": [
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "da8ce695-8435-4099-b0fd-ade7b298f540",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Funzioni di costo\"\n",
        "description: \"Questa lezione spiega cos'è una funzione di costo, come viene utilizzata negli algoritmi variazionali e in che modo può differire dall'Hamiltoniano.\"\n",
        "---\n",
        "\n",
        "{/* cspell:ignore resil nbsp eigenbasis expvals IIZZ IZIZ IZZI ZIIZ ZZII */}\n",
        "\n",
        "<span id=\"cost-functions\" />\n",
        "\n",
        "# Funzioni di costo\n",
        "\n",
        "In questa lezione impareremo a valutare una *funzione di costo* :\n",
        "\n",
        "* Per prima cosa, vedremo [le primitive di \" IBM Quantum \"](/docs/guides/primitives)\n",
        "* Definire una *funzione di costo* $C(\\vec\\theta)$. Si tratta di una funzione specifica del problema che definisce l'obiettivo del problema che l'ottimizzatore deve minimizzare (o massimizzare)\n",
        "* Definizione di una strategia di misurazione utilizzando le primitive di IBM Quantum per ottimizzare il rapporto tra velocità e precisione\n",
        "\n",
        " \n",
        "\n",
        "![Un diagramma che mostra i componenti chiave di una funzione di costo, compreso l'uso di primitive come lo stimatore e il campionatore.](https://quantum.cloud.ibm.com/learning/images/courses/variational-algorithm-design/cost-functions/cost-function-workflow.svg)\n",
        "\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "4a9b79a9-c040-43d3-a4cf-314277e30eea",
      "metadata": {
        "gloss": {
          "primitives": {
            "text": "Basic and fundamental operations or data type. Qiskit has the Sampler and Estimator primitives to serve as building blocks to easily construct complex workloads, like variational algorithms.",
            "title": "Primitives"
          }
        }
      },
      "source": [
        "<span id=\"primitives\" />\n",
        "\n",
        "## Primitivi\n",
        "\n",
        "Tutti i sistemi fisici, sia classici che quantistici, possono esistere in diversi stati. Ad esempio, un'auto su strada può avere una certa massa, posizione, velocità o accelerazione che ne caratterizzano lo stato. Allo stesso modo, anche i sistemi quantistici possono avere diverse configurazioni o stati, ma differiscono dai sistemi classici per il modo in cui trattiamo le misure e l'evoluzione dello stato. Questo porta a proprietà uniche, come la *sovrapposizione* e l' *entanglement*, che sono esclusive della meccanica quantistica. Così come possiamo descrivere lo stato di un'automobile usando proprietà fisiche come la velocità o l'accelerazione, possiamo anche descrivere lo stato di un sistema quantistico usando *osservabili*, che sono oggetti matematici.\n",
        "\n",
        "In meccanica quantistica, gli stati sono rappresentati da vettori colonna complessi normalizzati, o *ket* ( $|\\psi\\rangle$ ), e le osservabili sono operatori lineari hermitiani ( $\\hat{H}=\\hat{H}^{\\dagger}$ ) che agiscono sui ket. Un autovettore ( $|\\lambda\\rangle$ ) di un'osservabile è noto come *autostato*. Misurando un'osservabile per uno dei suoi autostati ( $|\\lambda\\rangle$ ) si ottiene come lettura l'autovalore corrispondente ( $\\lambda$ ).\n",
        "\n",
        "Se vi state chiedendo come misurare un sistema quantistico e cosa potete misurare, Qiskit offre due <DefinitionTooltip definition=\"Operazioni di base e fondamentali o tipo di dati. Qiskit dispone delle primitive Sampler ed Estimator, che servono come elementi costitutivi per costruire facilmente carichi di lavoro algoritmici complessi.\">Primitivi</DefinitionTooltip> che possono aiutarvi:\n",
        "\n",
        "* `Sampler`: Dato uno stato quantistico $|\\psi\\rangle$, questa primitiva ottiene la probabilità di ogni possibile stato base computazionale.\n",
        "* `Estimator`: Data un'osservabile quantistica $\\hat{H}$ e uno stato $|\\psi\\rangle$, questa primitiva calcola il valore atteso di $\\hat{H}$.\n",
        "\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "00aa7dfa-34f1-440e-bbdb-19b6e6a1aff8",
      "metadata": {},
      "source": [
        "<span id=\"the-sampler-primitive\" />\n",
        "\n",
        "### Il primitivo Sampler\n",
        "\n",
        "La primitiva `Sampler` calcola la probabilità di ottenere ogni possibile stato $|k\\rangle$ dalla base computazionale, dato un circuito quantistico che prepara lo stato $|\\psi\\rangle$. Calcola\n",
        "\n",
        "$$\n",
        "p_k = |\\langle k | \\psi \\rangle|^2 \\quad \\forall k \\in \\mathbb{Z}_2^n \\equiv \\{0,1,\\cdots,2^n-1\\},\n",
        "$$\n",
        "\n",
        "Dove $n$ è il numero di qubit e $k$ la rappresentazione intera di ogni possibile stringa binaria in uscita $\\{0,1\\}^n$ (cioè, numeri interi in base $2$ ).\n",
        "\n",
        "L' IBM Quantum [`Sampler`](/docs/api/qiskit-ibm-runtime/sampler-v2) esegue più volte il circuito su un dispositivo quantistico, effettuando misurazioni ad ogni esecuzione e ricostruendo la distribuzione di probabilità a partire dalle stringhe di bit recuperate. Più cicli (o *tentativi* ) viene eseguito, più accurati saranno i risultati, ma ciò richiede più tempo e risorse quantistiche.\n",
        "\n",
        "Tuttavia, poiché il numero di uscite possibili cresce esponenzialmente con il numero di qubit $n$ (cioè $2^n$ ), anche il numero di scatti dovrà crescere esponenzialmente per catturare una distribuzione di probabilità *densa*. Pertanto, `Sampler` è efficiente solo per distribuzioni di probabilità *rade*; dove lo stato target $|\\psi\\rangle$ deve essere esprimibile come una combinazione lineare degli stati della base computazionale, con un numero di termini che cresce al massimo in modo polinomiale con il numero di qubit:\n",
        "\n",
        "$$\n",
        "|\\psi\\rangle = \\sum^{\\text{Poly}(n)}_k w_k |k\\rangle.\n",
        "$$\n",
        "\n",
        "Il sito `Sampler` può anche essere configurato per recuperare le probabilità da una sottosezione del circuito, che rappresenta un sottoinsieme degli stati totali possibili.\n",
        "\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "96a74b2b-6aa0-4ef1-a8d1-448b591429e4",
      "metadata": {
        "gloss": {
          "pauli": {
            "text": "Set of matrices commonly used in quantum computing to represent and manipulate quantum states, consisting of the identity matrix and the three Pauli matrices (X, Y, and Z).",
            "title": "Pauli Operators"
          }
        }
      },
      "source": [
        "<span id=\"the-estimator-primitive\" />\n",
        "\n",
        "### Il primitivo Estimator\n",
        "\n",
        "La primitiva `Estimator` calcola il valore di aspettativa di un'osservabile $\\hat{H}$ per uno stato quantistico $|\\psi\\rangle$; dove le probabilità dell'osservabile possono essere espresse come $p_\\lambda = |\\langle\\lambda|\\psi\\rangle|^2$, essendo $|\\lambda\\rangle$ gli autovalori dell'osservabile $\\hat{H}$. Il valore di aspettativa è quindi definito come la media di tutti i possibili esiti $\\lambda$ (cioè gli autovalori dell'osservabile) di una misura dello stato $|\\psi\\rangle$, ponderata per le probabilità corrispondenti:\n",
        "\n",
        "$$\n",
        "\\langle\\hat{H}\\rangle_\\psi := \\sum_\\lambda p_\\lambda \\lambda = \\langle \\psi | \\hat{H} | \\psi \\rangle\n",
        "$$\n",
        "\n",
        "Tuttavia, non è sempre possibile calcolare il valore atteso di un osservabile, poiché spesso non ne conosciamo la base propria. L' IBM Quantum [`Estimator`](/docs/api/qiskit-ibm-runtime/estimator-v2) utilizza un complesso processo algebrico per stimare il valore atteso su un dispositivo quantistico reale, scomponendo l'osservabile in una combinazione di altri osservabili di cui conosciamo la base propria.\n",
        "\n",
        "In termini più semplici, `Estimator` scompone qualsiasi osservabile che non sa come misurare in osservabili più semplici e misurabili, chiamati <DefinitionTooltip definition=\"Insieme di matrici comunemente utilizzate nell'informatica quantistica per rappresentare e manipolare gli stati quantistici, composto dalla matrice identità e dalle tre matrici di Pauli (X, Y e Z).\">Operatori di Pauli</DefinitionTooltip>.\n",
        "\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "6dcabf55-4462-4cf9-b6e4-e7860fd25b31",
      "metadata": {},
      "source": [
        "Qualsiasi operatore può essere espresso come una combinazione di $4^n$ operatori di Pauli.\n",
        "\n",
        "$$\n",
        "\\hat{P}_k :=\n",
        "\\sigma_{k_{n-1}}\\otimes \\cdots \\otimes \\sigma_{k_0} \\quad\n",
        "\\forall k \\in \\mathbb{Z}_4^n \\equiv \\{0,1,\\cdots,4^n-1\\}, \\\\\n",
        "$$\n",
        "\n",
        "Tale che\n",
        "\n",
        "$$\n",
        "\\hat{H} = \\sum^{4^n-1}_{k=0} w_k \\hat{P}_k\n",
        "$$\n",
        "\n",
        "dove $n$ è il numero di qubit, $k \\equiv k_{n-1} \\cdots k_0$ per $k_l \\in \\mathbb{Z}_4 \\equiv \\{0, 1, 2, 3\\}$ (cioè la base dei numeri interi $4$ ), e $(\\sigma_0, \\sigma_1, \\sigma_2, \\sigma_3) := (I, X, Y, Z)$.\n",
        "\n",
        "Dopo aver eseguito questa decomposizione, `Estimator` ricava un nuovo circuito $V_k|\\psi\\rangle$ per ogni osservabile $\\hat{P}_k$ (dal circuito originale), per *diagonalizzare* effettivamente l'osservabile di Pauli nella base computazionale e misurarla. Possiamo misurare facilmente le osservabili di Pauli perché conosciamo $V_k$ in anticipo, cosa che non avviene in generale per le altre osservabili.\n",
        "\n",
        "Per ogni $\\hat{P}_{k}$ circuito, `Estimator` esegue il circuito corrispondente su un dispositivo quantistico più volte, misura lo stato di uscita nella base computazionale e calcola la probabilità $p_{kj}$ di ottenere ogni possibile uscita $j$. Quindi cerca l'autovalore $\\lambda_{kj}$ di $P_k$ corrispondente a ogni uscita $j$, moltiplica per $w_k$ e somma tutti i risultati per ottenere il valore atteso dell'osservabile $\\hat{H}$ per il dato stato $|\\psi\\rangle$.\n",
        "\n",
        "$$\n",
        "\\langle\\hat{H}\\rangle_\\psi =\n",
        "\\sum_{k=0}^{4^n-1} w_k \\sum_{j=0}^{2^n-1}p_{kj} \\lambda_{kj},\n",
        "$$\n",
        "\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "dcffb899-ef2d-4dca-b2a7-11c197d45ed0",
      "metadata": {
        "gloss": {
          "sampling": {
            "text": "The process of taking several measurements of one or several things.",
            "title": "Sampling"
          }
        }
      },
      "source": [
        "Poiché il calcolo del valore di aspettativa di $4^n$ Pauli è poco pratico (cioè cresce esponenzialmente), `Estimator` può essere efficiente solo quando una grande quantità di $w_k$ è nulla (cioè, decomposizione Pauli *rada* invece che *densa* ). Formalmente diciamo che, affinché questo calcolo sia *risolvibile in modo efficiente*, il numero di termini non nulli deve crescere al massimo polinomialmente con il numero di qubit $n$ : $\\hat{H} = \\sum^{\\text{Poly}(n)}_k w_k \\hat{P}_k.$\n",
        "\n",
        "Il lettore può notare l'assunzione implicita che anche la probabilità <DefinitionTooltip definition=\"Il processo di misurazione di una o più cose.\">campionamento</DefinitionTooltip> deve essere efficiente come spiegato per `Sampler`, il che significa che\n",
        "\n",
        "$$\n",
        "\\langle\\hat{H}\\rangle_\\psi =\n",
        "\\sum_{k}^{\\text{Poly}(n)} w_k \\sum_{j}^{\\text{Poly}(n)}p_{kj} \\lambda_{kj}.\n",
        "$$\n",
        "\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "c1547f3e-3c05-46d9-be5a-3e7e4141dc92",
      "metadata": {},
      "source": [
        "<span id=\"guided-example-to-calculate-expectation-values\" />\n",
        "\n",
        "### Esempio guidato per calcolare i valori attesi\n",
        "\n",
        "Assumiamo lo stato a singolo qubit $|+\\rangle := H|0\\rangle = \\frac{1}{\\sqrt{2}}(|0\\rangle + |1\\rangle)$, e l'osservabile\n",
        "\n",
        "$$\n",
        "\\begin{aligned}\n",
        "\\hat{H}\n",
        "& = \\begin{pmatrix}\n",
        "-1 & 2 \\\\\n",
        "2 & 1 \\\\\n",
        "\\end{pmatrix}\\\\[1mm]\n",
        "& = 2X - Z\n",
        "\n",
        "\\end{aligned}\n",
        "$$\n",
        "\n",
        "con il seguente valore di aspettativa teorico $\\langle\\hat{H}\\rangle_+ = \\langle+|\\hat{H}|+\\rangle = 2.$\n",
        "\n",
        "Poiché non sappiamo come misurare questa osservabile, non possiamo calcolare direttamente il suo valore di aspettativa e dobbiamo riesprimerlo come $\\langle\\hat{H}\\rangle_+ = 2\\langle X \\rangle_+ - \\langle Z \\rangle_+ $. Si può dimostrare che il risultato è lo stesso, notando che $\\langle+|X|+\\rangle = 1$, e $\\langle+|Z|+\\rangle = 0$.\n",
        "\n",
        "Vediamo come calcolare direttamente $\\langle X \\rangle_+$ e $\\langle Z \\rangle_+$. Poiché $X$ e $Z$ non commutano (cioè non condividono la stessa autobase), non possono essere misurati simultaneamente, quindi abbiamo bisogno dei circuiti ausiliari:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "id": "7c32e970-f9cb-48ec-a60b-2dccb0744141",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/courses/variational-algorithm-design/cost-functions/extracted-outputs/7c32e970-f9cb-48ec-a60b-2dccb0744141-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",
        "\n",
        "# The following code will work for any other initial single-qubit state and observable\n",
        "original_circuit = QuantumCircuit(1)\n",
        "original_circuit.h(0)\n",
        "\n",
        "H = SparsePauliOp([\"X\", \"Z\"], [2, -1])\n",
        "\n",
        "aux_circuits = []\n",
        "for pauli in H.paulis:\n",
        "    aux_circ = original_circuit.copy()\n",
        "    aux_circ.barrier()\n",
        "    if str(pauli) == \"X\":\n",
        "        aux_circ.h(0)\n",
        "    elif str(pauli) == \"Y\":\n",
        "        aux_circ.sdg(0)\n",
        "        aux_circ.h(0)\n",
        "    else:\n",
        "        aux_circ.id(0)\n",
        "    aux_circ.measure_all()\n",
        "    aux_circuits.append(aux_circ)\n",
        "\n",
        "original_circuit.draw(\"mpl\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "ec3cf7af-66b2-4ea9-94d7-6ec6ba2bfc5e",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/courses/variational-algorithm-design/cost-functions/extracted-outputs/ec3cf7af-66b2-4ea9-94d7-6ec6ba2bfc5e-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 2,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "# Auxiliary circuit for X\n",
        "aux_circuits[0].draw(\"mpl\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "id": "678d3bd0-d8fd-4767-b0a6-77cae945810a",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/courses/variational-algorithm-design/cost-functions/extracted-outputs/678d3bd0-d8fd-4767-b0a6-77cae945810a-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 3,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "# Auxiliary circuit for Z\n",
        "aux_circuits[1].draw(\"mpl\")"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "beef97f0-c968-4a3f-9b7b-fed39b88cbe3",
      "metadata": {},
      "source": [
        "Possiamo ora eseguire il calcolo manualmente utilizzando `Sampler` e verificare i risultati su `Estimator`:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "d59dffe0-85cb-4849-8421-03bbcc9668df",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Sampler results:\n",
            "  >> Expected value of X: 1.00000\n",
            "  >> Expected value of Z: 0.00420\n",
            "  >> Total expected value: 1.99580\n",
            "Estimator results:\n",
            "  >> Expected value of X: 1.00000\n",
            "  >> Expected value of Z: 0.00000\n",
            "  >> Total expected value: 2.00000\n"
          ]
        }
      ],
      "source": [
        "from qiskit.primitives import StatevectorSampler, StatevectorEstimator\n",
        "from qiskit.result import QuasiDistribution\n",
        "import numpy as np\n",
        "\n",
        "\n",
        "## SAMPLER\n",
        "shots = 10000\n",
        "sampler = StatevectorSampler()\n",
        "job = sampler.run(aux_circuits, shots=shots)\n",
        "\n",
        "# Run the sampler job and step through results\n",
        "expvals = []\n",
        "for index, pauli in enumerate(H.paulis):\n",
        "    data_pub = job.result()[index].data\n",
        "    bitstrings = data_pub.meas.get_bitstrings()\n",
        "    counts = data_pub.meas.get_counts()\n",
        "    quasi_dist = QuasiDistribution(\n",
        "        {outcome: freq / shots for outcome, freq in counts.items()}\n",
        "    )\n",
        "\n",
        "    # Use the probabilities and known eigenvalues of Pauli operators to estimate\n",
        "    # the expectation value.\n",
        "    val = 0\n",
        "\n",
        "    if str(pauli) == \"X\":\n",
        "        val += -1 * quasi_dist.get(1, 0)\n",
        "        val += 1 * quasi_dist.get(0, 0)\n",
        "\n",
        "    if str(pauli) == \"Y\":\n",
        "        val += -1 * quasi_dist.get(1, 0)\n",
        "        val += 1 * quasi_dist.get(0, 0)\n",
        "\n",
        "    if str(pauli) == \"Z\":\n",
        "        val += 1 * quasi_dist.get(0, 0)\n",
        "        val += -1 * quasi_dist.get(1, 0)\n",
        "\n",
        "    expvals.append(val)\n",
        "\n",
        "# Print expectation values\n",
        "\n",
        "print(\"Sampler results:\")\n",
        "for pauli, expval in zip(H.paulis, expvals):\n",
        "    print(f\"  >> Expected value of {str(pauli)}: {expval:.5f}\")\n",
        "\n",
        "total_expval = np.sum(H.coeffs * expvals).real\n",
        "print(f\"  >> Total expected value: {total_expval:.5f}\")\n",
        "\n",
        "# Use estimator for comparison\n",
        "observables = [\n",
        "    *H.paulis,\n",
        "    H,\n",
        "]  # Note: run for individual Paulis as well as full observable H\n",
        "\n",
        "estimator = StatevectorEstimator()\n",
        "job = estimator.run([(original_circuit, observables)])\n",
        "estimator_expvals = job.result()[0].data.evs\n",
        "\n",
        "# Print results\n",
        "print(\"Estimator results:\")\n",
        "for obs, expval in zip(observables, estimator_expvals):\n",
        "    if obs is not H:\n",
        "        print(f\"  >> Expected value of {str(obs)}: {expval:.5f}\")\n",
        "    else:\n",
        "        print(f\"  >> Total expected value: {expval:.5f}\")"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "44f13c37-50e3-426c-90cb-f730a81e20d1",
      "metadata": {},
      "source": [
        "<span id=\"mathematical-rigor-optional\" />\n",
        "\n",
        "### Rigore matematico (facoltativo)\n",
        "\n",
        "Esprimendo $|\\psi\\rangle$ rispetto alla base degli autostati di $\\hat{H}$, $|\\psi\\rangle = \\sum_\\lambda a_\\lambda |\\lambda\\rangle$, si ottiene quanto segue:\n",
        "\n",
        "$$\n",
        "\\begin{aligned}\n",
        "\\langle \\psi | \\hat{H} | \\psi \\rangle\n",
        "& = \\bigg(\\sum_{\\lambda'}a^*_{\\lambda'} \\langle \\lambda'|\\bigg) \\hat{H}\n",
        "  \\bigg(\\sum_{\\lambda} a_\\lambda | \\lambda\\rangle\\bigg)\\\\[1mm]\n",
        "\n",
        "& = \\sum_{\\lambda}\\sum_{\\lambda'} a^*_{\\lambda'}a_{\\lambda}\n",
        "  \\langle \\lambda'|\\hat{H}| \\lambda\\rangle\\\\[1mm]\n",
        "\n",
        "& = \\sum_{\\lambda}\\sum_{\\lambda'} a^*_{\\lambda'}a_{\\lambda} \\lambda\n",
        "\\langle \\lambda'| \\lambda\\rangle\\\\[1mm]\n",
        "\n",
        "& = \\sum_{\\lambda}\\sum_{\\lambda'} a^*_{\\lambda'}a_{\\lambda} \\lambda\n",
        "\\cdot \\delta_{\\lambda, \\lambda'}\\\\[1mm]\n",
        "\n",
        "& = \\sum_\\lambda |a_\\lambda|^2 \\lambda\\\\[1mm]\n",
        "\n",
        "& = \\sum_\\lambda p_\\lambda \\lambda\\\\[1mm]\n",
        "\n",
        "\\end{aligned}\n",
        "$$\n",
        "\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "6a3bea3d-a2f6-4033-85f9-9a10c5d5c64c",
      "metadata": {
        "gloss": {
          "hermitian": {
            "text": "A hermitian is a square matrix that is equal to its own conjugate transpose, or a linear operator that is self-adjoint.",
            "title": "Hermitian"
          }
        }
      },
      "source": [
        "Poiché non conosciamo gli autovalori o gli autostati dell'osservabile target $\\hat{H}$, dobbiamo prima considerare la sua diagonalizzazione. Dato che $\\hat{H}$ è <DefinitionTooltip definition=\"Un'Hermitiana è una matrice quadrata che è uguale alla sua trasposizione coniugata, o un operatore lineare che è autoaggiunto.\">Ermitiano</DefinitionTooltip>, esiste una trasformazione unitaria $V$ tale che $\\hat{H}=V^\\dagger \\Lambda V,$ dove $\\Lambda$ è la matrice diagonale degli autovalori, quindi $\\langle j | \\Lambda | k \\rangle = 0$ se $j\\neq k$, e $\\langle j | \\Lambda | j \\rangle = \\lambda_j$.\n",
        "\n",
        "Ciò implica che il valore atteso può essere riscritto come:\n",
        "\n",
        "$$\n",
        "\\begin{aligned}\n",
        "\\langle\\psi|\\hat{H}|\\psi\\rangle\n",
        "& = \\langle\\psi|V^\\dagger \\Lambda V|\\psi\\rangle\\\\[1mm]\n",
        "\n",
        "& = \\langle\\psi|V^\\dagger \\bigg(\\sum_{j=0}^{2^n-1} |j\\rangle\n",
        "\\langle j|\\bigg) \\Lambda \\bigg(\\sum_{k=0}^{2^n-1} |k\\rangle \\langle k|\\bigg) V|\\psi\\rangle\\\\[1mm]\n",
        "\n",
        "& = \\sum_{j=0}^{2^n-1} \\sum_{k=0}^{2^n-1}\\langle\\psi|V^\\dagger |j\\rangle\n",
        "\\langle j| \\Lambda  |k\\rangle \\langle k| V|\\psi\\rangle\\\\[1mm]\n",
        "\n",
        "& = \\sum_{j=0}^{2^n-1}\\langle\\psi|V^\\dagger |j\\rangle\n",
        "\\langle j| \\Lambda  |j\\rangle \\langle j| V|\\psi\\rangle\\\\[1mm]\n",
        "\n",
        "& = \\sum_{j=0}^{2^n-1}|\\langle j| V|\\psi\\rangle|^2 \\lambda_j\\\\[1mm]\n",
        "\n",
        "\\end{aligned}\n",
        "$$\n",
        "\n",
        "Dato che se un sistema si trova nello stato $|\\phi\\rangle = V |\\psi\\rangle$ la probabilità di misurare $| j\\rangle$ è $p_j = |\\langle j|\\phi \\rangle|^2$, il valore atteso di cui sopra può essere espresso come segue:\n",
        "\n",
        "$$\n",
        "\\langle\\psi|\\hat{H}|\\psi\\rangle =\n",
        "\\sum_{j=0}^{2^n-1} p_j \\lambda_j.\n",
        "$$\n",
        "\n",
        "È molto importante notare che le probabilità sono prese dallo stato $V |\\psi\\rangle$ invece che da $|\\psi\\rangle$. Per questo motivo la matrice $V$ è assolutamente necessaria.\n",
        "\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "c21a9a05-6e07-45dc-a090-4414dfdf2521",
      "metadata": {},
      "source": [
        "Ci si potrebbe chiedere come ottenere la matrice $V$ e gli autovalori $\\Lambda$. Se si disponesse già degli autovalori, non ci sarebbe bisogno di usare un computer quantistico, poiché l'obiettivo degli algoritmi variazionali è trovare gli autovalori di $\\hat{H}$.\n",
        "\n",
        "Fortunatamente, esiste un modo per aggirare questo problema: qualsiasi matrice $2^n \\times 2^n$ può essere scritta come combinazione lineare di $4^n$ prodotti tensoriali di $n$ matrici di Pauli e identità, tutte sia ermitiane che unitarie con $V$ e $\\Lambda$ noti. Questo è ciò che fa `Estimator` internamente il IBM Quantum, scomponendo qualsiasi oggetto [`Operator`](/docs/api/qiskit/qiskit.quantum_info.Operator) in un [`SparsePauliOp`](/docs/api/qiskit/qiskit.quantum_info.SparsePauliOp).\n",
        "\n",
        "Ecco gli operatori che possono essere utilizzati:\n",
        "\n",
        "$$\n",
        "\\begin{array}{c|c|c|c}\n",
        "  \\text{Operator} & \\sigma & V & \\Lambda \\\\[1mm]\n",
        "  \\hline\n",
        "  I & \\sigma_0 = \\begin{pmatrix} 1 & 0 \\\\ 0 & 1 \\end{pmatrix} & V_0 = I & \\Lambda_0 = I = \\begin{pmatrix} 1 & 0 \\\\ 0 & 1 \\end{pmatrix} \\\\[4mm]\n",
        "\n",
        "  X & \\sigma_1 = \\begin{pmatrix} 0 & 1 \\\\ 1 & 0 \\end{pmatrix} & V_1 = H =\\frac{1}{\\sqrt{2}} \\begin{pmatrix} 1 & 1 \\\\ 1 & -1 \\end{pmatrix} & \\Lambda_1 = \\sigma_3 = \\begin{pmatrix} 1 & 0 \\\\ 0 & -1 \\end{pmatrix} \\\\[4mm]\n",
        "\n",
        "  Y & \\sigma_2 = \\begin{pmatrix} 0 & -i \\\\ i & 0 \\end{pmatrix} & V_2 = HS^\\dagger  =\\frac{1}{\\sqrt{2}} \\begin{pmatrix} 1 & 1 \\\\ 1 & -1 \\end{pmatrix}\\cdot  \\begin{pmatrix} 1 & 0 \\\\ 0 & -i \\end{pmatrix} = \\frac{1}{\\sqrt{2}} \\begin{pmatrix} 1 & -i \\\\ 1 & i \\end{pmatrix}\\quad & \\Lambda_2 = \\sigma_3 = \\begin{pmatrix} 1 & 0 \\\\ 0 & -1 \\end{pmatrix} \\\\[4mm]\n",
        "\n",
        "  Z & \\sigma_3 = \\begin{pmatrix} 1 & 0 \\\\ 0 & -1 \\end{pmatrix} & V_3 = I & \\Lambda_3 = \\sigma_3 = \\begin{pmatrix} 1 & 0 \\\\ 0 & -1 \\end{pmatrix}\n",
        "\\end{array}\n",
        "$$\n",
        "\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "9e1c0933-5fbe-466d-9114-92d66d21785f",
      "metadata": {},
      "source": [
        "Riscriviamo quindi $\\hat{H}$ rispetto alle identità e alle Paulis:\n",
        "\n",
        "$$\n",
        "\\hat{H} =\n",
        "\\sum_{k_{n-1}=0}^3...\n",
        "\\sum_{k_0=0}^3 w_{k_{n-1}...k_0}\n",
        "\\sigma_{k_{n-1}}\\otimes ... \\otimes \\sigma_{k_0} = \\sum_{k=0}^{4^n-1} w_k \\hat{P}_k,\n",
        "$$\n",
        "\n",
        "dove $k = \\sum_{l=0}^{n-1} 4^l k_l \\equiv k_{n-1}...k_0$ per $k_{n-1},...,k_0\\in \\{0,1,2,3\\}$ (cioè la base $4$ ), e $\\hat{P}_{k} := \\sigma_{k_{n-1}}\\otimes ... \\otimes \\sigma_{k_0}$ :\n",
        "\n",
        "$$\n",
        "\\begin{aligned}\n",
        "\\langle\\psi|\\hat{H}|\\psi\\rangle\n",
        "& = \\sum_{k=0}^{4^n-1} w_k\n",
        "\\sum_{j=0}^{2^n-1}|\\langle j| V_k|\\psi\\rangle|^2 \\langle j| \\Lambda_k |j\\rangle \\\\[1mm]\n",
        "\n",
        "& = \\sum_{k=0}^{4^n-1} w_k \\sum_{j=0}^{2^n-1}p_{kj} \\lambda_{kj}, \\\\[1mm]\n",
        "\n",
        "\n",
        "\\end{aligned}\n",
        "$$\n",
        "\n",
        "dove $V_k := V_{k_{n-1}}\\otimes ... \\otimes V_{k_0}$ e $\\Lambda_k := \\Lambda_{k_{n-1}}\\otimes ... \\otimes \\Lambda_{k_0}$, tali che: $\\hat{P_k}=V_k^\\dagger \\Lambda_k V_k.$\n",
        "\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "42f771a0-486d-4a5f-9df1-a1720d7bf252",
      "metadata": {
        "gloss": {
          "oracle": {
            "text": " A hypothetical device or software component that performs a specific function, but the internal workings of which are unknown. A user is only aware of the inputs and outputs of the black box, and has no knowledge of how the black box processes the inputs to produce the outputs.",
            "title": "Black-box oracle"
          }
        }
      },
      "source": [
        "<span id=\"cost-functions\" />\n",
        "\n",
        "## Funzioni di costo\n",
        "\n",
        "In generale, le funzioni di costo vengono utilizzate per descrivere l'obiettivo di un problema e la performance di uno stato di prova rispetto a tale obiettivo. Questa definizione può essere applicata a vari esempi in chimica, apprendimento automatico, finanza, ottimizzazione e così via.\n",
        "\n",
        "Consideriamo un semplice esempio di ricerca dello stato fondamentale di un sistema. Il nostro obiettivo è minimizzare il valore di aspettativa dell'osservabile che rappresenta l'energia (hamiltoniana $\\hat{\\mathcal{H}}$ ):\n",
        "\n",
        "$$\n",
        "\\min_{\\vec\\theta} \\langle\\psi(\\vec\\theta)|\\hat{\\mathcal{H}}|\\psi(\\vec\\theta)\\rangle\n",
        "$$\n",
        "\n",
        "Possiamo usare `Estimator` per valutare il valore di aspettativa e passare questo valore a un ottimizzatore per minimizzarlo. Se l'ottimizzazione ha successo, restituirà un insieme di valori ottimali dei parametri $\\vec\\theta^*$, dai quali potremo costruire lo stato di soluzione proposto $|\\psi(\\vec\\theta^*)\\rangle$ e calcolare il valore di aspettativa osservato come $C(\\vec\\theta^*)$.\n",
        "\n",
        "Si noti che saremo in grado di minimizzare la funzione di costo solo per l'insieme limitato di stati che stiamo considerando. Questo ci porta a due possibilità distinte:\n",
        "\n",
        "* **Il nostro ansatz non definisce lo stato della soluzione nello spazio di ricerca** : Se questo è il caso, il nostro ottimizzatore non troverà mai la soluzione e dobbiamo sperimentare altri ansatz che potrebbero rappresentare il nostro spazio di ricerca in modo più accurato.\n",
        "* **Il nostro ottimizzatore non è in grado di trovare questa soluzione valida** : L'ottimizzazione può essere definita globalmente e localmente. Analizzeremo il significato di questo aspetto nella sezione successiva.\n",
        "\n",
        "In definitiva, eseguiremo un ciclo di ottimizzazione classico, ma affidando la valutazione della funzione di costo a un computer quantistico. Da questo punto di vista, si potrebbe pensare all'ottimizzazione come a un'impresa puramente classica in cui si chiamano alcuni <DefinitionTooltip definition=\"Un ipotetico dispositivo o componente software che svolge una funzione specifica, ma di cui non si conosce il funzionamento interno. L'utente è a conoscenza solo degli ingressi e delle uscite della scatola nera e non sa come la scatola nera elabora gli ingressi per produrre le uscite.\">oracolo quantistico black-box</DefinitionTooltip> ogni volta che l'ottimizzatore deve valutare la funzione di costo.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "650faaab-b964-4587-a39f-d6cc80b225b2",
      "metadata": {},
      "outputs": [],
      "source": [
        "def cost_func_vqe(params, circuit, 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 = (circuit, hamiltonian, params)\n",
        "    cost = estimator.run([pub]).result()[0].data.evs\n",
        "    return cost"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "a01a54f2-da16-4008-b422-fa7ded531f67",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/courses/variational-algorithm-design/cost-functions/extracted-outputs/a01a54f2-da16-4008-b422-fa7ded531f67-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 6,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "from qiskit.circuit.library import TwoLocal\n",
        "\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",
        "theta_list = (2 * np.pi * np.random.rand(1, 8)).tolist()\n",
        "ansatz.decompose().draw(\"mpl\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "7357e05e-f68a-4cd5-bf0d-77eab963a86d",
      "metadata": {},
      "source": [
        "Per prima cosa, utilizzeremo un simulatore: StatevectorEstimator. Questo è solitamente consigliabile per il debug, ma seguiremo immediatamente l'esecuzione del debug con un calcolo su un vero hardware quantistico. Sempre più spesso i problemi di interesse non sono più simulabili classicamente senza strutture di supercalcolo all'avanguardia.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 7,
      "id": "883bcb28-15d0-4046-a654-f43d9480a642",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "[-0.58744589]\n"
          ]
        }
      ],
      "source": [
        "estimator = StatevectorEstimator()\n",
        "cost = cost_func_vqe(theta_list, ansatz, observable, estimator)\n",
        "print(cost)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "1a94eba3-146d-4eee-970d-9a966d1e15f3",
      "metadata": {},
      "source": [
        "Ora procederemo con l'esecuzione su un vero computer quantistico. Si notino le modifiche alla sintassi. Le operazioni relative a pass\\_manager saranno illustrate più nel dettaglio nel prossimo esempio. Una fase di particolare importanza negli algoritmi variazionali è l'utilizzo di una sessione del servizio di calcolo \" IBM Quantum \". L'avvio di una sessione consente di eseguire più iterazioni di un algoritmo variazionale senza dover attendere in una nuova coda ogni volta che i parametri vengono aggiornati. Questo è importante se i tempi di attesa sono lunghi e/o sono necessarie molte iterazioni. Si noti che gli utenti di Open Plan non possono utilizzare le sessioni di Quantum Compute. Se non hai accesso alle sessioni, puoi ridurre il numero di iterazioni da inviare in un dato momento e salvare i parametri più recenti per utilizzarli nelle esecuzioni future. Se si inviano troppe iterazioni o si riscontrano tempi di attesa in coda eccessivamente lunghi, potrebbe comparire il codice di errore 1217, che indica lunghi ritardi tra l'invio di un lavoro e l'altro.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "e4ca4722-46ab-418f-8a1f-740a65e92eb9",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Estimated usage: < 1 min. Benchmarked at 7 seconds on an Eagle processor\n",
        "# Load necessary packages:\n",
        "\n",
        "from qiskit_ibm_runtime import (\n",
        "    QiskitRuntimeService,\n",
        "    Session,\n",
        "    EstimatorOptions,\n",
        "    EstimatorV2 as Estimator,\n",
        ")\n",
        "from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager\n",
        "\n",
        "# Select the least busy backend:\n",
        "\n",
        "service = QiskitRuntimeService()\n",
        "backend = service.least_busy(\n",
        "    operational=True, min_num_qubits=ansatz.num_qubits, simulator=False\n",
        ")\n",
        "# Or get a specific backend:\n",
        "# backend = service.backend(\"ibm_brisbane\")\n",
        "\n",
        "# Use a pass manager to transpile the circuit and observable for the specific backend being used:\n",
        "\n",
        "pm = generate_preset_pass_manager(backend=backend, optimization_level=1)\n",
        "isa_ansatz = pm.run(ansatz)\n",
        "isa_observable = observable.apply_layout(layout=isa_ansatz.layout)\n",
        "\n",
        "\n",
        "# Set estimator options\n",
        "estimator_options = EstimatorOptions(resilience_level=1, default_shots=10_000)\n",
        "\n",
        "# Open a Quantum Compute session:\n",
        "\n",
        "with Session(backend=backend) as session:\n",
        "    estimator = Estimator(mode=session, options=estimator_options)\n",
        "    cost = cost_func_vqe(theta_list, isa_ansatz, isa_observable, estimator)\n",
        "\n",
        "session.close()\n",
        "print(cost)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "95dc0e83-46ce-4839-91c3-af550aef9ddf",
      "metadata": {},
      "source": [
        "Si noti che i valori ottenuti dai due calcoli precedenti sono molto simili. Le tecniche per migliorare i risultati saranno discusse più avanti.\n",
        "\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "7f3951a3-47ad-4ddb-90a0-2218b1e11d15",
      "metadata": {},
      "source": [
        "<span id=\"example-mapping-to-non-physical-systems\" />\n",
        "\n",
        "### Esempio di mappatura su sistemi non fisici\n",
        "\n",
        "Il problema del taglio massimo (max-cut) è un problema di ottimizzazione combinatoria che consiste nel suddividere i vertici di un grafo in due insiemi disgiunti in modo tale da massimizzare il numero di spigoli tra i due insiemi. Più formalmente, dato un grafo non orientato $G=(V,E)$, dove $V$ è l'insieme dei vertici e $E$ è l'insieme dei bordi, il problema del taglio massimo consiste nel suddividere i vertici in due sottoinsiemi disgiunti, $S$ e $T$, in modo tale da massimizzare il numero di bordi con un'estremità in $S$ e l'altra in $T$.\n",
        "\n",
        "È possibile applicare il metodo max-cut per risolvere una serie di problemi, tra cui: il clustering, la progettazione di reti, le transizioni di fase e così via. Inizieremo creando un grafico del problema:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 16,
      "id": "739ca7a0-fae3-4389-a8de-7ae93c13b9e1",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/courses/variational-algorithm-design/cost-functions/extracted-outputs/739ca7a0-fae3-4389-a8de-7ae93c13b9e1-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "import rustworkx as rx\n",
        "from rustworkx.visualization import mpl_draw\n",
        "\n",
        "n = 4\n",
        "G = rx.PyGraph()\n",
        "G.add_nodes_from(range(n))\n",
        "# The edge syntax is (start, end, weight)\n",
        "edges = [(0, 1, 1.0), (0, 2, 1.0), (0, 3, 1.0), (1, 2, 1.0), (2, 3, 1.0)]\n",
        "G.add_edges_from(edges)\n",
        "\n",
        "mpl_draw(\n",
        "    G, pos=rx.shell_layout(G), with_labels=True, edge_labels=str, node_color=\"#1192E8\"\n",
        ")"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "3246e173-b1a8-48c2-bce8-f80a1524bcfb",
      "metadata": {},
      "source": [
        "Questo problema può essere espresso come un problema di ottimizzazione binaria. Per ogni nodo $0 \\leq i < n$, dove $n$ è il numero di nodi del grafo (in questo caso $n=4$ ), considereremo la variabile binaria $x_i$. Questa variabile avrà il valore $1$ se il nodo $i$ è uno dei gruppi che etichetteremo $1$ e $0$ se è nell'altro gruppo, che etichetteremo come $0$. Indicheremo inoltre come $w_{ij}$ (elemento $(i,j)$ della matrice di adiacenza $w$ ) il peso del bordo che va dal nodo $i$ al nodo $j$. Poiché il grafo non è diretto, $w_{ij}=w_{ji}$. Possiamo quindi formulare il nostro problema come una massimizzazione della seguente funzione di costo:\n",
        "\n",
        "$$\n",
        "\\begin{aligned}\n",
        "C(\\vec{x})\n",
        "& =\\sum_{i,j=0}^n w_{ij} x_i(1-x_j)\\\\[1mm]\n",
        "\n",
        "& = \\sum_{i,j=0}^n w_{ij} x_i - \\sum_{i,j=0}^n w_{ij} x_ix_j\\\\[1mm]\n",
        "\n",
        "& = \\sum_{i,j=0}^n w_{ij} x_i - \\sum_{i=0}^n \\sum_{j=0}^i 2w_{ij} x_ix_j\n",
        "\\end{aligned}\n",
        "$$\n",
        "\n",
        "Per risolvere questo problema con un computer quantistico, esprimeremo la funzione di costo come valore atteso di un'osservabile. Tuttavia, le osservabili che Qiskit ammette nativamente consistono in operatori di Pauli, che hanno autovalori $1$ e $-1$ invece di $0$ e $1$. Per questo motivo, si procederà al seguente cambio di variabile:\n",
        "\n",
        "Dove $\\vec{x}=(x_0,x_1,\\cdots ,x_{n-1})$. Possiamo usare la matrice di adiacenza $w$ per accedere comodamente ai pesi di tutti i bordi. Questo verrà utilizzato per ottenere la nostra funzione di costo:\n",
        "\n",
        "$$\n",
        "z_i = 1-2x_i \\rightarrow x_i = \\frac{1-z_i}{2}\n",
        "$$\n",
        "\n",
        "Ciò implica che:\n",
        "\n",
        "$$\n",
        "\\begin{array}{lcl} x_i=0 & \\rightarrow & z_i=1 \\\\ x_i=1 & \\rightarrow & z_i=-1.\\end{array}\n",
        "$$\n",
        "\n",
        "Quindi la nuova funzione di costo che vogliamo massimizzare è:\n",
        "\n",
        "$$\n",
        "\\begin{aligned}\n",
        "C(\\vec{z})\n",
        "& = \\sum_{i,j=0}^n w_{ij} \\bigg(\\frac{1-z_i}{2}\\bigg)\\bigg(1-\\frac{1-z_j}{2}\\bigg)\\\\[1mm]\n",
        "\n",
        "& = \\sum_{i,j=0}^n \\frac{w_{ij}}{4} - \\sum_{i,j=0}^n \\frac{w_{ij}}{4} z_iz_j\\\\[1mm]\n",
        "\n",
        "& = \\sum_{i=0}^n \\sum_{j=0}^i \\frac{w_{ij}}{2} -  \\sum_{i=0}^n \\sum_{j=0}^i \\frac{w_{ij}}{2} z_iz_j\n",
        "\\end{aligned}\n",
        "$$\n",
        "\n",
        "Inoltre, la tendenza naturale di un computer quantistico è quella di trovare i minimi (di solito l'energia più bassa) anziché i massimi, quindi invece di massimizzare $C(\\vec{z})$, minimizzeremo:\n",
        "\n",
        "$$\n",
        "-C(\\vec{z}) =  \\sum_{i=0}^n \\sum_{j=0}^i \\frac{w_{ij}}{2} z_iz_j -  \\sum_{i=0}^n \\sum_{j=0}^i \\frac{w_{ij}}{2}\n",
        "$$\n",
        "\n",
        "Ora che abbiamo una funzione di costo da minimizzare le cui variabili possono avere i valori $-1$ e $1$, possiamo fare la seguente analogia con il Pauli $Z$ :\n",
        "\n",
        "$$\n",
        "z_i \\equiv Z_i = \\overbrace{I}^{n-1}\\otimes ... \\otimes \\overbrace{Z}^{i} \\otimes ... \\otimes \\overbrace{I}^{0}\n",
        "$$\n",
        "\n",
        "In altre parole, la variabile $z_i$ sarà equivalente a un gate $Z$ che agisce sul qubit $i$. Inoltre:\n",
        "\n",
        "$$\n",
        "Z_i|x_{n-1}\\cdots x_0\\rangle = z_i|x_{n-1}\\cdots x_0\\rangle \\rightarrow \\langle x_{n-1}\\cdots x_0 |Z_i|x_{n-1}\\cdots x_0\\rangle = z_i\n",
        "$$\n",
        "\n",
        "Allora l'osservabile che stiamo per considerare è:\n",
        "\n",
        "$$\n",
        "\\hat{H} = \\sum_{i=0}^n \\sum_{j=0}^i \\frac{w_{ij}}{2} Z_iZ_j\n",
        "$$\n",
        "\n",
        "a cui dovremo aggiungere successivamente il termine indipendente:\n",
        "\n",
        "$$\n",
        "\\texttt{offset} = - \\sum_{i=0}^n \\sum_{j=0}^i \\frac{w_{ij}}{2}\n",
        "$$\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "83c86ba1-8b9d-4222-bb7e-637e6f29e5cd",
      "metadata": {},
      "source": [
        "L'operatore è una combinazione lineare di termini con operatori Z su nodi collegati da un bordo (ricordiamo che il qubit 0 è il più lontano a destra): $IIZZ + IZIZ + IZZI + ZIIZ + ZZII$. Una volta costruito l'operatore, l'ansatz per l'algoritmo QAOA può essere facilmente costruito utilizzando il circuito `QAOAAnsatz` dalla libreria di circuiti Qiskit.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 17,
      "id": "29398315-3baf-4363-88fc-69b6438e4afa",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/courses/variational-algorithm-design/cost-functions/extracted-outputs/29398315-3baf-4363-88fc-69b6438e4afa-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 17,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "from qiskit.circuit.library import QAOAAnsatz\n",
        "from qiskit.quantum_info import SparsePauliOp\n",
        "\n",
        "hamiltonian = SparsePauliOp.from_list(\n",
        "    [(\"IIZZ\", 1), (\"IZIZ\", 1), (\"IZZI\", 1), (\"ZIIZ\", 1), (\"ZZII\", 1)]\n",
        ")\n",
        "\n",
        "\n",
        "ansatz = QAOAAnsatz(hamiltonian, reps=2)\n",
        "# Draw\n",
        "ansatz.decompose(reps=3).draw(\"mpl\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 18,
      "id": "91d02379-9da6-426b-a5f0-d1fb75a891ee",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Offset: -2.5\n"
          ]
        }
      ],
      "source": [
        "# Sum the weights, and divide by 2\n",
        "\n",
        "offset = -sum(edge[2] for edge in edges) / 2\n",
        "print(f\"\"\"Offset: {offset}\"\"\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "84a98a08-00e4-4b05-a3f0-de29a7e73a60",
      "metadata": {},
      "source": [
        "Poiché lo stimatore \" IBM Quantum \" accetta direttamente un ansatz hamiltoniano e parametrizzato e restituisce l'energia necessaria, la funzione di costo per un'istanza QAOA è piuttosto semplice:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 19,
      "id": "73ac80dd-87cf-4a43-b19b-e89a4782a44a",
      "metadata": {},
      "outputs": [],
      "source": [
        "def cost_func(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",
        "    #    cost = estimator.run(ansatz, hamiltonian, parameter_values=params).result().values[0]\n",
        "    return cost"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 20,
      "id": "fc6af87b-a838-43b8-96ce-0af790fe0172",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "1.473098768180865\n"
          ]
        }
      ],
      "source": [
        "import numpy as np\n",
        "\n",
        "x0 = 2 * np.pi * np.random.rand(ansatz.num_parameters)\n",
        "\n",
        "estimator = StatevectorEstimator()\n",
        "cost = cost_func_vqe(x0, ansatz, hamiltonian, estimator)\n",
        "print(cost)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "389b7e86-cd61-4184-96c3-ae91b8ad59f5",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "1.1120776913677988\n"
          ]
        }
      ],
      "source": [
        "# Estimated usage: < 1 min, benchmarked at 6 seconds on ibm_osaka, 5-23-24\n",
        "# Load some necessary packages:\n",
        "\n",
        "from qiskit_ibm_runtime import QiskitRuntimeService\n",
        "from qiskit_ibm_runtime import Session, EstimatorV2 as Estimator\n",
        "\n",
        "# Select the least busy backend:\n",
        "\n",
        "backend = service.least_busy(\n",
        "    operational=True, min_num_qubits=ansatz.num_qubits, simulator=False\n",
        ")\n",
        "\n",
        "# Or get a specific backend:\n",
        "# backend = service.backend(\"ibm_brisbane\")\n",
        "\n",
        "# Use a pass manager to transpile the circuit and observable for the specific backend being used:\n",
        "\n",
        "pm = generate_preset_pass_manager(backend=backend, optimization_level=1)\n",
        "isa_ansatz = pm.run(ansatz)\n",
        "isa_hamiltonian = hamiltonian.apply_layout(layout=isa_ansatz.layout)\n",
        "\n",
        "# Set estimator options\n",
        "estimator_options = EstimatorOptions(resilience_level=1, default_shots=10_000)\n",
        "\n",
        "# Open a Quantum Compute session:\n",
        "\n",
        "with Session(backend=backend) as session:\n",
        "    estimator = Estimator(mode=session, options=estimator_options)\n",
        "    cost = cost_func_vqe(x0, isa_ansatz, isa_hamiltonian, estimator)\n",
        "\n",
        "# Close session after done\n",
        "session.close()\n",
        "print(cost)"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "16c94186-366c-4ac4-8745-4e7da283f8da",
      "metadata": {},
      "source": [
        "Rivedremo questo esempio nelle Applicazioni per esplorare come sfruttare un ottimizzatore per iterare attraverso lo spazio di ricerca. In generale, questo include:\n",
        "\n",
        "* Sfruttare un ottimizzatore per trovare i parametri ottimali\n",
        "* Legare i parametri ottimali all'ansatz per trovare gli autovalori\n",
        "* Tradurre gli autovalori nella definizione del nostro problema\n",
        "\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "d14d5800-24c1-4e4b-b692-9bdb9ef4440f",
      "metadata": {},
      "source": [
        "<span id=\"measurement-strategy-speed-versus-accuracy\" />\n",
        "\n",
        "## Strategia di misurazione: velocità contro precisione\n",
        "\n",
        "Come accennato, stiamo usando un computer quantistico rumoroso come *oracolo black-box*, dove il rumore può rendere i valori recuperati non deterministici, portando a fluttuazioni casuali che, a loro volta, danneggeranno - o addirittura impediranno del tutto - la convergenza di alcuni ottimizzatori verso una soluzione proposta. Si tratta di un problema generale che dobbiamo affrontare man mano che esploriamo l'utilità quantistica e progrediamo verso il vantaggio quantistico:\n",
        "\n",
        "![Un grafico che mostra come il costo della simulazione varia con la complessità del circuito. Utilizzando un computer classico, la crescita è esponenziale. Con la mitigazione degli errori quantistici, dovrebbe esserci un punto di incrocio in cui ciò diventa vantaggioso. La correzione quantistica degli errori consente una crescita lineare del costo della simulazione e porterà sicuramente dei vantaggi.](https://quantum.cloud.ibm.com/learning/images/courses/variational-algorithm-design/cost-functions/cost-function-path-to-quantum-advantage.svg)\n",
        "\n",
        "Possiamo avvalerci delle opzioni di soppressione e mitigazione degli errori delle primitive dell’ IBM Quantum e per gestire il rumore e massimizzare l’utilità dei computer quantistici odierni.\n",
        "\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "f23f0d82-d4f9-4485-aad3-cd2f8fd19b4f",
      "metadata": {
        "gloss": {
          "overhead": {
            "text": "Extra costs introduced by new techniques, relative to a base implementation.",
            "title": "Overhead"
          }
        }
      },
      "source": [
        "<span id=\"error-suppression\" />\n",
        "\n",
        "### Soppressione degli errori\n",
        "\n",
        "Per [\"soppressione degli errori\"](/docs/guides/error-mitigation-and-suppression-techniques) si intendono le tecniche utilizzate per ottimizzare e modificare un circuito durante la compilazione, al fine di ridurre al minimo gli errori. Si tratta di una tecnica di gestione degli errori di base che solitamente comporta una classica fase di pre-elaborazione <DefinitionTooltip definition=\"Costi aggiuntivi introdotti da nuove tecniche, rispetto a un'implementazione di base.\">sovraccarico</DefinitionTooltip> rispetto al tempo di esecuzione complessivo. Il carico di lavoro comprende la traslazione dei circuiti per l'esecuzione su hardware quantistico tramite:\n",
        "\n",
        "* Esprimere il circuito utilizzando le porte native disponibili su un sistema quantistico\n",
        "* Mappatura dei qubit virtuali in qubit fisici\n",
        "* Aggiunta di SWAP in base ai requisiti di connettività\n",
        "* Ottimizzazione dei gate 1Q e 2Q\n",
        "* Aggiunta del disaccoppiamento dinamico ai qubit inattivi per prevenire gli effetti della decoerenza.\n",
        "\n",
        "Le primitive consentono di utilizzare tecniche di soppressione degli errori impostando l'opzione `optimization_level` e selezionando le opzioni di transpilazione avanzate. In un corso successivo, approfondiremo diversi metodi di costruzione dei circuiti per migliorare i risultati, ma per la maggior parte dei casi si consiglia di impostare `optimization_level=3`.\n",
        "\n",
        "Visualizzeremo il valore dell'ottimizzazione crescente nel processo di trasposizione esaminando un circuito di esempio con un semplice comportamento ideale.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 24,
      "id": "9e0bf552-32b0-477e-9ac3-4ab643c30623",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/courses/variational-algorithm-design/cost-functions/extracted-outputs/9e0bf552-32b0-477e-9ac3-4ab643c30623-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 24,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "from qiskit.circuit import Parameter, QuantumCircuit\n",
        "from qiskit.quantum_info import SparsePauliOp\n",
        "\n",
        "theta = Parameter(\"theta\")\n",
        "\n",
        "qc = QuantumCircuit(2)\n",
        "qc.x(1)\n",
        "qc.h(0)\n",
        "qc.cp(theta, 0, 1)\n",
        "qc.h(0)\n",
        "observables = SparsePauliOp.from_list([(\"ZZ\", 1)])\n",
        "\n",
        "qc.draw(\"mpl\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "1c2b0408-e004-4a04-8aa5-cf519744082b",
      "metadata": {},
      "source": [
        "Il circuito di cui sopra può produrre valori di aspettativa sinusoidali dell'osservabile data, a condizione di inserire fasi che coprono un intervallo appropriato, come $[0,2\\pi]$.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 25,
      "id": "99b23f88-ddcb-45dc-a86d-3d48ff990319",
      "metadata": {},
      "outputs": [],
      "source": [
        "## Setup phases\n",
        "import numpy as np\n",
        "\n",
        "phases = np.linspace(0, 2 * np.pi, 50)\n",
        "\n",
        "# phases need to be expressed as a list of lists in order to work\n",
        "individual_phases = [[phase] for phase in phases]"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ca040383-9c8c-4a38-8e40-bad1b26c6085",
      "metadata": {},
      "source": [
        "Possiamo usare un simulatore per mostrare l'utilità di una trasposizione ottimizzata. Di seguito torneremo a utilizzare l'hardware reale per dimostrare l'utilità della mitigazione degli errori. Utilizzeremo QiskitRuntimeService per ottenere un backend reale (in questo caso, ibm\\_brisbane) e useremo AerSimulator per simulare quel backend, compreso il suo comportamento di rumore.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "6f0e6f32-02f7-471f-a9f3-f30d22f8cca4",
      "metadata": {},
      "outputs": [],
      "source": [
        "from qiskit_ibm_runtime import QiskitRuntimeService\n",
        "from qiskit_aer import AerSimulator\n",
        "\n",
        "# get a real backend from the runtime service\n",
        "service = QiskitRuntimeService()\n",
        "backend = service.backend(\"ibm_brisbane\")\n",
        "\n",
        "# generate a simulator that mimics the real quantum system with the latest calibration results\n",
        "backend_sim = AerSimulator.from_backend(backend)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "38df43e8-b11e-4671-b8fb-7a2eb74f648a",
      "metadata": {},
      "source": [
        "Ora possiamo utilizzare un gestore di passaggi per traspilare il circuito nell'“architettura del set di istruzioni” (ISA) del backend. Si tratta di un nuovo requisito introdotto nel servizio di calcolo \" IBM Quantum \": tutti i circuiti inviati a un backend devono rispettare i vincoli del target del backend, il che significa che devono essere scritti in base all'ISA del backend, ovvero all'insieme di istruzioni che il dispositivo è in grado di comprendere ed eseguire. Questi vincoli di destinazione sono definiti da fattori quali i gate di base nativi del dispositivo, la connettività dei qubit e, ove pertinente, le specifiche temporali relative agli impulsi e ad altre istruzioni.\n",
        "\n",
        "Si noti che nel caso in esame, questa operazione verrà eseguita due volte: una volta con optimization\\_level = 0, e una volta con un livello di ottimizzazione pari a 3. Ogni volta useremo la primitiva Estimator per stimare i valori di aspettativa dell'osservabile a diversi valori di fase.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "4dc699cd-0bb4-4b48-adcc-0bbe99218a26",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Import estimator and specify that we are using the simulated backend:\n",
        "\n",
        "from qiskit_ibm_runtime import EstimatorV2 as Estimator\n",
        "\n",
        "estimator = Estimator(mode=backend_sim)\n",
        "\n",
        "circuit = qc"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "7990eb62-879d-4beb-b452-f3d86b164d11",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Use a pass manager to transpile the circuit and observable for the backend being simulated.\n",
        "# Start with no optimization:\n",
        "\n",
        "from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager\n",
        "\n",
        "pm = generate_preset_pass_manager(backend=backend_sim, optimization_level=0)\n",
        "isa_circuit = pm.run(circuit)\n",
        "isa_observables = observables.apply_layout(layout=isa_circuit.layout)\n",
        "\n",
        "noisy_exp_values = []\n",
        "pub = (isa_circuit, isa_observables, [individual_phases])\n",
        "cost = estimator.run([pub]).result()[0].data.evs\n",
        "noisy_exp_values = cost[0]\n",
        "\n",
        "# Repeat above steps, but now with optimization = 3:\n",
        "\n",
        "exp_values_with_opt_es = []\n",
        "pm = generate_preset_pass_manager(backend=backend_sim, optimization_level=3)\n",
        "isa_circuit = pm.run(circuit)\n",
        "isa_observables = observables.apply_layout(layout=isa_circuit.layout)\n",
        "\n",
        "pub = (isa_circuit, isa_observables, [individual_phases])\n",
        "cost = estimator.run([pub]).result()[0].data.evs\n",
        "exp_values_with_opt_es = cost[0]"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "0cc8d4ea-81ef-4146-b8ed-9c7edae72be3",
      "metadata": {},
      "source": [
        "Infine, possiamo tracciare i risultati e vediamo che la precisione del calcolo era abbastanza buona anche senza l'ottimizzazione, ma è decisamente migliorata aumentando l'ottimizzazione al livello 3. Si noti che nei circuiti più profondi e complicati, la differenza tra i livelli di ottimizzazione 0 e 3 è probabilmente più significativa. Si tratta di un circuito molto semplice utilizzato come modello giocattolo.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 29,
      "id": "f3d3f805-f8d0-474d-9a88-2d1f164639b0",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/courses/variational-algorithm-design/cost-functions/extracted-outputs/f3d3f805-f8d0-474d-9a88-2d1f164639b0-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "import matplotlib.pyplot as plt\n",
        "\n",
        "plt.plot(phases, noisy_exp_values, \"o\", label=\"opt=0\")\n",
        "plt.plot(phases, exp_values_with_opt_es, \"o\", label=\"opt=3\")\n",
        "plt.plot(phases, 2 * np.sin(phases / 2) ** 2 - 1, label=\"ideal\")\n",
        "plt.ylabel(\"Expectation\")\n",
        "plt.legend()\n",
        "plt.show()"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "2394d082-2278-48cf-95b0-3363ed723ec1",
      "metadata": {
        "gloss": {
          "bias": {
            "text": "A systematic drift in the measured quantities, usually caused by errors.",
            "title": "Bias"
          }
        }
      },
      "source": [
        "<span id=\"error-mitigation\" />\n",
        "\n",
        "### Mitigazione degli errori\n",
        "\n",
        "Per [\"mitigazione degli errori\"](/docs/guides/error-mitigation-and-suppression-techniques) si intendono le tecniche che consentono agli utenti di ridurre gli errori di circuito modellando il rumore del dispositivo al momento dell'esecuzione. In genere, ciò comporta un sovraccarico di pre-elaborazione quantistica legato all'addestramento del modello e un sovraccarico di post-elaborazione classica volto a mitigare gli errori nei risultati grezzi mediante l'utilizzo del modello generato.\n",
        "\n",
        "L'opzione `resilience_level` della primitiva `IBM Quantum` specifica il livello di resilienza da implementare per far fronte agli errori. Livelli più elevati generano risultati più accurati, ma comportano tempi di elaborazione più lunghi a causa del sovraccarico legato al campionamento quantistico. I livelli di resilienza possono essere utilizzati per configurare il compromesso tra costo e precisione quando si applica la mitigazione degli errori alla query primitiva.\n",
        "\n",
        "Quando si implementa una qualsiasi tecnica di mitigazione degli errori, ci aspettiamo che il <DefinitionTooltip definition=\"Una deriva sistematica delle grandezze misurate, solitamente causata da errori.\">distorsione</DefinitionTooltip> nei nostri risultati si riduca rispetto al bias precedente, non mitigato. In alcuni casi, il pregiudizio può addirittura scomparire. Tuttavia, questo ha un costo. Riducendo la distorsione delle quantità stimate, aumenterà la variabilità statistica (cioè la varianza), di cui possiamo tenere conto aumentando ulteriormente il numero di scatti per circuito nel nostro processo di campionamento. Questo comporta un sovraccarico superiore a quello necessario per ridurre la distorsione, quindi non viene eseguito per impostazione predefinita. È possibile aderire facilmente a questo comportamento regolando il numero di colpi per circuito in options.executions.shots, come mostrato nell'esempio seguente.\n",
        "\n",
        "![Un diagramma che mostra distribuzioni più ampie o più ristrette, come nel compromesso bias /var iance.](https://quantum.cloud.ibm.com/learning/images/courses/variational-algorithm-design/cost-functions/cost-function-bias-variance-trade-off.svg)\n",
        "\n",
        "In questo corso analizzeremo questi modelli di mitigazione degli errori a livello generale, per illustrare le funzionalità di mitigazione degli errori che le primitive di IBM Quantum sono in grado di offrire, senza entrare nei dettagli dell'implementazione.\n",
        "\n",
        "<span id=\"twirled-readout-error-extinction-t-rex\" />\n",
        "\n",
        "### Estinzione dell'errore di lettura con rotazione (T-REx)\n",
        "\n",
        "L'estinzione dell'errore di lettura con torsione (T-REx) utilizza una tecnica nota come torsione di Pauli per ridurre il rumore introdotto durante il processo di misurazione quantistica. Questa tecnica non presuppone alcuna forma specifica di rumore, il che la rende molto generale ed efficace.\n",
        "\n",
        "Flusso di lavoro complessivo:\n",
        "\n",
        "1. Acquisizione dei dati per lo stato zero con capovolgimenti di bit randomizzati (Pauli X prima della misurazione)\n",
        "2. Acquisire i dati per lo stato desiderato (rumoroso) con capovolgimenti di bit randomizzati (Pauli X prima della misurazione)\n",
        "3. Calcolare la funzione speciale per ogni set di dati e dividere.\n",
        "\n",
        " \n",
        "\n",
        "![Schema dei circuiti di misura e calibrazione per T-REX.](https://quantum.cloud.ibm.com/learning/images/courses/variational-algorithm-design/cost-functions/cost-function-trex-data-collection.svg)\n",
        "\n",
        "Si può impostare con `options.resilience_level = 1`, come mostrato nell'esempio seguente.\n",
        "\n",
        "<span id=\"zero-noise-extrapolation\" />\n",
        "\n",
        "### Estrapolazione a rumore zero\n",
        "\n",
        "L'estrapolazione a rumore zero (ZNE) funziona amplificando prima il rumore nel circuito che prepara lo stato quantico desiderato, ottenendo misurazioni per diversi livelli di rumore e utilizzando tali misurazioni per dedurre il risultato senza rumore.\n",
        "\n",
        "Flusso di lavoro complessivo:\n",
        "\n",
        "1. Amplificare il rumore del circuito per diversi fattori di rumore\n",
        "2. Eseguire ogni circuito amplificato dal rumore\n",
        "3. Estrapolare fino al limite di rumore zero\n",
        "\n",
        " \n",
        "\n",
        "![Un diagramma che mostra le fasi della ZNE. Il rumore viene amplificato artificialmente da diversi fattori. Quindi i valori vengono estrapolati in base a ciò che dovrebbero essere a rumore zero.](https://quantum.cloud.ibm.com/learning/images/courses/variational-algorithm-design/cost-functions/cost-function-zne-stages.svg)\n",
        "\n",
        "Possiamo impostarlo con `options.resilience_level = 2`. Potremmo ottimizzare ulteriormente questo aspetto esaminando una serie di `noise_factors`, `noise_amplifiers`, e `extrapolators`, ma ciò esula dall'ambito di questo corso. Ti invitiamo a provare queste [opzioni seguendo le istruzioni riportate qui](/docs/guides/error-mitigation-and-suppression-techniques).\n",
        "\n",
        "Ogni metodo ha un proprio overhead associato: un compromesso tra il numero di calcoli quantistici necessari (tempo) e l'accuratezza dei risultati:\n",
        "\n",
        "$$\n",
        "\\begin{array}{c|c|c|c}\n",
        "  \\text{Methods} & R=1 \\text{, T-REx} & R=2 \\text{, ZNE} \\\\[1mm]\n",
        "  \\hline\n",
        "  \\text{Assumptions} & \\text{None} & \\text{Ability to scale noise} \\\\[1mm]\n",
        "  \\text{Qubit overhead} & 1 & 1 \\\\[1mm]\n",
        "  \\text{Sampling overhead} & 2 & N_{\\text{noise-factors}} \\\\[1mm]\n",
        "  \\text{Bias} & 0 & \\mathcal{O}(\\lambda^{N_{\\text{noise-factors}}}) \\\\[1mm]\n",
        "\\end{array}\n",
        "$$\n",
        "\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "8b46120f-5312-4450-bf51-13be198428fa",
      "metadata": {},
      "source": [
        "<span id=\"using-the-options-in-quantum-compute-for-mitigation-and-suppression\" />\n",
        "\n",
        "### Utilizzo delle opzioni di Quantum Compute per la mitigazione e la soppressione\n",
        "\n",
        "Ecco come calcolare un valore atteso utilizzando le funzionalità di mitigazione e soppressione degli errori nel servizio di elaborazione dati \" IBM Quantum \". Possiamo utilizzare esattamente lo stesso circuito e la stessa grandezza osservabile di prima, ma questa volta mantenendo il livello di ottimizzazione fissato al livello 2 e regolando la *resilienza* o le tecniche di mitigazione dell'errore utilizzate. Questo processo di mitigazione degli errori si ripete più volte nel corso di un ciclo di ottimizzazione.\n",
        "\n",
        "Questa parte viene eseguita su hardware reale, poiché la mitigazione degli errori non è disponibile sui simulatori.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "57a6b723-6e73-4693-95bf-f0782a8eb854",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Estimated usage: 8 minutes, benchmarked on an Eagle processor, 5-23-24\n",
        "\n",
        "from qiskit_ibm_runtime import QiskitRuntimeService\n",
        "from qiskit_ibm_runtime import (\n",
        "    Session,\n",
        "    EstimatorOptions,\n",
        "    EstimatorV2 as Estimator,\n",
        ")\n",
        "\n",
        "# We select the least busy backend\n",
        "\n",
        "# Select the least busy backend\n",
        "# backend = service.least_busy(\n",
        "#    operational=True, min_num_qubits=ansatz.num_qubits, simulator=False\n",
        "# )\n",
        "\n",
        "# Or use a specific backend\n",
        "backend = service.backend(\"ibm_brisbane\")\n",
        "\n",
        "# Initialize some variables to save the results from different runs:\n",
        "\n",
        "exp_values_with_em0_es = []\n",
        "exp_values_with_em1_es = []\n",
        "exp_values_with_em2_es = []\n",
        "\n",
        "# Use a pass manager to optimize the circuit and observables for the backend chosen:\n",
        "\n",
        "pm = generate_preset_pass_manager(backend=backend, optimization_level=2)\n",
        "isa_circuit = pm.run(circuit)\n",
        "isa_observables = observables.apply_layout(layout=isa_circuit.layout)\n",
        "\n",
        "# Open a session and run with no error mitigation:\n",
        "\n",
        "estimator_options = EstimatorOptions(resilience_level=0, default_shots=10_000)\n",
        "\n",
        "with Session(backend=backend) as session:\n",
        "    estimator = Estimator(mode=session, options=estimator_options)\n",
        "\n",
        "    pub = (isa_circuit, isa_observables, [individual_phases])\n",
        "    cost = estimator.run([pub]).result()[0].data.evs\n",
        "\n",
        "session.close()\n",
        "\n",
        "exp_values_with_em0_es = cost[0]\n",
        "\n",
        "# Open a session and run with resilience = 1:\n",
        "\n",
        "estimator_options = EstimatorOptions(resilience_level=1, default_shots=10_000)\n",
        "\n",
        "with Session(backend=backend) as session:\n",
        "    estimator = Estimator(mode=session, options=estimator_options)\n",
        "\n",
        "    pub = (isa_circuit, isa_observables, [individual_phases])\n",
        "    cost = estimator.run([pub]).result()[0].data.evs\n",
        "\n",
        "session.close()\n",
        "\n",
        "exp_values_with_em1_es = cost[0]\n",
        "\n",
        "# Open a session and run with resilience = 2:\n",
        "\n",
        "estimator_options = EstimatorOptions(resilience_level=2, default_shots=10_000)\n",
        "\n",
        "with Session(backend=backend) as session:\n",
        "    estimator = Estimator(mode=session, options=estimator_options)\n",
        "\n",
        "    pub = (isa_circuit, isa_observables, [individual_phases])\n",
        "    cost = estimator.run([pub]).result()[0].data.evs\n",
        "\n",
        "session.close()\n",
        "\n",
        "exp_values_with_em2_es = cost[0]"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "48215ea6-3045-4880-8671-265e2fb33e5e",
      "metadata": {},
      "source": [
        "Come in precedenza, possiamo tracciare i valori di aspettativa risultanti in funzione dell'angolo di fase per i tre livelli di attenuazione dell'errore utilizzati. Con grande difficoltà, si può notare che l'attenuazione degli errori migliora leggermente i risultati. Anche in questo caso, questo effetto è molto più pronunciato nei circuiti più profondi e complicati.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 93,
      "id": "474f1a1a-ee90-468d-9390-fdb455aeb142",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/courses/variational-algorithm-design/cost-functions/extracted-outputs/474f1a1a-ee90-468d-9390-fdb455aeb142-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "import matplotlib.pyplot as plt\n",
        "\n",
        "plt.plot(phases, exp_values_with_em0_es, \"o\", label=\"unmitigated\")\n",
        "plt.plot(phases, exp_values_with_em1_es, \"o\", label=\"resil = 1\")\n",
        "plt.plot(phases, exp_values_with_em2_es, \"o\", label=\"resil = 2\")\n",
        "plt.plot(phases, 2 * np.sin(phases / 2) ** 2 - 1, label=\"ideal\")\n",
        "plt.ylabel(\"Expectation\")\n",
        "plt.legend()\n",
        "plt.show()"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "1e683c07-96f3-47d5-8981-8d3aeb8be81f",
      "metadata": {},
      "source": [
        "<span id=\"summary\" />\n",
        "\n",
        "## Riepilogo\n",
        "\n",
        "In questa lezione avete imparato a creare una funzione di costo:\n",
        "\n",
        "* Creare una funzione di costo\n",
        "* Come sfruttare le primitive di \" IBM Quantum \" per mitigare e sopprimere il rumore\n",
        "* Come definire una strategia di misurazione per ottimizzare la velocità rispetto all'accuratezza\n",
        "\n",
        "Ecco il nostro carico di lavoro variazionale di alto livello:\n",
        "\n",
        "![Schema del circuito quantistico con gli unitari che preparano lo stato di riferimento e lo stato variazionale, seguiti dalle misure. Questi vengono utilizzati per valutare la funzione di costo.](https://quantum.cloud.ibm.com/learning/images/courses/variational-algorithm-design/cost-functions/cost-function-circuit.svg)\n",
        "\n",
        "La nostra funzione di costo viene eseguita durante ogni iterazione del ciclo di ottimizzazione. La prossima lezione analizzerà come l'ottimizzatore classico utilizza la valutazione della funzione di costo per selezionare nuovi parametri.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 9,
      "id": "2751c16d-a9c1-407b-ab88-a9dcf768bc2e",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "1.1.0\n",
            "0.23.0\n"
          ]
        }
      ],
      "source": [
        "import qiskit\n",
        "import qiskit_ibm_runtime\n",
        "\n",
        "print(qiskit.version.get_version_info())\n",
        "print(qiskit_ibm_runtime.version.get_version_info())"
      ]
    },
    {
      "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
}