{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "70810e0b-08f9-48ae-beb5-8c863819000b",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Mitigazione degli errori di lettura per la primitiva Sampler utilizzando l' M3\"\n",
        "description: \"Utilizza l'add-on di mitigazione della lettura \\\" M3 \\\" con la primitiva Sampler\"\n",
        "---\n",
        "\n",
        "{/* cspell:ignore braket, Zgrm, newcommand, probs, quasis, topten */}\n",
        "\n",
        "<span id=\"readout-error-mitigation-for-the-sampler-primitive-using-m3\" />\n",
        "\n",
        "# Mitigazione degli errori di lettura per la primitiva Sampler utilizzando l' M3\n",
        "\n",
        "*Stima di utilizzo: meno di un minuto su un processore Heron r2 (NOTA: questa è solo una stima. Il tempo di esecuzione potrebbe variare)*\n",
        "\n",
        "<span id=\"background\" />\n",
        "\n",
        "## Sfondo\n",
        "\n",
        "A differenza della primitiva Estimator, la primitiva Sampler non dispone di un supporto integrato per la mitigazione degli errori.\n",
        "Molti dei metodi supportati dall'Estimatore sono progettati specificamente per i valori di aspettativa e quindi non sono applicabili alla primitiva Campionatore. Un'eccezione è rappresentata dalla mitigazione degli errori di lettura, un metodo molto efficace applicabile anche alla primitiva Sampler.\n",
        "\n",
        "L' [addon M3 Qiskit](https://qiskit.github.io/qiskit-addon-mthree/) implementa un metodo efficiente per la mitigazione degli errori di lettura. Questa esercitazione spiega come utilizzare l'addon M3 Qiskit per attenuare l'errore di lettura della primitiva Sampler.\n",
        "\n",
        "<span id=\"what-is-readout-error\" />\n",
        "\n",
        "### Che cos'è un errore di lettura?\n",
        "\n",
        "Immediatamente prima della misurazione, lo stato di un registro di qubit viene descritto da una sovrapposizione di stati base computazionali, o da una matrice di densità.\n",
        "La misurazione del registro di qubit in un registro di bit classico procede quindi in due fasi.\n",
        "Per prima cosa viene eseguita la misurazione quantistica vera e propria.\n",
        "Ciò significa che lo stato del registro di qubit è proiettato su un singolo stato base che è caratterizzato da una stringa di $1$ s e $0$ s.\n",
        "Il secondo passo consiste nel leggere la stringa di bit che caratterizza questo stato base e scriverla nella memoria classica del computer.\n",
        "Chiamiamo questo passo *lettura*.\n",
        "Risulta che la seconda fase (lettura) comporta un errore maggiore rispetto alla prima fase (proiezione sugli stati base).\n",
        "Questo ha senso se si ricorda che la lettura richiede la rilevazione di uno stato microscopico stato quantistico e amplificarlo nel regno macroscopico. Un risonatore di lettura è accoppiato al il qubit (transmon), sperimentando così un piccolissimo spostamento di frequenza. Un impulso a microonde viene fatto rimbalzare sul risonatore, che a sua volta subisce piccoli cambiamenti nelle sue caratteristiche caratteristiche.  L'impulso riflesso viene quindi amplificato e analizzato.  Si tratta di un processo delicato e soggetto a una serie di errori.\n",
        "\n",
        "Il punto importante è che, mentre sia la misura quantistica che la lettura sono soggette a errori, la seconda incorre in un errore dominante, chiamato errore di lettura, che è l'obiettivo di questo tutorial quest'ultima incorre nell'errore dominante, chiamato errore di lettura, che è l'obiettivo di questa esercitazione.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ba74fc81-30ac-4365-9ba6-a891ba082bd6",
      "metadata": {
        "jp-MarkdownHeadingCollapsed": true
      },
      "source": [
        "<span id=\"theoretical-background\" />\n",
        "\n",
        "### Contesto teorico\n",
        "\n",
        "Se la stringa di bit campionata (memorizzata nella memoria classica) differisce dalla stringa di bit che caratterizza lo stato quantistico proiettato, si dice che si è verificato un errore di lettura lo stato quantistico proiettato, si dice che si è verificato un errore di lettura.\n",
        "Si osserva che questi errori sono casuali e non correlati da un campione all'altro.\n",
        "Si è dimostrato utile modellare l'errore di lettura come un *canale classico rumoroso*.\n",
        "Cioè, per ogni coppia di $i$ e $j$, c'è una probabilità fissa che un valore vero di $j$ venga letto erroneamente come venga letto erroneamente come $i$.\n",
        "\n",
        "Più precisamente, per ogni coppia di bitstring $(i, j)$, esiste una probabilità (condizionata) ${M}_{i,j}$ che $i$ venga letto, dato che il valore vero è $j.$ Ovvero,\n",
        "\n",
        "$$\n",
        "    {M}_{i,j} =  \\Pr(\\text{readout value is } i | \\text{true value is } j)\n",
        "    \\text{ for } i,j \\in (0,...,2^n - 1), \\tag{1}\n",
        "$$\n",
        "\n",
        "dove $n$ è il numero di bit del registro di lettura.\n",
        "Per concretezza, assumiamo che $i$ sia un numero intero decimale la cui rappresentazione binaria sia la stringa di bit che etichetta gli stati della base computazionale.\n",
        "Chiamiamo la matrice $2^n \\times 2^n$ ${M}$ *matrice di assegnazione*.\n",
        "Per un valore vero fisso $j$, la somma delle probabilità su tutti i risultati rumorosi $i$ deve dare $1$. Cioè\n",
        "\n",
        "$$\n",
        "    \\sum_{i=0}^{2^n - 1} {M}_{i,j} = 1 \\text{ for all } j\n",
        "$$\n",
        "\n",
        "Una matrice senza voci negative che soddisfa la (1) è detta *sinistra-stocastica*.\n",
        "Una matrice sinistra-stocastica è anche detta *colonna-stocastica* perché ciascuna delle sue colonne somma a $1$. Determiniamo sperimentalmente valori approssimativi per ogni elemento ${M}_{i,j}$ preparando ripetutamente ogni stato base e poi calcolando le frequenze preparando ripetutamente ogni stato base $|j \\rangle$ e poi calcolando le frequenze di occorrenza delle stringhe di bit campionate.\n",
        "\n",
        "Se un esperimento prevede la stima di una distribuzione di probabilità sulle stringhe di bit in uscita mediante campionamento ripetuto, si può usare ${M}$ per mitigare l'errore di lettura a livello della distribuzione.\n",
        "Il primo passo consiste nel ripetere più volte un circuito fisso di interesse, creando un istogramma di stringhe di bit campionate.\n",
        "L'istogramma normalizzato è la distribuzione di probabilità misurata su $2^n$ possibili bitstringhe, che indichiamo con ${\\tilde{p}} \\in \\mathbb{R}^{2^n}$. La probabilità (stimata) ${{\\tilde{p}}}_i$ di campionare una bitstringa $i$ è uguale alla somma di tutte le bitstringhe vere $j$, ciascuna ponderata per la probabilità che venga scambiata per $i$. Questa affermazione in forma matriciale è\n",
        "\n",
        "$$\n",
        "    {\\tilde{p}} = {M} {\\vec{p}}, \\tag{2},\n",
        "$$\n",
        "\n",
        "dove ${\\vec{p}}$ è la distribuzione vera. In parole povere, l'errore di lettura ha l'effetto di moltiplicare la distribuzione ideale sulle stringhe di bit ${\\vec{p}}$ per la matrice di assegnazione ${M}$ per produrre la distribuzione osservata per produrre la distribuzione osservata ${\\tilde{p}}$. Abbiamo misurato ${\\tilde{p}}$ e ${M}$, ma non abbiamo accesso diretto a ${\\vec{p}}$. In linea di principio, otterremo la vera distribuzione dei bitstring per il nostro circuito ottenere la vera distribuzione delle stringhe di bit per il nostro circuito risolvendo numericamente l'equazione (2) per ${\\vec{p}}$.\n",
        "\n",
        "Prima di proseguire, vale la pena di notare alcune caratteristiche importanti di questo approccio ingenuo.\n",
        "\n",
        "* In pratica, l'equazione (2) non si risolve invertendo ${M}$. Le routine di algebra lineare nelle librerie software utilizzano metodi più stabili, accurati ed efficienti.\n",
        "* Nella stima di ${M}$, abbiamo ipotizzato che si siano verificati solo errori di lettura. In particolare, supponiamo che non ci siano stati errori di preparazione dello stato e di misurazione quantistica - o o almeno che siano stati altrimenti attenuati.\n",
        "  Nella misura in cui questa è una buona ipotesi, ${M}$ rappresenta in realtà solo un errore di lettura. Ma quando *utilizziamo* ${M}$ per correggere una distribuzione misurata sulle stringhe di bit, non facciamo questa ipotesi. In effetti, ci aspettiamo che un circuito interessante un circuito interessante introduca rumore, ad esempio errori di gate. La distribuzione \"vera\" include ancora gli effetti di eventuali errori che non sono stati attenuati in altro modo.\n",
        "\n",
        "Questo metodo, pur essendo utile in alcune circostanze, soffre di alcune limitazioni.\n",
        "\n",
        "Le risorse di spazio e di tempo necessarie per stimare ${M}$ crescono esponenzialmente in $n$ :\n",
        "\n",
        "* La stima di ${M}$ e ${\\tilde{p}}$ è soggetta a errori statistici dovuti al campionamento finito.\n",
        "  Questo rumore può essere ridotto a piacere al costo di un maggior numero di scatti (fino alla scala temporale della deriva dei parametri hardware che comportano errori sistematici in ${M}$ ). Tuttavia, se non si fanno assunzioni sulle stringhe di bit osservate quando si esegue la mitigazione, il numero di scatti necessari per stimare ${M}$ cresce in modo almeno esponenziale in almeno esponenzialmente in $n$.\n",
        "* ${M}$ è una matrice $2^n \\times 2^n$.\n",
        "  Quando $n>10$, la quantità di memoria necessaria per memorizzare ${M}$ è è superiore alla memoria disponibile in un potente computer portatile.\n",
        "\n",
        "Ulteriori limitazioni sono:\n",
        "\n",
        "* La distribuzione recuperata ${\\vec{p}}$ può avere una o più probabilità negative (pur sommando a uno). Una soluzione è quella di minimizzare $||{M} {\\vec{p}} - {\\tilde{p}}||^2$ con il vincolo che ogni voce di ${\\vec{p}}$ sia non negativa. Tuttavia, il tempo di esecuzione di tale è di ordini di grandezza superiore rispetto alla risoluzione diretta dell'equazione (2).\n",
        "* Questa procedura di mitigazione funziona a livello di una distribuzione di probabilità sulle stringhe di bit. In particolare, non può correggere un errore in una singola stringa di bit osservata.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c98d8409-66ad-452f-b87a-d8df886d77d4",
      "metadata": {},
      "source": [
        "<span id=\"qiskit-m3-addon-scaling-to-longer-bitstrings\" />\n",
        "\n",
        "### Componente aggiuntivo Qiskit M3 : scalabilità a stringhe di bit più lunghe\n",
        "\n",
        "La soluzione dell'equazione (2) utilizzando le routine standard di algebra lineare numerica è limitata a stringhe di bit non più lunghe di circa 10 bit. M3, tuttavia, è in grado di gestire bitstring molto più lunghi. Due proprietà chiave di M3 che rendono possibile questo risultato sono:\n",
        "\n",
        "* Le correlazioni nell'errore di lettura di ordine tre e superiore tra collezioni di bit sono considerate trascurabili e vengono ignorate. In linea di principio, al costo di un maggior numero di scatti, si potrebbero stimare anche correlazioni più elevate.\n",
        "* Piuttosto che costruire esplicitamente ${M}$, utilizziamo una matrice effettiva molto più piccola che registra probabilità solo per le stringhe di bit raccolte durante la costruzione di ${\\tilde{p}}$.\n",
        "\n",
        "Ad alto livello, la procedura funziona come segue.\n",
        "\n",
        "In primo luogo, costruiamo dei blocchi di costruzione a partire dai quali possiamo costruire una descrizione semplificata ed efficace di ${M}$. Quindi, eseguiamo ripetutamente il circuito di interesse e raccogliamo le stringhe di bit che utilizziamo per costruire sia sia, con l'aiuto dei blocchi, una descrizione efficace di ${\\tilde{p}}$ e, con l'aiuto dei blocchi, una descrizione efficace di ${M}$.\n",
        "\n",
        "Più precisamente,\n",
        "\n",
        "* Le matrici di assegnazione a singolo qubit sono stimate per ogni qubit. Per fare ciò, prepariamo ripetutamente il registro dei qubit nello stato tutto zero $|0 ... 0 \\rangle$ e poi nello stato tutto uno, e registriamo per ogni qubit la probabilità che venga letto $|1 ... 1 \\rangle$, e registriamo la probabilità per ogni qubit di essere letto in modo in modo errato.\n",
        "* Le correlazioni di ordine tre e superiore sono considerate trascurabili e vengono ignorate.\n",
        "\n",
        "  Costruiamo invece un numero $n$ di $2 \\times 2$ matrici di assegnazione a singolo-qubit matrici di assegnazione e un numero $n(n-1)/2$ di $4 \\times 4$ matrici di assegnazione a due qubit matrici di assegnazione a due bit. Queste matrici di assegnazione a uno e due qubit vengono memorizzate per un uso successivo uso successivo.\n",
        "* Dopo aver campionato ripetutamente un circuito per costruire ${\\tilde{p}}$, costruiamo un'approssimazione efficace di ${M}$ utilizzando solo i bitstringhe che vengono campionate durante la costruzione di ${\\tilde{p}}$. Questa matrice efficace è costruita utilizzando le matrici a uno e due qubit descritte nel punto precedente.\n",
        "  La dimensione lineare di questa matrice è al massimo dell'ordine del numero di scatti utilizzati per costruire, che è molto più piccolo del numero di scatti utilizzati per costruire di scatti utilizzati nella costruzione di ${\\tilde{p}}$, che è molto più piccola della dimensione $2^n$ della matrice di assegnazione completa ${M}$.\n",
        "\n",
        "Per i dettagli tecnici su M3, è possibile consultare [*Scalable Mitigation of Measurement Errors on Quantum Computers*](https://journals.aps.org/prxquantum/abstract/10.1103/PRXQuantum.2.040326).\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "14f04ac4-5142-4436-93e1-f712d47dde1a",
      "metadata": {},
      "source": [
        "<span id=\"application-of-m3-to-a-quantum-algorithm\" />\n",
        "\n",
        "### Applicazione dell' M3 o a un algoritmo quantistico\n",
        "\n",
        "Applicheremo la mitigazione della lettura di M3 al problema dello spostamento nascosto. Il problema dello spostamento nascosto e problemi strettamente correlati come il [problema del sottogruppo nascosto](https://en.wikipedia.org/wiki/Hidden_subgroup_problem) sono stati originariamente concepiti in un contesto fault-tolerant (più precisamente, prima che si dimostrasse la possibilità di usare QPU fault-tolerant!). Ma sono studiati anche con i processori disponibili. Un esempio di accelerazione algoritmica esponenziale ottenuta per una variante del problema dello spostamento nascosto ottenuta su QPU a 127-qubit IBM® si trova in [questo articolo](https://journals.aps.org/prx/accepted/a9074K06A8e1590147da9c69f8c4b64c28247be5a) ( [versione arXiv](https://arxiv.org/abs/2401.07934) ).\n",
        "\n",
        "Di seguito, tutta l'aritmetica è booleana.\n",
        "Cioè, per $a, b \\in \\mathbb{Z}_2 = \\{0, 1\\}$, l'addizione, $a + b$ è la funzione logica XOR.\n",
        "Inoltre, la moltiplicazione $a \\times b$ (o $a b$ ) è la funzione logica AND. Per $x, y \\in \\{0, 1\\}^n$, $x + y$ è definito dall'applicazione bitwise di XOR.\n",
        "Il prodotto di punti $\\cdot: {\\mathbb{Z}_2^n} \\rightarrow \\mathbb{Z}_2$ è definito da $x \\cdot y = \\sum_i x_i y_i$.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "81cb34b4-566b-4ee1-8309-7464a5fd3bb7",
      "metadata": {},
      "source": [
        "<span id=\"hadamard-operator-and-fourier-transform\" />\n",
        "\n",
        "#### Operatore di Hadamard e trasformata di Fourier\n",
        "\n",
        "Nell'implementazione degli algoritmi quantistici, è molto comune utilizzare l'operatore di Hadamard come una trasformata di Fourier.\n",
        "Gli stati della base computazionale sono talvolta chiamati *stati classici*. Sono in una relazione uno-a-uno con i bitstring classici.\n",
        "L'operatore di Hadamard di $n$ -qubit sugli stati classici può essere visto come una trasformata di Fourier sull'ipercubo booleano:\n",
        "\n",
        "$$\n",
        "H^{\\otimes n} =  \\frac{1}{\\sqrt{2^n}} \\sum_{x,y \\in {\\mathbb{Z}_2^n}} (-1)^{x \\cdot y} {|{y}\\rangle}{\\langle{x}|}.\n",
        "$$\n",
        "\n",
        "Consideriamo uno stato ${|{s}\\rangle}$ corrispondente alla stringa di bit fissa $s$. Applicando $H^{\\otimes n}$, e utilizzando ${\\langle {x}|{s}\\rangle} = \\delta_{x,s}$, vediamo che la trasformata di Fourier di ${|{s}\\rangle}$ può essere scritta come\n",
        "\n",
        "$$\n",
        "   H^{\\otimes n} {|{s}\\rangle} =  \\frac{1}{\\sqrt{2^n}} \\sum_{y \\in {\\mathbb{Z}_2^n}} (-1)^{s \\cdot y} {|{y}\\rangle}.\n",
        "$$\n",
        "\n",
        "L'Hadamard è il suo stesso inverso, cioè, $H^{\\otimes n} H^{\\otimes n} = (H H)^{\\otimes n} = I^{\\otimes n}$. Pertanto, la trasformata di Fourier inversa è lo stesso operatore, $H^{\\otimes n}$. Esplicitamente, si ha,\n",
        "\n",
        "$$\n",
        "  {|{s}\\rangle} =  H^{\\otimes n} H^{\\otimes n} {|{s}\\rangle}  =  H^{\\otimes n} \\frac{1}{\\sqrt{2^n}} \\sum_{y \\in {\\mathbb{Z}_2^n}} (-1)^{s \\cdot y} {|{y}\\rangle}.\n",
        "$$\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "d10fce93-3e08-4a27-b641-eda5e7b05ce0",
      "metadata": {},
      "source": [
        "<span id=\"the-hidden-shift-problem\" />\n",
        "\n",
        "#### Il problema del turno nascosto\n",
        "\n",
        "Consideriamo un semplice esempio di *problema di spostamento nascosto*.\n",
        "Il problema è identificare uno spostamento costante nell'ingresso di una funzione.\n",
        "La funzione che consideriamo è il prodotto di punti. È il membro più semplice di un'ampia classe di funzioni che ammettono un'accelerazione quantistica per il problema dello spostamento nascosto attraverso tecniche simili a quelle presentate di seguito.\n",
        "\n",
        "Sia $x,y \\in {\\mathbb{Z}_2^m}$ una stringa di bit di lunghezza $m$. Definiamo ${f}: {\\mathbb{Z}_2^m} \\times {\\mathbb{Z}_2^m} \\rightarrow \\{-1,1\\}$ con\n",
        "\n",
        "$$\n",
        "  {f}(x, y) = (-1)^{x \\cdot y}.\n",
        "$$\n",
        "\n",
        "Sia $a,b \\in {\\mathbb{Z}_2^m}$ una stringa fissa di bit di lunghezza $m$. Definiamo inoltre $g: {\\mathbb{Z}_2^m} \\times {\\mathbb{Z}_2^m} \\rightarrow \\{-1,1\\}$ con\n",
        "\n",
        "$$\n",
        "  g(x, y) = {f}(x+a, y+b) = (-1)^{(x+a) \\cdot (y+b)},\n",
        "$$\n",
        "\n",
        "dove $a$ e $b$ sono parametri (nascosti).\n",
        "Ci vengono fornite due scatole nere, una che implementa $f$ e l'altra $g$. Supponiamo di sapere che esse calcolano le funzioni definite sopra, ma non conosciamo né né né $a$ né $b$. Il gioco consiste nel determinare le bitstringhe nascoste (turni) $a$ e $b$ facendo delle interrogazioni a $f$ e $g$. È chiaro che se giochiamo il gioco in modo classico, abbiamo bisogno di interrogazioni a $O(2m)$ per determinare $a$ e $b$. Ad esempio, possiamo interrogare $g$ con tutte le coppie di stringhe in cui un elemento della coppia è costituito da tutti zeri e l'altro elemento ha esattamente un elemento impostato su $1$. Per ogni interrogazione, impariamo un elemento di $a$ o $b$. Tuttavia, vedremo che, se le scatole nere sono implementate come circuiti quantistici, possiamo determinare e con un'unica soluzione determinare $a$ e $b$ con una singola interrogazione a ciascuno di $f$ e $g$.\n",
        "\n",
        "Nel contesto della complessità algoritmica, una scatola nera è chiamata *oracolo*.\n",
        "Oltre a essere opaco, un oracolo ha la proprietà di consumare l'input e produrre l'output istantaneamente, senza aggiungere nulla al budget di complessità dell'algoritmo produce l'output istantaneamente, senza aggiungere nulla al budget di complessità dell'algoritmo in cui è incorporato. Infatti, nel caso in esame, gli oracoli che implementano $f$ e $g$ si dimostreranno efficienti.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "d5e4c1e2-d6f5-4093-afdb-f97e9f062179",
      "metadata": {
        "jp-MarkdownHeadingCollapsed": true
      },
      "source": [
        "<span id=\"quantum-circuits-for-$f$-and-$g$\" />\n",
        "\n",
        "#### Circuiti quantistici per $f$ e $g$\n",
        "\n",
        "Per implementare $f$ e $g$ come circuiti quantistici sono necessari i seguenti ingredienti.\n",
        "\n",
        "Per stati classici a singolo qubit ${|{x_1}\\rangle}, {|{y_1}\\rangle}$, con $x_1,y_1 \\in \\mathbb{Z}_2$, la porta controllata $Z$ ${CZ}$ può essere scritta come\n",
        "\n",
        "$$\n",
        "{CZ} {|{x_1}\\rangle}{|{y_1}\\rangle}{x_1} = (-1)^{x_1 y_1} {|{x_1}\\rangle}{x_1}{|{y_1}\\rangle}.\n",
        "$$\n",
        "\n",
        "Opereremo con le porte $m$ CZ, una su $(x_1, y_1)$, e una su $(x_2, y_2)$, e così via, fino a $(x_m, y_m)$. Chiamiamo questo operatore ${CZ}_{x,y}$.\n",
        "\n",
        "$U_f = {CZ}_{x,y}$ è una versione quantistica di ${f} = {f}(x,y)$ :\n",
        "\n",
        "$$\n",
        "%\\CZ_{x,y} {|#1\\rangle}{z} =\n",
        "U_f {|{x}\\rangle}{|{y}\\rangle} = {CZ}_{x,y} {|{x}\\rangle}{|{y}\\rangle} = (-1)^{x \\cdot y}  {|{x}\\rangle}{|{y}\\rangle}.\n",
        "$$\n",
        "\n",
        "Dobbiamo anche implementare un cambio di bitstringa.\n",
        "Denotiamo l'operatore sul registro $x$ $X^{a_1}\\cdots X^{a_m}$ con $X_a$ e allo stesso modo sul registro $y$ $X_b =  X^{b_1}\\cdots X^{b_m}$. Questi operatori applicano $X$ ovunque un singolo bit sia $1$, e l'identità $I$ ovunque sia $0$. Si ha quindi\n",
        "\n",
        "$$\n",
        " X_a X_b  {|{x}\\rangle}{|{y}\\rangle} = {|{x+a}\\rangle}{|{y+b}\\rangle}.\n",
        "$$\n",
        "\n",
        "La seconda scatola nera $g$ è implementata dall'unitario $U_g$, dato da\n",
        "\n",
        "$$\n",
        "%U_g {|{x}\\rangle}{|{y}\\rangle} = X_aX_b \\CZ_{x,y} X_aX_b {|{x}\\rangle}{|{y}\\rangle}.\n",
        "U_g = X_aX_b {CZ}_{x,y} X_aX_b.\n",
        "$$\n",
        "\n",
        "Per vedere questo, applichiamo gli operatori da destra a sinistra allo stato ${|{x}\\rangle}{|{y}\\rangle}$. Prima\n",
        "\n",
        "$$\n",
        " X_a X_b  {|{x}\\rangle}{|{y}\\rangle} = {|{x+a}\\rangle}{|{y+b}\\rangle}.\n",
        "$$\n",
        "\n",
        "Allora,\n",
        "\n",
        "$$\n",
        "  {CZ}_{x,y}  {|{x+a}\\rangle}{|{y+b}\\rangle} = (-1)^{(x+a)\\cdot (y+b)} {|{x+a}\\rangle}{|{y+b}\\rangle}.\n",
        "$$\n",
        "\n",
        "Infine,\n",
        "\n",
        "$$\n",
        "  X^a X^b (-1)^{(x+a)\\cdot (y+b)} {|{x+a}\\rangle}{|{y+b}\\rangle} = (-1)^{(x+a)\\cdot (y+b)} {|{x}\\rangle}{|{y}\\rangle},\n",
        "$$\n",
        "\n",
        "che è appunto la versione quantistica di $f(x+a, y+b)$.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "de0bb5f9-517e-4bea-91ab-f65e012e8ae9",
      "metadata": {
        "editable": true,
        "jp-MarkdownHeadingCollapsed": true,
        "slideshow": {
          "slide_type": ""
        },
        "tags": []
      },
      "source": [
        "<span id=\"the-hidden-shift-algorithm\" />\n",
        "\n",
        "#### L'algoritmo dello spostamento nascosto\n",
        "\n",
        "Ora mettiamo insieme i pezzi per risolvere il problema del turno nascosto.\n",
        "Cominciamo applicando Hadamard ai registri inizializzati allo stato tutto zero.\n",
        "\n",
        "$$\n",
        "H^{\\otimes 2m} = H^{\\otimes m} \\otimes H^{\\otimes m} {{|{0}\\rangle}^{\\otimes m}}{{|{0}\\rangle}^{\\otimes m}} = \\frac{1}{\\sqrt{2^{2m}}} \\sum_{x, y \\in {\\mathbb{Z}_2^m}} (-1)^{x \\cdot y} {|{x}\\rangle}{|{y}\\rangle}.\n",
        "$$\n",
        "\n",
        "Successivamente, interroghiamo l'oracolo $g$ per arrivare a\n",
        "\n",
        "$$\n",
        "U_g H^{\\otimes 2m} {{|{0}\\rangle}^{\\otimes m}}{{|{0}\\rangle}^{\\otimes m}}\n",
        "= \\frac{1}{\\sqrt{2^{2m}}} \\sum_{x, y \\in {\\mathbb{Z}_2^m}} (-1)^{(x+a) \\cdot (y+b)} {|{x}\\rangle}{|{y}\\rangle}\n",
        "$$\n",
        "\n",
        "$$\n",
        "\\approx \\frac{1}{\\sqrt{2^{2m}}} \\sum_{x, y \\in {\\mathbb{Z}_2^m}} (-1)^{x \\cdot y + x \\cdot b + y \\cdot a} {|{x}\\rangle}{|{y}\\rangle}.\n",
        "$$\n",
        "\n",
        "Nell'ultima riga, abbiamo omesso il fattore di fase globale costante $(-1)^{a \\cdot b}$, e denotiamo l'uguaglianza fino a una fase con $\\approx$. Successivamente, l'applicazione dell'oracolo $f$ introduce un altro fattore di $(-1)^{x \\cdot y}$, annullando quello già presente presente. Abbiamo quindi:\n",
        "\n",
        "$$\n",
        "U_f U_g H^{\\otimes 2m} {{|{0}\\rangle}^{\\otimes m}}{{|{0}\\rangle}^{\\otimes m}}\n",
        "\\approx \\frac{1}{\\sqrt{2^{2m}}} \\sum_{x, y \\in {\\mathbb{Z}_2^m}} (-1)^{x \\cdot b + y \\cdot a} {|{x}\\rangle}{|{y}\\rangle}.\n",
        "$$\n",
        "\n",
        "Il passo finale consiste nell'applicare la trasformata di Fourier inversa, $H^{\\otimes 2m} = H^{\\otimes m} \\otimes H^{\\otimes m}$, con il risultato di\n",
        "\n",
        "$$\n",
        "H^{\\otimes 2m} U_f U_g  H^{\\otimes 2m} {{|{0}\\rangle}^{\\otimes m}}{{|{0}\\rangle}^{\\otimes m}}\n",
        "\\approx {|{b}\\rangle}{|{a}\\rangle}.\n",
        "$$\n",
        "\n",
        "Il circuito è terminato. In assenza di rumore, il campionamento dei registri quantistici restituirà i bitstring con probabilità restituirà le stringhe di bit $b, a$ con probabilità $1$.\n",
        "\n",
        "Il prodotto interno booleano è un esempio delle cosiddette funzioni piegate.\n",
        "Non definiremo qui le funzioni piegate ma ci limitiamo a notare che esse \"sono massimamente resistenti contro gli attacchi che cercano di sfruttare una dipendenza delle delle uscite da qualche sottospazio lineare degli ingressi\"\n",
        "Questa citazione è tratta dall'articolo [*Algoritmi quantistici per funzioni booleane altamente non lineari*](https://arxiv.org/abs/0811.3208), che fornisce algoritmi efficienti di spostamento nascosto per diverse classi di funzioni piegate.\n",
        "L'algoritmo di questa esercitazione è riportato nella sezione 3.1 dell'articolo.\n",
        "\n",
        "Nel caso più generale, il circuito per trovare uno spostamento nascosto $s \\in \\mathbb{Z}^n$ è\n",
        "\n",
        "$$\n",
        " H^{\\otimes n} U_{\\tilde{f}}  H^{\\otimes n} U_g  H^{\\otimes n} {|{0}\\rangle}^{\\otimes n} = {|{s}\\rangle}.\n",
        "$$\n",
        "\n",
        "Nel caso generale, $f$ e $g$ sono funzioni di un'unica variabile.\n",
        "Il nostro esempio di prodotto interno ha questa forma se lasciamo $f(x, y) \\to f(z)$, con $z$ uguale alla concatenazione di $x$ e $y$, e $s$ uguale alla concatenazione di e di $a$ e $b$. Il caso generale richiede esattamente due oracoli: Un oracolo per $g$ e uno per $\\tilde{f}$, dove quest'ultimo è una funzione nota come *duale* della funzione piegata $f$. La funzione prodotto interno ha la proprietà auto-duale $\\tilde{f}=f$.\n",
        "\n",
        "Nel nostro circuito per lo spostamento nascosto sul prodotto interno abbiamo omesso lo strato intermedio di Hadamard che compare nel circuito per il caso generale di Hadamard che compare nel circuito per il caso generale. Mentre nel caso generale questo livello è necessario, abbiamo risparmiato un po' di profondità omettendolo, a scapito di un po' di post-elaborazione perché l'output è invece del desiderato di post-elaborazione, perché l'output è ${|{b}\\rangle}{|{a}\\rangle}$ invece del desiderato ${|{a}\\rangle}{|{b}\\rangle}$.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ccc0aec5-63d3-4a43-8fad-fea56ea24ec7",
      "metadata": {},
      "source": [
        "<span id=\"requirements\" />\n",
        "\n",
        "## Requisiti\n",
        "\n",
        "Prima di iniziare questa esercitazione, assicuratevi di aver installato quanto segue:\n",
        "\n",
        "* Qiskit SDK v2.1 o versioni successive, con supporto [alla visualizzazione](/docs/api/qiskit/visualization)\n",
        "* Qiskit Runtime v0.41 o più tardi (`pip install qiskit-ibm-runtime`)\n",
        "* M3 Addon Qiskit v3.0 (`pip install mthree`)\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c090b8dd-754f-4390-93e5-663f818e61d0",
      "metadata": {
        "jp-MarkdownHeadingCollapsed": true
      },
      "source": [
        "<span id=\"setup\" />\n",
        "\n",
        "## Configura\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "233dfc67-d280-4827-910d-8e7170ee95ad",
      "metadata": {},
      "outputs": [],
      "source": [
        "from collections.abc import Iterator, Sequence\n",
        "from random import Random\n",
        "from qiskit.circuit import (\n",
        "    CircuitInstruction,\n",
        "    QuantumCircuit,\n",
        "    QuantumRegister,\n",
        "    Qubit,\n",
        ")\n",
        "from qiskit.circuit.library import CZGate, HGate, XGate\n",
        "from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager\n",
        "from qiskit_ibm_runtime import QiskitRuntimeService\n",
        "import timeit\n",
        "import matplotlib.pyplot as plt\n",
        "from qiskit_ibm_runtime import SamplerV2 as Sampler\n",
        "import mthree"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "5cf89647-261d-4af1-af54-75548345df6e",
      "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"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "940cc113-5cf3-4688-9747-93268be10088",
      "metadata": {},
      "source": [
        "Per prima cosa, scriviamo le funzioni per implementare il problema dello spostamento nascosto come `QuantumCircuit`.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "675fe72b-6a86-42ea-a65f-b29a60725ecb",
      "metadata": {
        "editable": true,
        "scrolled": true,
        "slideshow": {
          "slide_type": ""
        },
        "tags": []
      },
      "outputs": [],
      "source": [
        "def apply_hadamards(qubits: Sequence[Qubit]) -> Iterator[CircuitInstruction]:\n",
        "    \"\"\"Apply a Hadamard gate to every qubit.\"\"\"\n",
        "    for q in qubits:\n",
        "        yield CircuitInstruction(HGate(), [q], [])\n",
        "\n",
        "\n",
        "def apply_shift(\n",
        "    qubits: Sequence[Qubit], shift: int\n",
        ") -> Iterator[CircuitInstruction]:\n",
        "    \"\"\"Apply X gates where the bits of the shift are equal to 1.\"\"\"\n",
        "    for i, q in zip(range(shift.bit_length()), qubits):\n",
        "        if shift >> i & 1:\n",
        "            yield CircuitInstruction(XGate(), [q], [])\n",
        "\n",
        "\n",
        "def oracle_f(qubits: Sequence[Qubit]) -> Iterator[CircuitInstruction]:\n",
        "    \"\"\"Apply the f oracle.\"\"\"\n",
        "    for i in range(0, len(qubits) - 1, 2):\n",
        "        yield CircuitInstruction(CZGate(), [qubits[i], qubits[i + 1]])\n",
        "\n",
        "\n",
        "def oracle_g(\n",
        "    qubits: Sequence[Qubit], shift: int\n",
        ") -> Iterator[CircuitInstruction]:\n",
        "    \"\"\"Apply the g oracle.\"\"\"\n",
        "    yield from apply_shift(qubits, shift)\n",
        "    yield from oracle_f(qubits)\n",
        "    yield from apply_shift(qubits, shift)\n",
        "\n",
        "\n",
        "def determine_hidden_shift(\n",
        "    qubits: Sequence[Qubit], shift: int\n",
        ") -> Iterator[CircuitInstruction]:\n",
        "    \"\"\"Determine the hidden shift.\"\"\"\n",
        "    yield from apply_hadamards(qubits)\n",
        "    yield from oracle_g(qubits, shift)\n",
        "    # We omit this layer in exchange for post processing\n",
        "    # yield from apply_hadamards(qubits)\n",
        "    yield from oracle_f(qubits)\n",
        "    yield from apply_hadamards(qubits)\n",
        "\n",
        "\n",
        "def run_hidden_shift_circuit(n_qubits, rng):\n",
        "    hidden_shift = rng.getrandbits(n_qubits)\n",
        "\n",
        "    qubits = QuantumRegister(n_qubits, name=\"q\")\n",
        "    circuit = QuantumCircuit.from_instructions(\n",
        "        determine_hidden_shift(qubits, hidden_shift), qubits=qubits\n",
        "    )\n",
        "    circuit.measure_all()\n",
        "    # Format the hidden shift as a string.\n",
        "    hidden_shift_string = format(hidden_shift, f\"0{n_qubits}b\")\n",
        "    return (circuit, hidden_shift, hidden_shift_string)\n",
        "\n",
        "\n",
        "def display_circuit(circuit):\n",
        "    return circuit.remove_final_measurements(inplace=False).draw(\n",
        "        \"mpl\", idle_wires=False, scale=0.5, fold=-1\n",
        "    )"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "6960aa96-f612-40b3-9f26-3d3afb07262b",
      "metadata": {},
      "source": [
        "Cominciamo con un piccolo esempio:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "8297843e-00c3-4bb5-9d33-a7e558d1698c",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Hidden shift string 011010\n"
          ]
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/readout-error-mitigation-sampler/extracted-outputs/8297843e-00c3-4bb5-9d33-a7e558d1698c-1.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 2,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "n_qubits = 6\n",
        "random_seed = 12345\n",
        "rng = Random(random_seed)\n",
        "circuit, hidden_shift, hidden_shift_string = run_hidden_shift_circuit(\n",
        "    n_qubits, rng\n",
        ")\n",
        "\n",
        "print(f\"Hidden shift string {hidden_shift_string}\")\n",
        "\n",
        "display_circuit(circuit)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c136f784-e105-4244-a483-b8221577afc3",
      "metadata": {
        "editable": true,
        "slideshow": {
          "slide_type": ""
        },
        "tags": []
      },
      "source": [
        "<span id=\"step-2-optimize-circuits-for-quantum-hardware-execution\" />\n",
        "\n",
        "## Fase 2: Ottimizzazione dei circuiti per l'esecuzione dell'hardware quantistico\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "id": "ee4d384a-15e2-4a3f-a52a-def3d184c4fa",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "['shift 011010', 'n_qubits 6', 'seed = 12345']"
            ]
          },
          "execution_count": 3,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "job_tags = [\n",
        "    f\"shift {hidden_shift_string}\",\n",
        "    f\"n_qubits {n_qubits}\",\n",
        "    f\"seed = {random_seed}\",\n",
        "]\n",
        "job_tags"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "f2b77d93-c34a-43a4-b436-e7a25024a94a",
      "metadata": {
        "editable": true,
        "scrolled": true,
        "slideshow": {
          "slide_type": ""
        },
        "tags": []
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Using backend ibm_kingston\n"
          ]
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/readout-error-mitigation-sampler/extracted-outputs/f2b77d93-c34a-43a4-b436-e7a25024a94a-1.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 4,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "# Uncomment this to run the circuits on a quantum computer on IBMCloud.\n",
        "service = QiskitRuntimeService()\n",
        "backend = service.least_busy(\n",
        "    operational=True, simulator=False, min_num_qubits=100\n",
        ")\n",
        "\n",
        "# from qiskit_ibm_runtime.fake_provider import FakeMelbourneV2\n",
        "# backend = FakeMelbourneV2()\n",
        "# backend.refresh(service)\n",
        "\n",
        "print(f\"Using backend {backend.name}\")\n",
        "\n",
        "\n",
        "def get_isa_circuit(circuit, backend):\n",
        "    pass_manager = generate_preset_pass_manager(\n",
        "        optimization_level=3, backend=backend, seed_transpiler=1234\n",
        "    )\n",
        "    isa_circuit = pass_manager.run(circuit)\n",
        "    return isa_circuit\n",
        "\n",
        "\n",
        "isa_circuit = get_isa_circuit(circuit, backend)\n",
        "display_circuit(isa_circuit)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "f2f426da-3601-4442-9593-16bb19abf91e",
      "metadata": {},
      "source": [
        "<span id=\"step-3-execute-circuits-using-qiskit-primitives\" />\n",
        "\n",
        "## Fase 3: Eseguire i circuiti utilizzando Qiskit primitives\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "6ebaea63-e352-4d7c-a7ee-57a2f0a66306",
      "metadata": {},
      "outputs": [],
      "source": [
        "# submit job for solving the hidden shift problem using the Sampler primitive\n",
        "NUM_SHOTS = 50_000\n",
        "\n",
        "\n",
        "def run_sampler(backend, isa_circuit, num_shots):\n",
        "    sampler = Sampler(mode=backend)\n",
        "    sampler.options.environment.job_tags\n",
        "    pubs = [(isa_circuit, None, NUM_SHOTS)]\n",
        "    job = sampler.run(pubs)\n",
        "    return job\n",
        "\n",
        "\n",
        "def setup_mthree_mitigation(isa_circuit, backend):\n",
        "    # retrieve the final qubit mapping so mthree knows which qubits to calibrate\n",
        "    qubit_mapping = mthree.utils.final_measurement_mapping(isa_circuit)\n",
        "\n",
        "    # submit jobs for readout error calibration\n",
        "    mit = mthree.M3Mitigation(backend)\n",
        "    mit.cals_from_system(qubit_mapping, rep_delay=None)\n",
        "\n",
        "    return mit, qubit_mapping"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 6,
      "id": "9b76b943-fd71-4287-804b-980cacc078f8",
      "metadata": {},
      "outputs": [],
      "source": [
        "job = run_sampler(backend, isa_circuit, NUM_SHOTS)\n",
        "mit, qubit_mapping = setup_mthree_mitigation(isa_circuit, backend)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "674ad6e1-c496-44ca-9598-470edf4c15dc",
      "metadata": {},
      "source": [
        "<span id=\"step-4-post-process-and-return-results-in-classical-format\" />\n",
        "\n",
        "## Fase 4: Post-elaborazione e restituzione dei risultati in formato classico\n",
        "\n",
        "Nella discussione teorica precedente, abbiamo stabilito che per l'ingresso $ab$, ci aspettiamo l'uscita $ba$. Un'ulteriore complicazione è data dal fatto che, per avere un circuito più semplice (pre-traspilato), abbiamo inserito le porte CZ richieste tra coppie di qubit vicine. Ciò equivale a interlacciare le stringhe di bit $a$ e $b$ come $a1 b1 a2 b2 \\ldots$. La stringa di uscita $ba$ sarà interlacciata in modo analogo: $b1 a1 b2 a2 \\ldots$. La funzione `unscramble` qui sotto trasforma la stringa di uscita da $b1 a1 b2 a2 \\ldots$ a $a1 b1 a2 b2 \\ldots$ in modo che le stringhe di ingresso e di uscita possano essere confrontate direttamente.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 7,
      "id": "aada9b41-0600-4814-a5a3-917d7fb171f9",
      "metadata": {},
      "outputs": [],
      "source": [
        "# retrieve bitstring counts\n",
        "def get_bitstring_counts(job):\n",
        "    result = job.result()\n",
        "    pub_result = result[0]\n",
        "    counts = pub_result.data.meas.get_counts()\n",
        "    return counts, pub_result"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 8,
      "id": "3930e290-89ff-404d-946b-cdf90119cb48",
      "metadata": {},
      "outputs": [],
      "source": [
        "counts, pub_result = get_bitstring_counts(job)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "138ef822-8e66-4f01-94f6-5a634465d2ac",
      "metadata": {},
      "source": [
        "La distanza di Hamming tra due stringhe di bit è il numero di indici in cui i bit differiscono.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 9,
      "id": "3488f07f-7705-4ef2-b033-ebc2cbe19d45",
      "metadata": {},
      "outputs": [],
      "source": [
        "def hamming_distance(s1, s2):\n",
        "    weight = 0\n",
        "    for c1, c2 in zip(s1, s2):\n",
        "        (c1, c2) = (int(c1), int(c2))\n",
        "        if (c1 == 1 and c2 == 1) or (c1 == 0 and c2 == 0):\n",
        "            weight += 1\n",
        "\n",
        "    return weight"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 10,
      "id": "9e2f66e7-cad3-4893-9fde-ddaec077d938",
      "metadata": {
        "scrolled": true
      },
      "outputs": [],
      "source": [
        "# Replace string of form a1b1a2b2... with b1a1b2a1...\n",
        "# That is, reverse order of successive pairs of bits.\n",
        "def unscramble(bitstring):\n",
        "    ps = [bitstring[i : i + 2][::-1] for i in range(0, len(bitstring), 2)]\n",
        "    return \"\".join(ps)\n",
        "\n",
        "\n",
        "def find_hidden_shift_bitstring(counts, hidden_shift_string):\n",
        "    # convert counts to probabilities\n",
        "    probs = {\n",
        "        unscramble(bitstring): count / NUM_SHOTS\n",
        "        for bitstring, count in counts.items()\n",
        "    }\n",
        "\n",
        "    # Retrieve the most probable bitstring.\n",
        "    most_probable = max(probs, key=lambda x: probs[x])\n",
        "\n",
        "    print(f\"Expected hidden shift string: {hidden_shift_string}\")\n",
        "    if most_probable == hidden_shift_string:\n",
        "        print(\"Most probable bitstring matches hidden shift 😊.\")\n",
        "    else:\n",
        "        print(\"Most probable bitstring didn't match hidden shift ☹️.\")\n",
        "    print(\"Top 10 bitstrings and their probabilities:\")\n",
        "    display(\n",
        "        {\n",
        "            k: (v, hamming_distance(hidden_shift_string, k))\n",
        "            for k, v in sorted(\n",
        "                probs.items(), key=lambda x: x[1], reverse=True\n",
        "            )[:10]\n",
        "        }\n",
        "    )\n",
        "\n",
        "    return probs, most_probable"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 11,
      "id": "9d3dd975-b628-48fc-8565-86b0ed88c973",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Expected hidden shift string: 011010\n",
            "Most probable bitstring matches hidden shift 😊.\n",
            "Top 10 bitstrings and their probabilities:\n"
          ]
        },
        {
          "data": {
            "text/plain": [
              "{'011010': (0.9743, 6),\n",
              " '001010': (0.00812, 5),\n",
              " '010010': (0.0063, 5),\n",
              " '011000': (0.00554, 5),\n",
              " '011011': (0.00492, 5),\n",
              " '011110': (0.00044, 5),\n",
              " '001000': (0.00012, 4),\n",
              " '010000': (8e-05, 4),\n",
              " '001011': (6e-05, 4),\n",
              " '000010': (6e-05, 4)}"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "probs, most_probable = find_hidden_shift_bitstring(\n",
        "    counts, hidden_shift_string\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "67a88958-c900-4bd1-8329-d530ba3427ea",
      "metadata": {},
      "source": [
        "Registriamo la probabilità della stringa di bit più probabile prima di applicare la mitigazione dell'errore di lettura con M3.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 12,
      "id": "a95daf38-009e-44ea-a9c5-8041bf7b22e2",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "0.9743"
            ]
          },
          "execution_count": 12,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "max_probability_before_M3 = probs[most_probable]\n",
        "max_probability_before_M3"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "9531e7bd-e134-4110-ba00-d5a08fe612d9",
      "metadata": {},
      "source": [
        "Ora applichiamo ai conteggi la correzione di lettura appresa dall' M3.\n",
        "La funzione `apply_corrections` restituisce una distribuzione di quasi-probabilità. Questo è un elenco di `float` oggetti che sommati danno come risultato $1$. Tuttavia, alcuni valori potrebbero essere negativi.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 13,
      "id": "3a24d85c-a980-48cf-96ab-5237d370d7af",
      "metadata": {},
      "outputs": [],
      "source": [
        "def perform_mitigation(mit, counts, qubit_mapping):\n",
        "    # mitigate readout error\n",
        "    quasis = mit.apply_correction(counts, qubit_mapping)\n",
        "\n",
        "    # print results\n",
        "    most_probable_after_m3 = unscramble(max(quasis, key=lambda x: quasis[x]))\n",
        "\n",
        "    is_hidden_shift_identified = most_probable_after_m3 == hidden_shift_string\n",
        "    if is_hidden_shift_identified:\n",
        "        print(\"Most probable bitstring matches hidden shift 😊.\")\n",
        "    else:\n",
        "        print(\"Most probable bitstring didn't match hidden shift ☹️.\")\n",
        "    print(\"Top 10 bitstrings and their quasi-probabilities:\")\n",
        "    topten = {\n",
        "        unscramble(k): f\"{v:.2e}\"\n",
        "        for k, v in sorted(quasis.items(), key=lambda x: x[1], reverse=True)[\n",
        "            :10\n",
        "        ]\n",
        "    }\n",
        "    max_probability_after_M3 = float(topten[most_probable_after_m3])\n",
        "    display(topten)\n",
        "\n",
        "    return max_probability_after_M3, is_hidden_shift_identified"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 14,
      "id": "d304c727-f163-4842-a96a-dad8a067c8d1",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Expected hidden shift string: 011010\n",
            "Most probable bitstring matches hidden shift 😊.\n",
            "Top 10 bitstrings and their quasi-probabilities:\n"
          ]
        },
        {
          "data": {
            "text/plain": [
              "{'011010': '1.01e+00',\n",
              " '001010': '8.75e-04',\n",
              " '001000': '7.38e-05',\n",
              " '010000': '4.51e-05',\n",
              " '111000': '2.18e-05',\n",
              " '001011': '1.74e-05',\n",
              " '000010': '6.42e-06',\n",
              " '011001': '-7.18e-06',\n",
              " '011000': '-4.53e-04',\n",
              " '010010': '-1.28e-03'}"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "print(f\"Expected hidden shift string: {hidden_shift_string}\")\n",
        "max_probability_after_M3, is_hidden_shift_identified = perform_mitigation(\n",
        "    mit, counts, qubit_mapping\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "b62fac82-e883-4030-9793-86af8299abaa",
      "metadata": {},
      "source": [
        "<span id=\"compare-identifying-the-hidden-shift-string-before-and-after-applying-m3-correction\" />\n",
        "\n",
        "#### Confronta l'identificazione della stringa di spostamento nascosta prima e dopo l'applicazione di una correzion M3\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 15,
      "id": "a3864692-582e-4fe0-8059-f0be59cfd229",
      "metadata": {},
      "outputs": [],
      "source": [
        "def compare_before_and_after_M3(\n",
        "    max_probability_before_M3,\n",
        "    max_probability_after_M3,\n",
        "    is_hidden_shift_identified,\n",
        "):\n",
        "    is_probability_improved = (\n",
        "        max_probability_after_M3 > max_probability_before_M3\n",
        "    )\n",
        "    print(f\"Most probable probability before M3: {max_probability_before_M3}\")\n",
        "    print(f\"Most probable probability after M3: {max_probability_after_M3}\")\n",
        "    if is_hidden_shift_identified and is_probability_improved:\n",
        "        print(\"Readout error mitigation effective! 😊\")\n",
        "    else:\n",
        "        print(\"Readout error mitigation not effective. ☹️\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 16,
      "id": "98f92ec2-50b5-4456-b40a-3f619cc42ff1",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Most probable probability before M3: 0.9743\n",
            "Most probable probability after M3: 1.01\n",
            "Readout error mitigation effective! 😊\n"
          ]
        }
      ],
      "source": [
        "compare_before_and_after_M3(\n",
        "    max_probability_before_M3,\n",
        "    max_probability_after_M3,\n",
        "    is_hidden_shift_identified,\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "90c3bec4-9a3d-43b7-8160-bcb615230d01",
      "metadata": {},
      "source": [
        "<span id=\"plot-how-cpu-time-required-by-m3-scales-with-shots\" />\n",
        "\n",
        "### Traccia un grafico che mostri come il tempo di CPU richiesto da M3 varia in base al numero di scatti\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "33addc38-f738-48ed-a29d-9790f446c036",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Applying M3 correction to 5000 shots...\n",
            "\tDone in 0.003321983851492405 seconds.\n",
            "Applying M3 correction to 7500 shots...\n",
            "\tDone in 0.004425413906574249 seconds.\n",
            "Applying M3 correction to 10000 shots...\n",
            "\tDone in 0.006366567220538855 seconds.\n",
            "Applying M3 correction to 12500 shots...\n",
            "\tDone in 0.0071477219462394714 seconds.\n",
            "Applying M3 correction to 15000 shots...\n",
            "\tDone in 0.00860048783943057 seconds.\n",
            "Applying M3 correction to 17500 shots...\n",
            "\tDone in 0.010026784148067236 seconds.\n",
            "Applying M3 correction to 20000 shots...\n",
            "\tDone in 0.011459112167358398 seconds.\n",
            "Applying M3 correction to 22500 shots...\n",
            "\tDone in 0.012727141845971346 seconds.\n",
            "Applying M3 correction to 25000 shots...\n",
            "\tDone in 0.01406092382967472 seconds.\n",
            "Applying M3 correction to 27500 shots...\n",
            "\tDone in 0.01546052098274231 seconds.\n",
            "Applying M3 correction to 30000 shots...\n",
            "\tDone in 0.016769016161561012 seconds.\n",
            "Applying M3 correction to 32500 shots...\n",
            "\tDone in 0.019537431187927723 seconds.\n",
            "Applying M3 correction to 35000 shots...\n",
            "\tDone in 0.019739801064133644 seconds.\n",
            "Applying M3 correction to 37500 shots...\n",
            "\tDone in 0.021093040239065886 seconds.\n",
            "Applying M3 correction to 40000 shots...\n",
            "\tDone in 0.022840639110654593 seconds.\n",
            "Applying M3 correction to 42500 shots...\n",
            "\tDone in 0.023974396288394928 seconds.\n",
            "Applying M3 correction to 45000 shots...\n",
            "\tDone in 0.026412792038172483 seconds.\n",
            "Applying M3 correction to 47500 shots...\n",
            "\tDone in 0.026364430785179138 seconds.\n",
            "Applying M3 correction to 50000 shots...\n",
            "\tDone in 0.02820305060595274 seconds.\n"
          ]
        },
        {
          "data": {
            "text/plain": [
              "Text(0.5, 1.0, 'Time to apply M3 correction')"
            ]
          },
          "execution_count": 17,
          "metadata": {},
          "output_type": "execute_result"
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/readout-error-mitigation-sampler/extracted-outputs/33addc38-f738-48ed-a29d-9790f446c036-2.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "# Collect samples for numbers of shots varying from 5000 to 25000.\n",
        "shots_range = range(5000, NUM_SHOTS + 1, 2500)\n",
        "times = []\n",
        "for shots in shots_range:\n",
        "    print(f\"Applying M3 correction to {shots} shots...\")\n",
        "    t0 = timeit.default_timer()\n",
        "    _ = mit.apply_correction(\n",
        "        pub_result.data.meas.slice_shots(range(shots)).get_counts(),\n",
        "        qubit_mapping,\n",
        "    )\n",
        "    t1 = timeit.default_timer()\n",
        "    print(f\"\\tDone in {t1 - t0} seconds.\")\n",
        "    times.append(t1 - t0)\n",
        "\n",
        "fig, ax = plt.subplots()\n",
        "ax.plot(shots_range, times, \"o--\")\n",
        "ax.set_xlabel(\"Shots\")\n",
        "ax.set_ylabel(\"Time (s)\")\n",
        "ax.set_title(\"Time to apply M3 correction\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "bdfbeddd-e90e-4a5c-ad19-810603c4f695",
      "metadata": {},
      "source": [
        "<span id=\"interpreting-the-plot\" />\n",
        "\n",
        "#### Interpretazione della trama\n",
        "\n",
        "Il grafico precedente mostra che il tempo necessario per applicare la correzione M3 cresce linearmente con il numero di scatti.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "4988be5c-46ad-473d-b1b1-c8280d8f6324",
      "metadata": {},
      "source": [
        "<span id=\"scaling-up\" />\n",
        "\n",
        "## Scalabilità\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 18,
      "id": "1bf46124-9261-472e-864f-ee2ed0209079",
      "metadata": {
        "jp-MarkdownHeadingCollapsed": true
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Hidden shift string 00000010100110101011101110010001010000110011101001101010101001111001100110000111\n"
          ]
        }
      ],
      "source": [
        "n_qubits = 80\n",
        "rng = Random(12345)\n",
        "circuit, hidden_shift, hidden_shift_string = run_hidden_shift_circuit(\n",
        "    n_qubits, rng\n",
        ")\n",
        "\n",
        "print(f\"Hidden shift string {hidden_shift_string}\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 19,
      "id": "53cd9086-27f8-481b-ba96-9bac9b146188",
      "metadata": {},
      "outputs": [],
      "source": [
        "isa_circuit = get_isa_circuit(circuit, backend)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 20,
      "id": "b176b2b3-de30-4a9b-905d-a06c7365376a",
      "metadata": {},
      "outputs": [],
      "source": [
        "job = run_sampler(backend, isa_circuit, NUM_SHOTS)\n",
        "mit, qubit_mapping = setup_mthree_mitigation(isa_circuit, backend)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 21,
      "id": "c4dda06e-e2c6-46b9-9e74-d90bb318419b",
      "metadata": {},
      "outputs": [],
      "source": [
        "counts, pub_result = get_bitstring_counts(job)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 22,
      "id": "3421d2f3-083f-44a9-bee4-5f05e43ebb4f",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Expected hidden shift string: 00000010100110101011101110010001010000110011101001101010101001111001100110000111\n",
            "Most probable bitstring matches hidden shift 😊.\n",
            "Top 10 bitstrings and their probabilities:\n"
          ]
        },
        {
          "data": {
            "text/plain": [
              "{'00000010100110101011101110010001010000110011101001101010101001111001100110000111': (0.50402,\n",
              "  80),\n",
              " '00000010100110101011101110010001010000110011100001101010101001111001100110000111': (0.0396,\n",
              "  79),\n",
              " '00000010100110101011101110010001010000110011101001101010101001111001100100000111': (0.0323,\n",
              "  79),\n",
              " '00000010100110101011101110010001010000110011101001101010101001101001100110000111': (0.01936,\n",
              "  79),\n",
              " '00000010100110101011101110010011010000110011101001101010101001111001100110000111': (0.01432,\n",
              "  79),\n",
              " '00000010100110101011101110010001010000110011101001101010101001011001100110000111': (0.0101,\n",
              "  79),\n",
              " '00000010100110101011101110010001010000110011101001101010101001110001100110000111': (0.00924,\n",
              "  79),\n",
              " '00000010100110101011101110010001010000010011101001101010101001111001100110000111': (0.00908,\n",
              "  79),\n",
              " '00000010100110101011100110010001010000110011101001101010101001111001100110000111': (0.00888,\n",
              "  79),\n",
              " '00000010100110101011101110010001010000110011101001100010101001111001100110000111': (0.0082,\n",
              "  79)}"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "probs, most_probable = find_hidden_shift_bitstring(\n",
        "    counts, hidden_shift_string\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "2ec8335f-d3ef-4951-8bbd-b4d01f899be1",
      "metadata": {},
      "source": [
        "Vediamo che è stata trovata la stringa di spostamento nascosta corretta. Inoltre, le nove stringhe più probabili sono sbagliate in una sola posizione.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "98d1fb7b-e696-4cef-a8b8-18e44b96eece",
      "metadata": {},
      "source": [
        "Registrare la probabilità più probabile:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 23,
      "id": "c5a9dbdc-ebb7-470a-ac31-10b41217eeed",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "0.50402"
            ]
          },
          "execution_count": 23,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "max_probability_before_M3 = probs[most_probable]\n",
        "max_probability_before_M3"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 24,
      "id": "b4514b88-3f2d-4e96-88ce-6d291576568d",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Expected hidden shift string: 00000010100110101011101110010001010000110011101001101010101001111001100110000111\n",
            "Most probable bitstring matches hidden shift 😊.\n",
            "Top 10 bitstrings and their quasi-probabilities:\n"
          ]
        },
        {
          "data": {
            "text/plain": [
              "{'00000010100110101011101110010001010000110011101001101010101001111001100110000111': '9.85e-01',\n",
              " '00000010100110101011101110010001010000110011100001101010101001111001100110000111': '6.84e-03',\n",
              " '00000010100110101011100110010001010000110011101001101010101001111001100110000111': '3.87e-03',\n",
              " '00000010100110101011101110010011010000110011101001101010101001111001100110000111': '3.42e-03',\n",
              " '00000010100110101011101110010001010000110011101001101010101001111001100100000111': '3.30e-03',\n",
              " '00000010100110101011101110010001010000110011101001101010101001110001100110000111': '3.28e-03',\n",
              " '00000010100010101011101110010001010000110011101001101010101001111001100110000111': '2.62e-03',\n",
              " '00000010100110101011101110010001010000110011101001101010101001101001100110000111': '2.43e-03',\n",
              " '00000010100110101011101110010000010000110011101001101010101001111001100110000111': '1.73e-03',\n",
              " '00000010100110101011101110010001010000110011101001101010101001111001000110000111': '1.63e-03'}"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "print(f\"Expected hidden shift string: {hidden_shift_string}\")\n",
        "max_probability_after_M3, is_hidden_shift_identified = perform_mitigation(\n",
        "    mit, counts, qubit_mapping\n",
        ")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 24,
      "id": "ccd14379-6c34-4be8-93ab-728356bf81e0",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Most probable probability before M3: 0.54348\n",
            "Most probable probability after M3: 0.99\n",
            "Readout error mitigation effective! 😊\n"
          ]
        }
      ],
      "source": [
        "compare_before_and_after_M3(\n",
        "    max_probability_before_M3,\n",
        "    max_probability_after_M3,\n",
        "    is_hidden_shift_identified,\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "96b6648b-5c26-4a0c-92f2-2d1810fa14b8",
      "metadata": {},
      "source": [
        "I risultati mostrano che l'errore di lettura è stato la fonte di errore dominante e che la mitigazione di M3 è stata efficace.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "id": "a1b8767d",
      "source": "© IBM Corp., 2017-2026"
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3"
    },
    "toc": {
      "base_numbering": 0
    },
    "hours": 1,
    "qpuSeconds": 60
  },
  "nbformat": 4,
  "nbformat_minor": 4
}