Diagonalizzazione quantistica di Krylov
In questa lezione sulla diagonalizzazione quantistica di Krylov (KQD) risponderemo a quanto segue:
- Che cos'è il metodo di Krylov, in generale?
- Perché il metodo di Krylov funziona e a quali condizioni?
- Che ruolo ha il calcolo quantistico?
La parte quantistica dei calcoli si basa in gran parte sul lavoro svolto in Ref [1].
Il video che segue offre una panoramica dei metodi di Krylov nell'informatica classica, ne motiva l'uso e spiega come l'informatica quantistica possa svolgere un ruolo in questo flusso di lavoro. Il testo successivo offre maggiori dettagli e implementa un metodo di Krylov sia in modo classico, sia utilizzando un computer quantistico.
1. Introduzione ai metodi di Krylov
Un metodo del sottospazio di Krylov può riferirsi a uno qualsiasi dei diversi metodi costruiti attorno al cosiddetto sottospazio di Krylov. Una rassegna completa di questi argomenti va al di là dello scopo di questa lezione, ma i Rif. [2-4] possono fornire un quadro sostanzialmente più completo. In questa sede ci concentreremo su cosa sia un sottospazio di Krylov, su come e perché sia utile per risolvere problemi di autovalori e infine su come possa essere implementato su un computer quantistico.
Definizione: Data una matrice simmetrica, positiva e semidefinita , lo spazio di Krylov di ordine è lo spazio spaziato dai vettori ottenuti moltiplicando le potenze superiori di una matrice , fino a , con un vettore di riferimento .
Sebbene i vettori di cui sopra coprano quello che chiamiamo un sottospazio di Krylov, non c'è motivo di pensare che siano ortogonali. Spesso si utilizza un processo iterativo di ortonormalizzazione simile all' ortogonalizzazione di Gram-Schmidt. In questo caso il processo è leggermente diverso, poiché ogni nuovo vettore viene reso ortogonale agli altri man mano che viene generato. In questo contesto si parla di iterazione di Arnoldi. Partendo dal vettore iniziale , si genera il vettore successivo , e si assicura che questo secondo vettore sia ortogonale al primo sottraendo la sua proiezione su . Ovvero
È ormai facile capire che , poiché
Facciamo lo stesso per il vettore successivo, assicurandoci che sia ortogonale ai due precedenti:
Se si ripete questo procedimento per tutti i vettori , si ottiene una base ortonormale completa per uno spazio di Krylov. Si noti che il processo di ortogonalizzazione in questo caso darà come risultato zero una volta , poiché vettore ortogonale copre necessariamente l'intero spazio. Il processo darà anche zero se un vettore è un autovettore di , poiché tutti i vettori successivi saranno multipli di quel vettore.
1.1 Un esempio semplice: Krylov a mano
Esaminiamo la generazione di un sottospazio di Krylov su una matrice banalmente piccola, in modo da vedere il processo. Partiamo da una matrice iniziale di nostro interesse:
Per questo piccolo esempio, possiamo determinare facilmente gli autovettori e gli autovalori anche a mano. Mostriamo qui la soluzione numerica.
# One might use linalg.eigh here, but later matrices may not be Hermitian. So we use
# linalg.eig in this lesson.
import numpy as np
A = np.array([[4, -1, 0], [-1, 4, -1], [0, -1, 4]])
eigenvalues, eigenvectors = np.linalg.eig(A)
print("The eigenvalues are ", eigenvalues)
print("The eigenvectors are ", eigenvectors)Output:
The eigenvalues are [2.58578644 4. 5.41421356]
The eigenvectors are [[ 5.00000000e-01 -7.07106781e-01 5.00000000e-01]
[ 7.07106781e-01 1.37464400e-16 -7.07106781e-01]
[ 5.00000000e-01 7.07106781e-01 5.00000000e-01]]
Li registriamo qui per un successivo confronto:
Vorremmo studiare come questo processo funziona (o fallisce) quando aumentiamo la dimensione del nostro sottospazio di Krylov, . A tal fine, applicheremo questo processo:
- Generare un sottospazio dello spazio vettoriale completo partendo da un vettore scelto a caso (chiamarlo se è già normalizzato, come sopra).
- Proiettare la matrice completa su quel sottospazio e trovare gli autovalori della matrice proiettata .
- Aumentare la dimensione del sottospazio generando più vettori, assicurandosi che siano ortonormali, utilizzando un processo simile all'ortogonalizzazione di Gram-Schmidt.
- Proiettare sul sottospazio più grande e trovare gli autovalori della matrice risultante, .
- Ripetere l'operazione finché gli autovalori non convergono (o, in questo caso giocattolo, finché non si sono generati vettori che coprono l'intero spazio vettoriale della matrice originale ).
Una normale implementazione del metodo di Krylov non dovrebbe risolvere il problema degli autovalori per la matrice proiettata su ogni sottospazio di Krylov man mano che viene costruito. Si può costruire il sottospazio della dimensione desiderata, proiettare la matrice su tale sottospazio e diagonalizzare la matrice proiettata. La proiezione e la diagonalizzazione in ogni dimensione del sottospazio vengono effettuate solo per verificare la convergenza.
i dimensionali:
Scegliamo un vettore casuale, ad esempio
Se non è già normalizzato, normalizzarlo.
Ora proiettiamo la nostra matrice sul sottospazio di questo unico vettore:
Questa è la proiezione della matrice sul nostro sottospazio di Krylov quando contiene un solo vettore, . L'autovalore di questa matrice è banalmente 4. Si tratta di una stima di ordine zerotico degli autovalori (in questo caso uno solo) di . Sebbene si tratti di una stima insufficiente, è l'ordine di grandezza corretto.
i dimensionali:
Ora generiamo il vettore successivo nel nostro sottospazio attraverso l'operazione con sul vettore precedente:
Ora sottraiamo la proiezione di questo vettore sul vettore precedente per garantire l'ortogonalità.
Se non è già normalizzato, normalizzarlo. In questo caso, il vettore era già normalizzato, quindi
Ora proiettiamo la nostra matrice A sul sottospazio di questi due vettori:
Rimane il problema di determinare gli autovalori di questa matrice. Ma questa matrice è leggermente più piccola della matrice completa. Nei problemi che coinvolgono matrici molto grandi, lavorare con questo sottospazio più piccolo può essere molto vantaggioso.
Sebbene non sia ancora una buona stima, è migliore di quella di ordine zero. Lo faremo per un'altra iterazione, per assicurarci che il processo sia chiaro. In questo modo, però, si vanifica l'utilità del metodo, poiché si finisce per diagonalizzare una matrice 3x3 nell'iterazione successiva, il che significa che non si è risparmiato né tempo né potenza di calcolo.
i dimensionali:
Ora generiamo il vettore successivo nel nostro sottospazio attraverso l'operazione con A sul vettore precedente:
Ora sottraiamo la proiezione di questo vettore su entrambi i vettori precedenti per garantire l'ortogonalità.
Se non è già normalizzato, normalizzarlo. In questo caso, il vettore era già normalizzato, quindi
Ora proiettiamo la nostra matrice sul sottospazio di questi vettori:
Determiniamo ora gli autovalori:
Questi autovalori sono esattamente gli autovalori della matrice originale . Questo deve essere il caso, poiché abbiamo espanso il nostro sottospazio di Krylov per coprire l'intero spazio vettoriale della matrice originale .
In questo esempio, il metodo di Krylov non sembra particolarmente più semplice della diagonalizzazione diretta. Infatti, come vedremo nelle sezioni successive, il metodo di Krylov è vantaggioso solo al di sopra di una certa dimensione della matrice; ciò ha lo scopo di aiutarci a risolvere problemi di autovalori/autovettori di matrici estremamente grandi.
Questo è l'unico esempio che mostreremo lavorato "a mano", ma la sezione 2 mostra esempi computazionali.
Chiarimento dei termini
Un'idea sbagliata comune è che esista un solo sottospazio di Krylov per un determinato problema. Ma naturalmente, poiché esistono molti vettori iniziali a cui applicare la nostra matrice, esistono molti possibili sottospazi di Krylov. Useremo l'espressione "il sottospazio di Krylov" solo per riferirci a uno specifico sottospazio di Krylov già definito per un esempio specifico. Per gli approcci generali alla risoluzione dei problemi ci riferiremo a "un sottospazio di Krylov". Un'ultima precisazione: è valido il riferimento a uno " spazio di Krylov". Spesso viene chiamato " sottospazio di Krylov" per il suo utilizzo nel contesto della proiezione di matrici da uno spazio iniziale in un sottospazio. In linea con questo contesto, in questa sede ci riferiremo a questo spazio come a un sottospazio.
Verifica la tua comprensione
Spiegare perché non è (a) utile e (b) possibile estendere la dimensione del sottospazio di Krylov oltre la dimensione della matrice di interesse.
(a) Poiché ortonormalizziamo i vettori man mano che li generiamo, un insieme di i di tali vettori formerà una base completa, il che significa che una combinazione lineare di essi può essere utilizzata per ricreare qualsiasi vettore dello spazio.
(b) Il processo di ortogonalizzazione consiste nel sottrarre la proiezione di un nuovo vettore su tutti i vettori precedenti. Se tutti i vettori precedenti generano l'intero spazio vettoriale, sottraendo le proiezioni sul sottospazio completo otterremo sempre un vettore nullo.
Supponiamo che un collega ricercatore stia illustrando il metodo di Krylov applicato a una piccola matrice di esempio. C'è qualcosa che non va nella loro scelta della matrice e del vettore iniziale ?
e
Il vostro collega ha accidentalmente scelto un autovettore per il suo vettore iniziale. Agendo con la matrice sul vettore iniziale, si restituirà semplicemente lo stesso vettore, scalato dell'autovalore. Questo non genererà un sottospazio di dimensione crescente. Consigliare al collega di scegliere un vettore iniziale diverso, assicurandosi che non sia un autovettore.
Applicare il metodo di Krylov alla matrice data, scegliendo un nuovo vettore iniziale adeguato. Indica le stime dell'autovalore minimo al 0° e al 1° ordine del tuo sottospazio di Krylov.
Ci sono molte risposte possibili a seconda della scelta del vettore iniziale. Sceglieremo:
Per ottenere applichiamo una volta a , quindi rendiamo ortogonale a .
All'ordine 0, la proiezione sul nostro sottospazio di Krylov è
Al 1° ordine, la proiezione su questo sottospazio di Krylov è
Questa operazione può essere eseguita a mano, ma è più facile da realizzare utilizzando numpy:
import numpy as np vstar = np.array([[1/np.sqrt(3),1/np.sqrt(3),1/np.sqrt(3)],[-1/np.sqrt(6),np.sqrt(2/3),-1/np.sqrt(6)]] ) A = np.array([[1, 1, 0], [1, 1, 1], [0, 1, 1]]) v = np.array([[1/np.sqrt(3),-1/np.sqrt(6)],[1/np.sqrt(3),np.sqrt(2/3)],[1/np.sqrt(3),-1/np.sqrt(6)]]) proj = vstar@A@v print(proj) eigenvalues, eigenvectors = np.linalg.eig(proj) print("The eigenvalues are ", eigenvalues) print("The eigenvectors are ", eigenvectors)output:
[[ 2.33333333 0.47140452] [ 0.47140452 -0.33333333]] The eigenvalues are [ 2.41421356 -0.41421356] The eigenvectors are [[ 0.98559856 -0.16910198] [ 0.16910198 0.98559856]]La stima dell'autovalore minimo è -0.414.
1.2 Tipi di metodi di Krylov
i "metodi dei sottospazi di Krylov" possono riferirsi a una qualsiasi delle numerose tecniche iterative utilizzate per risolvere grandi sistemi lineari e problemi di autovalori. Ciò che hanno in comune è che costruiscono una soluzione approssimativa da un sottospazio di Krylov
dove è l'ipotesi iniziale (cfr. Rif. [5] ). Si differenziano per il modo in cui scelgono la migliore approssimazione da questo sottospazio, bilanciando fattori come la velocità di convergenza, l'uso della memoria e il costo computazionale complessivo. L'obiettivo di questa lezione è quello di sfruttare il calcolo quantistico nel contesto dei metodi dei sottospazi di Krylov; una discussione esaustiva di questi metodi esula dal suo scopo. Le brevi definizioni riportate di seguito servono solo come contesto e includono alcuni riferimenti per approfondire questi metodi.
Il metodo del gradiente coniugato (CG) : Questo metodo viene utilizzato per risolvere sistemi lineari simmetrici e definiti positivamente [6]. Minimizza la norma A dell'errore a ogni iterazione, il che lo rende particolarmente efficace per i sistemi derivanti da PDE ellittiche discretizzate [7]. Nella prossima sezione utilizzeremo questo approccio per motivare il motivo per cui un sottospazio di Krylov sarebbe un sottospazio efficace in cui sondare le soluzioni migliori dei sistemi lineari.
Il metodo del residuo minimo generalizzato (GMRES) : È progettato per risolvere sistemi lineari generali non simmetrici. Minimizza la norma residua su uno spazio di Krylov a ogni iterazione, il che lo rende robusto ma potenzialmente impegnativo in termini di memoria per sistemi di grandi dimensioni [7].
Il metodo del residuo minimo (MINRES) : Questo metodo viene utilizzato per risolvere sistemi lineari indefiniti simmetrici. È simile a GMRES, ma sfrutta la simmetria della matrice per ridurre il costo computazionale [8].
Altri approcci degni di nota includono il metodo di ortogonalizzazione completa (FOM), che è strettamente correlato al metodo di Arnoldi per i problemi agli autovalori, il metodo del gradiente biconiugato ( BiCG ) e il metodo di riduzione dimensionale indotta (IDR).
1.3 Perché funziona il metodo del sottospazio di Krylov
In questa sede motiveremo che il metodo del sottospazio di Krylov dovrebbe essere un modo efficiente per approssimare gli autovalori delle matrici attraverso il perfezionamento iterativo delle approssimazioni degli autovettori, attraverso la lente della discesa più ripida. Si sosterrà che, data un'ipotesi iniziale di stato fondamentale, lo spazio delle correzioni successive a tale ipotesi iniziale che produce la convergenza più rapida è un sottospazio di Krylov. Ci fermiamo a una prova rigorosa del comportamento di convergenza.
Supponiamo che la matrice di interesse sia simmetrica e definita positivamente. Ciò rende la nostra argomentazione più pertinente al metodo CG di cui sopra. Non facciamo alcuna ipotesi sulla sparsità, né sosteniamo che debba essere un'Hermitiana (che deve essere tale se è un'Hamiltoniana).
In genere si vuole risolvere un problema della forma
Si potrebbe immaginare che dove è una costante, come in un problema di autovalori. Ma il nostro problema rimane più generale per ora.
Partiamo da un vettore che rappresenta una soluzione approssimativa. Sebbene vi siano dei parallelismi tra questa ipotesi e nella sezione 1.1, in questa sede non li stiamo prendendo in considerazione. A nostro avviso, l' e presenta un errore, che chiamiamo “ ”:
Definiamo inoltre l' residuale:
Qui usiamo la maiuscola per distinguere il residuo dalla dimensione del nostro sottospazio di Krylov .
Si vuole ora effettuare un passo di correzione della forma
che speriamo migliori la nostra approssimazione. Qui è un vettore ancora da determinare. Sia l'errore dopo la correzione. Allora
Ci interessa sapere come si comporta il nostro errore quando viene trasformato dalla nostra matrice. Calcoliamo quindi la -norm dell'errore. Cioè
dove abbiamo sfruttato la simmetria di e anche il fatto che . Qui è una costante indipendente da . Come menzionato nella Sezione 1.2, la norma dell'errore non è l'unica grandezza che potremmo scegliere di minimizzare, ma è una buona scelta. Vogliamo vedere come questa grandezza vari in funzione della scelta dei vettori di correzione . Definiamo quindi la funzione ponendo
è solo l'errore in funzione della correzione misurata nella norma . Pertanto, vogliamo scegliere in modo che sia il più piccolo possibile. A questo scopo, calcoliamo il gradiente di . Utilizzando la simmetria di abbiamo
Il gradiente indica la direzione di massima crescita; ciò significa che la sua opposta ci indica la direzione in cui la funzione diminuisce maggiormente: la direzione di massima diminuzione. Partendo dalla nostra ipotesi iniziale , dove , si ha che . Pertanto, la funzione diminuisce maggiormente nella direzione del residuo . Quindi la nostra scelta iniziale trarrebbe il massimo beneficio dall’aggiunta del vettore per un certo scalare .
Nella fase successiva, scegliamo nuovamente un vettore e aggiungiamo il suo valore all'approssimazione corrente. Utilizzando lo stesso ragionamento di prima, scegliamo per un qualche scalare . Continuiamo in questo modo, in modo tale che l'iterazione del nostro vettore sia
In modo equivalente, vogliamo costruire lo spazio da cui scegliere le nostre stime migliorate aggiungendo , , e così via, in ordine. Il vettore stimato da si trova in
Ora, utilizzando la relazione che
vediamo che
In altre parole, lo spazio che costruiamo e che si avvicina nel modo più efficiente alla soluzione corretta è esattamente lo spazio costruito mediante l’applicazione successiva della matrice su . Un sottospazio di Krylov è lo spazio generato dai vettori delle direzioni successive di discesa più ripida.
Infine, ribadiamo che non abbiamo fatto alcuna affermazione numerica sulla scalabilità di questo approccio, né abbiamo discusso i vantaggi comparativi per le matrici rade. Questo ha solo lo scopo di motivare l'uso dei metodi dei sottospazi di Krylov e di aggiungere un senso intuitivo a questi metodi. Ora esploreremo il comportamento di questi metodi dal punto di vista numerico.
Verifica la tua comprensione
Nel flusso di lavoro precedente, abbiamo proposto di minimizzare la -norm dell'errore. Quali altre grandezze si potrebbero minimizzare nella ricerca dello stato fondamentale e del suo autovalore?
Si potrebbe immaginare di utilizzare il vettore dei residui invece della -norm dell'errore. Ci possono essere casi in cui è utile considerare il vettore di errore stesso.
2. Metodi di Krylov nel calcolo classico
In questa sezione implementiamo le iterazioni di Arnoldi dal punto di vista computazionale, in modo da poter sfruttare un sottospazio di Krylov nella risoluzione di problemi di autovalori. Applicheremo questo metodo dapprima a un esempio su piccola scala, quindi esamineremo come il tempo di calcolo scala all'aumentare delle dimensioni della matrice di interesse. L'idea chiave è che la generazione dei vettori che coprono lo spazio di Krylov contribuirà notevolmente al tempo di calcolo totale richiesto. La memoria richiesta varia a seconda dei metodi di Krylov. Ma i vincoli di memoria possono limitare l'uso dei metodi di Krylov tradizionali.
2.1 Semplice esempio su piccola scala
Nel processo di creazione di un sottospazio di Krylov, dovremo ortonormalizzare i vettori nel nostro sottospazio. Definiamo una funzione che prenda un vettore stabilito dal nostro sottospazio vknown (che non si presume sia normalizzato) e un vettore candidato da aggiungere al nostro sottospazio vnext e che renda vnext ortogonale a vknown e normalizzato. Definiamo inoltre una funzione che esegue questo processo per tutti i vettori stabiliti nel nostro sottospazio di Krylov per garantire un insieme completamente ortonormale.
# vknown is some established vector in our subspace. vnext is one we wish to add,
# which must be orthogonal to vknown.
def orthog_pair(vknown, vnext):
vknown = vknown / np.sqrt(vknown.T @ vknown)
diffvec = vknown.T @ vnext * vknown
vnext = vnext - diffvec
return vnext
# v is the candidate vector to be added to our subspace. s is the existing subspace.
def orthoset(v, s):
v = v / np.sqrt(v.T @ v)
temp = v
for i in range(len(s)):
temp = orthog_pair(s[i], temp)
v = temp / np.sqrt(temp.T @ temp)
return vDefiniamo ora una funzione che costruisce un sottospazio di Krylov iterativamente sempre più grande, fino a quando lo spazio dei vettori di Krylov si estende all'intero spazio della matrice originale. Questo ci permetterà di vedere quanto gli autovalori ottenuti con il nostro metodo del sottospazio di Krylov corrispondano ai valori esatti, in funzione della dimensione del sottospazio di Krylov. È importante notare che la nostra funzione krylov_full_build restituisce i vettori di Krylov, gli hamiltoniani proiettati, gli autovalori e il tempo richiesto.
# Necessary imports and definitions to track time in microseconds
import time
def time_mus():
return int(time.time() * 1000000)
# This function constructs a Krylov subspace that spans the whole space of the original matrix.
# Input:
# v0 : initial vector
# matrix : original matrix to be diagonalized
# Output:
# ks : Krylov vectors
# Hs : projected Hamiltonians
# eigs : eigenvalues
# k_tot_times : time required for the operation
def krylov_full_build(v0, matrix):
t0 = time_mus()
b = v0 / np.sqrt(v0 @ v0.T)
A = matrix
ks = []
ks.append(b)
Hs = []
eigs = []
Hs.append(b.T @ A @ b)
eigs.append(np.array([b.T @ A @ b]))
k_tot_times = []
for j in range(len(A) - 1):
vec = A @ ks[j].T
ortho = orthoset(vec, ks)
ks.append(ortho)
ksarray = np.array(ks)
Hs.append(ksarray @ A @ ksarray.T)
eigs.append(np.linalg.eig(Hs[j + 1]).eigenvalues)
k_tot_times.append(time_mus() - t0)
# Return the Krylov vectors, the projected Hamiltonians, the eigenvalues,
# and the total time required.
return (ks, Hs, eigs, k_tot_times)Lo proveremo su una matrice ancora piuttosto piccola, ma più grande di quella che si potrebbe fare a mano.
# Define our small test matrix
test_matrix = np.array(
[
[4, -1, 0, 1, 0],
[-1, 4, -1, 2, 1],
[0, -1, 4, 3, 3],
[1, 2, 3, 4, 0],
[0, 1, 3, 0, 4],
]
)
# Give the test matrix and an initial guess as arguments in the function defined above.
# Calculate outputs.
test_ks, test_Hs, test_eigs, text_k_tot_times = krylov_full_build(
np.array([0.5, 0.5, 0, 0.5, 0.5]), test_matrix
)Possiamo verificare le nostre funzioni assicurandoci che nell'ultimo passo (quando lo spazio di Krylov è lo spazio vettoriale completo della matrice originale) gli autovalori del metodo di Krylov corrispondano esattamente a quelli della diagonalizzazione numerica esatta:
print(np.linalg.eig(test_matrix).eigenvalues)
print(test_eigs[len(test_matrix) - 1])Output:
[-1.36956923 8.43756009 2.9040308 5.34436028 4.68361806]
[-1.36956923 8.43756009 2.9040308 4.68361806 5.34436028]
È stato un successo. Naturalmente, ciò che conta davvero è la bontà della nostra approssimazione in funzione della dimensione del nostro sottospazio di Krylov. Poiché spesso ci preoccupiamo di trovare stati fondamentali e altri autovalori minimi (e per altre ragioni più algebriche spiegate più avanti), esaminiamo la nostra stima dell'autovalore più basso in funzione della dimensione del sottospazio di Krylov. Cioè
def errors(matrix, krylov_eigs):
targ_min = min(np.linalg.eig(matrix).eigenvalues)
err = []
for i in range(len(matrix)):
err.append(min(krylov_eigs[i]) - targ_min)
return errimport matplotlib.pyplot as plt
krylov_error = errors(test_matrix, test_eigs)
plt.plot(krylov_error)
plt.axhline(y=0, color="red", linestyle="--") # Add dashed red line at y=0
plt.xlabel("Order of Krylov subspace") # Add x-axis label
plt.ylabel("Error in minimum eigenvalue") # Add y-axis label
plt.show()Output:
Si osserva che l’autovalore minimo viene raggiunto con discreta precisione una volta che il sottospazio di Krylov ha raggiunto una dimensione pari a ed è perfetto quando .
2.2 Scalatura temporale con dimensione della matrice
Per convincerci che il metodo di Krylov può essere vantaggioso rispetto agli eigensolver numerici esatti, procediamo nel modo seguente:
- Costruire matrici casuali (non rade, non l'applicazione ideale per KQD)
- Determinare gli autovalori con due metodi: direttamente con NumPy e con un sottospazio di Krylov.
- Scegliamo un limite per la precisione degli autovalori prima di accettare le stime di Krylov.
- Confrontate il tempo di parete richiesto per risolvere questi due modi.
Avvertenze: Come vedremo in dettaglio più avanti, la diagonalizzazione quantistica di Krylov si applica meglio agli operatori le cui rappresentazioni matriciali sono rade e/o possono essere scritte utilizzando un piccolo numero di gruppi di operatori di Pauli commutativi. Le matrici casuali che stiamo utilizzando non rientrano in questa descrizione. Questi sono utili solo per sondare la scala alla quale i metodi di Krylov classici potrebbero essere utili. In secondo luogo, utilizzando il metodo di Krylov, calcoleremo gli autovalori utilizzando molti sottospazi di Krylov di dimensioni diverse. Riporteremo il tempo necessario per ottenere il sottospazio di Krylov di dimensione minima che raggiunge l'accuratezza richiesta per l'autovalore dello stato fondamentale. Anche in questo caso, si tratta di una soluzione un po' diversa da quella di un problema intrattabile per gli eigensolver esatti, poiché stiamo usando la soluzione esatta per valutare la dimensione necessaria.
Iniziamo generando il nostro insieme di matrici casuali.
import numpy as np
# Set the random seed
np.random.seed(42)
# how many random matrices will we make
num_matrix = 200
matrices = []
for m in range(1, num_matrix):
matrices.append(np.random.rand(m, m))Ora diagonalizziamo ogni matrice direttamente, utilizzando numpy. Per un successivo confronto, calcoliamo il tempo necessario per la diagonalizzazione.
matrix_numpy_times = []
matrix_numpy_eigs = []
for mm in range(num_matrix - 1):
t0 = time_mus()
matrix_numpy_eigs.append(min(np.linalg.eig(matrices[mm]).eigenvalues))
matrix_numpy_times.append(time_mus() - t0)
plt.plot(matrix_numpy_times)
plt.xlabel("Dimension of matrix") # Add x-axis label
plt.ylabel("Time to diagonalize (microsec)") # Add y-axis label
plt.show()Output:
Si noti che nell'immagine precedente, il tempo anomalo intorno a una dimensione di 125 può essere dovuto alla natura casuale delle matrici o all'implementazione sul processore classico utilizzato, ma non è riproducibile. Eseguendo nuovamente il codice si otterrà un profilo diverso con diversi picchi anomali.
Ora per ogni matrice costruiremo un sottospazio di Krylov e calcoleremo gli autovalori per gradi. A ogni passo, controlleremo se l'autovalore più basso è stato ottenuto entro l'errore assoluto specificato. Il sottospazio che per primo ci fornisce autovalori entro l'errore specificato è il sottospazio per il quale registreremo i tempi di calcolo. L'esecuzione di questa cella può richiedere alcuni minuti, a seconda della velocità del processore. È possibile saltare la valutazione o ridurre la dimensione massima delle matrici diagonalizzate. È sufficiente guardare i risultati precalcolati.
# Choose the absolute error you can tolerate, and make a list for tracking the Krylov subspace size
# at which that error is achieved.
abserr = 0.05
accept_subspace_size = []
# Lists to store total time spent on the Krylov method, and the subset of that time spent on
# diagonalizing the projected matrix.
matrix_krylov_tot_times = []
matrix_krylov_dim = []
# Step through all our random matrices
for mm in range(0, num_matrix - 1):
test_ks, test_Hs, test_eigs, test_k_tot_times = krylov_full_build(
np.ones(len(matrices[mm])), matrices[mm]
)
# We have not yet found a Krylov subspace that produces our minimum eigenvalue to
# within the required error.
found = 0
for j in range(0, len(matrices[mm]) - 1):
# If we still haven't found the desired subspace...
if found == 0:
# ...but if this one satisfies the requirement, then record everything
if (
abs((min(test_eigs[j]) - matrix_numpy_eigs[mm]) / matrix_numpy_eigs[mm])
< abserr
):
accept_subspace_size.append(j)
matrix_krylov_tot_times.append(test_k_tot_times[j])
matrix_krylov_dim.append(mm)
found = 1Tracciamo per confronto i tempi ottenuti con questi due metodi:
plt.plot(matrix_numpy_times, color="blue")
plt.plot(matrix_krylov_dim, matrix_krylov_tot_times, color="green")
plt.xlabel("Dimension of matrix") # Add x-axis label
plt.ylabel("Time to diagonalize (microsec)") # Add y-axis label
plt.show()Output:
Questi sono i tempi effettivi richiesti, ma ai fini della discussione, smussiamo queste curve calcolando la media su alcuni punti adiacenti / dimensioni della matrice. Questo viene fatto di seguito:
smooth_numpy_times = []
smooth_krylov_times = []
# Choose the number of adjacent points over which to average forward;
# the same will be used backward.
smooth_steps = 10
# We will do this smoothing for all points/matrix dimensions
for i in range(len(matrix_krylov_tot_times)):
# Ensure we don't exceed the boundaries of our lists
start = max(0, i - smooth_steps)
end = min(len(matrix_krylov_tot_times) - 1, i + smooth_steps)
# Dummy variables for accumulating an average over adjacent points. This is done for both Krylov
# and the NumPy calculations.
smooth_count = 0
smooth_numpy_sum = 0
smooth_krylov_sum = 0
for j in range(start, end):
smooth_numpy_sum = smooth_numpy_sum + matrix_numpy_times[j]
smooth_krylov_sum = smooth_krylov_sum + matrix_krylov_tot_times[j]
smooth_count = smooth_count + 1
# Appending the averaged adjacent values to our new smooth lists
smooth_numpy_times.append(smooth_numpy_sum / smooth_count)
smooth_krylov_times.append(smooth_krylov_sum / smooth_count)plt.plot(smooth_numpy_times, color="blue")
plt.plot(smooth_krylov_times, color="green")
plt.xlabel("Dimension of matrix") # Add x-axis label
plt.ylabel("Time to diagonalize (smoothed, microsec)") # Add y-axis label
plt.show()Output:
Si noti che il tempo richiesto per la costruzione di un sottospazio di Krylov inizialmente supera il tempo richiesto per la diagonalizzazione completa di numpy. Ma quando le dimensioni della matrice crescono, il metodo di Krylov diventa vantaggioso. Ciò è vero anche se riduciamo l'errore accettabile, ma il vantaggio si manifesta con una matrice di dimensioni maggiori. Vale la pena di analizzare questo aspetto.
La complessità temporale della diagonalizzazione numerica è (con qualche variazione tra gli algoritmi). Anche la complessità temporale della generazione di una base ortonormale di vettori è . Quindi il vantaggio del metodo di Krylov non è legato all'uso di basi ortonormali, ma all'uso di una particolare base ortonormale che seleziona efficacemente gli autovalori di interesse. Lo abbiamo già visto nello schizzo di una prova nella prima sezione di questa lezione, e questo è fondamentale per le garanzie di convergenza dei metodi di Krylov.
Ripercorriamo i progressi compiuti finora:
- Per matrici molto grandi, il metodo del sottospazio di Krylov può produrre autovalori approssimati entro le tolleranze richieste, più velocemente degli algoritmi di diagonalizzazione tradizionali.
- Per matrici così grandi, la generazione di un sottospazio di Krylov è la parte più lunga del metodo dei sottospazi di Krylov.
- Pertanto, un modo efficiente di generare un sottospazio di Krylov sarebbe molto utile. È qui che entra in gioco il computer quantistico.
Verifica la tua comprensione
Si veda il grafico livellato dei tempi di diagonalizzazione in funzione della dimensione della matrice riportato sopra.
(a) In base a questo grafico, a partire da quale dimensione approssimativa della matrice il metodo di Krylov ha iniziato a essere più veloce?
(b) Quali aspetti del calcolo potrebbero modificare la dimensione a partire dalla quale il metodo di Krylov diventa più veloce?
(a) I risultati possono variare se si esegue nuovamente il calcolo, ma il metodo di Krylov diventa più veloce a partire da una dimensione di circa 80-85.
(b) Le risposte possibili sono molte. Alcuni fattori importanti sono il livello di precisione richiesto e la sparsità delle matrici da diagonalizzare.
3. Krylov tramite evoluzione temporale
Tutto ciò che abbiamo descritto finora può essere fatto in modo classico. Come e quando potremmo usare un computer quantistico? Per matrici molto grandi, il metodo di Krylov può richiedere lunghi tempi di calcolo e grandi quantità di memoria. Il tempo richiesto per l'operazione matriciale di su scala come nel caso peggiore. Anche la moltiplicazione di matrici rade su un vettore (il caso tipico dei solutori classici di tipo Krylov) ha una complessità temporale che scala come . Questo viene fatto per ogni vettore che vogliamo nel nostro sottospazio. La dimensione del sottospazio di solito non è una frazione significativa di , e spesso scala come . Quindi, nel caso peggiore, la generazione di tutti i vettori scala come . Anche se ci sono altri passaggi, come l'ortogonalizzazione, questa è la scalatura dominante da tenere a mente.
L'informatica quantistica ci permette di cambiare gli attributi del problema che determinano la scalabilità del tempo e delle risorse necessarie. Invece della dipendenza dalla dimensione della matrice in generale, vedremo cose come il numero di scatti e il numero di termini di Pauli non commutativi che compongono l'Hamiltoniana. Vediamo come funziona.
3.1 Evoluzione temporale
Ricordiamo che l'operatore che fa evolvere nel tempo uno stato quantistico è (ed è molto comune, soprattutto nell'informatica quantistica, eliminare dalla notazione). Un modo per comprendere e persino realizzare una funzione esponenziale di un operatore è osservare la sua espansione in serie di Taylor. Si noti che questa operazione che agisce su un vettore iniziale produce una somma di termini con potenze crescenti di applicate allo stato iniziale. Sembra che possiamo creare il nostro sottospazio di Krylov facendo evolvere nel tempo il nostro stato di ipotesi iniziale!
Il problema sta nel realizzare l'evoluzione temporale su un vero computer quantistico. Molti dei termini dell'Hamiltoniana non si scambiano tra loro. Quindi, mentre alcuni semplici operatori esponenziali come corrispondono a circuiti semplici, gli hamiltoniani generali non lo sono. E poiché contengono termini non commutativi, non possiamo semplicemente decomporre l'esponenziale in un prodotto di termini semplici, come possiamo fare con i numeri.
Non si tratta quindi di un processo banale, ma è un processo ben studiato nell'informatica quantistica. Realizziamo l'evoluzione del tempo sui computer quantistici utilizzando un processo chiamato trotterizzazione, che di per sé è un argomento molto ricco [10]. Ma a un livello molto alto, spezzando l'evoluzione temporale in passi molto piccoli, ad esempio passi di dimensione , limitiamo gli effetti della non commutatività dei termini.
dove .
Chiamiamo un sottospazio di Krylov di ordine r che abbiamo generato nel contesto classico usando direttamente potenze di H un "sottospazio di Krylov di potenza".
Ora generiamo uno spazio simile usando l'operatore unitario di evoluzione temporale ; ci riferiremo a questo come allo "spazio unitario di Krylov" . Il sottospazio di Krylov di potenza che usiamo classicamente non può essere generato direttamente su un computer quantistico, poiché non è un operatore unitario. È possibile dimostrare che l'utilizzo del sottospazio unitario di Krylov fornisce garanzie di convergenza simili a quelle del sottospazio di Krylov di potenza, ossia che l'errore di stato a terra converge in modo efficiente finché lo stato iniziale ha una sovrapposizione con lo stato a terra vero che non è esponenzialmente svanente e finché c'è un intervallo sufficiente tra gli autovalori. Per una discussione più precisa sulla convergenza, si veda Ref [1].
Qui le potenze di diventano passi temporali diversi (la potenza di avanza di un tempo ). Possiamo etichettare l'elemento del sottospazio che si evolve nel tempo per il tempo totale come .
Possiamo proiettare la nostra hamiltoniana H sul sottospazio unitario di Krylov, . In altre parole, calcoliamo ogni elemento della matrice di nella base di . Ci riferiremo a questa matrice proiettata come .
3.2 Come implementare su un computer quantistico
Gli elementi della matrice sono dati dai valori di aspettativa , che possono essere stimati utilizzando il computer quantistico. Si tenga presente che può essere scritto come una somma di operatori Pauli su diversi qubit e che non tutti gli operatori Pauli possono essere misurati contemporaneamente. Possiamo ordinare i termini di Pauli in gruppi di termini commutativi e misurarli tutti insieme. Ma potrebbero essere necessari molti gruppi di questo tipo per coprire tutti i termini. Diventa quindi importante il numero di gruppi pendolari distinti in cui i termini possono essere suddivisi, .
Qui indica una stringa di Pauli della forma oppure un insieme di tali stringhe di Pauli che commutano tra loro. Dato che possiamo scrivere come somma di operatori misurabili, le seguenti espressioni per gli elementi di matrice di possono essere realizzate utilizzando l’Estimator primitivo IBM Quantum.
Dove sono i vettori dello spazio unitario di Krylov e sono i multipli del passo temporale scelto. Su un computer quantistico, il calcolo di ciascun elemento della matrice può essere effettuato con qualsiasi algoritmo che consenta di ottenere una sovrapposizione tra stati quantistici. In questa lezione ci concentreremo sul test di Hadamard. Dato che l’ e ha dimensione , l’Hamiltoniano proiettato nel sottospazio avrà dimensione . Se è sufficientemente piccolo (in genere è sufficiente per ottenere la convergenza delle stime degli autovalori), possiamo quindi facilmente diagonalizzare l’Hamiltoniano proiettato , in termini classici. Tuttavia, non è possibile diagonalizzare direttamente l' e a causa della non ortogonalità dei vettori dello spazio di Krylov. Dovremo misurare le loro sovrapposizioni e costruire una matrice
Questo ci permette di risolvere il problema degli autovalori in uno spazio non ortogonale (detto anche problema degli autovalori generalizzato)
Si possono quindi ottenere stime degli autovalori e degli autostati di osservando le soluzioni di questo problema generalizzato degli autovalori. Ad esempio, la stima dell'energia dello stato fondamentale si ottiene prendendo l'autovalore più piccolo e lo stato fondamentale dal corrispondente autovettore . I coefficienti in determinano il contributo dei diversi vettori che coprono .
Problema generalizzato degli autovalori
Perché non possiamo semplicemente diagonalizzare ? Poiché contiene le informazioni sulla geometria della base di Krylov (che è non ortogonale in tutti i casi tranne quelli molto speciali), da solo non descrive una proiezione dell'hamiltoniana completa, quindi i suoi autovalori non hanno alcuna relazione particolare con quelli dell'hamiltoniana completa: potrebbero essere qualsiasi valore casuale. La soluzione del problema degli autovalori generalizzati è necessaria per ottenere gli autovalori e gli autovettori approssimati corrispondenti alla proiezione dell'hamiltoniana completa nello spazio di Krylov.
La figura mostra una rappresentazione circuitale del test di Hadamard modificato, un metodo utilizzato per calcolare la sovrapposizione tra diversi stati quantistici. Per ogni elemento della matrice , si esegue un test di Hadamard tra gli stati , . Ciò è evidenziato nella figura dallo schema di colori degli elementi della matrice e dalle corrispondenti operazioni , . Pertanto, per calcolare tutti gli elementi della matrice dell'Hamiltoniana proiettata è necessario un insieme di test di Hadamard per tutte le possibili combinazioni di vettori dello spazio di Krylov. Il filo superiore del circuito del test di Hadamard è un qubit ancilla che viene misurato nella base X o Y; il suo valore di aspettativa determina il valore della sovrapposizione tra gli stati. Il filo inferiore rappresenta tutti i qubit dell'hamiltoniana del sistema. L'operazione prepara il qubit del sistema nello stato controllato dallo stato del qubit ancilla (analogamente per ) e l'operazione rappresenta la decomposizione Pauli dell'hamiltoniana del sistema . L'implementazione di questa operazione su un computer quantistico è illustrata in dettaglio qui di seguito.
4. Diagonalizzazione quantistica di Krylov su un computer quantistico
Ora implementeremo la diagonalizzazione quantistica di Krylov su un computer quantistico reale. Iniziamo importando alcuni pacchetti utili.
import numpy as np
import scipy as sp
import matplotlib.pylab as plt
from typing import Union, List
import warnings
from qiskit.quantum_info import SparsePauliOp, Pauli
from qiskit.circuit import Parameter
from qiskit import QuantumCircuit, QuantumRegister
from qiskit.circuit.library import PauliEvolutionGate
from qiskit.synthesis import LieTrotter
# from qiskit.providers.fake_provider import Fake20QV1
from qiskit_ibm_runtime import QiskitRuntimeService, EstimatorV2 as Estimator, Batch
import itertools as it
warnings.filterwarnings("ignore")Definiamo la funzione che segue per risolvere il problema generalizzato degli autovalori appena spiegato sopra.
def solve_regularized_gen_eig(
h: np.ndarray,
s: np.ndarray,
threshold: float,
k: int = 1,
return_dimn: bool = False,
) -> Union[float, List[float]]:
"""
Method for solving the generalized eigenvalue problem with regularization
Args:
h (numpy.ndarray):
The effective representation of the matrix in our Krylov subspace
s (numpy.ndarray):
The matrix of overlaps between vectors of our Krylov subspace
threshold (float):
Cut-off value for the eigenvalue of s
k (int):
Number of eigenvalues to return
return_dimn (bool):
Whether to return the size of the regularized subspace
Returns:
lowest k-eigenvalue(s) that are the solution of the regularized generalized eigenvalue problem
"""
s_vals, s_vecs = sp.linalg.eigh(s)
s_vecs = s_vecs.T
good_vecs = np.array([vec for val, vec in zip(s_vals, s_vecs) if val > threshold])
h_reg = good_vecs.conj() @ h @ good_vecs.T
s_reg = good_vecs.conj() @ s @ good_vecs.T
if k == 1:
if return_dimn:
return sp.linalg.eigh(h_reg, s_reg)[0][0], len(good_vecs)
else:
return sp.linalg.eigh(h_reg, s_reg)[0][0]
else:
if return_dimn:
return sp.linalg.eigh(h_reg, s_reg)[0][:k], len(good_vecs)
else:
return sp.linalg.eigh(h_reg, s_reg)[0][:k]Almeno nel benchmarking iniziale, è utile conoscere una soluzione classica esatta per verificare il comportamento della convergenza. La funzione seguente calcola l'energia di stato fondamentale di un'hamiltoniana, utilizzando come argomenti l'hamiltoniana e il numero di qubit.
def single_particle_gs(H_op, n_qubits):
"""
Find the ground state of the single particle(excitation) sector
"""
H_x = []
for p, coeff in H_op.to_list():
H_x.append(set([i for i, v in enumerate(Pauli(p).x) if v]))
H_z = []
for p, coeff in H_op.to_list():
H_z.append(set([i for i, v in enumerate(Pauli(p).z) if v]))
H_c = H_op.coeffs
print("n_sys_qubits", n_qubits)
n_exc = 1
sub_dimn = int(sp.special.comb(n_qubits + 1, n_exc))
print("n_exc", n_exc, ", subspace dimension", sub_dimn)
few_particle_H = np.zeros((sub_dimn, sub_dimn), dtype=complex)
sparse_vecs = [
set(vec) for vec in it.combinations(range(n_qubits + 1), r=n_exc)
] # list all of the possible sets of n_exc indices of 1s in n_exc-particle states
m = 0
for i, i_set in enumerate(sparse_vecs):
for j, j_set in enumerate(sparse_vecs):
m += 1
if len(i_set.symmetric_difference(j_set)) <= 2:
for p_x, p_z, coeff in zip(H_x, H_z, H_c):
if i_set.symmetric_difference(j_set) == p_x:
sgn = ((-1j) ** len(p_x.intersection(p_z))) * (
(-1) ** len(i_set.intersection(p_z))
)
else:
sgn = 0
few_particle_H[i, j] += sgn * coeff
gs_en = min(np.linalg.eigvalsh(few_particle_H))
print("single particle ground state energy: ", gs_en)
return gs_en4.1 Fase 1: Mappare il problema su circuiti quantistici e operatori
Ora definiremo un'hamiltoniana. Questa funzione si distingue dalla funzione precedente in quanto quest'ultima prende come argomento un'hamiltoniana e restituisce solo lo stato fondamentale, e lo fa in modo classico. L'hamiltoniana che definiamo qui determina i livelli energetici di tutti gli autostati energetici e può essere costruita utilizzando gli operatori di Pauli e implementata su un computer quantistico.
Scegliamo un'hamiltoniana corrispondente a una catena di spin che può avere qualsiasi orientamento nello spazio, chiamata "catena di Heisenberg". Assumiamo che lo spin possa essere influenzato dai suoi vicini più prossimi (gli spin e ) ma non da quelli più lontani. Si ammette anche la possibilità che l'interazione tra gli spin sia diversa quando gli spin puntano lungo assi diversi. Questa asimmetria si verifica talvolta, ad esempio, a causa della struttura del reticolo cristallino in cui sono inseriti gli spin.
# Define problem Hamiltonian.
n_qubits = 10
# coupling strength for XX, YY, and ZZ interactions
JX = 1
JY = 3
JZ = 2
# Define the Hamiltonian:
H_int = [["I"] * n_qubits for _ in range(3 * (n_qubits - 1))]
for i in range(n_qubits - 1):
H_int[i][i] = "Z"
H_int[i][i + 1] = "Z"
for i in range(n_qubits - 1):
H_int[n_qubits - 1 + i][i] = "X"
H_int[n_qubits - 1 + i][i + 1] = "X"
for i in range(n_qubits - 1):
H_int[2 * (n_qubits - 1) + i][i] = "Y"
H_int[2 * (n_qubits - 1) + i][i + 1] = "Y"
H_int = ["".join(term) for term in H_int]
H_tot = [
(term, JZ)
if term.count("Z") == 2
else (term, JY)
if term.count("Y") == 2
else (term, JX)
for term in H_int
]
# Get operator
H_op = SparsePauliOp.from_list(H_tot)
print(H_tot)Output:
[('ZZIIIIIIII', 2), ('IZZIIIIIII', 2), ('IIZZIIIIII', 2), ('IIIZZIIIII', 2), ('IIIIZZIIII', 2), ('IIIIIZZIII', 2), ('IIIIIIZZII', 2), ('IIIIIIIZZI', 2), ('IIIIIIIIZZ', 2), ('XXIIIIIIII', 1), ('IXXIIIIIII', 1), ('IIXXIIIIII', 1), ('IIIXXIIIII', 1), ('IIIIXXIIII', 1), ('IIIIIXXIII', 1), ('IIIIIIXXII', 1), ('IIIIIIIXXI', 1), ('IIIIIIIIXX', 1), ('YYIIIIIIII', 3), ('IYYIIIIIII', 3), ('IIYYIIIIII', 3), ('IIIYYIIIII', 3), ('IIIIYYIIII', 3), ('IIIIIYYIII', 3), ('IIIIIIYYII', 3), ('IIIIIIIYYI', 3), ('IIIIIIIIYY', 3)]
Il codice seguente limita l'hamiltoniana a stati di una singola particella e utilizza la norma spettrale per stabilire una buona dimensione per il nostro passo temporale . Scegliamo euristicamente un valore per il passo temporale dt (basato sui limiti superiori della norma hamiltoniana). Ref [9] ha dimostrato che un passo temporale sufficientemente piccolo è , e che è preferibile fino a un certo punto sottostimare questo valore piuttosto che sovrastimarlo, poiché una sovrastima può permettere ai contributi degli stati ad alta energia di corrompere anche lo stato ottimale nello spazio di Krylov. D'altra parte, la scelta di troppo piccola porta a un condizionamento peggiore del sottospazio di Krylov, poiché i vettori base di Krylov differiscono meno da un passo all'altro.
# Get Hamiltonian restricted to single-particle states
single_particle_H = np.zeros((n_qubits, n_qubits))
for i in range(n_qubits):
for j in range(i + 1):
for p, coeff in H_op.to_list():
p_x = Pauli(p).x
p_z = Pauli(p).z
if all(p_x[k] == ((i == k) + (j == k)) % 2 for k in range(n_qubits)):
sgn = ((-1j) ** sum(p_z[k] and p_x[k] for k in range(n_qubits))) * (
(-1) ** p_z[i]
)
else:
sgn = 0
single_particle_H[i, j] += sgn * coeff
for i in range(n_qubits):
for j in range(i + 1, n_qubits):
single_particle_H[i, j] = np.conj(single_particle_H[j, i])
# Set dt according to spectral norm
dt = np.pi / np.linalg.norm(single_particle_H, ord=2)
dtOutput:
np.float64(0.17453292519943295)
Specifichiamo il numero di passi di Trotter da utilizzare nell'evoluzione temporale. Specifichiamo inoltre una dimensione massima di Krylov pari a 4. Questa dimensione di Krylov non è sufficientemente ampia per applicazioni realistiche. Ma è sufficiente per questo esempio. Inoltre, verificheremo la convergenza a dimensioni ancora più piccole. Nelle lezioni successive esploreremo metodi che ci consentono di scalare e proiettare i nostri hamiltoniani su sottospazi più grandi.
# Set parameters for quantum Krylov algorithm
krylov_dim = 4 # size of krylov subspace
num_trotter_steps = 4
dt_circ = dt / num_trotter_stepsPreparazione dello Stato
Scegliere uno stato di riferimento che abbia una certa sovrapposizione con lo stato fondamentale. Per questa hamiltoniana, utilizziamo come stato di riferimento lo stato a con un'eccitazione nel qubit centrale .
qc_state_prep = QuantumCircuit(n_qubits)
qc_state_prep.x(int(n_qubits / 2) + 1)
qc_state_prep.draw("mpl", scale=0.5)Output:
Evoluzione temporale
Possiamo realizzare l'operatore di evoluzione temporale generato da una data hamiltoniana: tramite l' approssimazione di Lie-Trotter. Per semplicità utilizziamo il circuito integrato PauliEvolutionGate nel circuito di evoluzione temporale. La sintassi generale è la seguente.
t = Parameter("t")
## Create the time-evo op circuit
evol_gate = PauliEvolutionGate(
H_op, time=t, synthesis=LieTrotter(reps=num_trotter_steps)
)
qr = QuantumRegister(n_qubits)
qc_evol = QuantumCircuit(qr)
qc_evol.append(evol_gate, qargs=qr)Output:
<qiskit.circuit.instructionset.InstructionSet at 0x7ccaa4664250>
Di seguito utilizzeremo una versione di questo metodo nel test di Hadamard, ma con un passo in avanti per i tempi .
test di Hadamard
Ricordiamo che intendiamo calcolare gli elementi della matrice di e e della matrice di Gram utilizzando il test di Hadamard. Vediamo come funziona in questo contesto, concentrandoci innanzitutto sulla costruzione di un “ ”. Il processo complessivo è illustrato graficamente qui di seguito. Gli strati di blocchi colorati di preparazione dello stato servono a ricordare che questo processo viene eseguito per tutte le combinazioni di e nel nostro sottospazio.
Gli stati del sistema ai passi indicati sono:
Qui è un termine di Pauli nella decomposizione dell'hamiltoniana (si noti che non può essere una combinazione lineare di più termini di Pauli commutativi, poiché non sarebbe unitaria - il raggruppamento è possibile utilizzando una costruzione diversa che mostreremo in seguito) , sono operazioni controllate che preparano , vettori dello spazio di Krylov unitario, con . Applicando le misure di e a questo circuito si calcolano le parti reali e immaginarie, rispettivamente, degli elementi della matrice di cui abbiamo bisogno.
Partendo dal punto 4, applicare il gate di Hadamard allo zeroth qubit.
Quindi misurare o .
Dall'identità . Analogamente, misurando si ottiene
Aggiungendo questi passaggi all'evoluzione temporale impostata in precedenza, scriviamo quanto segue.
## Create the time-evo op circuit
evol_gate = PauliEvolutionGate(
H_op, time=dt, synthesis=LieTrotter(reps=num_trotter_steps)
)
## Create the time-evo op dagger circuit
evol_gate_d = PauliEvolutionGate(
H_op, time=dt, synthesis=LieTrotter(reps=num_trotter_steps)
)
evol_gate_d = evol_gate_d.inverse()
# Put pieces together
qc_reg = QuantumRegister(n_qubits)
qc_temp = QuantumCircuit(qc_reg)
qc_temp.compose(qc_state_prep, inplace=True)
for _ in range(num_trotter_steps):
qc_temp.append(evol_gate, qargs=qc_reg)
for _ in range(num_trotter_steps):
qc_temp.append(evol_gate_d, qargs=qc_reg)
qc_temp.compose(qc_state_prep.inverse(), inplace=True)
# Create controlled version of the circuit
controlled_U = qc_temp.to_gate().control(1)
# Create hadamard test circuit for real part
qr = QuantumRegister(n_qubits + 1)
qc_real = QuantumCircuit(qr)
qc_real.h(0)
qc_real.append(controlled_U, list(range(n_qubits + 1)))
qc_real.h(0)
print("Circuit for calculating the real part of the overlap in S via Hadamard test")
qc_real.draw("mpl", fold=-1, scale=0.5)Output:
Circuit for calculating the real part of the overlap in S via Hadamard test
Abbiamo già messo in guardia sulla profondità dei circuiti Trotter. L'esecuzione del test di Hadamard in queste condizioni può produrre un circuito ancora più profondo, soprattutto una volta decomposto in porte native. Questo dato aumenta ulteriormente se si tiene conto della topologia del dispositivo. Quindi, prima di utilizzare qualsiasi momento del computer quantistico, è bene verificare la profondità di 2 qubit del nostro circuito.
print(
"Number of layers of 2Q operations",
qc_real.decompose(reps=2).depth(lambda x: x[0].num_qubits == 2),
)Output:
Number of layers of 2Q operations 14401
Un circuito di questa complessità non può fornire risultati utilizzabili sui moderni computer quantistici. Se vogliamo creare e , ci serve un metodo migliore. Questo è il motivo per cui è stato introdotto il test di Hadamard, che verrà illustrato di seguito.
4. 2 Passaggio 2. Ottimizzare circuiti e operatori per l'hardware di destinazione
Test di Hadamard efficiente
Possiamo ottimizzare i circuiti profondi per il test di Hadamard che abbiamo ottenuto introducendo alcune approssimazioni e facendo affidamento su alcune ipotesi sull'hamiltoniana del modello. Ad esempio, si consideri il seguente circuito per il test di Hadamard:
Supponiamo di poter calcolare classicamente , l'autovalore di sotto l'hamiltoniana . Questo è soddisfatto quando l'hamiltoniana preserva la simmetria U(1). Anche se questa può sembrare un'ipotesi forte, in molti casi è lecito supporre che esista uno stato di vuoto (che in questo caso corrisponde allo stato ) che non è influenzato dall'azione dell'hamiltoniana. Questo è vero, ad esempio, per gli hamiltoniani della chimica che descrivono molecole stabili (dove il numero di elettroni è conservato). Dato che il gate , prepara lo stato di riferimento desiderato , ad esempio, preparare lo stato HF per la chimica sarebbe un prodotto di NOT a singolo qubit, quindi controllato- è solo un prodotto di CNOT. Il circuito di cui sopra implementa il seguente stato prima della misurazione:
dove abbiamo utilizzato il classico sfasamento simulabile dal passo 2 al passo 3. Pertanto i valori di aspettativa sono
Utilizzando queste ipotesi, siamo stati in grado di scrivere i valori di aspettativa degli operatori di interesse con un minor numero di operazioni controllate. In effetti, dobbiamo implementare solo la preparazione controllata degli stati e non le evoluzioni temporali controllate. Riformulare il calcolo come sopra ci permetterà di ridurre notevolmente la profondità dei circuiti risultanti.
Si noti che, come bonus, poiché l'operatore di Pauli appare ora come una misura alla fine del circuito anziché come una porta controllata nel mezzo, può essere misurato insieme ad altri operatori di Pauli commutativi come nella decomposizione data sopra.
Scomporre l'operatore di evoluzione temporale con la decomposizione di Trotter
Anziché implementare esattamente l'operatore di evoluzione temporale, possiamo ricorrere alla decomposizione di Trotter per implementarne un'approssimazione. Ripetendo più volte una determinata sequenza di decomposizione di Trotter, si ottiene un’ulteriore riduzione dell’errore derivante dall’approssimazione. Di seguito, realizziamo direttamente l’implementazione di Trotter nel modo più efficiente per il grafo di interazione dell’hamiltoniano che stiamo considerando (solo interazioni tra vicini più prossimi). In pratica inseriamo le rotazioni di Pauli , , con intensità di accoppiamento , e e un angolo parametrizzato , che corrispondono all’implementazione approssimativa di . Data la differenza nella definizione delle rotazioni di Pauli e dell’evoluzione temporale che stiamo cercando di implementare, dovremo utilizzare il parametro per ottenere un’evoluzione temporale di . Inoltre, invertiamo l’ordine delle operazioni per un numero dispari di ripetizioni dei passi di Trotter, il che è funzionalmente equivalente ma consente di sintetizzare operazioni adiacenti in un unico unitar . In questo modo si ottiene un circuito molto meno profondo rispetto a quello che si ottiene utilizzando la funzionalità PauliEvolutionGate() generica.
t = Parameter("t")
# Create instruction for rotation about XX+YY-ZZ:
Rxyz_circ = QuantumCircuit(2)
Rxyz_circ.rxx(2 * JX * t, 0, 1)
Rxyz_circ.ryy(2 * JY * t, 0, 1)
Rxyz_circ.rzz(2 * JZ * t, 0, 1)
Rxyz_instr = Rxyz_circ.to_instruction(label="R J_x XX + J_y YY + J_z ZZ")
interaction_list = [
[[i, i + 1] for i in range(0, n_qubits - 1, 2)],
[[i, i + 1] for i in range(1, n_qubits - 1, 2)],
] # linear chain
qr = QuantumRegister(n_qubits)
trotter_step_circ = QuantumCircuit(qr)
for i, color in enumerate(interaction_list):
for interaction in color:
trotter_step_circ.append(Rxyz_instr, interaction)
if i < len(interaction_list) - 1:
trotter_step_circ.barrier()
reverse_trotter_step_circ = trotter_step_circ.reverse_ops()
qc_evol = QuantumCircuit(qr)
for step in range(num_trotter_steps):
if step % 2 == 0:
qc_evol = qc_evol.compose(trotter_step_circ)
else:
qc_evol = qc_evol.compose(reverse_trotter_step_circ)
qc_evol.decompose().draw("mpl", fold=-1, scale=0.5)Output:
Prepariamo nuovamente uno stato iniziale per questo efficiente test di Hadamard.
control = 0
excitation = int(n_qubits / 2) + 1
controlled_state_prep = QuantumCircuit(n_qubits + 1)
controlled_state_prep.cx(control, excitation)
controlled_state_prep.draw("mpl", fold=-1, scale=0.5)Output:
Circuiti modello per il calcolo degli elementi di matrice di e tramite il test di Hadamard
L'unica differenza tra i circuiti utilizzati nel test di Hadamard sarà la fase dell'operatore di evoluzione temporale e le osservabili misurate. Possiamo quindi preparare un circuito modello che rappresenti il circuito generico per il test di Hadamard, con segnaposti per le porte che dipendono dall'operatore di evoluzione temporale.
# Parameters for the template circuits
parameters = []
for idx in range(1, krylov_dim):
parameters.append(dt_circ * (idx))# Create modified hadamard test circuit
qr = QuantumRegister(n_qubits + 1)
qc = QuantumCircuit(qr)
qc.h(0)
qc.compose(controlled_state_prep, list(range(n_qubits + 1)), inplace=True)
qc.barrier()
qc.compose(qc_evol, list(range(1, n_qubits + 1)), inplace=True)
qc.barrier()
qc.x(0)
qc.compose(controlled_state_prep.inverse(), list(range(n_qubits + 1)), inplace=True)
qc.x(0)
qc.decompose().draw("mpl", fold=-1)Output:
print(
"The optimized circuit has 2Q gates depth: ",
qc.decompose().decompose().depth(lambda x: x[0].num_qubits == 2),
)Output:
The optimized circuit has 2Q gates depth: 50
Questa profondità è sostanzialmente ridotta rispetto al test di Hadamard originale. Questa profondità è gestibile sui moderni computer quantistici, anche se è ancora piuttosto elevata. Per ottenere risultati utili, dovremo utilizzare una mitigazione degli errori all'avanguardia.
Selezionare un backend su cui eseguire il nostro calcolo quantistico di Krylov, in modo da poter transpilare il nostro circuito per eseguirlo su quel computer quantistico.
# Use the least-busy backend or specify a quantum computer using the syntax commented out below.
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)
# Or you may choose a specify backend and channel if necessary for your workflow.
# service = QiskitRuntimeService(channel="ibm_quantum_platform")
# backend = service.backend("ibm_fez")Ora transpiliamo i nostri circuiti e operatori.
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
target = backend.target
basis_gates = list(target.operation_names)
pm = generate_preset_pass_manager(
optimization_level=3, backend=backend, basis_gates=basis_gates
)
qc_trans = pm.run(qc)print(qc_trans.depth(lambda x: x[0].num_qubits == 2))
print(qc_trans.count_ops())
qc_trans.draw("mpl", fold=-1, idle_wires=False, scale=0.5)Output:
36
OrderedDict([('rz', 410), ('sx', 361), ('cz', 156), ('x', 18), ('barrier', 6)])
Dopo l'ottimizzazione, la nostra profondità a due qubit traspirati si riduce ulteriormente.
4.3 Fase 3. Eseguire utilizzando una primitiva di tipo “ IBM Quantum ”
Ora creiamo i PUB per l'esecuzione con Estimator.
# Define observables to measure for S
observable_S_real = "I" * (n_qubits) + "X"
observable_S_imag = "I" * (n_qubits) + "Y"
observable_op_real = SparsePauliOp(
observable_S_real
) # define a sparse pauli operator for the observable
observable_op_imag = SparsePauliOp(observable_S_imag)
layout = qc_trans.layout # get layout of transpiled circuit
observable_op_real = observable_op_real.apply_layout(
layout
) # apply physical layout to the observable
observable_op_imag = observable_op_imag.apply_layout(layout)
observable_S_real = (
observable_op_real.paulis.to_labels()
) # get the label of the physical observable
observable_S_imag = observable_op_imag.paulis.to_labels()
observables_S = [[observable_S_real], [observable_S_imag]]
# Define observables to measure for H
# Hamiltonian terms to measure
observable_list = []
for pauli, coeff in zip(H_op.paulis, H_op.coeffs):
# print(pauli)
observable_H_real = pauli[::-1].to_label() + "X"
observable_H_imag = pauli[::-1].to_label() + "Y"
observable_list.append([observable_H_real])
observable_list.append([observable_H_imag])
layout = qc_trans.layout
observable_trans_list = []
for observable in observable_list:
observable_op = SparsePauliOp(observable)
observable_op = observable_op.apply_layout(layout)
observable_trans_list.append([observable_op.paulis.to_labels()])
observables_H = observable_trans_list
# Define a sweep over parameter values
params = np.vstack(parameters).T
# Estimate the expectation value for all combinations of
# observables and parameter values, where the pub result will have
# shape (# observables, # parameter values).
pub = (qc_trans, observables_S + observables_H, params)I circuiti per sono calcolabili in modo classico. Eseguiamo questa operazione prima di passare al caso utilizzando un computer quantistico.
from qiskit.quantum_info import StabilizerState, Pauli
qc_cliff = qc.assign_parameters({t: 0})
# Get expectation values from experiment
S_expval_real = StabilizerState(qc_cliff).expectation_value(
Pauli("I" * (n_qubits) + "X")
)
S_expval_imag = StabilizerState(qc_cliff).expectation_value(
Pauli("I" * (n_qubits) + "Y")
)
# Get expectation values
S_expval = S_expval_real + 1j * S_expval_imag
H_expval = 0
for obs_idx, (pauli, coeff) in enumerate(zip(H_op.paulis, H_op.coeffs)):
# Get expectation values from experiment
expval_real = StabilizerState(qc_cliff).expectation_value(
Pauli(pauli[::-1].to_label() + "X")
)
expval_imag = StabilizerState(qc_cliff).expectation_value(
Pauli(pauli[::-1].to_label() + "Y")
)
expval = expval_real + 1j * expval_imag
# Fill-in matrix elements
H_expval += coeff * expval
print(H_expval)Output:
(10+0j)
Sebbene siamo riusciti a ridurre la profondità del gate di ordini di grandezza utilizzando l'efficiente test di Hadamard, la profondità è ancora sufficiente per richiedere una mitigazione degli errori all'avanguardia. Di seguito, vengono specificati gli attributi della mitigazione utilizzata. Tutti i metodi utilizzati sono importanti, ma vale la pena di richiamare in particolare l' amplificazione probabilistica degli errori (PEA). Questa potente tecnica comporta una grande quantità di overhead quantistico. Il calcolo effettuato qui può richiedere 20 minuti o più per essere eseguito su un vero computer quantistico. Si può giocare con i parametri sottostanti per aumentare o diminuire la precisione e, di conseguenza, l'overhead. Le impostazioni predefinite di seguito riportate producono risultati ad alta fedeltà.
# Experiment options
num_randomizations = 300
num_randomizations_learning = 20
max_batch_circuits = 20
shots_per_randomization = 100
learning_pair_depths = [0, 4, 24]
noise_factors = [1, 1.3, 1.6]
# Base option formatting
options = {
# Builtin resilience settings for ZNE
"resilience": {
"measure_mitigation": True,
"zne_mitigation": True,
"zne": {"noise_factors": noise_factors},
# TREX noise learning configuration
"measure_noise_learning": {
"num_randomizations": num_randomizations_learning,
"shots_per_randomization": shots_per_randomization,
},
# PEA noise model configuration
"layer_noise_learning": {
"max_layers_to_learn": 10,
"layer_pair_depths": learning_pair_depths,
"shots_per_randomization": shots_per_randomization,
"num_randomizations": num_randomizations_learning,
},
},
# Randomization configuration
"twirling": {
"num_randomizations": num_randomizations,
"shots_per_randomization": shots_per_randomization,
"strategy": "all",
},
# Experimental settings for PEA method
"experimental": {
# # Just in case, disable any further qiskit transpilation not related to twirling / DD
# "skip_transpilation": True,
# Execution configuration
"execution": {
"max_pubs_per_batch_job": max_batch_circuits,
"fast_parametric_update": True,
},
# Error Mitigation configuration
"resilience": {
# ZNE Configuration
"zne": {
"amplifier": "pea",
"return_all_extrapolated": True,
"return_unextrapolated": True,
"extrapolated_noise_factors": [0] + noise_factors,
}
},
},
}Infine, eseguiamo i circuiti per e con Estimator.
# This job required 17 minutes of QPU time to run on a Heron r2 processor. This is only an estimate.
# Your execution time may vary.
with Batch(backend=backend) as batch:
# Estimator
estimator = Estimator(mode=batch, options=options)
job = estimator.run([pub], precision=1)4.4 Fase 4. Post-elaborazione e analisi dei risultati
Ciò che abbiamo ottenuto dal computer quantistico sono i singoli elementi della matrice di e i gruppi di Pauli commutativi che compongono gli elementi della matrice di . Questi termini devono essere combinati per recuperare le nostre matrici, in modo da poter risolvere il problema degli autovalori generalizzati.
# Store the outputs as 'results'.
results = job.result()[0]Calcolare le matrici Hamiltoniane efficaci e di sovrapposizione
Calcolare innanzitutto la fase accumulata dallo stato durante l'evoluzione temporale non controllata
prefactors = [
np.exp(-1j * sum([c for p, c in H_op.to_list() if "Z" in p]) * i * dt)
for i in range(1, krylov_dim)
]Una volta ottenuti i risultati delle esecuzioni dei circuiti, possiamo post-elaborare i dati per calcolare gli elementi della matrice di
# Assemble S, the overlap matrix of dimension D:
S_first_row = np.zeros(krylov_dim, dtype=complex)
S_first_row[0] = 1 + 0j
# Add in ancilla-only measurements:
for i in range(krylov_dim - 1):
# Get expectation values from experiment
expval_real = results.data.evs[0][0][i] # automatic extrapolated evs if ZNE is used
expval_imag = results.data.evs[1][0][i] # automatic extrapolated evs if ZNE is used
# Get expectation values
expval = expval_real + 1j * expval_imag
S_first_row[i + 1] += prefactors[i] * expval
S_first_row_list = S_first_row.tolist() # for saving purposes
S_circ = np.zeros((krylov_dim, krylov_dim), dtype=complex)
# Distribute entries from first row across matrix:
for i, j in it.product(range(krylov_dim), repeat=2):
if i >= j:
S_circ[j, i] = S_first_row[i - j]
else:
S_circ[j, i] = np.conj(S_first_row[j - i])from sympy import Matrix
Matrix(S_circ)Output:
E gli elementi della matrice di
import itertools
# Assemble S, the overlap matrix of dimension D:
H_first_row = np.zeros(krylov_dim, dtype=complex)
H_first_row[0] = H_expval
for obs_idx, (pauli, coeff) in enumerate(zip(H_op.paulis, H_op.coeffs)):
# Add in ancilla-only measurements:
for i in range(krylov_dim - 1):
# Get expectation values from experiment
expval_real = results.data.evs[2 + 2 * obs_idx][0][
i
] # automatic extrapolated evs if ZNE is used
expval_imag = results.data.evs[2 + 2 * obs_idx + 1][0][
i
] # automatic extrapolated evs if ZNE is used
# Get expectation values
expval = expval_real + 1j * expval_imag
H_first_row[i + 1] += prefactors[i] * coeff * expval
H_first_row_list = H_first_row.tolist()
H_eff_circ = np.zeros((krylov_dim, krylov_dim), dtype=complex)
# Distribute entries from first row across matrix:
for i, j in itertools.product(range(krylov_dim), repeat=2):
if i >= j:
H_eff_circ[j, i] = H_first_row[i - j]
else:
H_eff_circ[j, i] = np.conj(H_first_row[j - i])from sympy import Matrix
Matrix(H_eff_circ)Output:
Infine, possiamo risolvere il problema generalizzato degli autovalori per :
e ottenere una stima dell'energia dello stato fondamentale
gnd_en_circ_est_list = []
for d in range(1, krylov_dim + 1):
# Solve generalized eigenvalue problem
gnd_en_circ_est = solve_regularized_gen_eig(
H_eff_circ[:d, :d], S_circ[:d, :d], threshold=1e-1
)
gnd_en_circ_est_list.append(gnd_en_circ_est)
print("The estimated ground state energy is: ", gnd_en_circ_est)Output:
The estimated ground state energy is: 10.0
The estimated ground state energy is: 5.933953916292923
The estimated ground state energy is: 4.4101773995740645
The estimated ground state energy is: 3.921288588521255
Per un settore a una sola particella, possiamo calcolare in modo efficiente lo stato fondamentale di questo settore dell'hamiltoniana in modo classico
gs_en = single_particle_gs(H_op, n_qubits)Output:
n_sys_qubits 10
n_exc 1 , subspace dimension 11
single particle ground state energy: 2.391547869638771
len(H_op)Output:
27
plt.plot(
range(1, krylov_dim + 1),
gnd_en_circ_est_list,
color="blue",
linestyle="-.",
label="KQD estimate",
)
plt.plot(
range(1, krylov_dim + 1),
[gs_en] * krylov_dim,
color="red",
linestyle="-",
label="exact",
)
plt.xticks(range(1, krylov_dim + 1), range(1, krylov_dim + 1))
plt.legend()
plt.xlabel("Krylov space dimension")
plt.ylabel("Energy")
plt.title("Estimating Ground state energy with Krylov Quantum Diagonalization")
plt.show()Output:
5. Discussione ed estensione
Ricapitolando, si parte da uno stato di riferimento e lo si fa evolvere per diversi periodi di tempo per generare il sottospazio unitario di Krylov. Proiettiamo la nostra hamiltoniana su questo sottospazio. Stimiamo anche le sovrapposizioni dei vettori del sottospazio. Infine, risolviamo in modo classico il problema degli autovalori generalizzati in bassa dimensione.
Confrontiamo ciò che determina i costi computazionali dell'utilizzo della tecnica di Krylov in modo classico e quantomeccanico. Non esistono analogie perfette tra l'approccio classico e quello quantistico per tutte le fasi. Questa tabella raccoglie alcune scalature di diversi passaggi da prendere in considerazione.
Ricordiamo che gli hamiltoniani contengono generalmente termini che non possono essere misurati contemporaneamente (poiché non commutano tra loro). Classifichiamo i termini dell’Hamiltoniano in gruppi di operatori di Pauli commutanti che possono essere misurati tutti contemporaneamente, e potremmo aver bisogno di molti di questi gruppi per tenere conto di tutti i termini che non commutano tra loro. Per costruire un’ e su un computer quantistico sono necessarie misurazioni separate per ciascun gruppo di stringhe di Pauli commutanti nell’Hamiltoniano, e ciascuna di esse richiede numerose prove. Dobbiamo farlo per diversi elementi di matrice, corrispondenti a combinazioni di diversi fattori di evoluzione temporale. A volte esistono modi per ridurre questo tempo, ma in questa approssimazione grossolana il tempo necessario varia proporzionalmente a . Gli elementi di devono essere stimati, il che varia proporzionalmente a . Infine, la risoluzione del problema degli autovalori generalizzati nello spazio proiettato, secondo l’approccio classico, richiede .
Vediamo che la diagonalizzazione quantistica di Krylov può essere utile nei casi in cui il numero di gruppi di Pauli commutanti nell'hamiltoniana è relativamente piccolo. Queste dipendenze scalari suggeriscono alcune applicazioni in cui il metodo di Krylov può essere utile e altre in cui probabilmente non lo sarà. Alcuni hamiltoniani presentano un'elevata complessità quando vengono mappati su qubit, coinvolgendo molte stringhe di Pauli non comunicanti che non possono essere facilmente partizionate in pochi gruppi comunicanti. Questo è spesso vero, ad esempio, per i problemi di chimica quantistica. Questa complessità presenta due sfide principali per i computer quantistici a breve termine:
- La stima di ogni elemento di diventa computazionalmente costosa a causa dell'elevato numero di termini.
- I circuiti Trotter necessari diventano proibitivi.
Entrambi i punti di cui sopra saranno meno problematici quando i computer quantistici raggiungeranno la tolleranza ai guasti, ma devono essere presi in considerazione nel breve termine. Anche i sistemi con mappature più "semplici" di quelle della chimica quantistica possono incontrare gli stessi impedimenti, se gli hamiltoniani hanno troppi termini non comunicanti. Il metodo di Krylov è più utile quando l'Hamiltoniana può essere suddivisa in un numero relativamente basso di gruppi Pauli commutativi e quando è facile da implementare nei circuiti trotter. Entrambe le condizioni sono soddisfatte, ad esempio, per molti modelli reticolari di interesse fisico. La KQD è particolarmente utile se si conosce molto poco dello stato fondamentale. Ciò deriva dalle sue garanzie di convergenza intrinseche e dalla sua applicabilità in scenari in cui i metodi alternativi sono insostenibili a causa dell'insufficiente conoscenza dello stato di terra.
Sebbene la KQD sia uno strumento potente, gli aspetti del protocollo che richiedono molto tempo, in particolare la stima di ogni elemento dell'hamiltoniana proiettata e la sovrapposizione degli stati di Krylov, rappresentano opportunità di miglioramento. Un approccio alternativo prevede l'utilizzo di metodi di Krylov in combinazione con metodi basati sul campionamento, che sono l'oggetto della prossima lezione.
6. Appendici
Appendice I: Sottospazio di Krylov dalle evoluzioni in tempo reale
Lo spazio di Krylov unitario è definito come
per qualche passo temporale che determineremo in seguito. Assumiamo temporaneamente che sia pari: allora definiamo . Si noti che quando proiettiamo l'Hamiltoniana nello spazio di Krylov di cui sopra, essa è indistinguibile dallo spazio di Krylov
cioè dove tutte le evoluzioni temporali sono spostate all'indietro di passi temporali. Il motivo per cui è indistinguibile è che gli elementi della matrice
sono invarianti sotto spostamenti complessivi del tempo di evoluzione, poiché le evoluzioni temporali commutano con l'hamiltoniana. Per dispari, possiamo utilizzare l'analisi per .
Vogliamo dimostrare che da qualche parte in questo spazio di Krylov è garantita l'esistenza di uno stato a bassa energia. Lo facciamo attraverso il seguente risultato, derivato dal teorema 3.1 di [3] :
Asserzione 1: esiste una funzione tale che per le energie nell'intervallo spettrale dell'hamiltoniana (cioè tra l'energia dello stato fondamentale e l'energia massima)...
- per tutti i valori di che si trovano lontano da , cioè è soppresso esponenzialmente
- è una combinazione lineare di per
Di seguito viene fornita una dimostrazione, che può essere tranquillamente saltata a meno che non si voglia comprendere l'argomentazione completa e rigorosa. Per ora ci concentriamo sulle implicazioni di questa affermazione. In base alla proprietà 3, possiamo vedere che lo spazio di Krylov spostato sopra contiene lo stato . Questo è il nostro stato a bassa energia. Per capire perché, scriviamo nell'autobase dell'energia:
dove è l'autostato di energia k e è la sua ampiezza nello stato iniziale . Espresso in termini di questo, è dato da
utilizzando il fatto che possiamo sostituire con quando agisce sull'autostato . L'errore energetico di questo stato è quindi
Per trasformare questo risultato in un limite superiore di più facile comprensione, separiamo prima la somma nel numeratore in termini con e termini con :
Il primo termine può essere limitato in alto da ,
dove il primo passo segue perché per ogni nella somma, e il secondo passo segue perché la somma nel numeratore è un sottoinsieme della somma nel denominatore. Per il secondo termine, prima di tutto abbassiamo il denominatore di , dato che : sommando il tutto, si ottiene
Per semplificare ciò che rimane, si noti che per tutti questi , dalla definizione di sappiamo che . Inoltre, l'upper bounding di e l'upper bounding di dà
Questo vale per qualsiasi , quindi se impostiamo uguale all'errore che ci prefiggiamo, il limite dell'errore di cui sopra converge verso tale valore in modo esponenziale con la dimensione di Krylov . Si noti inoltre che se allora il termine scompare del tutto nel limite di cui sopra.
Per completare l'argomentazione, notiamo innanzitutto che quanto sopra è solo l'errore energetico del particolare stato , piuttosto che l'errore energetico dello stato a più bassa energia nello spazio di Krylov. Tuttavia, in base al principio variazionale (di Rayleigh-Ritz), l'errore di energia dello stato a più bassa energia nello spazio di Krylov è limitato dall'errore di energia di qualsiasi stato nello spazio di Krylov, quindi quanto sopra è anche un limite superiore sull'errore di energia dello stato a più bassa energia, cioè l'uscita dell'algoritmo di diagonalizzazione quantistica di Krylov.
È possibile eseguire un'analisi simile a quella precedente, che tenga conto anche del rumore e della procedura di sogliatura descritta nel quaderno. Per questa analisi si veda [2] e [4].
Appendice II: prova della rivendicazione 1
Quanto segue deriva in gran parte da [3], Teorema 3.1: Sia e sia lo spazio dei polinomi residui (polinomi il cui valore a 0 è 1) di grado massimo . La soluzione di
è
e il corrispondente valore minimo è
Vogliamo convertire questa funzione in una funzione che possa essere espressa naturalmente in termini di esponenziali complessi, perché queste sono le reali evoluzioni temporali che generano lo spazio di Krylov quantistico. Per fare ciò, è conveniente introdurre la seguente trasformazione delle energie all'interno dell'intervallo spettrale dell'hamiltoniano in numeri nell'intervallo : definire
dove è un passo temporale tale che . Si noti che e crescono quando si allontana da .
Ora, utilizzando il polinomio con i parametri a, b, d impostati a , , e d = int( r/2 ), definiamo la funzione:
dove è l'energia dello stato fondamentale. Inserendo si vede che è un polinomio trigonometrico di grado , cioè una combinazione lineare di per . Inoltre, dalla definizione di abbiamo che e per qualsiasi nell'intervallo spettrale tale che abbiamo
Riferimenti:
[1] https://arxiv.org/abs/2407.14431
[2] https://arxiv.org/abs/1811.09025
[3] https://people.math.ethz.ch/~mhg/pub/biksm.pdf
[4] https://academic.oup.com/book/36426
[5] https://en.wikipedia.org/wiki/Krylov\Sottospazio
[6] Metodi del sottospazio di Krylov: Principi e analisi, Jörg Liesen, Zdenek Strakos https://academic.oup.com/book/36426
[7] Metodi iterativi per sistemi lineari sparsi" di Yousef Saad
[8] "MINRES-QLP: A Krylov Subspace Method for Indefinite or Singular Symmetric Systems" di Sou-Cheng Choi, Christopher Paige e Michael Saunders Michael Saunders ( https://epubs.siam.org/doi/10.1137/100787921 )
[9] Ethan N. Epperly, Lin Lin e Yuji Nakatsukasa. "Una teoria della diagonalizzazione dei sottospazi quantistici". SIAM Journal on Matrix Analysis and Applications 43, 1263-1290 (2022).