Skip to main content
IBM Quantum Platform

Mitigazione degli errori di lettura per la primitiva Sampler utilizzando l' M3

Stima di utilizzo: meno di un minuto su un processore Heron r2 (NOTA: questa è solo una stima. Il tempo di esecuzione potrebbe variare)


Sfondo

A differenza della primitiva Estimator, la primitiva Sampler non dispone di un supporto integrato per la mitigazione degli errori. 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.

L' addon M3 Qiskit 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.

Che cos'è un errore di lettura?

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à. La misurazione del registro di qubit in un registro di bit classico procede quindi in due fasi. Per prima cosa viene eseguita la misurazione quantistica vera e propria. Ciò significa che lo stato del registro di qubit è proiettato su un singolo stato base che è caratterizzato da una stringa di 11 s e 00 s. Il secondo passo consiste nel leggere la stringa di bit che caratterizza questo stato base e scriverla nella memoria classica del computer. Chiamiamo questo passo lettura. Risulta che la seconda fase (lettura) comporta un errore maggiore rispetto alla prima fase (proiezione sugli stati base). 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.

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.

Contesto teorico

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. Si osserva che questi errori sono casuali e non correlati da un campione all'altro. Si è dimostrato utile modellare l'errore di lettura come un canale classico rumoroso. Cioè, per ogni coppia di ii e jj, c'è una probabilità fissa che un valore vero di jj venga letto erroneamente come venga letto erroneamente come ii.

Più precisamente, per ogni coppia di bitstring (i,j)(i, j), esiste una probabilità (condizionata) Mi,j{M}_{i,j} che ii venga letto, dato che il valore vero è j.j. Ovvero,

Mi,j=Pr(readout value is itrue value is j) for i,j(0,...,2n1),(1) {M}_{i,j} = \Pr(\text{readout value is } i | \text{true value is } j) \text{ for } i,j \in (0,...,2^n - 1), \tag{1}

dove nn è il numero di bit del registro di lettura. Per concretezza, assumiamo che ii sia un numero intero decimale la cui rappresentazione binaria sia la stringa di bit che etichetta gli stati della base computazionale. Chiamiamo la matrice 2n×2n2^n \times 2^n M{M} matrice di assegnazione. Per un valore vero fisso jj, la somma delle probabilità su tutti i risultati rumorosi ii deve dare 11. Cioè

i=02n1Mi,j=1 for all j \sum_{i=0}^{2^n - 1} {M}_{i,j} = 1 \text{ for all } j

Una matrice senza voci negative che soddisfa la (1) è detta sinistra-stocastica. Una matrice sinistra-stocastica è anche detta colonna-stocastica perché ciascuna delle sue colonne somma a 11. Determiniamo sperimentalmente valori approssimativi per ogni elemento Mi,j{M}_{i,j} preparando ripetutamente ogni stato base e poi calcolando le frequenze preparando ripetutamente ogni stato base j|j \rangle e poi calcolando le frequenze di occorrenza delle stringhe di bit campionate.

Se un esperimento prevede la stima di una distribuzione di probabilità sulle stringhe di bit in uscita mediante campionamento ripetuto, si può usare M{M} per mitigare l'errore di lettura a livello della distribuzione. Il primo passo consiste nel ripetere più volte un circuito fisso di interesse, creando un istogramma di stringhe di bit campionate. L'istogramma normalizzato è la distribuzione di probabilità misurata su 2n2^n possibili bitstringhe, che indichiamo con p~R2n{\tilde{p}} \in \mathbb{R}^{2^n}. La probabilità (stimata) p~i{{\tilde{p}}}_i di campionare una bitstringa ii è uguale alla somma di tutte le bitstringhe vere jj, ciascuna ponderata per la probabilità che venga scambiata per ii. Questa affermazione in forma matriciale è

p~=Mp,,(2) {\tilde{p}} = {M} {\vec{p}}, \tag{2},

dove p{\vec{p}} è la distribuzione vera. In parole povere, l'errore di lettura ha l'effetto di moltiplicare la distribuzione ideale sulle stringhe di bit p{\vec{p}} per la matrice di assegnazione M{M} per produrre la distribuzione osservata per produrre la distribuzione osservata p~{\tilde{p}}. Abbiamo misurato p~{\tilde{p}} e M{M}, ma non abbiamo accesso diretto a p{\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 p{\vec{p}}.

Prima di proseguire, vale la pena di notare alcune caratteristiche importanti di questo approccio ingenuo.

  • In pratica, l'equazione (2) non si risolve invertendo M{M}. Le routine di algebra lineare nelle librerie software utilizzano metodi più stabili, accurati ed efficienti.
  • Nella stima di M{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. Nella misura in cui questa è una buona ipotesi, M{M} rappresenta in realtà solo un errore di lettura. Ma quando utilizziamo M{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.

Questo metodo, pur essendo utile in alcune circostanze, soffre di alcune limitazioni.

Le risorse di spazio e di tempo necessarie per stimare M{M} crescono esponenzialmente in nn :

  • La stima di M{M} e p~{\tilde{p}} è soggetta a errori statistici dovuti al campionamento finito. 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{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{M} cresce in modo almeno esponenziale in almeno esponenzialmente in nn.
  • M{M} è una matrice 2n×2n2^n \times 2^n. Quando n>10n>10, la quantità di memoria necessaria per memorizzare M{M} è è superiore alla memoria disponibile in un potente computer portatile.

Ulteriori limitazioni sono:

  • La distribuzione recuperata p{\vec{p}} può avere una o più probabilità negative (pur sommando a uno). Una soluzione è quella di minimizzare Mpp~2||{M} {\vec{p}} - {\tilde{p}}||^2 con il vincolo che ogni voce di p{\vec{p}} sia non negativa. Tuttavia, il tempo di esecuzione di tale è di ordini di grandezza superiore rispetto alla risoluzione diretta dell'equazione (2).
  • 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.

Componente aggiuntivo Qiskit M3 : scalabilità a stringhe di bit più lunghe

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:

  • 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.
  • Piuttosto che costruire esplicitamente M{M}, utilizziamo una matrice effettiva molto più piccola che registra probabilità solo per le stringhe di bit raccolte durante la costruzione di p~{\tilde{p}}.

Ad alto livello, la procedura funziona come segue.

In primo luogo, costruiamo dei blocchi di costruzione a partire dai quali possiamo costruire una descrizione semplificata ed efficace di M{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 p~{\tilde{p}} e, con l'aiuto dei blocchi, una descrizione efficace di M{M}.

Più precisamente,

  • 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|0 ... 0 \rangle e poi nello stato tutto uno, e registriamo per ogni qubit la probabilità che venga letto 1...1|1 ... 1 \rangle, e registriamo la probabilità per ogni qubit di essere letto in modo in modo errato.

  • Le correlazioni di ordine tre e superiore sono considerate trascurabili e vengono ignorate.

    Costruiamo invece un numero nn di 2×22 \times 2 matrici di assegnazione a singolo-qubit matrici di assegnazione e un numero n(n1)/2n(n-1)/2 di 4×44 \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.

  • Dopo aver campionato ripetutamente un circuito per costruire p~{\tilde{p}}, costruiamo un'approssimazione efficace di M{M} utilizzando solo i bitstringhe che vengono campionate durante la costruzione di p~{\tilde{p}}. Questa matrice efficace è costruita utilizzando le matrici a uno e due qubit descritte nel punto precedente. 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 p~{\tilde{p}}, che è molto più piccola della dimensione 2n2^n della matrice di assegnazione completa M{M}.

Per i dettagli tecnici su M3, è possibile consultare Scalable Mitigation of Measurement Errors on Quantum Computers.

Applicazione dell' M3 o a un algoritmo quantistico

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 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 ( versione arXiv ).

Di seguito, tutta l'aritmetica è booleana. Cioè, per a,bZ2={0,1}a, b \in \mathbb{Z}_2 = \{0, 1\}, l'addizione, a+ba + b è la funzione logica XOR. Inoltre, la moltiplicazione a×ba \times b (o aba b ) è la funzione logica AND. Per x,y{0,1}nx, y \in \{0, 1\}^n, x+yx + y è definito dall'applicazione bitwise di XOR. Il prodotto di punti :Z2nZ2\cdot: {\mathbb{Z}_2^n} \rightarrow \mathbb{Z}_2 è definito da xy=ixiyix \cdot y = \sum_i x_i y_i.

Operatore di Hadamard e trasformata di Fourier

Nell'implementazione degli algoritmi quantistici, è molto comune utilizzare l'operatore di Hadamard come una trasformata di Fourier. Gli stati della base computazionale sono talvolta chiamati stati classici. Sono in una relazione uno-a-uno con i bitstring classici. L'operatore di Hadamard di nn -qubit sugli stati classici può essere visto come una trasformata di Fourier sull'ipercubo booleano:

Hn=12nx,yZ2n(1)xyyx.H^{\otimes n} = \frac{1}{\sqrt{2^n}} \sum_{x,y \in {\mathbb{Z}_2^n}} (-1)^{x \cdot y} {|{y}\rangle}{\langle{x}|}.

Consideriamo uno stato s{|{s}\rangle} corrispondente alla stringa di bit fissa ss. Applicando HnH^{\otimes n}, e utilizzando xs=δx,s{\langle {x}|{s}\rangle} = \delta_{x,s}, vediamo che la trasformata di Fourier di s{|{s}\rangle} può essere scritta come

Hns=12nyZ2n(1)syy. H^{\otimes n} {|{s}\rangle} = \frac{1}{\sqrt{2^n}} \sum_{y \in {\mathbb{Z}_2^n}} (-1)^{s \cdot y} {|{y}\rangle}.

L'Hadamard è il suo stesso inverso, cioè, HnHn=(HH)n=InH^{\otimes n} H^{\otimes n} = (H H)^{\otimes n} = I^{\otimes n}. Pertanto, la trasformata di Fourier inversa è lo stesso operatore, HnH^{\otimes n}. Esplicitamente, si ha,

s=HnHns=Hn12nyZ2n(1)syy. {|{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}.

Il problema del turno nascosto

Consideriamo un semplice esempio di problema di spostamento nascosto. Il problema è identificare uno spostamento costante nell'ingresso di una funzione. 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.

Sia x,yZ2mx,y \in {\mathbb{Z}_2^m} una stringa di bit di lunghezza mm. Definiamo f:Z2m×Z2m{1,1}{f}: {\mathbb{Z}_2^m} \times {\mathbb{Z}_2^m} \rightarrow \{-1,1\} con

f(x,y)=(1)xy. {f}(x, y) = (-1)^{x \cdot y}.

Sia a,bZ2ma,b \in {\mathbb{Z}_2^m} una stringa fissa di bit di lunghezza mm. Definiamo inoltre g:Z2m×Z2m{1,1}g: {\mathbb{Z}_2^m} \times {\mathbb{Z}_2^m} \rightarrow \{-1,1\} con

g(x,y)=f(x+a,y+b)=(1)(x+a)(y+b), g(x, y) = {f}(x+a, y+b) = (-1)^{(x+a) \cdot (y+b)},

dove aa e bb sono parametri (nascosti). Ci vengono fornite due scatole nere, una che implementa ff e l'altra gg. Supponiamo di sapere che esse calcolano le funzioni definite sopra, ma non conosciamo né né né aabb. Il gioco consiste nel determinare le bitstringhe nascoste (turni) aa e bb facendo delle interrogazioni a ff e gg. È chiaro che se giochiamo il gioco in modo classico, abbiamo bisogno di interrogazioni a O(2m)O(2m) per determinare aa e bb. Ad esempio, possiamo interrogare gg 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 11. Per ogni interrogazione, impariamo un elemento di aa o bb. Tuttavia, vedremo che, se le scatole nere sono implementate come circuiti quantistici, possiamo determinare e con un'unica soluzione determinare aa e bb con una singola interrogazione a ciascuno di ff e gg.

Nel contesto della complessità algoritmica, una scatola nera è chiamata oracolo. 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 ff e gg si dimostreranno efficienti.

Circuiti quantistici per ff e gg

Per implementare ff e gg come circuiti quantistici sono necessari i seguenti ingredienti.

Per stati classici a singolo qubit x1,y1{|{x_1}\rangle}, {|{y_1}\rangle}, con x1,y1Z2x_1,y_1 \in \mathbb{Z}_2, la porta controllata ZZ CZ{CZ} può essere scritta come

CZx1y1x1=(1)x1y1x1x1y1.{CZ} {|{x_1}\rangle}{|{y_1}\rangle}{x_1} = (-1)^{x_1 y_1} {|{x_1}\rangle}{x_1}{|{y_1}\rangle}.

Opereremo con le porte mm CZ, una su (x1,y1)(x_1, y_1), e una su (x2,y2)(x_2, y_2), e così via, fino a (xm,ym)(x_m, y_m). Chiamiamo questo operatore CZx,y{CZ}_{x,y}.

Uf=CZx,yU_f = {CZ}_{x,y} è una versione quantistica di f=f(x,y){f} = {f}(x,y) :

Ufxy=CZx,yxy=(1)xyxy.%\CZ_{x,y} {|#1\rangle}{z} = U_f {|{x}\rangle}{|{y}\rangle} = {CZ}_{x,y} {|{x}\rangle}{|{y}\rangle} = (-1)^{x \cdot y} {|{x}\rangle}{|{y}\rangle}.

Dobbiamo anche implementare un cambio di bitstringa. Denotiamo l'operatore sul registro xx Xa1XamX^{a_1}\cdots X^{a_m} con XaX_a e allo stesso modo sul registro yy Xb=Xb1XbmX_b = X^{b_1}\cdots X^{b_m}. Questi operatori applicano XX ovunque un singolo bit sia 11, e l'identità II ovunque sia 00. Si ha quindi

XaXbxy=x+ay+b. X_a X_b {|{x}\rangle}{|{y}\rangle} = {|{x+a}\rangle}{|{y+b}\rangle}.

La seconda scatola nera gg è implementata dall'unitario UgU_g, dato da

Ug=XaXbCZx,yXaXb.%U_g {|{x}\rangle}{|{y}\rangle} = X_aX_b \CZ_{x,y} X_aX_b {|{x}\rangle}{|{y}\rangle}. U_g = X_aX_b {CZ}_{x,y} X_aX_b.

Per vedere questo, applichiamo gli operatori da destra a sinistra allo stato xy{|{x}\rangle}{|{y}\rangle}. Prima

XaXbxy=x+ay+b. X_a X_b {|{x}\rangle}{|{y}\rangle} = {|{x+a}\rangle}{|{y+b}\rangle}.

Allora,

CZx,yx+ay+b=(1)(x+a)(y+b)x+ay+b. {CZ}_{x,y} {|{x+a}\rangle}{|{y+b}\rangle} = (-1)^{(x+a)\cdot (y+b)} {|{x+a}\rangle}{|{y+b}\rangle}.

Infine,

XaXb(1)(x+a)(y+b)x+ay+b=(1)(x+a)(y+b)xy, 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},

che è appunto la versione quantistica di f(x+a,y+b)f(x+a, y+b).

L'algoritmo dello spostamento nascosto

Ora mettiamo insieme i pezzi per risolvere il problema del turno nascosto. Cominciamo applicando Hadamard ai registri inizializzati allo stato tutto zero.

H2m=HmHm0m0m=122mx,yZ2m(1)xyxy.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}.

Successivamente, interroghiamo l'oracolo gg per arrivare a

UgH2m0m0m=122mx,yZ2m(1)(x+a)(y+b)xyU_g H^{\otimes 2m} {{|{0}\rangle}^{\otimes m}}{{|{0}\rangle}^{\otimes m}} = \frac{1}{\sqrt{2^{2m}}} \sum_{x, y \in {\mathbb{Z}_2^m}} (-1)^{(x+a) \cdot (y+b)} {|{x}\rangle}{|{y}\rangle} 122mx,yZ2m(1)xy+xb+yaxy.\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}.

Nell'ultima riga, abbiamo omesso il fattore di fase globale costante (1)ab(-1)^{a \cdot b}, e denotiamo l'uguaglianza fino a una fase con \approx. Successivamente, l'applicazione dell'oracolo ff introduce un altro fattore di (1)xy(-1)^{x \cdot y}, annullando quello già presente presente. Abbiamo quindi:

UfUgH2m0m0m122mx,yZ2m(1)xb+yaxy.U_f U_g H^{\otimes 2m} {{|{0}\rangle}^{\otimes m}}{{|{0}\rangle}^{\otimes m}} \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}.

Il passo finale consiste nell'applicare la trasformata di Fourier inversa, H2m=HmHmH^{\otimes 2m} = H^{\otimes m} \otimes H^{\otimes m}, con il risultato di

H2mUfUgH2m0m0mba.H^{\otimes 2m} U_f U_g H^{\otimes 2m} {{|{0}\rangle}^{\otimes m}}{{|{0}\rangle}^{\otimes m}} \approx {|{b}\rangle}{|{a}\rangle}.

Il circuito è terminato. In assenza di rumore, il campionamento dei registri quantistici restituirà i bitstring con probabilità restituirà le stringhe di bit b,ab, a con probabilità 11.

Il prodotto interno booleano è un esempio delle cosiddette funzioni piegate. 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" Questa citazione è tratta dall'articolo Algoritmi quantistici per funzioni booleane altamente non lineari, che fornisce algoritmi efficienti di spostamento nascosto per diverse classi di funzioni piegate. L'algoritmo di questa esercitazione è riportato nella sezione 3.1 dell'articolo.

Nel caso più generale, il circuito per trovare uno spostamento nascosto sZns \in \mathbb{Z}^n è

HnUf~HnUgHn0n=s. H^{\otimes n} U_{\tilde{f}} H^{\otimes n} U_g H^{\otimes n} {|{0}\rangle}^{\otimes n} = {|{s}\rangle}.

Nel caso generale, ff e gg sono funzioni di un'unica variabile. Il nostro esempio di prodotto interno ha questa forma se lasciamo f(x,y)f(z)f(x, y) \to f(z), con zz uguale alla concatenazione di xx e yy, e ss uguale alla concatenazione di e di aa e bb. Il caso generale richiede esattamente due oracoli: Un oracolo per gg e uno per f~\tilde{f}, dove quest'ultimo è una funzione nota come duale della funzione piegata ff. La funzione prodotto interno ha la proprietà auto-duale f~=f\tilde{f}=f.

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 è ba{|{b}\rangle}{|{a}\rangle} invece del desiderato ab{|{a}\rangle}{|{b}\rangle}.


Requisiti

Prima di iniziare questa esercitazione, assicuratevi di aver installato quanto segue:

  • Qiskit SDK v2.1 o versioni successive, con supporto alla visualizzazione
  • Qiskit Runtime v0.41 o più tardi (pip install qiskit-ibm-runtime)
  • M3 Addon Qiskit v3.0 (pip install mthree)

Configura

from collections.abc import Iterator, Sequence
from random import Random
from qiskit.circuit import (
    CircuitInstruction,
    QuantumCircuit,
    QuantumRegister,
    Qubit,
)
from qiskit.circuit.library import CZGate, HGate, XGate
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService
import timeit
import matplotlib.pyplot as plt
from qiskit_ibm_runtime import SamplerV2 as Sampler
import mthree

Fase 1: mappare gli input classici su un problema quantistico

Per prima cosa, scriviamo le funzioni per implementare il problema dello spostamento nascosto come QuantumCircuit.

def apply_hadamards(qubits: Sequence[Qubit]) -> Iterator[CircuitInstruction]:
    """Apply a Hadamard gate to every qubit."""
    for q in qubits:
        yield CircuitInstruction(HGate(), [q], [])


def apply_shift(
    qubits: Sequence[Qubit], shift: int
) -> Iterator[CircuitInstruction]:
    """Apply X gates where the bits of the shift are equal to 1."""
    for i, q in zip(range(shift.bit_length()), qubits):
        if shift >> i & 1:
            yield CircuitInstruction(XGate(), [q], [])


def oracle_f(qubits: Sequence[Qubit]) -> Iterator[CircuitInstruction]:
    """Apply the f oracle."""
    for i in range(0, len(qubits) - 1, 2):
        yield CircuitInstruction(CZGate(), [qubits[i], qubits[i + 1]])


def oracle_g(
    qubits: Sequence[Qubit], shift: int
) -> Iterator[CircuitInstruction]:
    """Apply the g oracle."""
    yield from apply_shift(qubits, shift)
    yield from oracle_f(qubits)
    yield from apply_shift(qubits, shift)


def determine_hidden_shift(
    qubits: Sequence[Qubit], shift: int
) -> Iterator[CircuitInstruction]:
    """Determine the hidden shift."""
    yield from apply_hadamards(qubits)
    yield from oracle_g(qubits, shift)
    # We omit this layer in exchange for post processing
    # yield from apply_hadamards(qubits)
    yield from oracle_f(qubits)
    yield from apply_hadamards(qubits)


def run_hidden_shift_circuit(n_qubits, rng):
    hidden_shift = rng.getrandbits(n_qubits)

    qubits = QuantumRegister(n_qubits, name="q")
    circuit = QuantumCircuit.from_instructions(
        determine_hidden_shift(qubits, hidden_shift), qubits=qubits
    )
    circuit.measure_all()
    # Format the hidden shift as a string.
    hidden_shift_string = format(hidden_shift, f"0{n_qubits}b")
    return (circuit, hidden_shift, hidden_shift_string)


def display_circuit(circuit):
    return circuit.remove_final_measurements(inplace=False).draw(
        "mpl", idle_wires=False, scale=0.5, fold=-1
    )

Cominciamo con un piccolo esempio:

n_qubits = 6
random_seed = 12345
rng = Random(random_seed)
circuit, hidden_shift, hidden_shift_string = run_hidden_shift_circuit(
    n_qubits, rng
)

print(f"Hidden shift string {hidden_shift_string}")

display_circuit(circuit)

Output:

Hidden shift string 011010
Output of the previous code cell

Fase 2: Ottimizzazione dei circuiti per l'esecuzione dell'hardware quantistico

job_tags = [
    f"shift {hidden_shift_string}",
    f"n_qubits {n_qubits}",
    f"seed = {random_seed}",
]
job_tags

Output:

['shift 011010', 'n_qubits 6', 'seed = 12345']
# Uncomment this to run the circuits on a quantum computer on IBMCloud.
service = QiskitRuntimeService()
backend = service.least_busy(
    operational=True, simulator=False, min_num_qubits=100
)

# from qiskit_ibm_runtime.fake_provider import FakeMelbourneV2
# backend = FakeMelbourneV2()
# backend.refresh(service)

print(f"Using backend {backend.name}")


def get_isa_circuit(circuit, backend):
    pass_manager = generate_preset_pass_manager(
        optimization_level=3, backend=backend, seed_transpiler=1234
    )
    isa_circuit = pass_manager.run(circuit)
    return isa_circuit


isa_circuit = get_isa_circuit(circuit, backend)
display_circuit(isa_circuit)

Output:

Using backend ibm_kingston
Output of the previous code cell

Fase 3: Eseguire i circuiti utilizzando Qiskit primitives

# submit job for solving the hidden shift problem using the Sampler primitive
NUM_SHOTS = 50_000


def run_sampler(backend, isa_circuit, num_shots):
    sampler = Sampler(mode=backend)
    sampler.options.environment.job_tags
    pubs = [(isa_circuit, None, NUM_SHOTS)]
    job = sampler.run(pubs)
    return job


def setup_mthree_mitigation(isa_circuit, backend):
    # retrieve the final qubit mapping so mthree knows which qubits to calibrate
    qubit_mapping = mthree.utils.final_measurement_mapping(isa_circuit)

    # submit jobs for readout error calibration
    mit = mthree.M3Mitigation(backend)
    mit.cals_from_system(qubit_mapping, rep_delay=None)

    return mit, qubit_mapping
job = run_sampler(backend, isa_circuit, NUM_SHOTS)
mit, qubit_mapping = setup_mthree_mitigation(isa_circuit, backend)

Fase 4: Post-elaborazione e restituzione dei risultati in formato classico

Nella discussione teorica precedente, abbiamo stabilito che per l'ingresso abab, ci aspettiamo l'uscita baba. 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 aa e bb come a1b1a2b2a1 b1 a2 b2 \ldots. La stringa di uscita baba sarà interlacciata in modo analogo: b1a1b2a2b1 a1 b2 a2 \ldots. La funzione unscramble qui sotto trasforma la stringa di uscita da b1a1b2a2b1 a1 b2 a2 \ldots a a1b1a2b2a1 b1 a2 b2 \ldots in modo che le stringhe di ingresso e di uscita possano essere confrontate direttamente.

# retrieve bitstring counts
def get_bitstring_counts(job):
    result = job.result()
    pub_result = result[0]
    counts = pub_result.data.meas.get_counts()
    return counts, pub_result
counts, pub_result = get_bitstring_counts(job)

La distanza di Hamming tra due stringhe di bit è il numero di indici in cui i bit differiscono.

def hamming_distance(s1, s2):
    weight = 0
    for c1, c2 in zip(s1, s2):
        (c1, c2) = (int(c1), int(c2))
        if (c1 == 1 and c2 == 1) or (c1 == 0 and c2 == 0):
            weight += 1

    return weight
# Replace string of form a1b1a2b2... with b1a1b2a1...
# That is, reverse order of successive pairs of bits.
def unscramble(bitstring):
    ps = [bitstring[i : i + 2][::-1] for i in range(0, len(bitstring), 2)]
    return "".join(ps)


def find_hidden_shift_bitstring(counts, hidden_shift_string):
    # convert counts to probabilities
    probs = {
        unscramble(bitstring): count / NUM_SHOTS
        for bitstring, count in counts.items()
    }

    # Retrieve the most probable bitstring.
    most_probable = max(probs, key=lambda x: probs[x])

    print(f"Expected hidden shift string: {hidden_shift_string}")
    if most_probable == hidden_shift_string:
        print("Most probable bitstring matches hidden shift 😊.")
    else:
        print("Most probable bitstring didn't match hidden shift ☹️.")
    print("Top 10 bitstrings and their probabilities:")
    display(
        {
            k: (v, hamming_distance(hidden_shift_string, k))
            for k, v in sorted(
                probs.items(), key=lambda x: x[1], reverse=True
            )[:10]
        }
    )

    return probs, most_probable
probs, most_probable = find_hidden_shift_bitstring(
    counts, hidden_shift_string
)

Output:

Expected hidden shift string: 011010
Most probable bitstring matches hidden shift 😊.
Top 10 bitstrings and their probabilities:
{'011010': (0.9743, 6),
 '001010': (0.00812, 5),
 '010010': (0.0063, 5),
 '011000': (0.00554, 5),
 '011011': (0.00492, 5),
 '011110': (0.00044, 5),
 '001000': (0.00012, 4),
 '010000': (8e-05, 4),
 '001011': (6e-05, 4),
 '000010': (6e-05, 4)}

Registriamo la probabilità della stringa di bit più probabile prima di applicare la mitigazione dell'errore di lettura con M3.

max_probability_before_M3 = probs[most_probable]
max_probability_before_M3

Output:

0.9743

Ora applichiamo ai conteggi la correzione di lettura appresa dall' M3. La funzione apply_corrections restituisce una distribuzione di quasi-probabilità. Questo è un elenco di float oggetti che sommati danno come risultato 11. Tuttavia, alcuni valori potrebbero essere negativi.

def perform_mitigation(mit, counts, qubit_mapping):
    # mitigate readout error
    quasis = mit.apply_correction(counts, qubit_mapping)

    # print results
    most_probable_after_m3 = unscramble(max(quasis, key=lambda x: quasis[x]))

    is_hidden_shift_identified = most_probable_after_m3 == hidden_shift_string
    if is_hidden_shift_identified:
        print("Most probable bitstring matches hidden shift 😊.")
    else:
        print("Most probable bitstring didn't match hidden shift ☹️.")
    print("Top 10 bitstrings and their quasi-probabilities:")
    topten = {
        unscramble(k): f"{v:.2e}"
        for k, v in sorted(quasis.items(), key=lambda x: x[1], reverse=True)[
            :10
        ]
    }
    max_probability_after_M3 = float(topten[most_probable_after_m3])
    display(topten)

    return max_probability_after_M3, is_hidden_shift_identified
print(f"Expected hidden shift string: {hidden_shift_string}")
max_probability_after_M3, is_hidden_shift_identified = perform_mitigation(
    mit, counts, qubit_mapping
)

Output:

Expected hidden shift string: 011010
Most probable bitstring matches hidden shift 😊.
Top 10 bitstrings and their quasi-probabilities:
{'011010': '1.01e+00',
 '001010': '8.75e-04',
 '001000': '7.38e-05',
 '010000': '4.51e-05',
 '111000': '2.18e-05',
 '001011': '1.74e-05',
 '000010': '6.42e-06',
 '011001': '-7.18e-06',
 '011000': '-4.53e-04',
 '010010': '-1.28e-03'}

Confronta l'identificazione della stringa di spostamento nascosta prima e dopo l'applicazione di una correzion M3

def compare_before_and_after_M3(
    max_probability_before_M3,
    max_probability_after_M3,
    is_hidden_shift_identified,
):
    is_probability_improved = (
        max_probability_after_M3 > max_probability_before_M3
    )
    print(f"Most probable probability before M3: {max_probability_before_M3}")
    print(f"Most probable probability after M3: {max_probability_after_M3}")
    if is_hidden_shift_identified and is_probability_improved:
        print("Readout error mitigation effective! 😊")
    else:
        print("Readout error mitigation not effective. ☹️")
compare_before_and_after_M3(
    max_probability_before_M3,
    max_probability_after_M3,
    is_hidden_shift_identified,
)

Output:

Most probable probability before M3: 0.9743
Most probable probability after M3: 1.01
Readout error mitigation effective! 😊

Traccia un grafico che mostri come il tempo di CPU richiesto da M3 varia in base al numero di scatti

# Collect samples for numbers of shots varying from 5000 to 25000.
shots_range = range(5000, NUM_SHOTS + 1, 2500)
times = []
for shots in shots_range:
    print(f"Applying M3 correction to {shots} shots...")
    t0 = timeit.default_timer()
    _ = mit.apply_correction(
        pub_result.data.meas.slice_shots(range(shots)).get_counts(),
        qubit_mapping,
    )
    t1 = timeit.default_timer()
    print(f"\tDone in {t1 - t0} seconds.")
    times.append(t1 - t0)

fig, ax = plt.subplots()
ax.plot(shots_range, times, "o--")
ax.set_xlabel("Shots")
ax.set_ylabel("Time (s)")
ax.set_title("Time to apply M3 correction")

Output:

Applying M3 correction to 5000 shots...
	Done in 0.003321983851492405 seconds.
Applying M3 correction to 7500 shots...
	Done in 0.004425413906574249 seconds.
Applying M3 correction to 10000 shots...
	Done in 0.006366567220538855 seconds.
Applying M3 correction to 12500 shots...
	Done in 0.0071477219462394714 seconds.
Applying M3 correction to 15000 shots...
	Done in 0.00860048783943057 seconds.
Applying M3 correction to 17500 shots...
	Done in 0.010026784148067236 seconds.
Applying M3 correction to 20000 shots...
	Done in 0.011459112167358398 seconds.
Applying M3 correction to 22500 shots...
	Done in 0.012727141845971346 seconds.
Applying M3 correction to 25000 shots...
	Done in 0.01406092382967472 seconds.
Applying M3 correction to 27500 shots...
	Done in 0.01546052098274231 seconds.
Applying M3 correction to 30000 shots...
	Done in 0.016769016161561012 seconds.
Applying M3 correction to 32500 shots...
	Done in 0.019537431187927723 seconds.
Applying M3 correction to 35000 shots...
	Done in 0.019739801064133644 seconds.
Applying M3 correction to 37500 shots...
	Done in 0.021093040239065886 seconds.
Applying M3 correction to 40000 shots...
	Done in 0.022840639110654593 seconds.
Applying M3 correction to 42500 shots...
	Done in 0.023974396288394928 seconds.
Applying M3 correction to 45000 shots...
	Done in 0.026412792038172483 seconds.
Applying M3 correction to 47500 shots...
	Done in 0.026364430785179138 seconds.
Applying M3 correction to 50000 shots...
	Done in 0.02820305060595274 seconds.
Text(0.5, 1.0, 'Time to apply M3 correction')
Output of the previous code cell

Interpretazione della trama

Il grafico precedente mostra che il tempo necessario per applicare la correzione M3 cresce linearmente con il numero di scatti.


Scalabilità

n_qubits = 80
rng = Random(12345)
circuit, hidden_shift, hidden_shift_string = run_hidden_shift_circuit(
    n_qubits, rng
)

print(f"Hidden shift string {hidden_shift_string}")

Output:

Hidden shift string 00000010100110101011101110010001010000110011101001101010101001111001100110000111
isa_circuit = get_isa_circuit(circuit, backend)
job = run_sampler(backend, isa_circuit, NUM_SHOTS)
mit, qubit_mapping = setup_mthree_mitigation(isa_circuit, backend)
counts, pub_result = get_bitstring_counts(job)
probs, most_probable = find_hidden_shift_bitstring(
    counts, hidden_shift_string
)

Output:

Expected hidden shift string: 00000010100110101011101110010001010000110011101001101010101001111001100110000111
Most probable bitstring matches hidden shift 😊.
Top 10 bitstrings and their probabilities:
{'00000010100110101011101110010001010000110011101001101010101001111001100110000111': (0.50402,
  80),
 '00000010100110101011101110010001010000110011100001101010101001111001100110000111': (0.0396,
  79),
 '00000010100110101011101110010001010000110011101001101010101001111001100100000111': (0.0323,
  79),
 '00000010100110101011101110010001010000110011101001101010101001101001100110000111': (0.01936,
  79),
 '00000010100110101011101110010011010000110011101001101010101001111001100110000111': (0.01432,
  79),
 '00000010100110101011101110010001010000110011101001101010101001011001100110000111': (0.0101,
  79),
 '00000010100110101011101110010001010000110011101001101010101001110001100110000111': (0.00924,
  79),
 '00000010100110101011101110010001010000010011101001101010101001111001100110000111': (0.00908,
  79),
 '00000010100110101011100110010001010000110011101001101010101001111001100110000111': (0.00888,
  79),
 '00000010100110101011101110010001010000110011101001100010101001111001100110000111': (0.0082,
  79)}

Vediamo che è stata trovata la stringa di spostamento nascosta corretta. Inoltre, le nove stringhe più probabili sono sbagliate in una sola posizione.

Registrare la probabilità più probabile:

max_probability_before_M3 = probs[most_probable]
max_probability_before_M3

Output:

0.50402
print(f"Expected hidden shift string: {hidden_shift_string}")
max_probability_after_M3, is_hidden_shift_identified = perform_mitigation(
    mit, counts, qubit_mapping
)

Output:

Expected hidden shift string: 00000010100110101011101110010001010000110011101001101010101001111001100110000111
Most probable bitstring matches hidden shift 😊.
Top 10 bitstrings and their quasi-probabilities:
{'00000010100110101011101110010001010000110011101001101010101001111001100110000111': '9.85e-01',
 '00000010100110101011101110010001010000110011100001101010101001111001100110000111': '6.84e-03',
 '00000010100110101011100110010001010000110011101001101010101001111001100110000111': '3.87e-03',
 '00000010100110101011101110010011010000110011101001101010101001111001100110000111': '3.42e-03',
 '00000010100110101011101110010001010000110011101001101010101001111001100100000111': '3.30e-03',
 '00000010100110101011101110010001010000110011101001101010101001110001100110000111': '3.28e-03',
 '00000010100010101011101110010001010000110011101001101010101001111001100110000111': '2.62e-03',
 '00000010100110101011101110010001010000110011101001101010101001101001100110000111': '2.43e-03',
 '00000010100110101011101110010000010000110011101001101010101001111001100110000111': '1.73e-03',
 '00000010100110101011101110010001010000110011101001101010101001111001000110000111': '1.63e-03'}
compare_before_and_after_M3(
    max_probability_before_M3,
    max_probability_after_M3,
    is_hidden_shift_identified,
)

Output:

Most probable probability before M3: 0.54348
Most probable probability after M3: 0.99
Readout error mitigation effective! 😊

I risultati mostrano che l'errore di lettura è stato la fonte di errore dominante e che la mitigazione di M3 è stata efficace.

Questa pagina è stata utile?
Segnala un bug, un errore di battitura o richiedi contenuti su GitHub.