Vai al contenuto principale

Algoritmi quantistici: Stima della fase

nota

Kento Ueda (15 maggio 2024)

Questo notebook fornisce i concetti fondamentali e l'implementazione della Trasformata di Fourier Quantistica (QFT) e della Stima della Fase Quantistica (QPE).

Scarica il pdf della lezione originale. Nota che alcuni frammenti di codice potrebbero risultare deprecati poiché si tratta di immagini statiche.

Il tempo QPU approssimativo per eseguire questo esperimento è di 7 secondi.

1. Introduzione

Trasformata di Fourier Quantistica (QFT)

La Trasformata di Fourier Quantistica è l'equivalente quantistico della trasformata di Fourier discreta classica. È una trasformazione lineare applicata agli stati quantistici, che mappa le basi computazionali nelle loro rappresentazioni nella base di Fourier. La QFT svolge un ruolo chiave in molti algoritmi quantistici, offrendo un metodo efficiente per estrarre informazioni di periodicità dagli stati quantistici. La QFT può essere implementata con O(N2)O(N^2) operazioni tramite gate quantistici come i gate di Hadamard e i gate di fase controllata per NN qubit, consentendo un'accelerazione esponenziale rispetto alla trasformata di Fourier classica.

  • Applicazioni: È una componente fondamentale in algoritmi quantistici come l'algoritmo di Shor per la fattorizzazione di interi grandi e il logaritmo discreto.

Stima della Fase Quantistica (QPE)

La Stima della Fase Quantistica è un algoritmo quantistico utilizzato per stimare la fase associata a un autovettore di un operatore unitario. Questo algoritmo fornisce un ponte tra le proprietà matematiche astratte degli stati quantistici e le loro applicazioni computazionali.

  • Applicazioni: Può risolvere problemi come il calcolo degli autovalori di matrici unitarie e la simulazione di sistemi quantistici.

Insieme, QFT e QPE costituiscono la spina dorsale essenziale di molti algoritmi quantistici che risolvono problemi impossibili per i computer classici. Al termine di questo notebook, avrai acquisito una comprensione di come queste tecniche vengono implementate.

2. Basi della Trasformata di Fourier Quantistica (QFT)

# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit qiskit-aer qiskit-ibm-runtime
from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister
from qiskit_aer import AerSimulator
from qiskit.visualization import plot_histogram, plot_bloch_multivector
from qiskit.quantum_info import Statevector
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import Sampler

from numpy import pi

Per analogia con la trasformata di Fourier discreta, la QFT agisce su uno stato quantistico X=j=0N1xjj\vert X\rangle = \sum_{j=0}^{N-1} x_j \vert j \rangle per NN qubit e lo mappa nello stato quantistico Y=k=0N1ykk\vert Y\rangle = \sum_{k=0}^{N-1} y_k \vert k \rangle.

QFTN:j1Nk=0N1ωNjkkQFT_{N}: \vert j \rangle \mapsto \frac{1}{\sqrt{N}}\sum_{k=0}^{N-1}\omega_N^{jk} \vert k \rangle

dove ωNjk=e2πijkN\omega_N^{jk} = e^{2\pi i \frac{jk}{N}}.

Oppure nella rappresentazione matriciale unitaria:

UQFT=1Nj=0N1k=0N1ωNjkkjU_{QFT} = \frac{1}{\sqrt{N}} \sum_{j=0}^{N-1} \sum_{k=0}^{N-1} \omega_N^{jk} \vert k \rangle \langle j \vert

2.1. Intuizione

La trasformata di Fourier quantistica (QFT) opera una trasformazione tra due basi: la base computazionale (Z) e la base di Fourier. Ma cosa significa la base di Fourier in questo contesto? Probabilmente ricordi che la trasformata di Fourier F(ω)F(\omega) di una funzione f(x)f(x) descrive la convoluzione di f(x)f(x) su una funzione sinusoidale con frequenza ω\omega. In termini semplici: la trasformata di Fourier è una funzione che descrive quanta parte di ciascuna frequenza ω\omega sarebbe necessaria per costruire una funzione f(x)f(x) a partire da funzioni seno (o coseno). Per avere un'idea più chiara di cosa significhi la QFT in questo contesto, considera le immagini in sequenza qui sotto, che mostrano un numero codificato in binario usando quattro qubit:

Nella base computazionale, i numeri vengono memorizzati in binario usando gli stati 0|0\rangle e 1|1\rangle.

Nota la frequenza con cui i diversi qubit cambiano: il qubit più a sinistra si capovolge a ogni incremento del numero, il successivo ogni 2 incrementi, il terzo ogni 4 incrementi, e così via.

Se applichiamo una trasformata di Fourier quantistica a questi stati, otteniamo la mappa:

Stato nella Base ComputazionaleQFTStato nella Base di Fourier|\text{Stato nella Base Computazionale}\rangle \quad \xrightarrow[]{\text{QFT}} \quad |\text{Stato nella Base di Fourier}\rangle QFTx=x~\text{QFT}|x\rangle = |\widetilde{x}\rangle

(Spesso denotiamo gli stati nella base di Fourier con la tilde (~)).

Nella base di Fourier, i numeri vengono memorizzati utilizzando diverse rotazioni attorno all'asse Z:

Il numero che vogliamo memorizzare determina l'angolo di rotazione di ciascun qubit attorno all'asse Z. Nello stato 0~|\widetilde{0}\rangle, tutti i qubit sono nello stato +|{+}\rangle. Come mostrato nell'esempio precedente, per codificare lo stato 5~|\widetilde{5}\rangle su 4 qubit, abbiamo ruotato il qubit più a sinistra di 52n=516\tfrac{5}{2^n} = \tfrac{5}{16} giri completi (516×2π\tfrac{5}{16}\times 2\pi radianti). Il qubit successivo viene ruotato del doppio (1016×2π\tfrac{10}{16}\times 2\pi radianti, ovvero 10/1610/16 giri completi), questo angolo viene poi raddoppiato per il qubit successivo, e così via.

Ancora una volta, nota la frequenza con cui cambia ciascun qubit. Il qubit più a sinistra (qubit 0) in questo caso ha la frequenza più bassa, e quello più a destra la più alta.

2.2 Esempio: QFT a 1 qubit

Consideriamo il caso N=21=2N = 2^1 = 2.

La matrice unitaria può essere scritta come:

UQFT=12j=01k=01ω2jkkj=12(ω2000+ω2001+ω2010+ω2111)=12(00+01+1011)=H\begin{aligned} U_{QFT} & = \frac{1}{\sqrt{2}} \sum_{j=0}^{1} \sum_{k=0}^{1} \omega_2^{jk} \vert k \rangle \langle j \vert \\ & = \frac{1}{\sqrt{2}} (\omega_2^{0} \vert 0 \rangle \langle 0 \vert + \omega_2^{0} \vert 0 \rangle \langle 1 \vert + \omega_2^{0} \vert 1 \rangle \langle 0 \vert + \omega_2^{1} \vert 1 \rangle \langle 1 \vert) \\ & = \frac{1}{\sqrt{2}} (\vert 0 \rangle \langle 0 \vert + \vert 0 \rangle \langle 1 \vert + \vert 1 \rangle \langle 0 \vert - \vert 1 \rangle \langle 1 \vert) \\ & = H \end{aligned}

Questa operazione è il risultato dell'applicazione del gate di Hadamard (HH).

2.3 Rappresentazione a prodotto della QFT

Generalizziamo la trasformazione per N=2nN = 2^n, QFTNQFT_{N} applicata allo stato x=x1xn\vert x \rangle = \vert x_1\ldots x_n \rangle.

QFTNx=1Ny=0N1ωNxyy=1Ny=0N1e2πixy/2ny sinceωNxy=e2πixyNeN=2n=1Ny=0N1e2πi(k=1nyk/2k)xy1ynrewriting in fractional binary notationy=y1yn,y/2n=k=1nyk/2k=1Ny=0N1k=1ne2πixyk/2ky1ynafter expanding the exponential of a sum to a product of exponentials,=1Nk=1n(0+e2πix/2k1)after rearranging the sum and products, and expandingy=0N1=y1=01y2=01yn=01=1N(0+e2πi2x1)(0+e2πi22x1)(0+e2πi2n1x1)(0+e2πi2nx1)\begin{aligned} QFT_N\vert x \rangle & = \frac{1}{\sqrt{N}} \sum_{y=0}^{N-1}\omega_N^{xy} \vert y \rangle \\ & = \frac{1}{\sqrt{N}} \sum_{y=0}^{N-1} e^{2 \pi i xy / 2^n} \vert y \rangle ~\text{since}\: \omega_N^{xy} = e^{2\pi i \frac{xy}{N}} \:\text{e}\: N = 2^n \\ & = \frac{1}{\sqrt{N}} \sum_{y=0}^{N-1} e^{2 \pi i \left(\sum_{k=1}^n y_k/2^k\right) x} \vert y_1 \ldots y_n \rangle \:\text{rewriting in fractional binary notation}\: y = y_1\ldots y_n, y/2^n = \sum_{k=1}^n y_k/2^k \\ & = \frac{1}{\sqrt{N}} \sum_{y=0}^{N-1} \prod_{k=1}^n e^{2 \pi i x y_k/2^k } \vert y_1 \ldots y_n \rangle \:\text{after expanding the exponential of a sum to a product of exponentials,} \\ & = \frac{1}{\sqrt{N}} \bigotimes_{k=1}^n \left(\vert0\rangle + e^{2 \pi i x /2^k } \vert1\rangle \right) \:\text{after rearranging the sum and products, and expanding} \sum_{y=0}^{N-1} = \sum_{y_1=0}^{1}\sum_{y_2=0}^{1}\ldots\sum_{y_n=0}^{1} \\ & = \frac{1}{\sqrt{N}} \left(\vert0\rangle + e^{\frac{2\pi i}{2}x} \vert1\rangle\right) \otimes \left(\vert0\rangle + e^{\frac{2\pi i}{2^2}x} \vert1\rangle\right) \otimes \ldots \otimes \left(\vert0\rangle + e^{\frac{2\pi i}{2^{n-1}}x} \vert1\rangle\right) \otimes \left(\vert0\rangle + e^{\frac{2\pi i}{2^n}x} \vert1\rangle\right) \end{aligned}

2.4 Esempio: Costruzione del circuito QFT a 3 qubit

Dalla descrizione precedente, potrebbe non essere immediatamente chiaro come costruire un circuito QFT. Per ora, nota semplicemente che ci aspettiamo che i tre qubit abbiano fasi che evolvono a "velocità" diverse. Capire esattamente come questo si traduca in un circuito è lasciato come esercizio al lettore. Nella dispensa in pdf sono presenti diversi diagrammi ed esempi. Ulteriori risorse includono questa lezione del corso sui Fondamenti degli algoritmi quantistici.

Dimostreremo la QFT usando solo simulatori, quindi non utilizzeremo il framework Qiskit patterns fino a quando non passeremo alla stima della fase quantistica.

# Prepare for 3 qubits circuit
qr = QuantumRegister(3)
cr = ClassicalRegister(3)
qc = QuantumCircuit(qr, cr)
qc.h(2)
qc.cp(pi / 2, 1, 2) # Controlled-phase gate from qubit 1 to qubit 2
qc.cp(pi / 4, 0, 2) # Controlled-phase gate from qubit 0 to qubit 2
qc.draw(output="mpl")

Output of the previous code cell

qc.h(1)
qc.cp(pi / 2, 0, 1) # Controlled-phase gate from qubit 0 to qubit 1

qc.draw(output="mpl")

Output of the previous code cell

qc.h(0)

qc.draw(output="mpl")

Output of the previous code cell

qc.swap(0, 2)

qc.draw(output="mpl")

Output of the previous code cell

Proviamo ad applicare la QFT a 5|5\rangle come esempio.

Per prima cosa, confermiamo la notazione binaria dell'intero 5 e creiamo il circuito che codifica lo stato 5:

bin(5)
'0b101'
qc = QuantumCircuit(3)

qc.x(0)
qc.x(2)
qc.draw(output="mpl")

Output of the previous code cell

Verifichiamo gli stati quantistici usando il simulatore Aer:

statevector = Statevector(qc)
plot_bloch_multivector(statevector)

Output of the previous code cell

Infine, aggiungiamo la QFT e visualizziamo lo stato finale dei nostri qubit:

qc.h(2)
qc.cp(pi / 2, 1, 2)
qc.cp(pi / 4, 0, 2)

qc.h(1)
qc.cp(pi / 2, 0, 1)

qc.h(0)

qc.swap(0, 2)

qc.draw(output="mpl")

Output of the previous code cell

statevector = Statevector(qc)
plot_bloch_multivector(statevector)

Output of the previous code cell

Possiamo vedere che la nostra funzione QFT ha funzionato correttamente. Il qubit 0 è stato ruotato di 58\tfrac{5}{8} di giro completo, il qubit 1 di 108\tfrac{10}{8} giri completi (equivalente a 14\tfrac{1}{4} di giro completo), e il qubit 2 di 208\tfrac{20}{8} giri completi (equivalente a 12\tfrac{1}{2} di giro completo).

2.5 Esercizio: QFT

(1) Implementa la QFT a 4 qubit.

##your code goes here##

(2) Applica la QFT a 14|14\rangle, simula e traccia il vettore di stato usando la sfera di Bloch.

##your code goes here##

Soluzione dell'esercizio: QFT

(1)

qr = QuantumRegister(4)
cr = ClassicalRegister(4)
qc = QuantumCircuit(qr, cr)

qc.h(3)
qc.cp(pi / 2, 2, 3)
qc.cp(pi / 4, 1, 3)
qc.cp(pi / 8, 0, 3)

qc.h(2)
qc.cp(pi / 2, 1, 2)
qc.cp(pi / 4, 0, 2)

qc.h(1)
qc.cp(pi / 2, 0, 1)

qc.h(0)

qc.swap(0, 3)
qc.swap(1, 2)

qc.draw(output="mpl")

Output of the previous code cell

(2)

bin(14)
'0b1110'
qc = QuantumCircuit(4)

qc.x(1)
qc.x(2)
qc.x(3)
qc.draw("mpl")

Output of the previous code cell

qc.h(3)
qc.cp(pi / 2, 2, 3)
qc.cp(pi / 4, 1, 3)
qc.cp(pi / 8, 0, 3)

qc.h(2)
qc.cp(pi / 2, 1, 2)
qc.cp(pi / 4, 0, 2)

qc.h(1)
qc.cp(pi / 2, 0, 1)
qc.h(0)

qc.swap(0, 3)
qc.swap(1, 2)

qc.draw(output="mpl")

Output of the previous code cell

statevector = Statevector(qc)
plot_bloch_multivector(statevector)

Output of the previous code cell

3. Basi della Stima della Fase Quantistica (QPE)

Data un'operazione unitaria UU, la QPE stima θ\theta in Uψ=e2πiθψU\vert\psi \rangle =e^{\boldsymbol{2\pi i} \theta }|\psi \rangle; poiché UU è unitario, tutti i suoi autovalori hanno norma 1.

3.1 Procedura

1. Configurazione

ψ\vert\psi\rangle si trova in un insieme di registri qubit. Un ulteriore insieme di nn qubit costituisce il registro di conteggio in cui memorizzeremo il valore 2nθ2^n\theta:

ψ0=0nψ|\psi_0\rangle = \lvert 0 \rangle^{\otimes n} \lvert \psi \rangle

2. Sovrapposizione

Applica un'operazione di gate di Hadamard a nn bit HnH^{\otimes n} sul registro di conteggio:

ψ1=12n2(0+1)nψ|\psi_1\rangle = {\frac {1}{2^{\frac {n}{2}}}}\left(|0\rangle +|1\rangle \right)^{\otimes n} \lvert \psi \rangle

3. Operazioni unitarie controllate

Dobbiamo introdurre l'unitario controllato CUCU che applica l'operatore unitario UU al registro target solo se il corrispondente bit di controllo è 1|1\rangle. Poiché UU è un operatore unitario con autovettore ψ|\psi\rangle tale che Uψ=e2πiθψU|\psi \rangle =e^{\boldsymbol{2\pi i} \theta }|\psi \rangle, questo implica:

U2jψ=U2j1Uψ=U2j1e2πiθψ==e2πi2jθψU^{2^{j}}|\psi \rangle =U^{2^{j}-1}U|\psi \rangle =U^{2^{j}-1}e^{2\pi i\theta }|\psi \rangle =\cdots =e^{2\pi i2^{j}\theta }|\psi \rangle

3.2 Esempio: QPE con il gate T

Usiamo il gate TT come esempio di QPE e stimiamo la sua fase θ\theta.

T1=(100eiπ4)(01)=eiπ41T|1\rangle = \begin{pmatrix} 1 & 0\\ 0 & e^\frac{i\pi}{4}\\ \end{pmatrix} \begin{pmatrix} 0\\ 1\\ \end{pmatrix} = e^\frac{i\pi}{4}|1\rangle

Ci aspettiamo di trovare:

θ=18\theta = \frac{1}{8}

dove

T1=e2iπθ1T|1\rangle = e^{2i\pi\theta}|1\rangle

Inizializziamo ψ=1\vert\psi\rangle = \vert1\rangle come autovettore del gate TT applicando un gate XX:

qpe = QuantumCircuit(4, 3)
qpe.x(3)
qpe.draw(output="mpl")

Output of the previous code cell

Successivamente, applichiamo i gate di Hadamard ai qubit di conteggio:

for qubit in range(3):
qpe.h(qubit)
qpe.draw(output="mpl")

Output of the previous code cell

Eseguiamo le operazioni unitarie controllate:

repetitions = 1
for counting_qubit in range(3):
for i in range(repetitions):
qpe.cp(pi / 4, counting_qubit, 3) # This is C-U
repetitions *= 2
qpe.draw(output="mpl")

Output of the previous code cell

Applichiamo la trasformata di Fourier quantistica inversa per convertire lo stato del registro di conteggio, poi misuriamo il registro di conteggio:

from qiskit.circuit.library import QFT
# Apply inverse QFT
qpe.append(QFT(3, inverse=True), [0, 1, 2])
qpe.draw(output="mpl")

Output of the previous code cell

for n in range(3):
qpe.measure(n, n)
qpe.draw(output="mpl")

Output of the previous code cell

Possiamo simulare usando il simulatore Aer:

aer_sim = AerSimulator()
shots = 2048

pm = generate_preset_pass_manager(backend=aer_sim, optimization_level=1)
t_qpe = pm.run(qpe)

sampler = Sampler(mode=aer_sim)
job = sampler.run([t_qpe], shots=shots)
result = job.result()
answer = result[0].data.c.get_counts()

plot_histogram(answer)

Output of the previous code cell

Vediamo che otteniamo un solo risultato (001) con certezza, che si traduce in decimale: 1. Ora dobbiamo dividere il nostro risultato (1) per 2n2^n per ottenere θ\theta:

θ=123=18\theta = \frac{1}{2^3} = \frac{1}{8}

L'algoritmo di Shor ci permette di fattorizzare un numero costruendo un circuito con θ\theta incognito e ottenendo θ\theta.

3.3 Esercizio

Stima θ=1/3\theta = 1/3 usando 3 qubit per il conteggio e un qubit per un autovettore.

P1=eiλ1P|1\rangle = e^{i\lambda}|1\rangle. Poiché vogliamo implementare U1=e2πi131U|1\rangle = e^{2\pi i \tfrac{1}{3}}|1\rangle, dobbiamo impostare λ=2π3\lambda = \tfrac{2 \pi}{3}.

##your code goes here##

Soluzione dell'esercizio: θ=1/3\theta = 1/3

# Create and set up circuit
qpe = QuantumCircuit(4, 3)

# Apply H-Gates to counting qubits:
for qubit in range(3):
qpe.h(qubit)

# Prepare our eigenstate |psi>:
qpe.x(3)

# Do the controlled-U operations:
angle = 2 * pi / 3
repetitions = 1
for counting_qubit in range(3):
for i in range(repetitions):
qpe.cp(angle, counting_qubit, 3)
repetitions *= 2

# Do the inverse QFT:
qpe.append(QFT(3, inverse=True), [0, 1, 2])

for n in range(3):
qpe.measure(n, n)

qpe.draw(output="mpl")

Output of the previous code cell

aer_sim = AerSimulator()
shots = 4096

pm = generate_preset_pass_manager(backend=aer_sim, optimization_level=1)
t_qpe = pm.run(qpe)

sampler = Sampler(mode=aer_sim)
job = sampler.run([t_qpe], shots=shots)
result = job.result()
answer = result[0].data.c.get_counts()

plot_histogram(answer)

Output of the previous code cell

4. Esecuzione con la primitiva Sampler di Qiskit Runtime

Eseguiremo la QPE usando un dispositivo quantistico reale e seguiremo i 4 passi dei pattern Qiskit.

  1. Mappa il problema in un formato nativo quantistico
  2. Ottimizza i circuiti
  3. Esegui il circuito target
  4. Post-elabora i risultati
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import Sampler
# Loading your IBM Quantum® account(s)

service = QiskitRuntimeService()

4.1 Passo 1: Mappa il problema in circuiti e operatori quantistici

qpe = QuantumCircuit(4, 3)
qpe.x(3)
for qubit in range(3):
qpe.h(qubit)
repetitions = 1
for counting_qubit in range(3):
for i in range(repetitions):
qpe.cp(pi / 4, counting_qubit, 3) # This is C-U
repetitions *= 2
qpe.append(QFT(3, inverse=True), [0, 1, 2])
for n in range(3):
qpe.measure(n, n)
qpe.draw(output="mpl")

Output of the previous code cell

backend = service.least_busy(simulator=False, operational=True, min_num_qubits=4)

print(backend)
<IBMBackend('ibm_strasbourg')>

4.2 Passo 2: Ottimizza per l'hardware target

# Transpile the circuit into basis gates executable on the hardware
pm = generate_preset_pass_manager(backend=backend, optimization_level=2)
qc_compiled = pm.run(qpe)

qc_compiled.draw("mpl", idle_wires=False)

Output of the previous code cell

4.3 Passo 3: Esegui sull'hardware target

real_sampler = Sampler(mode=backend)
job = real_sampler.run([qc_compiled], shots=1024)
job_id = job.job_id()
print("job id:", job_id)
job id: d13p4zb5z6q00087ag00
job = service.job(job_id)  # Input your job-id between the quotations
job.status()
'DONE'
result_real = job.result()
print(result_real)
PrimitiveResult([SamplerPubResult(data=DataBin(c=BitArray(<shape=(), num_shots=1024, num_bits=3>)), metadata={'circuit_metadata': {}})], metadata={'execution': {'execution_spans': ExecutionSpans([DoubleSliceSpan(<start='2025-06-09 22:39:00', stop='2025-06-09 22:39:00', size=1024>)])}, 'version': 2})

4.4 Passo 4: Post-elabora i risultati

from qiskit.visualization import plot_histogram

plot_histogram(result_real[0].data.c.get_counts())

Output of the previous code cell

# See the version of Qiskit
import qiskit

qiskit.__version__
'2.0.2'