{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "e98de65a",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Algoritmo di Grover\"\n",
        "description: \"Scopri come l'algoritmo di Grover utilizza l'informatica quantistica per risolvere problemi di ricerca non strutturati.\"\n",
        "---\n",
        "\n",
        "{/* cspell:ignore bitstr */}\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "9857bace",
      "metadata": {},
      "source": [
        "<span id=\"grovers-algorithm\" />\n",
        "\n",
        "# Algoritmo di Grover\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "5c6854a5",
      "metadata": {},
      "source": [
        "Per questo modulo Qiskit in Classrooms, gli studenti devono avere un ambiente Python funzionante con i seguenti pacchetti installati:\n",
        "\n",
        "* `qiskit` v2.1.0 o più recente\n",
        "* `qiskit-ibm-runtime` v0.40.1 o più recente\n",
        "* `qiskit-aer` v0.17.0 o più recente\n",
        "* `qiskit.visualization`\n",
        "* `numpy`\n",
        "* `pylatexenc`\n",
        "\n",
        "Per configurare e installare i pacchetti di cui sopra, consultare la guida [Installare Qiskit](/docs/guides/install-qiskit).\n",
        "Per poter eseguire lavori su veri computer quantistici, gli studenti dovranno creare un account con IBM Quantum® seguendo i passaggi della guida [Set up your IBM Cloud account](/docs/guides/cloud-setup).\n",
        "\n",
        "Questo modulo è stato testato e ha utilizzato 12 secondi di tempo della QPU. Si tratta di una stima in buona fede; l'utilizzo effettivo può variare.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "e16858b0",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Uncomment and modify this line as needed to install dependencies\n",
        "#!pip install 'qiskit>=2.1.0' 'qiskit-ibm-runtime>=0.40.1' 'qiskit-aer>=0.17.0' 'numpy' 'pylatexenc'"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "e57d1e6b",
      "metadata": {},
      "source": [
        "<span id=\"introduction\" />\n",
        "\n",
        "## Introduzione\n",
        "\n",
        "**L'algoritmo di Grover** è un algoritmo quantistico fondamentale che affronta il *problema della ricerca non strutturata* : dato un insieme di $N$ elementi e un modo per verificare se un dato elemento è quello che stai cercando, quanto velocemente puoi trovare l'elemento desiderato? Nell'informatica classica, se i dati non sono ordinati e non c'è una struttura da sfruttare, l'approccio migliore è quello di controllare ogni elemento uno per uno, il che porta a una complessità di interrogazione di $O(N)$ - in media, è necessario controllare circa la metà degli elementi prima di trovare l'obiettivo.\n",
        "\n",
        "![Un diagramma di ricerca classica non strutturata.](https://quantum.cloud.ibm.com/learning/images/modules/computer-science/grovers/classical-uss.avif)\n",
        "\n",
        "L'algoritmo di Grover, introdotto da Lov Grover nel 1996, dimostra come un computer quantistico possa risolvere questo problema in modo molto più efficiente, richiedendo solo $O(\\sqrt{N})$ passaggi per trovare l'oggetto contrassegnato con alta probabilità. Ciò rappresenta una *velocità quadratica* rispetto ai metodi classici, che è significativa per i grandi insiemi di dati.\n",
        "\n",
        "L'algoritmo opera nel seguente contesto:\n",
        "\n",
        "* **Impostazione del problema:** Si dispone di una funzione $f(x)$ che restituisce 1 se $x$ è l'elemento desiderato e 0 altrimenti. Questa funzione è spesso chiamata *oracolo* o *scatola nera*, poiché è possibile conoscere i dati solo interrogando $f(x)$.\n",
        "* **Utilità dei quanti:** Mentre gli algoritmi classici per questo problema richiedono, in media, $N/2$ interrogazioni, l'algoritmo di Grover può trovare la soluzione in circa $\\pi\\sqrt{N}/4$ interrogazioni, che è molto più veloce per grandi $N$.\n",
        "* **Come funziona (ad alto livello):**\n",
        "  * Il computer quantistico crea innanzitutto una *sovrapposizione* di tutti gli stati possibili, rappresentando tutti gli elementi possibili in una sola volta.\n",
        "  * Quindi applica ripetutamente una sequenza di operazioni quantistiche (l'iterazione di Grover) che amplifica la probabilità della risposta corretta e diminuisce le altre.\n",
        "  * Dopo un numero sufficiente di iterazioni, la misurazione dello stato quantistico fornisce la risposta corretta con alta probabilità.\n",
        "\n",
        "Ecco un diagramma molto elementare dell'algoritmo di Grover che salta molte sfumature. Per un diagramma più dettagliato, si veda [questo documento](https://arxiv.org/pdf/2211.04543).\n",
        "\n",
        "![Diagramma di alto livello delle fasi di implementazione dell'algoritmo di Grover.](https://quantum.cloud.ibm.com/learning/images/modules/computer-science/grovers/quantum-uss2.avif)\n",
        "\n",
        "Alcune cose da notare sull'algoritmo di Grover:\n",
        "\n",
        "* È ottimale per la ricerca non strutturata: nessun algoritmo quantistico può risolvere il problema con meno di $O(\\sqrt{N})$ query.\n",
        "* Fornisce solo una velocità quadratica, non esponenziale, a differenza di altri algoritmi quantistici (ad esempio, l'algoritmo di Shor per la fattorizzazione).\n",
        "* Ha implicazioni pratiche, come la possibilità di accelerare gli attacchi di forza bruta ai sistemi crittografici, anche se l'accelerazione non è sufficiente a rompere la maggior parte delle crittografie moderne.\n",
        "\n",
        "Per gli studenti universitari che hanno familiarità con i concetti di base dell'informatica e con i modelli di interrogazione, l'algoritmo di Grover offre una chiara illustrazione di come l'informatica quantistica possa superare gli approcci classici per alcuni problemi, anche quando il miglioramento è \"solo\" quadratico. È anche una porta d'accesso alla comprensione di algoritmi quantistici più avanzati e al più ampio potenziale dell'informatica quantistica.\n",
        "\n",
        "L'amplificazione di ampiezza è un algoritmo quantistico di uso generale, o una subroutine, che può essere utilizzata per ottenere una velocità quadratica rispetto a una manciata di algoritmi classici. L ['algoritmo di Grover](https://arxiv.org/abs/quant-ph/9605043) è stato il primo a dimostrare questa accelerazione su problemi di ricerca non strutturati. La formulazione di un problema di ricerca di Grover richiede una funzione oracolo che contrassegna uno o più stati della base computazionale come gli stati che ci interessa trovare e un circuito di amplificazione che aumenta l'ampiezza degli stati contrassegnati, sopprimendo di conseguenza gli stati rimanenti.\n",
        "\n",
        "In questa sede illustriamo come costruire oracoli di Grover e come utilizzare la libreria di circuiti di `GroverOperator` Qiskit per configurare facilmente un'istanza della ricerca di Grover. La primitiva `Sampler` `IBM Quantum` consente l'esecuzione senza interruzioni dei circuiti di Grover.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "4f900ae6",
      "metadata": {},
      "source": [
        "<span id=\"theory\" />\n",
        "\n",
        "## Teoria\n",
        "\n",
        "Supponiamo che esista una funzione $f$ che mappa stringhe binarie in una singola variabile binaria, ovvero\n",
        "\n",
        "$$\n",
        "f: \\Sigma^n \\rightarrow \\Sigma\n",
        "$$\n",
        "\n",
        "Un esempio definito su $\\Sigma^6$ è\n",
        "\n",
        "$$\n",
        "f(x)= \\begin{cases} 1 \\qquad \\text{if }x=\\{010101\\}\\\\\n",
        "0 \\qquad \\text{otherwise }\n",
        "\\end{cases}\n",
        "$$\n",
        "\n",
        "Un altro esempio definito su $\\Sigma^{2n}$ è\n",
        "\n",
        "$$\n",
        "f(x)= \\begin{cases} 1 \\qquad \\text{if equal numbers of 1's and 0's in string}\\\\\n",
        "0 \\qquad \\text{otherwise }\n",
        "\\end{cases}\n",
        "$$\n",
        "\n",
        "Il compito è quello di trovare gli stati quantici corrispondenti agli argomenti $x$ di $f(x)$ che sono mappati a 1. In altre parole, trovare tutti i $\\{x_1\\}\\in \\Sigma^n$ tali che $f(x_1)=1$ (o se non c'è soluzione, segnalarlo). Ci riferiremo alle non soluzioni come $x_0$. Naturalmente, faremo tutto questo su un computer quantistico, usando gli stati quantistici, quindi è utile esprimere queste stringhe binarie come stati:\n",
        "\n",
        "$$\n",
        "\\{|x_1\\rangle\\} \\in |\\Sigma^n\\rangle\n",
        "$$\n",
        "\n",
        "Utilizzando la notazione degli stati quantistici (Dirac), cerchiamo uno o più stati speciali $\\{|x_1\\rangle\\}$ in un insieme di $N=2^n$ stati possibili, dove $n$ è il numero di qubit, e con le non-soluzioni denotate $\\{|x_0\\rangle\\}.$\n",
        "\n",
        "Possiamo pensare alla funzione $f$ come se fosse fornita da un oracolo: una scatola nera che possiamo interrogare per determinare il suo effetto su uno stato $|x\\rangle.$ In pratica, spesso conosceremo la funzione, ma potrebbe essere molto complicata da implementare, il che significa che ridurre il numero di interrogazioni o applicazioni di $f$ potrebbe essere importante. In alternativa, possiamo immaginare un paradigma in cui una persona interroga un oracolo controllato da un'altra persona, in modo tale che non conosciamo la funzione dell'oracolo, ma conosciamo solo la sua azione su particolari stati grazie all'interrogazione.\n",
        "\n",
        "Si tratta di un \"problema di ricerca non strutturato\", in quanto non c'è nulla di speciale in $f$ che ci aiuti nella ricerca. Le uscite non sono ordinate, né si sa che le soluzioni si raggruppano, e così via. Considerate l'uso di vecchi elenchi telefonici cartacei come analogia. Questa ricerca non strutturata sarebbe come una scansione alla ricerca di un determinato **numero**, e non come una ricerca in un elenco alfabetico di nomi.\n",
        "\n",
        "Nel caso in cui si cerchi una singola soluzione, classicamente ciò richiede un numero di interrogazioni lineare in $N$. È chiaro che si può trovare una soluzione al primo tentativo, oppure che non si trova alcuna soluzione nelle prime $N-1$ ipotesi, per cui è necessario interrogare l'input $N^{th}$ per vedere se c'è qualche soluzione. Dal momento che le funzioni non hanno una struttura sfruttabile, in media sono necessarie $N/2$ ipotesi. L'algoritmo di Grover richiede un numero di interrogazioni o di calcoli di $f$ che varia a seconda dei casi $\\sqrt{N}.$\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a1a51eb6",
      "metadata": {},
      "source": [
        "<span id=\"sketch-of-circuits-in-grovers-algorithm\" />\n",
        "\n",
        "### Schizzo dei circuiti nell'algoritmo di Grover\n",
        "\n",
        "Una spiegazione matematica completa dell'algoritmo di Grover si trova, ad esempio, in [Fundamentals of quantum algorithms](/learning/courses/fundamentals-of-quantum-algorithms), un corso di John Watrous su IBM Quantum Learning. Una trattazione sintetica è riportata in appendice alla fine di questo modulo. Ma per ora ci limiteremo a esaminare la struttura complessiva del circuito quantistico che implementa l'algoritmo di Grover.\n",
        "\n",
        "L'algoritmo di Grover può essere suddiviso nelle seguenti fasi:\n",
        "\n",
        "* Preparazione di una superposizione iniziale (applicando le porte di Hadamard a tutti i qubit)\n",
        "* \"Marcatura\" dello stato (o degli stati) di destinazione con un capovolgimento di fase\n",
        "* Una fase di \"diffusione\" in cui le porte di Hadamard e un capovolgimento di fase vengono applicati a **tutti i** qubit.\n",
        "* Possibili ripetizioni delle fasi di marcatura e di diffusione per massimizzare la probabilità di misurare lo stato target\n",
        "* Misurazione\n",
        "\n",
        "![Schema di un circuito quantistico che mostra la configurazione di base dell'algoritmo di Grover. Questo esempio utilizza quattro qubit.](https://quantum.cloud.ibm.com/learning/images/modules/computer-science/grovers/grover-circuit-diagram-2.avif)\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "15b9e48c",
      "metadata": {},
      "source": [
        "Spesso, la porta di marcatura $Z_f$ e gli strati di diffusione costituiti da $H,$ $Z_{\\text{OR}},$ e $H$ sono indicati collettivamente come \"operatore Grover\". In questo diagramma è mostrata una sola ripetizione dell'operatore Grover.\n",
        "\n",
        "Le porte di Hadamard $H$ sono ben note e ampiamente utilizzate nell'informatica quantistica. La porta di Hadamard crea stati di sovrapposizione. In particolare, è definito da\n",
        "\n",
        "$$\n",
        "H|0\\rangle = \\frac{1}{\\sqrt{2}}\\left(|0\\rangle+|1\\rangle\\right)\\\\\n",
        "H|1\\rangle = \\frac{1}{\\sqrt{2}}\\left(|0\\rangle-|1\\rangle\\right)\n",
        "$$\n",
        "\n",
        "Il suo funzionamento su qualsiasi altro stato è definito dalla linearità.\n",
        "In particolare, uno strato di porte di Hadamard ci permette di passare dallo stato iniziale con tutti i qubit in $|0\\rangle$ (indicato con $|0\\rangle^{\\otimes n}$ ) a uno stato in cui ogni qubit ha una certa probabilità di essere misurato in $|0\\rangle$ o $|1\\rangle;$ Questo ci permette di sondare lo spazio di tutti gli stati possibili in modo diverso da quello dell'informatica classica.\n",
        "\n",
        "Un'importante proprietà corollaria della porta di Hadamard è che agendo una seconda volta si possono annullare tali stati di sovrapposizione:\n",
        "\n",
        "$$\n",
        "H\\frac{1}{\\sqrt{2}}\\left(|0\\rangle+|1\\rangle\\right)=|0\\rangle\\\\\n",
        "H\\frac{1}{\\sqrt{2}}\\left(|0\\rangle-|1\\rangle\\right)=|1\\rangle\n",
        "$$\n",
        "\n",
        "Questo aspetto sarà importante tra poco.\n",
        "\n",
        "<span id=\"check-your-understanding\" />\n",
        "\n",
        "#### Verifica la tua comprensione\n",
        "\n",
        "Partendo dalla definizione di porta di Hadamard, dimostrate che una seconda applicazione della porta di Hadamard annulla tali sovrapposizioni come affermato sopra.\n",
        "\n",
        "<Accordion>\n",
        "  <AccordionItem title=\"Risposta\">\n",
        "    Quando applichiamo X allo stato $|+\\rangle$, otteniamo il valore e +1 e allo stato $|-\\rangle$ otteniamo -1, quindi, se avessimo una distribuzione 50-50, otterremmo un valore di aspettativa pari a 0.\n",
        "  </AccordionItem>\n",
        "</Accordion>\n",
        "\n",
        "Il gate $Z_\\text{OR}$ è meno comune ed è definito in base a\n",
        "\n",
        "$$\n",
        "\\text{Z}_\\text{OR}|x\\rangle = \\begin{cases}\n",
        "|x\\rangle & \\text{if } x = 0^n \\\\\n",
        "    -|x\\rangle  & \\text{if } x \\neq 0^n\n",
        "\\end{cases}\n",
        "\\qquad \\forall x \\in \\Sigma^n\n",
        "$$\n",
        "\n",
        "Infine, il gate $Z_f$ è definito da\n",
        "\n",
        "$$\n",
        "Z_f:|x\\rangle \\rightarrow (-1)^{f(x)}|x\\rangle \\qquad \\forall x \\in \\Sigma^n\n",
        "$$\n",
        "\n",
        "Si noti che l'effetto è che $Z_f$ inverte il segno su uno stato bersaglio per il quale $f(x) = 1$ e lascia inalterati gli altri stati.\n",
        "\n",
        "A un livello molto alto e astratto si può pensare alle fasi del circuito nei seguenti modi:\n",
        "\n",
        "* Primo strato di Hadamard: mette i qubit in una sovrapposizione di tutti gli stati possibili.\n",
        "* $Z_f$ : contrassegnare lo stato o gli stati di destinazione aggiungendo un segno \"-\" davanti. Questo non modifica immediatamente le probabilità di misurazione, ma cambia il comportamento dello stato target nelle fasi successive.\n",
        "* Un altro strato di Hadamard: Il segno \"-\" introdotto nel passaggio precedente cambierà il segno relativo tra alcuni termini. Poiché le porte di Hadamard trasformano una miscela di stati computazionali $(|0\\rangle+|1\\rangle)/\\sqrt{2}$ in uno stato computazionale, $|0\\rangle,$ e trasformano $(|0\\rangle-|1\\rangle)/\\sqrt{2}$ in $|1\\rangle$, questa differenza di segno relativo può ora iniziare a svolgere un ruolo nella misurazione degli stati.\n",
        "* Si applica un ultimo strato di porte di Hadamard e si effettuano le misurazioni.\n",
        "  Vedremo in dettaglio come funziona nella prossima sezione.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "bf41c02c",
      "metadata": {},
      "source": [
        "<span id=\"example\" />\n",
        "\n",
        "### Esempio\n",
        "\n",
        "Per capire meglio come funziona l'algoritmo di Grover, facciamo un piccolo esempio a due qubit. Questo può essere considerato opzionale per coloro che non si concentrano sulla meccanica quantistica e sulla notazione di Dirac. Ma per coloro che sperano di lavorare in modo sostanziale con i computer quantistici, questo è altamente raccomandato.\n",
        "\n",
        "Ecco lo schema del circuito con gli stati quantici etichettati in varie posizioni. Si noti che con due soli qubit, ci sono solo quattro possibili stati che possono essere misurati in qualsiasi circostanza: $|00\\rangle$, $|01\\rangle$, $|10\\rangle$, e $|11\\rangle$.\n",
        "\n",
        "![Schema di un circuito quantistico che implementa l'algoritmo di Grover su due qubit.](https://quantum.cloud.ibm.com/learning/images/modules/computer-science/grovers/grover-circuit-diagram-2-q-ex.avif)\n",
        "\n",
        "Supponiamo che l'oracolo ( $Z_f$, a noi sconosciuto) segni lo stato $|01\\rangle$. Esamineremo le azioni di ciascun insieme di porte quantistiche, compreso l'oracolo, e vedremo quale distribuzione di stati possibili emerge al momento della misurazione.\n",
        "All'inizio, abbiamo\n",
        "\n",
        "$$\n",
        "|\\psi_0\\rangle = |00\\rangle\n",
        "$$\n",
        "\n",
        "Utilizzando la definizione di porte di Hadamard, abbiamo\n",
        "\n",
        "$$\n",
        "|\\psi_1\\rangle = \\frac{1}{2}\\left(|0\\rangle+|1\\rangle\\right)\\left(|0\\rangle+|1\\rangle\\right)=\\frac{1}{2}\\left(|00\\rangle+|01\\rangle+|10\\rangle+|11\\rangle\\right)\n",
        "$$\n",
        "\n",
        "Ora l'oracolo contrassegna lo stato di destinazione:\n",
        "\n",
        "$$\n",
        "|\\psi_2\\rangle = \\frac{1}{2}\\left(|00\\rangle-|01\\rangle+|10\\rangle+|11\\rangle\\right)\n",
        "$$\n",
        "\n",
        "Si noti che in questo stato, tutti e quattro i possibili risultati hanno la stessa probabilità di essere misurati. Tutti hanno un peso di magnitudo $1/2,$, il che significa che ognuno di essi ha una probabilità $|1/2|^2=1/4$ di essere misurato. Quindi, mentre lo stato $|01\\rangle$ è contrassegnato dalla fase \"-\", ciò non ha ancora comportato un aumento della probabilità di misurare quello stato. Continuiamo applicando il livello successivo di porte di Hadamard.\n",
        "\n",
        "$$\n",
        "\\begin{aligned}\n",
        "|\\psi_3\\rangle = &\\frac{1}{4}\\left(|00\\rangle+|01\\rangle+|10\\rangle+|11\\rangle\\right)\\\\\n",
        "-&\\frac{1}{4}\\left(|00\\rangle-|01\\rangle+|10\\rangle-|11\\rangle\\right)\\\\\n",
        "+&\\frac{1}{4}\\left(|00\\rangle+|01\\rangle-|10\\rangle-|11\\rangle\\right)\\\\\n",
        "+&\\frac{1}{4}\\left(|00\\rangle-|01\\rangle-|10\\rangle+|11\\rangle\\right)\n",
        "\\end{aligned}\n",
        "$$\n",
        "\n",
        "Combinando i termini simili, troviamo\n",
        "\n",
        "$$\n",
        "|\\psi_3\\rangle = \\frac{1}{2}\\left(|00\\rangle+|01\\rangle-|10\\rangle+|11\\rangle\\right)\n",
        "$$\n",
        "\n",
        "Ora $Z_{\\text{OR}}$ capovolge il segno su tutti gli stati tranne che su $|00\\rangle$ :\n",
        "\n",
        "$$\n",
        "|\\psi_4\\rangle = \\frac{1}{2}\\left(|00\\rangle-|01\\rangle+|10\\rangle-|11\\rangle\\right)\n",
        "$$\n",
        "\n",
        "Infine, applichiamo l'ultimo strato di porte di Hadamard:\n",
        "\n",
        "$$\n",
        "\\begin{aligned}\n",
        "|\\psi_5\\rangle =&\\frac{1}{4}\\left(|00\\rangle+|01\\rangle+|10\\rangle+|11\\rangle\\right)\\\\\n",
        "-&\\frac{1}{4}\\left(|00\\rangle-|01\\rangle+|10\\rangle-|11\\rangle\\right)\\\\\n",
        "+&\\frac{1}{4}\\left(|00\\rangle+|01\\rangle-|10\\rangle-|11\\rangle\\right)\\\\\n",
        "-&\\frac{1}{4}\\left(|00\\rangle-|01\\rangle-|10\\rangle+|11\\rangle\\right)\n",
        "\\end{aligned}\n",
        "$$\n",
        "\n",
        "Vale la pena di lavorare sulla combinazione di questi termini per convincersi che il risultato è effettivamente tale:\n",
        "\n",
        "$$\n",
        "|\\psi_5\\rangle =|01\\rangle\n",
        "$$\n",
        "\n",
        "Cioè, la probabilità di misurare $|01\\rangle$ è del 100% (in assenza di rumore ed errori) e la probabilità di misurare qualsiasi altro stato è pari a zero.\n",
        "\n",
        "L'esempio dei due qubit è stato un caso particolarmente pulito; l'algoritmo di Grover non sempre riesce a produrre una probabilità del 100% di misurare lo stato target. Piuttosto, amplificherà la probabilità di misurare lo stato target. Inoltre, potrebbe essere necessario ripetere l'operatore Grover più di una volta.\n",
        "\n",
        "Nella prossima sezione metteremo in pratica questo algoritmo utilizzando computer quantistici reali IBM®.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "geo_picture_01",
      "metadata": {},
      "source": [
        "<span id=\"the-geometric-picture\" />\n",
        "\n",
        "### L'immagine geometrica\n",
        "\n",
        "L'esempio a due qubit riportato sopra ha illustrato come funziona l'algebra in un caso semplice, ma esiste un modo molto più intuitivo per comprendere l'algoritmo di Grover: come una sequenza di riflessioni geometriche su un piano bidimensionale. Di seguito descriviamo questa immagine. Per ulteriori dettagli, puoi anche consultare il corso di John Watrous [intitolato \"Fondamenti degli algoritmi quantistici\"](/learning/courses/fundamentals-of-quantum-algorithms/grover-algorithm/analysis).\n",
        "\n",
        "**Preparazione dell'aereo.** Possiamo scomporre lo stato di sovrapposizione iniziale $|\\psi\\rangle$ in due componenti. Lo stato corretto — quello che stiamo cercando — lo chiamiamo « $|A_1\\rangle$ ». Tutti gli altri stati, considerati nel loro insieme, li chiamiamo « $|A_0\\rangle$ ». Per definizione, « $|A_1\\rangle$ » e « $|A_0\\rangle$ » sono ortogonali tra loro, quindi possiamo rappresentarli come assi perpendicolari in uno spazio astratto bidimensionale. Poiché $|\\psi\\rangle$ è una combinazione lineare di queste due componenti, forma un angolo minimo $\\theta$ rispetto all'asse $|A_0\\rangle$ — vicino a $|A_0\\rangle$, poiché all'inizio solo una minuscola frazione dello stato si trova nella componente corretta $|A_1\\rangle$.\n",
        "\n",
        "**Riflessioni.** Il fatto matematico fondamentale di cui abbiamo bisogno è che un operatore della forma\n",
        "\n",
        "$$\n",
        "2|v\\rangle\\langle v| - I\n",
        "$$\n",
        "\n",
        "riflette qualsiasi stato lungo l'asse definito da $|v\\rangle.$ Per capirne il motivo, si considerino due casi: uno stato lungo $|v\\rangle$ rimane invariato, mentre uno stato perpendicolare a $|v\\rangle$ subisce un'inversione di segno. Qualsiasi altro stato può essere scomposto in queste due componenti, e l'operatore agisce su ciascuna di esse di conseguenza — il che è esattamente una riflessione sull' $|v\\rangle$ o.\n",
        "\n",
        "Risulta che sia la fase dell'oracolo che quella di diffusione nell'algoritmo di Grover possano essere rappresentate come riflessioni in questo schema geometrico.\n",
        "\n",
        "**L'oracolo come riflesso.** L'oracolo inverte il segno dello stato \" $|A_1\\rangle$ \" e lascia tutto il resto invariato. È come un riflesso sull'asse dell' $|A_0\\rangle$ e.\n",
        "\n",
        "![Rappresentazione geometrica dello stato quantistico.](https://quantum.cloud.ibm.com/learning/images/modules/computer-science/grovers/grover-geometric-setup.avif)\n",
        "\n",
        "**La diffusione come riflesso.** È un po' più complicato capire come l'operatore di diffusione sia anche una riflessione. L'operatore di diffusione è\n",
        "\n",
        "$$\n",
        "H^{\\otimes n}\\, Z_{\\text{OR}}\\, H^{\\otimes n}\n",
        "$$\n",
        "\n",
        "$Z_{\\text{OR}}$ di per sé rappresenta una riflessione sullo stato tutto a zero, poiché inverte il segno di ogni stato che non sia un \" $|0\\rangle^{\\otimes n}$ \". Ciò può essere scritto come $2|0\\rangle\\langle 0| - I$. Gli strati di Hadamard circostanti effettuano di fatto un cambio di base, trasformando l'asse di riflessione. Ricordiamo che l'operatore di Hadamard ( $H^{\\otimes n}$ ) mappa l'operatore di Hadamard ( $|0\\rangle^{\\otimes n}$ ) sulla sovrapposizione uniforme ( $|u\\rangle = \\frac{1}{\\sqrt{N}}\\sum_{x}|x\\rangle$ ). Poiché l'operatore di Hadamard è il proprio inverso, l'espressione completa diventa\n",
        "\n",
        "$$\n",
        "H^{\\otimes n}\\left(2|0\\rangle\\langle 0| - I\\right)H^{\\otimes n} = 2|u\\rangle\\langle u| - I\n",
        "$$\n",
        "\n",
        "che è una riflessione su $|u\\rangle$. Poiché $|u\\rangle$ è molto vicino a $|\\psi\\rangle$ (entrambi si trovano quasi in linea con $|A_0\\rangle$ ), questa seconda riflessione invia lo stato a un angolo $2\\theta$ rispetto al punto di partenza.\n",
        "\n",
        "![Interpretazione geometrica dell'operatore di Grover come rotazione.](https://quantum.cloud.ibm.com/learning/images/modules/computer-science/grovers/grover-geometric-reflections.avif)\n",
        "\n",
        "**Rotazione di $2\\theta$.** L'effetto combinato di questi due riflessi è una rotazione di $2\\theta$ in direzione di $|A_1\\rangle$. Ogni iterazione successiva dell'operatore di Grover ruota lo stato di un altro $2\\theta.$\n",
        "\n",
        "**Numero ottimale di iterazioni.** Il nostro obiettivo è ruotare lo stato il più possibile verso l'orientamento \" $|A_1\\rangle$ \", il che significa ruotarlo di circa $\\pi/2$ radianti (un quarto di giro). Se ogni iterazione contribuisce con un valore pari a $2\\theta$, il numero ottimale di iterazioni $t$ soddisfa\n",
        "\n",
        "$$\n",
        "(2t + 1)\\theta \\approx \\frac{\\pi}{2}\n",
        "$$\n",
        "\n",
        "Per una soluzione singola tra gli stati di un sistema a un' $N$ e, l'angolo iniziale è $\\theta \\approx \\sin^{-1}(1/\\sqrt{N}) \\approx 1/\\sqrt{N}$ (per $N$ molto grande). Sostituendo,\n",
        "\n",
        "$$\n",
        "t \\approx \\frac{\\pi}{4}\\sqrt{N} - \\frac{1}{2}\n",
        "$$\n",
        "\n",
        "È da qui che deriva il famoso miglioramento di velocità dell'algoritmo \" $\\sqrt{N}$ \": bastano infatti solo $O(\\sqrt{N})$ iterazioni per raggiungere l'obiettivo, anziché le $O(N)$ verifiche che richiederebbe una ricerca classica.\n",
        "\n",
        "Più in generale, se tra gli $N$ i stati possibili ve ne sono $|A_1|$, il numero ottimale di iterazioni è\n",
        "\n",
        "$$\n",
        "t \\approx \\frac{\\pi}{4}\\sqrt{\\frac{N}{|A_1|}} - \\frac{1}{2}\n",
        "$$\n",
        "\n",
        "Si noti che, se si applicano troppe iterazioni, si supera l' $|A_1\\rangle$ e e la probabilità di trovare lo stato desiderato ricomincerà a diminuire. È importante individuare il numero corretto di iterazioni, anche se su hardware quantistico soggetto a rumore il numero ottimale dal punto di vista sperimentale potrebbe differire da questa formula ideale.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "3fd8fbb3",
      "metadata": {},
      "source": [
        "<span id=\"why-is-grovers-algorithm-useful\" />\n",
        "\n",
        "### Perché l'algoritmo di Grover è utile?\n",
        "\n",
        "A questo punto vi starete forse chiedendo: abbiamo appena creato un oracolo che indica uno stato finale, ma per farlo dovevamo conoscere lo stato finale. Ma cosa stiamo cercando, in realtà?\n",
        "\n",
        "È una domanda legittima, e ci sono diverse risposte valide.\n",
        "\n",
        "* **Il modello di interrogazione è uno strumento teorico.** Il modello di calcolo basato sulle query non è mai stato concepito per essere direttamente applicabile. Il suo scopo è quello di fornirci un metodo chiaro per analizzare la complessità algoritmica, suddividendo un problema in due parti: l'oracolo e tutto il resto. Quanto è difficile la ricerca, visto che la verifica è gratuita? In che modo il numero di query varia in funzione delle dimensioni dei dati in ingresso? Si tratta di domande utili, anche se nessun sistema reale funziona esattamente in questo modo.\n",
        "\n",
        "* Si può anche considerarla **un** 'attività a due: una persona conosce lo stato finale e costruisce l'oracolo; il compito dell'altra persona è trovare la risposta utilizzando l'oracolo come una scatola nera, senza sbirciare all'interno. Nell'attività 2 qui sotto, farete proprio questo con un compagno.\n",
        "\n",
        "* **L'amplificazione dell'ampiezza è una subroutine di ampia utilità.** Anche se questa prima dimostrazione può sembrare un circolo vizioso, il meccanismo alla base — chiamato *amplificazione di ampiezza* — ricorre continuamente nell'informatica quantistica. Ciò che stiamo realmente sviluppando qui è una comprensione intuitiva di uno strumento che compare come subroutine in molti algoritmi quantistici più complessi.\n",
        "\n",
        "* **Ci sono problemi per i quali è possibile costruire un oracolo senza conoscere la risposta.** L'idea fondamentale è che esiste un'intera classe di problemi per i quali è molto difficile *trovare* una soluzione, ma molto facile *verificare* che una data soluzione sia corretta. Il factoring ne è un esempio: dato il prodotto di due grandi numeri primi, è estremamente difficile individuare quali siano tali numeri primi, ma una volta individuati, è possibile moltiplicarli facilmente per verificare il risultato. (Abbiamo un algoritmo migliore di quello di Grover proprio per la fattorizzazione — vedi l'algoritmo di Shor — ma questo è ben lungi dall'essere l'unico problema di questa funzione.) Il Sudoku, la soddisfazione dei vincoli e persino il classico gioco \"Campo minato\" sono tutti problemi difficili da risolvere ma facili da verificare.\n",
        "\n",
        "Perché è importante? Ciò significa che possiamo conoscere tutte le *condizioni* e *i requisiti* che una soluzione deve soddisfare e che possiamo codificare tali requisiti in un circuito quantistico che funge da oracolo, anche se non conosciamo la soluzione stessa. L'algoritmo di Grover lo troverà per noi.\n",
        "\n",
        "Tenendo presenti questi concetti, esaminiamo alcuni esempi. Inizieremo con un esempio in cui lo stato della soluzione è chiaramente definito, in modo da poter seguire la logica dell'algoritmo. Passeremo quindi a un'attività a due parti e, infine, a un esempio in cui l'oracolo viene costruito sulla base dei vincoli del problema piuttosto che sulla conoscenza della risposta.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "90cfe463",
      "metadata": {},
      "source": [
        "<span id=\"general-imports-and-approach\" />\n",
        "\n",
        "### Importazioni generali e approccio\n",
        "\n",
        "Si inizia importando alcuni pacchetti necessari.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 11,
      "id": "27a7cc58",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Built-in modules\n",
        "import math\n",
        "\n",
        "# Imports from Qiskit\n",
        "from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister\n",
        "from qiskit.circuit.library import grover_operator, MCMTGate, ZGate\n",
        "from qiskit.visualization import plot_distribution\n",
        "from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "0d5be4ca",
      "metadata": {},
      "source": [
        "In questa e in altre esercitazioni, utilizzeremo un quadro di riferimento per l'informatica quantistica noto come \"schemi Qiskit\", che suddivide i flussi di lavoro nelle seguenti fasi:\n",
        "\n",
        "* Fase 1: mappare gli input classici in un problema quantistico\n",
        "* Fase 2: Ottimizzazione del problema per l'esecuzione quantistica\n",
        "* Fase 3: Esecuzione tramite le primitive \" IBM Quantum \"\n",
        "* Fase 4: post-elaborazione e analisi classica\n",
        "\n",
        "In genere seguiamo questi passaggi, anche se non sempre li indichiamo esplicitamente.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "5e1d0a46",
      "metadata": {},
      "source": [
        "<span id=\"activity-1-find-a-single-given-target-state\" />\n",
        "\n",
        "## Attività 1: Individuare un unico stato target prestabilito\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "23b5217f",
      "metadata": {},
      "source": [
        "<span id=\"step-1-map-classical-inputs-to-a-quantum-problem\" />\n",
        "\n",
        "### Fase 1: mappare gli input classici su un problema quantistico\n",
        "\n",
        "È necessario che il gate di interrogazione di fase inserisca una fase complessiva (-1) sugli stati di soluzione, lasciando inalterati gli stati di non soluzione. Un altro modo per dirlo è che l'algoritmo di Grover richiede un oracolo che specifichi uno o più stati base computazionali marcati, dove \"marcato\" significa uno stato con una fase di -1. Per farlo si utilizza una porta Z controllata, o la sua generalizzazione multicontrollata su $N$ qubit. Per vedere come funziona, consideriamo un esempio specifico di una stringa di bit `{110}`. Vorremmo un circuito che agisca su uno stato $|\\psi\\rangle = |q_2,q_1,q_0\\rangle$ e applichi una fase se $|\\psi\\rangle = |011\\rangle$ (dove abbiamo invertito l'ordine della stringa binaria, a causa della notazione in Qiskit, che mette il qubit meno significativo (spesso 0) a destra).\n",
        "\n",
        "Pertanto, vogliamo un circuito $Z_f$ che realizzi\n",
        "\n",
        "$$\n",
        "Z_f|\\psi\\rangle = \\begin{cases} -|\\psi\\rangle \\qquad \\text{if} \\qquad |\\psi\\rangle = |011\\rangle \\\\ |\\psi\\rangle \\qquad \\text{if} \\qquad |\\psi\\rangle \\neq |011\\rangle\\end{cases}\n",
        "$$\n",
        "\n",
        "Possiamo usare il gate a controllo multiplo e target multiplo (`MCMTGate`) per applicare un gate Z controllato da tutti i qubit (capovolgendo la fase se tutti i qubit sono nello stato $|1\\rangle$ ). Naturalmente, alcuni dei qubit nel nostro stato desiderato possono essere $|0\\rangle$. Pertanto, per questi qubit dobbiamo prima applicare un gate X, poi eseguire il gate Z controllato dalla moltiplicazione, quindi applicare un altro gate X per annullare la nostra modifica. Il sito `MCMTGate` si presenta così:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 23,
      "id": "66aeceae",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/modules/computer-science/grovers/extracted-outputs/66aeceae-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 23,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "mcmt_ex = QuantumCircuit(3)\n",
        "mcmt_ex.compose(MCMTGate(ZGate(), 3 - 1, 1), inplace=True)\n",
        "mcmt_ex.draw(output=\"mpl\", style=\"iqp\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "03b992b2",
      "metadata": {},
      "source": [
        "Si noti che molti qubit possono essere coinvolti nel processo di controllo (in questo caso tre qubit), ma nessun singolo qubit è indicato come bersaglio. Questo perché l'intero stato riceve un segno \"-\" complessivo (phase flip); il gate agisce su tutti i qubit in modo equivalente. Questo è diverso da molti altri gate a qubit multipli, come il gate `CX` , che ha un singolo qubit di controllo e un singolo qubit di destinazione.\n",
        "\n",
        "Nel codice che segue, definiamo un gate di interrogazione di fase (o oracolo) che fa ciò che abbiamo appena descritto sopra: contrassegna uno o più stati base in ingresso definiti attraverso la loro rappresentazione in bitstring. Il gate MCMT viene utilizzato per implementare la porta Z multicontrollata.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 12,
      "id": "53f8763f",
      "metadata": {},
      "outputs": [],
      "source": [
        "def grover_oracle(marked_states):\n",
        "    \"\"\"Build a Grover oracle for multiple marked states\n",
        "\n",
        "    Here we assume all input marked states have the same number of bits\n",
        "\n",
        "    Parameters:\n",
        "        marked_states (str or list): Marked states of oracle\n",
        "\n",
        "    Returns:\n",
        "        QuantumCircuit: Quantum circuit representing Grover oracle\n",
        "    \"\"\"\n",
        "    if not isinstance(marked_states, list):\n",
        "        marked_states = [marked_states]\n",
        "    # Compute the number of qubits in circuit\n",
        "    num_qubits = len(marked_states[0])\n",
        "\n",
        "    qc = QuantumCircuit(num_qubits)\n",
        "    # Mark each target state in the input list\n",
        "    for target in marked_states:\n",
        "        # Flip target bitstring to match Qiskit bit-ordering\n",
        "        rev_target = target[::-1]\n",
        "        # Find the indices of all the '0' elements in bitstring\n",
        "        zero_inds = [\n",
        "            ind for ind in range(num_qubits) if rev_target.startswith(\"0\", ind)\n",
        "        ]\n",
        "        # Add a multi-controlled Z-gate with pre- and post-applied X-gates (open-controls)\n",
        "        # where the target bitstring has a '0' entry\n",
        "        qc.x(zero_inds)\n",
        "        qc.compose(MCMTGate(ZGate(), num_qubits - 1, 1), inplace=True)\n",
        "        qc.x(zero_inds)\n",
        "    return qc"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "7349dd38",
      "metadata": {},
      "source": [
        "Ora scegliamo uno specifico stato \"marcato\" come obiettivo e applichiamo la funzione appena definita. Vediamo che tipo di circuito ha creato.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 13,
      "id": "6cb8ce21",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/modules/computer-science/grovers/extracted-outputs/6cb8ce21-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 13,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "marked_states = [\"1110\"]\n",
        "oracle = grover_oracle(marked_states)\n",
        "oracle.draw(output=\"mpl\", style=\"iqp\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "5632a1dd",
      "metadata": {},
      "source": [
        "Se i qubit 1-3 sono nello stato $|1\\rangle$ e il qubit 0 è inizialmente nello stato $|0\\rangle$, il primo gate X capovolgerà il qubit 0 in $|1\\rangle$ e tutti i qubit saranno in $|1\\rangle.$ Ciò significa che il gate MCMT applicherà un cambio di segno complessivo o un capovolgimento di fase, come desiderato. In qualsiasi altro caso, o i qubit 1-3 si trovano nello stato $|0\\rangle$, o il qubit 0 è ribaltato nello stato $|0\\rangle$, e il flip di fase non verrà applicato. Vediamo che questo circuito segna effettivamente il nostro stato desiderato $|0111\\rangle,$ o la stringa di bit `{1110}`.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "4cbe89d3",
      "metadata": {},
      "source": [
        "L'operatore di Grover completo consiste nella porta di interrogazione di fase (oracolo), negli strati di Hadamard e nell'operatore $Z_\\text{OR}$. Possiamo usare il built-in `grover_operator` per costruirlo a partire dall'oracolo che abbiamo definito in precedenza.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 14,
      "id": "9426f7a5",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/modules/computer-science/grovers/extracted-outputs/9426f7a5-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 14,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "grover_op = grover_operator(oracle)\n",
        "grover_op.decompose(reps=0).draw(output=\"mpl\", style=\"iqp\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "fb293e00",
      "metadata": {},
      "source": [
        "Come abbiamo visto nell'immagine geometrica qui sopra, potrebbe essere necessario applicare l'operatore di Grover più volte. Il numero ottimale di iterazioni $t$ per massimizzare l'ampiezza dello stato di destinazione in assenza di rumore è\n",
        "\n",
        "$$\n",
        "t\\approx \\frac{\\pi}{4} \\sqrt{\\frac{N}{|A_1|}}-\\frac{1}{2}\n",
        "$$\n",
        "\n",
        "dove $|A_1|$ è il numero di stati di soluzione e $N=2^n$ è il numero totale di stati. Sui moderni computer quantistici soggetti a rumore, il numero ottimale di iterazioni potrebbe essere diverso; tuttavia, in questo caso calcoliamo e utilizziamo tale numero ottimale teorico ricorrendo a un metodo di ottimizzazione ( $|A_1|=1$ ).\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "d07c701a",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "3\n"
          ]
        }
      ],
      "source": [
        "optimal_num_iterations = math.floor(\n",
        "    math.pi / (4 * math.asin(math.sqrt(len(marked_states) / 2**grover_op.num_qubits)))\n",
        ")\n",
        "print(optimal_num_iterations)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "4698589c",
      "metadata": {},
      "source": [
        "Costruiamo ora un circuito che includa le porte di Hadamard iniziali per creare una sovrapposizione di tutti gli stati possibili e applichiamo l'operatore di Grover il numero ottimale di volte.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 16,
      "id": "63006e25",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/modules/computer-science/grovers/extracted-outputs/63006e25-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 16,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "qc = QuantumCircuit(grover_op.num_qubits)\n",
        "# Create even superposition of all basis states\n",
        "qc.h(range(grover_op.num_qubits))\n",
        "# Apply Grover operator the optimal number of times\n",
        "qc.compose(grover_op.power(optimal_num_iterations), inplace=True)\n",
        "# Measure all qubits\n",
        "qc.measure_all()\n",
        "qc.draw(output=\"mpl\", style=\"iqp\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "f14e6ebd",
      "metadata": {},
      "source": [
        "Abbiamo costruito il nostro circuito Grover!\n",
        "\n",
        "<span id=\"step-2-optimize-problem-for-quantum-hardware-execution\" />\n",
        "\n",
        "### Fase 2: Ottimizzazione del problema per l'esecuzione su hardware quantistico\n",
        "\n",
        "Abbiamo definito il nostro circuito quantistico astratto, ma dobbiamo riscriverlo in termini di porte native del computer quantistico che vogliamo utilizzare. Dobbiamo anche specificare quali qubit del computer quantistico devono essere utilizzati. Per questi motivi e per altri ancora, ora dobbiamo trasporre il nostro circuito. Innanzitutto, specifichiamo il computer quantistico che vogliamo utilizzare.\n",
        "\n",
        "Di seguito è riportato un codice per salvare le credenziali al primo utilizzo. Assicurarsi di eliminare queste informazioni dal blocco note dopo averlo salvato nel proprio ambiente, in modo che le credenziali non vengano accidentalmente condivise quando si condivide il blocco note. Per ulteriori informazioni, vedere [Configurazione dell'account IBM Cloud](/docs/guides/initialize-account) e [Inizializzazione del servizio in un ambiente non attendibile](/docs/guides/cloud-setup-untrusted).\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "994ef054",
      "metadata": {},
      "outputs": [
        {
          "name": "stderr",
          "output_type": "stream",
          "text": [
            "qiskit_runtime_service._resolve_cloud_instances:WARNING:2025-08-08 14:14:19,931: Default instance not set. Searching all available instances.\n"
          ]
        },
        {
          "data": {
            "text/plain": [
              "'ibm_brisbane'"
            ]
          },
          "execution_count": 18,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "# To run on hardware, select the backend with the fewest number of jobs in the queue\n",
        "from qiskit_ibm_runtime import QiskitRuntimeService\n",
        "\n",
        "# Syntax for first saving your token.  Delete these lines after saving your credentials.\n",
        "\n",
        "# QiskitRuntimeService.save_account(channel='ibm_quantum_platform',\n",
        "# instance = '<YOUR_IBM_INSTANCE_CRN>', token='<YOUR_API_KEY>', overwrite=True, set_as_default=True)\n",
        "# service = QiskitRuntimeService(channel='ibm_quantum_platform')\n",
        "\n",
        "# Load saved credentials\n",
        "service = QiskitRuntimeService()\n",
        "\n",
        "backend = service.least_busy(operational=True, simulator=False)\n",
        "backend.name"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a65cf945",
      "metadata": {},
      "source": [
        "Ora utilizziamo un gestore di passaggi preimpostati per ottimizzare il nostro circuito quantistico per il backend selezionato.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 171,
      "id": "35fbd6ef",
      "metadata": {},
      "outputs": [],
      "source": [
        "target = backend.target\n",
        "pm = generate_preset_pass_manager(target=target, optimization_level=3)\n",
        "\n",
        "circuit_isa = pm.run(qc)\n",
        "# The transpiled circuit will be very large. Only draw it if you are really curious.\n",
        "# circuit_isa.draw(output=\"mpl\", idle_wires=False, style=\"iqp\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "014b7868",
      "metadata": {},
      "source": [
        "Vale la pena di notare che la profondità del circuito quantistico transpilato è notevole.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 172,
      "id": "d168576f",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "The total depth is  439\n",
            "The depth of two-qubit gates is  113\n"
          ]
        }
      ],
      "source": [
        "print(\"The total depth is \", circuit_isa.depth())\n",
        "print(\n",
        "    \"The depth of two-qubit gates is \",\n",
        "    circuit_isa.depth(lambda instruction: instruction.operation.num_qubits == 2),\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "aaea2d8d",
      "metadata": {},
      "source": [
        "Si tratta di numeri piuttosto grandi, anche per questo caso semplice. Poiché tutte le porte quantistiche (e in particolare quelle a due qubit) presentano errori e sono soggette a rumore, una serie di oltre 100 porte a due qubit non produrrebbe altro che rumore se i qubit non fossero estremamente performanti. Vediamo come si comportano.\n",
        "\n",
        "<span id=\"step-3-execute-using-ibm-quantum-primitives\" />\n",
        "\n",
        "### Fase 3: Esecuzione tramite le primitive \" IBM Quantum \"\n",
        "\n",
        "Vogliamo effettuare molte misurazioni e vedere quale stato sia il più probabile. Tale amplificazione dell'ampiezza è un problema di campionamento che si presta all'esecuzione con `Sampler` la primitiva IBM Quantum.\n",
        "\n",
        "Si noti che il metodo `run()` di IBM Quantum SamplerV2 accetta un iterabile di blocchi unificati primitivi (PUB). Per Sampler, ogni PUB è un iterabile nel formato (circuito, valori\\_parametri). Tuttavia, come minimo, occorre un elenco di circuiti quantistici.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "2a272d9e",
      "metadata": {},
      "outputs": [],
      "source": [
        "# To run on a real quantum computer (this was tested on a Heron r2 processor and\n",
        "# used 4 sec. of QPU time)\n",
        "\n",
        "from qiskit_ibm_runtime import SamplerV2 as Sampler\n",
        "\n",
        "sampler = Sampler(mode=backend)\n",
        "sampler.options.default_shots = 10_000\n",
        "result = sampler.run([circuit_isa]).result()\n",
        "dist = result[0].data.meas.get_counts()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "7a123100",
      "metadata": {},
      "source": [
        "Per ottenere il massimo da questa esperienza, vi consigliamo di eseguire i vostri esperimenti sui veri computer quantistici disponibili su IBM Quantum. Tuttavia, se avete esaurito il tempo a disposizione per la QPU, potete decommentare le righe sottostanti per completare questa attività utilizzando un simulatore.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "e60bcbec",
      "metadata": {},
      "outputs": [],
      "source": [
        "# To run on local simulator:\n",
        "# from qiskit.primitives import StatevectorSampler as Sampler\n",
        "# sampler = Sampler()\n",
        "# result = sampler.run([qc]).result()\n",
        "# dist = result[0].data.meas.get_counts()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "f19b1adb",
      "metadata": {},
      "source": [
        "<span id=\"step-4-post-process-and-return-result-in-desired-classical-format\" />\n",
        "\n",
        "### Fase 4: Post-elaborazione e restituzione del risultato nel formato classico desiderato\n",
        "\n",
        "Ora possiamo tracciare i risultati del nostro campionamento in un istogramma.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 97,
      "id": "96a9107e",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/modules/computer-science/grovers/extracted-outputs/96a9107e-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 97,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "plot_distribution(dist)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "9dabb017",
      "metadata": {},
      "source": [
        "Vediamo che l'algoritmo di Grover ha restituito lo stato desiderato con la probabilità di gran lunga più alta, almeno un ordine di grandezza superiore alle altre opzioni. Nella prossima attività, utilizzeremo l'algoritmo in un modo più coerente con il flusso di lavoro a due parti di un algoritmo di interrogazione.\n",
        "\n",
        "<span id=\"check-your-understanding\" />\n",
        "\n",
        "#### Verifica la tua comprensione\n",
        "\n",
        "Abbiamo appena cercato una singola soluzione in un insieme di $2^4=16$ stati possibili. Abbiamo determinato che il numero ottimale di ripetizioni dell'operatore di Grover è $t=3$. Questo numero ottimale sarebbe aumentato o diminuito se avessimo cercato (a) una qualsiasi delle diverse soluzioni o (b) una singola soluzione in uno spazio di più stati possibili?\n",
        "\n",
        "<Accordion>\n",
        "  <AccordionItem title=\"Risposta\">\n",
        "    Ricordiamo che, finché il numero di soluzioni è piccolo rispetto all'intero spazio delle soluzioni, possiamo espandere la funzione seno attorno a piccoli angoli e usare\n",
        "\n",
        "    $$\n",
        "    (2t+1)\\theta = (2t+1) \\sin^{-1}{\\sqrt{\\frac{|\\mathcal{A}_1|}{N}}}\\approx (2t+1) \\sqrt{\\frac{|\\mathcal{A}_1|}{N}} \\approx \\pi/2\\\\\n",
        "\n",
        "    t \\approx \\frac{\\pi}{4}\\sqrt{\\frac{N}{|\\mathcal{A}_1|}}-\\frac{1}{2}\n",
        "    $$\n",
        "\n",
        "    (a) Dall'espressione precedente si evince che aumentando il numero di stati di soluzione diminuisce il numero di iterazioni. A condizione che la frazione $\\frac{|\\mathcal{A}_1|}{N}$ sia ancora piccola, possiamo descrivere come $t$ diminuisca: $t~\\frac{1}{\\sqrt{|\\mathcal{A}_1|}}.$\n",
        "\n",
        "    (b) All'aumentare dello spazio delle possibili soluzioni ( $N$ ), il numero di iterazioni necessarie aumenta, ma solo come $t~\\sqrt{N}$.\n",
        "  </AccordionItem>\n",
        "</Accordion>\n",
        "\n",
        "Supponiamo di poter aumentare la dimensione della stringa di bit bersaglio fino a renderla arbitrariamente lunga e di ottenere comunque il risultato che lo stato bersaglio ha un'ampiezza di probabilità superiore di almeno un ordine di grandezza rispetto a qualsiasi altro stato. Questo significa che possiamo usare l'algoritmo di Grover per trovare in modo affidabile lo stato di destinazione?\n",
        "\n",
        "<Accordion>\n",
        "  <AccordionItem title=\"Risposta\">\n",
        "    Num. Supponiamo di aver ripetuto la prima attività con 20 qubit e di aver eseguito il circuito quantistico un certo numero di volte `num_shots = 10,000`. Una distribuzione di probabilità uniforme significherebbe che ogni stato ha una probabilità di $10,000/2^{20}=0.00954$ di essere misurato anche una sola volta. Se la probabilità di misurare lo stato target fosse 10 volte quella delle non-soluzioni (e la probabilità di ogni non-soluzione fosse corrispondentemente leggermente diminuita), ci sarebbe solo il 10% circa di possibilità di misurare lo stato target anche una sola volta. Sarebbe altamente improbabile misurare lo stato target più volte, il che lo renderebbe indistinguibile dai molti stati non risolutivi ottenuti casualmente. La buona notizia è che possiamo ottenere risultati ancora più fedeli utilizzando la soppressione e la mitigazione degli errori.\n",
        "  </AccordionItem>\n",
        "</Accordion>\n",
        "\n",
        "<span id=\"activity-2-an-accurate-query-algorithm-workflow\" />\n",
        "\n",
        "## Attività 2: Un accurato flusso di lavoro dell'algoritmo di query\n",
        "\n",
        "Inizieremo questa attività esattamente come la prima, solo che ora farete coppia con un altro appassionato di Qiskit. Voi sceglierete una bitstring segreta e il vostro partner una bitstring (generalmente) diversa. Ciascuno di voi genererà un circuito quantistico che funziona come un oracolo e lo scambierete. Si utilizzerà quindi l'algoritmo di Grover con quell'oracolo per determinare la bitstring segreta del partner.\n",
        "\n",
        "<span id=\"step-1-map-classical-inputs-to-a-quantum-problem\" />\n",
        "\n",
        "### Fase 1: mappare gli input classici su un problema quantistico\n",
        "\n",
        "Utilizzando la funzione `grover_oracle` definita sopra, costruire un circuito oracolo per uno o più stati marcati. Assicuratevi di dire al vostro partner quanti stati avete segnato, in modo che possa applicare l'operatore Grover il numero ottimale di volte. **Non allungate troppo la vostra stringa di bit. 3-5 bit dovrebbero funzionare senza difficoltà.** Le stringhe di bit più lunghe comporterebbero circuiti profondi che richiedono tecniche più avanzate, come la mitigazione degli errori.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 173,
      "id": "5be9092e",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Modify the marked states to mark those you wish to target.\n",
        "marked_states = [\"1000\"]\n",
        "oracle = grover_oracle(marked_states)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "b4874b93",
      "metadata": {},
      "source": [
        "Ora avete creato un circuito quantistico che inverte la fase del vostro stato target. È possibile salvare questo circuito come `my_circuit.qpy` utilizzando la sintassi seguente.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "77093258",
      "metadata": {},
      "outputs": [],
      "source": [
        "from qiskit import qpy\n",
        "\n",
        "# Save to a QPY file at a location where you can easily find it.\n",
        "# You might want to specify a global address.\n",
        "with open(\"C:\\\\Users\\\\...put your own address here...\\\\my_circuit.qpy\", \"wb\") as f:\n",
        "    qpy.dump(oracle, f)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "524f5577",
      "metadata": {},
      "source": [
        "Ora inviate questo file al vostro partner (tramite e-mail, servizio di messaggistica, una repo condivisa e così via). Chiedete al vostro partner di inviarvi anche il suo circuito. Assicuratevi di salvare il file in un posto dove possiate trovarlo facilmente. Una volta ottenuto il circuito del vostro interlocutore, potreste visualizzarlo, ma questo rompe il modello di query. In altre parole, stiamo modellando una situazione in cui è possibile interrogare l'oracolo (utilizzare il circuito dell'oracolo), ma non esaminarlo per determinare quale sia lo stato a cui si rivolge.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "24ba4869",
      "metadata": {},
      "outputs": [],
      "source": [
        "from qiskit import qpy\n",
        "\n",
        "# Load the circuit from your partner's qpy file from the folder where you saved it.\n",
        "with open(\"C:\\\\Users\\\\...file location here...\\\\my_circuit.qpy\", \"rb\") as f:\n",
        "    circuits = qpy.load(f)\n",
        "\n",
        "# qpy.load always returns a list of circuits\n",
        "oracle_partner = circuits[0]\n",
        "\n",
        "# You could visualize the circuit, but this would break the model of a query algorithm.\n",
        "# oracle_partner.draw(\"mpl\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "0c7b7012",
      "metadata": {},
      "source": [
        "Chiedete al vostro compagno quanti stati target ha codificato e inseritelo qui sotto.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 174,
      "id": "120e339c",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Update according to your partner's number of target states.\n",
        "num_marked_states = 1"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "f2c4b911",
      "metadata": {},
      "source": [
        "Questo dato viene utilizzato nella prossima espressione per determinare il numero ottimale di iterazioni di Grover.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 175,
      "id": "d199a8cc",
      "metadata": {},
      "outputs": [],
      "source": [
        "grover_op = grover_operator(oracle_partner)\n",
        "optimal_num_iterations = math.floor(\n",
        "    math.pi / (4 * math.asin(math.sqrt(num_marked_states / 2**grover_op.num_qubits)))\n",
        ")\n",
        "qc = QuantumCircuit(grover_op.num_qubits)\n",
        "qc.h(range(grover_op.num_qubits))\n",
        "qc.compose(grover_op.power(optimal_num_iterations), inplace=True)\n",
        "qc.measure_all()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "37eb709b",
      "metadata": {},
      "source": [
        "<span id=\"step-2-optimize-problem-for-quantum-hardware-execution\" />\n",
        "\n",
        "### Fase 2: Ottimizzazione del problema per l'esecuzione su hardware quantistico\n",
        "\n",
        "Si procede esattamente come in precedenza.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 176,
      "id": "e5e89707",
      "metadata": {},
      "outputs": [],
      "source": [
        "# To run on hardware, select the backend with the fewest number of jobs in the queue\n",
        "service = QiskitRuntimeService()\n",
        "backend = service.least_busy(operational=True, simulator=False)\n",
        "backend.name\n",
        "\n",
        "target = backend.target\n",
        "pm = generate_preset_pass_manager(target=target, optimization_level=3)\n",
        "circuit_partner_isa = pm.run(qc)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "de2af3e1",
      "metadata": {},
      "source": [
        "<span id=\"step-3-execute-using-ibm-quantum-primitives\" />\n",
        "\n",
        "### Fase 3: Esecuzione tramite le primitive \" IBM Quantum \"\n",
        "\n",
        "Anche questo procedimento è identico a quello della prima attività.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "16f97083",
      "metadata": {},
      "outputs": [],
      "source": [
        "# To run on a real quantum computer (this was tested on a Heron r2 processor and used\n",
        "# 4 seconds of QPU time)\n",
        "\n",
        "from qiskit_ibm_runtime import SamplerV2 as Sampler\n",
        "\n",
        "sampler = Sampler(mode=backend)\n",
        "sampler.options.default_shots = 10_000\n",
        "result = sampler.run([circuit_partner_isa]).result()\n",
        "dist = result[0].data.meas.get_counts()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "14bbde32",
      "metadata": {},
      "source": [
        "<span id=\"step-4-post-process-and-return-result-in-desired-classical-format\" />\n",
        "\n",
        "### Fase 4: Post-elaborazione e restituzione del risultato nel formato classico desiderato\n",
        "\n",
        "Visualizzare ora un istogramma dei risultati del campionamento. Uno o più stati dovrebbero avere una probabilità di misurazione molto più alta degli altri. Riferiteli al vostro partner e verificate se avete determinato correttamente gli stati target. Per impostazione predefinita, l'istogramma visualizzato è quello dello stesso circuito della prima attività. Dovreste ottenere risultati diversi dal circuito del vostro partner.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 114,
      "id": "ee7a59ac",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/modules/computer-science/grovers/extracted-outputs/ee7a59ac-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 114,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "plot_distribution(dist)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "9c03a936",
      "metadata": {},
      "source": [
        "<span id=\"check-your-understanding\" />\n",
        "\n",
        "#### Verifica la tua comprensione\n",
        "\n",
        "Dovreste aver ottenuto correttamente lo stato o gli stati di destinazione del vostro partner. In caso contrario, collaborate con il vostro partner per individuare cosa è andato storto. Cliccate qui sotto per alcune idee.\n",
        "\n",
        "<Accordion>\n",
        "  <AccordionItem title=\"Suggerimenti\">\n",
        "    * Visualizzate/disegnate il circuito del vostro partner e assicuratevi che sia stato caricato correttamente.\n",
        "    * Confrontate i circuiti utilizzati e confrontate il risultato atteso con quello ottenuto.\n",
        "    * Controllare la profondità dei circuiti utilizzati per assicurarsi che la stringa di bit non sia troppo lunga o che il numero di iterazioni di Grover sia proibitivo.\n",
        "  </AccordionItem>\n",
        "</Accordion>\n",
        "\n",
        "Se non l'avete ancora fatto, disegnate il circuito dell'oracolo che vi ha inviato il vostro partner. Vedete se riuscite a descrivere l'effetto di ciascun cancello e ad argomentare quale doveva essere lo stato di destinazione. Questo sarà molto più facile nel caso di un singolo stato marcato che nel caso di più stati.\n",
        "\n",
        "<Accordion>\n",
        "  <AccordionItem title=\"Suggerimenti\">\n",
        "    * Ricordiamo che il compito dell'oracolo è quello di invertire il segno sullo stato di destinazione.\n",
        "    * Ricordiamo che il MCMTGate inverte il segno di uno stato se e solo se tutti i qubit coinvolti nel controllo sono nello stato $|1\\rangle$.\n",
        "    * Se il vostro stato di destinazione avrà già un $|1\\rangle$ su un particolare qubit, non dovrete fare nulla a quel qubit. Se il vostro obiettivo ha un $|0\\rangle$ su un particolare qubit e volete che l'MCMTGate inverta il segno, dovete applicare un gate `X` a quel qubit nel vostro oracolo (e poi annullare il gate `X` dopo l'MCMTGate).\n",
        "  </AccordionItem>\n",
        "</Accordion>\n",
        "\n",
        "Ripetere l'esperimento con un'iterazione in meno dell'operatore Grover. Ottenete comunque la risposta corretta? Perché o perché no?\n",
        "\n",
        "<Accordion>\n",
        "  <AccordionItem title=\"Indicazioni\">\n",
        "    Probabilmente sì, anche se potrebbe dipendere dal numero di soluzioni codificate. Questo evidenzia una sottigliezza: il numero \"ottimale\" di iterazioni di Grover è quello che rende la probabilità di misurare lo stato marcato la più alta possibile. Ma un numero inferiore di iterazioni potrebbe comunque rendere lo stato contrassegnato sostanzialmente più probabile di altri stati. Pertanto, si potrebbe riuscire a fare a meno di un numero di iterazioni inferiore a quello ottimale. In questo modo si riduce la profondità del circuito e quindi il tasso di errore.\n",
        "  </AccordionItem>\n",
        "</Accordion>\n",
        "\n",
        "Perché qualcuno potrebbe voler utilizzare un numero di iterazioni di Grover inferiore al \"numero ottimale\" qui individuato?\n",
        "\n",
        "<Accordion>\n",
        "  <AccordionItem title=\"Risposta\">\n",
        "    Il numero \"ottimale\" di iterazioni di Grover è quello che rende la probabilità di misurare lo stato marcato la più alta possibile in assenza di rumore. Ma un numero inferiore di iterazioni potrebbe comunque rendere lo stato contrassegnato sostanzialmente più probabile di altri stati. Pertanto, è possibile che si riesca a ottenere un numero di iterazioni inferiore a quello ottimale. In questo modo si riduce la profondità del circuito e quindi il tasso di errore.\n",
        "  </AccordionItem>\n",
        "</Accordion>\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "mine_intro_01",
      "metadata": {},
      "source": [
        "<span id=\"activity-3-solve-a-minesweeper-grid-with-grovers-algorithm\" />\n",
        "\n",
        "## Attività 3: Risolvere una griglia di Campo minato con l'algoritmo di Grover\n",
        "\n",
        "Nella sezione precedente abbiamo osservato che l'algoritmo di Grover diventa davvero utile quando è possibile costruire un oracolo a partire dai *vincoli* di un problema, piuttosto che dalla conoscenza della risposta. Il gioco \"Campo minato\" ne è un esempio perfetto: le caselle numerate ci indicano quante mine si trovano nelle caselle adiacenti, e questi vincoli determinano interamente la posizione delle mine — ma per trovare la configurazione è necessario effettuare una ricerca.\n",
        "\n",
        "È stato dimostrato che il gioco \"Campo minato\" è NP-completo: è difficile da risolvere ma facile da verificare. Questo lo rende un candidato naturale per l'algoritmo di Grover. Ovviamente, non siamo ancora in grado di risolvere una griglia completa di 9× $\\times$ 9 su un computer quantistico soggetto a rumore: i circuiti sarebbero troppo complessi. Useremo invece una griglia molto piccola come esempio illustrativo di come ci si potrebbe approcciare a una scheda più grande su una futura macchina a tolleranza di guasti.\n",
        "\n",
        "Alcune precisazioni importanti. L'algoritmo di Grover offre solo un miglioramento quadratico rispetto alla ricerca classica *non strutturata*. È quasi certo che il gioco \"Campo minato\" presenti una struttura sfruttabile che un algoritmo classico ben congegnato potrebbe utilizzare. E in un ambito di ricerca in crescita esponenziale, anche il miglioramento offerto dall' $\\sqrt{N}$ e ha i suoi limiti. Ma mettiamo da parte queste preoccupazioni e utilizziamo questo problema di esempio per illustrare come i vincoli del problema vengono codificati in un oracolo quantistico.\n",
        "\n",
        "<span id=\"the-grid\" />\n",
        "\n",
        "### La rete\n",
        "\n",
        "Ecco la griglia del nostro Campo Minato per bambini:\n",
        "\n",
        "![Una semplice griglia di Campo minato con tre caselle vuote e tre caselle numerate.](https://quantum.cloud.ibm.com/learning/images/modules/computer-science/grovers/minesweeper-grid.avif)\n",
        "\n",
        "Ogni casella vuota può essere rappresentata da una variabile binaria che indica se contiene una mina. Li indichiamo con le sigle $x_0$, $x_1$ e $x_2$, dove $x_i = 1$ indica che in quella casella c'è una mina e $x_i = 0$ indica che non ce n'è:\n",
        "\n",
        "![La stessa griglia di Campo Minato con variabili x0, x1, x2 che indicano le caselle vuote.](https://quantum.cloud.ibm.com/learning/images/modules/computer-science/grovers/minesweeper-grid-labeled.avif)\n",
        "\n",
        "Potremmo risolverlo a mente in circa mezzo secondo, ma stiamo usando questo semplice esempio per illustrare come si potrebbe affrontare un problema molto più complesso con un computer quantistico.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "mine_bool_01",
      "metadata": {},
      "source": [
        "<span id=\"encode-the-constraints\" />\n",
        "\n",
        "### Codificare i vincoli\n",
        "\n",
        "Ogni cella numerata impone una condizione alle celle vuote adiacenti. Dobbiamo esprimere queste condizioni sotto forma di espressioni booleane che possano essere codificate in un circuito quantistico.\n",
        "\n",
        "La casella con il numero \"1\" adiacente a $x_0$ e $x_1$ indica che esattamente uno dei due siti contiene una mina. Si tratta proprio dell'operazione OR esclusivo (XOR), $\\oplus$, che restituisce vero quando esattamente uno dei suoi argomenti è vero:\n",
        "\n",
        "$$\n",
        "(x_0 \\oplus x_1)\n",
        "$$\n",
        "\n",
        "Allo stesso modo, l'altra cella contenente \"1\" (adiacente a $x_1$ e $x_2$ ) ci dà:\n",
        "\n",
        "$$\n",
        "(x_1 \\oplus x_2)\n",
        "$$\n",
        "\n",
        "La casella \"2\" indica che due delle tre caselle vuote devono contenere delle mine. Poiché l'operazione XOR è un'operazione di parità, l' $x_0 \\oplus x_1 \\oplus x_2$ e restituisce vero quando un numero *dispari* di variabili è vero. Vogliamo che sia vero un numero *pari* (in particolare due), quindi neghiamo con l' $\\lnot$ e:\n",
        "\n",
        "$$\n",
        "\\lnot(x_0 \\oplus x_1 \\oplus x_2)\n",
        "$$\n",
        "\n",
        "Di per sé, questa espressione sarebbe soddisfatta sia da zero che da due qubit nello stato \" $|1\\rangle$ \", poiché si tratta di un'affermazione relativa alla parità. Ma se si considerano le altre due condizioni, che richiedono ciascuna almeno una mina, l'unica soluzione valida prevede esattamente due mine.\n",
        "\n",
        "Tutte e tre le condizioni devono essere soddisfatte contemporaneamente, quindi le uniamo con i simboli \"e\" $\\land$ :\n",
        "\n",
        "$$\n",
        "(x_0 \\oplus x_1) \\;\\land\\; (x_1 \\oplus x_2) \\;\\land\\; \\lnot(x_0 \\oplus x_1 \\oplus x_2)\n",
        "$$\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "mine_oracle_01",
      "metadata": {},
      "source": [
        "<span id=\"step-1-map-classical-inputs-to-a-quantum-problem\" />\n",
        "\n",
        "### Fase 1: mappare gli input classici su un problema quantistico\n",
        "\n",
        "Ora dobbiamo codificare questa espressione booleana in un circuito quantistico che funga da oracolo. La versione quantistica dell'operazione XOR può essere realizzata utilizzando porte CX (CNOT): applicando due porte CX dai qubit di dati a un qubit dello spazio di lavoro (ancilla) si calcola di fatto la loro operazione XOR e si memorizza il risultato nell'ancilla.\n",
        "\n",
        "Introduciamo tre qubit di spazio di lavoro: uno per ogni clausola. Memorizziamo il risultato di ciascuna espressione booleana nel corrispondente qubit dello spazio di lavoro, quindi utilizziamo un gate Z a controllo multiplo per invertire la fase dello stato a tre qubit in modo che tutti e tre i qubit dello spazio di lavoro siano in uno stato \" $|1\\rangle$ \" (il che significa che tutte le clausole sono soddisfatte contemporaneamente).\n",
        "\n",
        "Nella prima cella di codice qui sotto, realizziamo la parte \"di calcolo\" dell'oracolo, ovvero quella che valuta ogni clausola e scrive il risultato nei qubit dell'area di lavoro.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "mine_code_oracle1",
      "metadata": {},
      "outputs": [],
      "source": [
        "x = QuantumRegister(3, \"x\")\n",
        "a = QuantumRegister(3, \"a\")\n",
        "qc = QuantumCircuit(x, a)\n",
        "\n",
        "# Clause 1: x0 XOR x1 -> stored in a[0]\n",
        "qc.cx(x[0], a[0])\n",
        "qc.cx(x[1], a[0])\n",
        "\n",
        "# Clause 2: x1 XOR x2 -> stored in a[1]\n",
        "qc.cx(x[1], a[1])\n",
        "qc.cx(x[2], a[1])\n",
        "\n",
        "# Clause 3: NOT(x0 XOR x1 XOR x2) -> stored in a[2]\n",
        "qc.cx(x[0], a[2])\n",
        "qc.cx(x[1], a[2])\n",
        "qc.cx(x[2], a[2])\n",
        "qc.x(a[2])  # The NOT\n",
        "\n",
        "qc.draw(\"mpl\", style=\"iqp\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "mine_mcz_01",
      "metadata": {},
      "source": [
        "A questo punto, il risultato di ciascuna clausola viene memorizzato nel qubit dell'area di lavoro corrispondente. Ora ci serve lo stato di dati a tre qubit che faccia sì che tutti e tre i qubit dello spazio di lavoro si trovino in una posizi $|1\\rangle$ e da assumere un segno negativo. A tal fine utilizziamo una porta Z a controlli multipli (implementata come porta MCX inserita tra due porte di Hadamard sul lato di destinazione).\n",
        "\n",
        "Dopo aver applicato l'inversione di fase, dobbiamo **eseguire l'«uncompute»** — ovvero annullare tutti i passaggi di valutazione delle clausole in ordine inverso — per riportare i qubit dello spazio di lavoro allo stato « $|0\\rangle.$ ». Ciò è fondamentale affinché i qubit dello spazio di lavoro siano «puliti» per le successive iterazioni dell'operatore di Grover.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "mine_code_oracle2",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Multi-controlled Z: flip phase if all workspace qubits are |1>\n",
        "qc.h(a[2])\n",
        "qc.mcx([a[0], a[1]], a[2])\n",
        "qc.h(a[2])\n",
        "\n",
        "# Uncompute clause 3: NOT(x0 XOR x1 XOR x2)\n",
        "qc.x(a[2])\n",
        "qc.cx(x[2], a[2])\n",
        "qc.cx(x[1], a[2])\n",
        "qc.cx(x[0], a[2])\n",
        "\n",
        "# Uncompute clause 2: x1 XOR x2\n",
        "qc.cx(x[2], a[1])\n",
        "qc.cx(x[1], a[1])\n",
        "\n",
        "# Uncompute clause 1: x0 XOR x1\n",
        "qc.cx(x[1], a[0])\n",
        "qc.cx(x[0], a[0])\n",
        "\n",
        "qc.draw(\"mpl\", style=\"iqp\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "mine_groverop_01",
      "metadata": {},
      "source": [
        "Questo circuito è il nostro oracolo: inverte la fase dello stato del qubit di dati che soddisfa tutti e tre i vincoli del \"Campo minato\" e riporta i qubit dell'area di lavoro in uno stato di \" $|0\\rangle.$ \"\n",
        "\n",
        "Ora costruiamo l'operatore di Grover completo a partire da questo oracolo. `x`Da notare l'argomento `reflection_qubits` : passiamo solo i qubit di dati, poiché i qubit dello spazio di lavoro non fanno parte dello spazio di ricerca. Il loro compito è terminato una volta che l'oracolo è stato applicato.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "mine_code_groverop",
      "metadata": {},
      "outputs": [],
      "source": [
        "grover_op = grover_operator(qc, reflection_qubits=x)\n",
        "grover_op.decompose(reps=0).draw(output=\"mpl\", style=\"iqp\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "mine_circuit_01",
      "metadata": {},
      "source": [
        "Con tre qubit di dati e uno stato di soluzione, il numero ottimale di iterazioni di Grover è pari a $t \\approx \\frac{\\pi}{4}\\sqrt{8} - \\frac{1}{2} \\approx 1.7$, quindi utilizziamo due iterazioni. Applichiamo le porte di Hadamard ai qubit di dati per creare la sovrapposizione iniziale, componiamo due volte l'operatore di Grover e misuriamo solo i qubit di dati.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "mine_code_circuit",
      "metadata": {},
      "outputs": [],
      "source": [
        "x = QuantumRegister(3, \"x\")\n",
        "a = QuantumRegister(4, \"a\")\n",
        "meas = ClassicalRegister(3, \"meas\")\n",
        "\n",
        "qc = QuantumCircuit(x, a, meas)\n",
        "# Create superposition over the data qubits only\n",
        "qc.h(x)\n",
        "# Apply 2 iterations of the Grover operator\n",
        "qc.compose(grover_op.power(2), inplace=True)\n",
        "# Measure only the data qubits\n",
        "qc.measure(x, meas)\n",
        "qc.decompose().draw(output=\"mpl\", style=\"iqp\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "mine_step2_01",
      "metadata": {},
      "source": [
        "<span id=\"step-2-optimize-problem-for-quantum-hardware-execution\" />\n",
        "\n",
        "### Fase 2: Ottimizzazione del problema per l'esecuzione su hardware quantistico\n",
        "\n",
        "Come in precedenza, compiliamo il circuito per il backend di destinazione.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "mine_code_transpile",
      "metadata": {},
      "outputs": [],
      "source": [
        "service = QiskitRuntimeService()\n",
        "backend = service.least_busy(operational=True, simulator=False)\n",
        "print(backend.name)\n",
        "\n",
        "target = backend.target\n",
        "pm = generate_preset_pass_manager(target=target, optimization_level=3)\n",
        "circuit_isa = pm.run(qc)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "mine_depth_01",
      "metadata": {},
      "source": [
        "Ora possiamo verificare la profondità del circuito transpilato. Poiché l'oracolo Minesweeper utilizza qubit di workspace e più porte CX, il circuito transpilato risulterà più complesso rispetto a quelli delle attività precedenti.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "mine_code_depth",
      "metadata": {},
      "outputs": [],
      "source": [
        "print(\"The total depth is \", circuit_isa.depth())\n",
        "print(\n",
        "    \"The depth of two-qubit gates is \",\n",
        "    circuit_isa.depth(lambda instruction: instruction.operation.num_qubits == 2),\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "mine_step3_01",
      "metadata": {},
      "source": [
        "<span id=\"step-3-execute-using-ibm-quantum-primitives\" />\n",
        "\n",
        "### Fase 3: Esecuzione tramite le primitive \" IBM Quantum \"\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "mine_code_run",
      "metadata": {},
      "outputs": [],
      "source": [
        "# To run on a real quantum computer (this was tested on a Heron r2 processor and\n",
        "#  used 4 sec. of QPU time)\n",
        "\n",
        "from qiskit_ibm_runtime import SamplerV2 as Sampler\n",
        "\n",
        "sampler = Sampler(mode=backend)\n",
        "sampler.options.default_shots = 10_000\n",
        "result = sampler.run([circuit_isa]).result()\n",
        "dist = result[0].data.meas.get_counts()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "mine_code_sim",
      "metadata": {},
      "outputs": [],
      "source": [
        "# To run on local simulator:\n",
        "# from qiskit.primitives import StatevectorSampler as Sampler\n",
        "# sampler = Sampler()\n",
        "# result = sampler.run([qc]).result()\n",
        "# dist = result[0].data.meas.get_counts()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "mine_step4_01",
      "metadata": {},
      "source": [
        "<span id=\"step-4-post-process-and-return-result-in-desired-classical-format\" />\n",
        "\n",
        "### Fase 4: Post-elaborazione e restituzione del risultato nel formato classico desiderato\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "mine_code_plot",
      "metadata": {},
      "outputs": [],
      "source": [
        "plot_distribution(dist)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "mine_conclusion_01",
      "metadata": {},
      "source": [
        "Lo `101` stato dovrebbe comparire con una probabilità di gran lunga superiore rispetto a qualsiasi altro, il che indica che le mine si trovano su $x_0$ e $x_2$. Abbiamo usato un computer quantistico per risolvere una minuscola partita a Campo minato!\n",
        "\n",
        "Ovviamente, i migliori algoritmi classici per il gioco del campo minato sono più efficaci di una ricerca per forza bruta su tutte le possibili configurazioni delle mine: sfruttano infatti la struttura della griglia. L'algoritmo di Grover offrirebbe un vantaggio solo su tabelle estremamente complesse, progettate per essere il più ambigue possibile, e anche in quel caso, l'accelerazione quadratica implica che non possa tenere il passo con la crescita esponenziale all'infinito. Ma il vero punto fondamentale è la tecnica: codificare i vincoli di un problema in un oracolo quantistico è un modello potente che si estende alla soddisfazione dei vincoli, all'ottimizzazione combinatoria e a molti altri ambiti.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "494c1799",
      "metadata": {},
      "source": [
        "<span id=\"questions-and-critical-concepts\" />\n",
        "\n",
        "## Domande e concetti chiave:\n",
        "\n",
        "<span id=\"critical-concepts\" />\n",
        "\n",
        "### Concetti fondamentali:\n",
        "\n",
        "In questo modulo abbiamo appreso alcune caratteristiche fondamentali dell'algoritmo di Grover:\n",
        "\n",
        "* Mentre i classici algoritmi di ricerca non strutturata richiedono un numero di interrogazioni che scala linearmente nella dimensione dello spazio, l'algoritmo di $N,$ Grover richiede un numero di interrogazioni che scala come $\\sqrt{N}.$\n",
        "* L'algoritmo di Grover prevede la ripetizione di una serie di operazioni (comunemente chiamate \"operatore di Grover\") per un numero di volte $t,$ scelto per rendere ottimale la probabilità di misurare gli stati target.\n",
        "* L'algoritmo di Grover può essere eseguito con meno di $t$ iterazioni e continuare ad amplificare gli stati target.\n",
        "* L'algoritmo di Grover si adatta al modello di query della computazione e ha più senso quando una persona controlla la ricerca e un'altra controlla/costruisce l'oracolo. Può anche essere utile come subroutine in altri calcoli quantistici.\n",
        "* È possibile costruire un oracolo partendo *dai vincoli del problema* piuttosto che dalla conoscenza della soluzione, come dimostrato dall'esempio del Campo minato.\n",
        "\n",
        "<span id=\"t/f-questions\" />\n",
        "\n",
        "### Domande vero/falso:\n",
        "\n",
        "1. T/F L'algoritmo di Grover fornisce un miglioramento esponenziale rispetto agli algoritmi classici nel numero di query necessarie per trovare un singolo stato marcato nella ricerca non strutturata.\n",
        "\n",
        "2. T/F L'algoritmo di Grover funziona aumentando iterativamente la probabilità che venga misurato uno stato di soluzione.\n",
        "\n",
        "3. T/F Più volte si itera l'operatore di Grover, più alta è la probabilità di misurare uno stato di soluzione.\n",
        "\n",
        "<span id=\"mc-questions\" />\n",
        "\n",
        "### Domande del moderatore:\n",
        "\n",
        "1. Selezionate l'opzione migliore per completare la frase. La strategia migliore per utilizzare con successo l'algoritmo di Grover sui moderni computer quantistici è quella di iterare l'operatore di Grover...\n",
        "\n",
        "* a. Solo una volta.\n",
        "* b. Sempre $t$ volte, per massimizzare l'ampiezza di probabilità dello stato (o degli stati) di soluzione.\n",
        "* c. Fino a $t$ volte, anche se un numero inferiore può essere sufficiente per far risaltare gli stati di soluzione.\n",
        "* d. Non meno di 10 volte.\n",
        "\n",
        "2. Viene mostrato un circuito di interrogazione di fase che funziona come un oracolo per contrassegnare un determinato stato con un salto di fase. Quali dei seguenti stati sono contrassegnati da questo circuito?\n",
        "\n",
        "![Immagine di un semplice oracolo di Grover.](https://quantum.cloud.ibm.com/learning/images/modules/computer-science/grovers/grover-oracle-question.avif)\n",
        "\n",
        "* a. $|0000\\rangle$\n",
        "* b. $|0101\\rangle$\n",
        "* c. $|0110\\rangle$\n",
        "* d. $|1001\\rangle$\n",
        "* e. $|1010\\rangle$\n",
        "* f. $|1111\\rangle$\n",
        "\n",
        "3. Supponiamo di voler cercare tre stati marcati da un insieme di 128. Qual è il numero ottimale di iterazioni dell'operatore di Grover per massimizzare le ampiezze degli stati marcati?\n",
        "\n",
        "* a. 1\n",
        "* b. 3\n",
        "* c. 5\n",
        "* d. 6\n",
        "* e. 20\n",
        "* f. 33\n",
        "\n",
        "<span id=\"discussion-questions\" />\n",
        "\n",
        "### Domande di discussione:\n",
        "\n",
        "1. Quali altri problemi potresti formulare come ricerca di Grover? Pensa a quei problemi per i quali è difficile trovare una soluzione, ma è facile verificarne la correttezza.\n",
        "\n",
        "2. Si possono riscontrare problemi di scalabilità dell'algoritmo di Grover sui moderni computer quantistici?\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "id": "a1b8767d",
      "source": "© IBM Corp., 2017-2026"
    }
  ],
  "metadata": {
    "in_page_toc_max_heading_level": 2,
    "in_page_toc_min_heading_level": 2,
    "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": 5
}