Krylov-Quanten-Diagonalisierung
In dieser Lektion über die Krylov-Quantendiagonalisierung (KQD) werden wir folgende Fragen beantworten:
- Was ist die Krylov-Methode im Allgemeinen?
- Warum funktioniert die Krylov-Methode und unter welchen Bedingungen?
- Welche Rolle spielt das Quantencomputing?
Der Quantenteil der Berechnungen stützt sich weitgehend auf die Arbeiten in [1].
Das folgende Video gibt einen Überblick über die Krylov-Methoden in der klassischen Datenverarbeitung, begründet ihre Verwendung und erklärt, wie die Quanteninformatik in diesem Arbeitsbereich eine Rolle spielen kann. Der folgende Text bietet mehr Details und implementiert eine Krylov-Methode sowohl klassisch als auch unter Verwendung eines Quantencomputers.
1. Einführung in die Krylov-Methoden
Eine Krylov-Unterraum-Methode kann sich auf eine von mehreren Methoden beziehen, die auf dem so genannten Krylov-Unterraum aufbauen. Eine vollständige Übersicht über diese Themen würde den Rahmen dieser Lektion sprengen, aber die Referenzen [2-4] können wesentlich mehr Hintergrundinformationen liefern. Hier werden wir uns darauf konzentrieren, was ein Krylov-Unterraum ist, wie und warum er bei der Lösung von Eigenwertproblemen nützlich ist, und schließlich, wie er auf einem Quantencomputer implementiert werden kann.
Definition: Bei einer symmetrischen, positiv semidefiniten Matrix ist der Krylov-Raum der Ordnung der Raum, der von Vektoren aufgespannt wird, die durch Multiplikation höherer Potenzen einer Matrix bis zu mit einem Referenzvektor erhalten werden.
Obwohl die obigen Vektoren einen so genannten Krylov-Unterraum aufspannen, gibt es keinen Grund zu der Annahme, dass sie orthogonal sein werden. Häufig wird ein iteratives Orthonormalisierungsverfahren ähnlich der Gram-Schmidt-Orthogonalisierung verwendet. Hier ist der Prozess etwas anders, da jeder neue Vektor bei seiner Erzeugung orthogonal zu den anderen gemacht wird. In diesem Zusammenhang wird dies als Arnoldi-Iteration bezeichnet. Ausgehend von dem Anfangsvektor erzeugt man den nächsten Vektor und stellt dann sicher, dass dieser zweite Vektor orthogonal zum ersten ist, indem man seine Projektion auf abzieht. Das bedeutet
Es ist nun leicht zu erkennen, dass gilt, da
Das Gleiche gilt für den nächsten Vektor, wobei sichergestellt wird, dass er orthogonal zu den beiden vorherigen Vektoren ist:
Wenn wir diesen Vorgang für alle Vektoren wiederholen, haben wir eine vollständige orthonormale Basis für einen Krylov-Raum. Man beachte, dass der Orthogonalisierungsprozess hier einmal zu Null führt, da orthogonale Vektoren notwendigerweise den gesamten Raum umfassen. Der Prozess wird auch Null ergeben, wenn ein beliebiger Vektor ein Eigenvektor von ist, da alle nachfolgenden Vektoren Vielfache dieses Vektors sind.
1.1 Ein einfaches Beispiel: Krylov von Hand
Gehen wir schrittweise durch die Generierung eines Krylov-Unterraums auf einer trivial kleinen Matrix, damit wir den Prozess sehen können. Wir beginnen mit einer Ausgangsmatrix , die für uns von Interesse ist:
Für dieses kleine Beispiel können wir die Eigenvektoren und Eigenwerte sogar leicht von Hand bestimmen. Wir zeigen hier die numerische Lösung.
# 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]]
Wir halten sie hier für einen späteren Vergleich fest:
Wir möchten untersuchen, wie dieser Prozess funktioniert (oder scheitert), wenn wir die Dimension unseres Krylov-Unterraums erhöhen. Zu diesem Zweck werden wir dieses Verfahren anwenden:
- Erzeugen Sie einen Unterraum des vollständigen Vektorraums, beginnend mit einem zufällig gewählten Vektor (nennen Sie ihn , wenn er bereits normalisiert ist, wie oben).
- Projizieren Sie die vollständige Matrix auf diesen Unterraum, und finden Sie die Eigenwerte dieser projizierten Matrix .
- Vergrößern Sie den Unterraum, indem Sie weitere Vektoren erzeugen und sicherstellen, dass sie orthonormal sind, indem Sie ein Verfahren ähnlich der Gram-Schmidt-Orthogonalisierung anwenden.
- Projizieren Sie auf den größeren Unterraum und finden Sie die Eigenwerte der resultierenden Matrix .
- Wiederholen Sie dies so lange, bis die Eigenwerte konvergieren (oder in diesem Spielzeugfall, bis Sie Vektoren erzeugt haben, die den gesamten Vektorraum der ursprünglichen Matrix abdecken ).
Bei einer normalen Implementierung der Krylov-Methode müsste das Eigenwertproblem für die auf jeden Krylov-Unterraum projizierte Matrix nicht gelöst werden, während sie aufgebaut wird. Man könnte den Unterraum der gewünschten Dimension konstruieren, die Matrix auf diesen Unterraum projizieren und die projizierte Matrix diagonalisieren. Die Projektion und Diagonalisierung in jeder Unterraumdimension wird nur zur Überprüfung der Konvergenz durchgeführt.
der Dimension:
Wir wählen einen Zufallsvektor, sagen wir
Wenn er nicht bereits normalisiert ist, normalisieren Sie ihn.
Wir projizieren nun unsere Matrix auf den Unterraum dieses einen Vektors:
Dies ist unsere Projektion der Matrix auf unseren Krylov-Unterraum, wenn dieser nur einen einzigen Vektor enthält, . Der Eigenwert dieser Matrix ist trivialerweise 4. Wir können uns dies als unsere Schätzung der Eigenwerte (in diesem Fall nur einer) von vorstellen. Obwohl es eine schlechte Schätzung ist, ist es die richtige Größenordnung.
der Dimension:
Wir erzeugen nun den nächsten Vektor in unserem Unterraum durch eine Operation mit auf dem vorherigen Vektor:
Nun subtrahieren wir die Projektion dieses Vektors auf unseren vorherigen Vektor, um die Orthogonalität zu gewährleisten.
Wenn er nicht bereits normalisiert ist, normalisieren Sie ihn. In diesem Fall wurde der Vektor bereits normalisiert, so dass
Wir projizieren nun unsere Matrix A auf den Unterraum dieser beiden Vektoren:
Bleibt noch das Problem, die Eigenwerte dieser Matrix zu bestimmen. Diese Matrix ist jedoch etwas kleiner als die Vollmatrix. Bei Problemen mit sehr großen Matrizen kann die Arbeit mit diesem kleineren Unterraum von großem Vorteil sein.
Dies ist zwar immer noch keine gute Schätzung, aber besser als die Schätzung nullter Ordnung. Wir werden dies in einer weiteren Iteration durchführen, um sicherzustellen, dass der Prozess klar ist. Dies untergräbt jedoch den Sinn der Methode, da wir in der nächsten Iteration eine 3x3 -Matrix diagonalisieren werden, was bedeutet, dass wir weder Zeit noch Rechenleistung gespart haben.
der Dimension:
Wir erzeugen nun den nächsten Vektor in unserem Unterraum durch eine Operation mit A auf dem vorherigen Vektor:
Nun subtrahieren wir die Projektion dieses Vektors auf die beiden vorherigen Vektoren, um die Orthogonalität sicherzustellen.
Wenn er nicht bereits normalisiert ist, normalisieren Sie ihn. In diesem Fall wurde der Vektor bereits normalisiert, so dass
Wir projizieren nun unsere Matrix auf den Unterraum dieser Vektoren:
Wir bestimmen nun die Eigenwerte:
Diese Eigenwerte sind genau die Eigenwerte der Originalmatrix . Dies muss der Fall sein, da wir unseren Krylov-Unterraum so erweitert haben, dass er den gesamten Vektorraum der Originalmatrix umfasst.
In diesem Beispiel mag die Krylov-Methode nicht besonders einfacher erscheinen als die direkte Diagonalisierung. Wie wir in späteren Abschnitten sehen werden, ist die Krylov-Methode erst ab einer bestimmten Matrixdimension vorteilhaft; dies soll uns helfen, Eigenwert-/Eigenvektor-Probleme von extrem großen Matrizen zu lösen.
Dies ist das einzige Beispiel, das wir zeigen werden, das "von Hand" bearbeitet wurde, aber Abschnitt 2 unten zeigt rechnerische Beispiele.
Begriffsklärung
Ein häufiges Missverständnis ist, dass es nur einen einzigen Krylov-Unterraum für ein bestimmtes Problem gibt. Da es aber viele Ausgangsvektoren gibt, auf die unsere Matrix angewendet werden könnte, gibt es natürlich auch viele mögliche Krylov-Unterräume. Wir werden den Ausdruck "die Krylov-Unterraum" nur verwenden, um auf einen bestimmten Krylov-Unterraum zu verweisen, der bereits für ein bestimmtes Beispiel definiert wurde. Für allgemeine Problemlösungsansätze sprechen wir von einem " Krylov-Unterraum". Eine letzte Klarstellung ist, dass die Bezeichnung " Krylov-Raum " zulässig ist. Er wird oft als " Krylov-Unterraum " bezeichnet, weil er im Zusammenhang mit der Projektion von Matrizen aus einem Ausgangsraum in einen Unterraum verwendet wird. In diesem Zusammenhang werden wir ihn hier meist als Unterraum bezeichnen.
Überprüfen Sie Ihr Verständnis
Erklären Sie, warum es (a) nicht sinnvoll und (b) nicht möglich ist, die Dimension des Krylov-Unterraums über die Dimension der interessierenden Matrix hinaus zu erweitern.
(a) Da wir die Vektoren bei ihrer Erzeugung orthonormalisieren, bildet eine Menge von solchen Vektoren eine vollständige Basis, was bedeutet, dass sich jeder Vektor im Raum als Linearkombination dieser Vektoren darstellen lässt.
(b) Der Orthogonalisierungsprozess besteht darin, die Projektion eines neuen Vektors auf alle vorherigen Vektoren zu subtrahieren. Wenn alle vorherigen Vektoren den gesamten Vektorraum aufspannen, erhalten wir durch Subtraktion der Projektionen auf den vollständigen Unterraum immer einen Nullvektor.
Angenommen, ein Kollege demonstriert die Krylov-Methode anhand einer kleinen Beispielmatrix. Stimmt etwas nicht mit ihrer Wahl der Matrix und des Startvektors ?
und
Ihr Kollege hat versehentlich einen Eigenvektor für seinen Ausgangsvektor gewählt. Wenn man mit der Matrix auf den Ausgangsvektor einwirkt, erhält man einfach denselben Vektor zurück, skaliert um den Eigenwert. Dadurch wird kein Unterraum mit zunehmender Dimension erzeugt. Raten Sie Ihrem Kollegen, einen anderen Ausgangsvektor zu wählen und darauf zu achten, dass es sich nicht um einen Eigenvektor handelt.
Wende die Krylov-Methode auf die angegebene Matrix an und wähle dabei einen geeigneten neuen Startvektor aus. Schreiben Sie die Schätzungen des minimalen Eigenwerts für die 0. und 1. Ordnung Ihres Krylov-Unterraums auf.
Je nach Wahl des Ausgangsvektors gibt es viele mögliche Antworten. Wir werden wählen:
Um „ “ zu erhalten, wenden wir „ “ einmal auf „ “ an und machen dann „ “ orthogonal zu „ “.
Bei 0-ter Ordnung ist die Projektion auf unseren Krylov-Unterraum
Bei 1. Ordnung ist die Projektion auf diesen Krylov-Unterraum
Dies kann von Hand geschehen, ist aber am einfachsten mit Numpy zu bewerkstelligen:
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)Outputs:
[[ 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]]Die minimale Eigenwertschätzung lautet -0.414.
1.2 Arten von Krylov-Methoden
"Krylov-Unterraum-Methoden" können sich auf verschiedene iterative Techniken beziehen, die zur Lösung großer linearer Systeme und Eigenwertprobleme verwendet werden. Sie alle haben gemeinsam, dass sie eine Näherungslösung aus einem Krylov-Unterraum konstruieren
wobei die anfängliche Schätzung ist (siehe Ref [5] ). Sie unterscheiden sich darin, wie sie die beste Annäherung aus diesem Unterraum auswählen und dabei Faktoren wie Konvergenzrate, Speicherverbrauch und Gesamtberechnungskosten ausgleichen. Der Schwerpunkt dieser Lektion liegt auf der Nutzung von Quantencomputern im Zusammenhang mit Krylov-Unterraummethoden; eine ausführliche Diskussion dieser Methoden würde den Rahmen sprengen. Die nachstehenden kurzen Definitionen dienen nur der Veranschaulichung und enthalten einige Verweise für die weitere Untersuchung dieser Methoden.
Die Methode des konjugierten Gradienten (CG) : Diese Methode wird zur Lösung symmetrischer, positiv definitiver linearer Systeme verwendet [6]. Es minimiert die A-Norm des Fehlers bei jeder Iteration, was es besonders effektiv für Systeme macht, die aus diskretisierten elliptischen PDEs entstehen [7]. Wir werden diesen Ansatz im nächsten Abschnitt verwenden, um zu begründen, warum ein Krylov-Unterraum ein effektiver Unterraum ist, in dem man nach besseren Lösungen für lineare Systeme suchen kann.
Die verallgemeinerte Methode der minimalen Residuen (GMRES) : Diese Methode ist für die Lösung allgemeiner, nicht symmetrischer linearer Systeme konzipiert. Es minimiert die Restnorm über einen Krylov-Raum bei jeder Iteration, was es robust, aber potenziell speicherintensiv für große Systeme macht [7].
Die Methode der minimalen Residuen (MINRES) : Diese Methode wird für die Lösung symmetrischer unbestimmter linearer Systeme verwendet. Es ist ähnlich wie GMRES, nutzt aber die Matrixsymmetrie, um die Rechenkosten zu senken [8].
Weitere erwähnenswerte Ansätze sind die Methode der vollständigen Orthogonalisierung (FOM ), die eng mit der Arnoldi-Methode für Eigenwertprobleme verwandt ist, die Methode des bi-konjugierten Gradienten ( BiCG ) und die Methode der induzierten Dimensionsreduktion (IDR).
1.3 Warum die Krylov-Unterraummethode funktioniert
Hier werden wir motivieren, dass die Krylov-Unterraum-Methode ein effizienter Weg sein sollte, Matrixeigenwerte durch iterative Verfeinerung von Eigenvektor-Näherungen durch die Linse des steilsten Abstiegs zu approximieren. Wir werden argumentieren, dass bei einer anfänglichen Schätzung eines Grundzustands der Raum der aufeinanderfolgenden Korrekturen dieser anfänglichen Schätzung, der die schnellste Konvergenz ergibt, ein Krylov-Unterraum ist. Wir verzichten auf einen rigorosen Nachweis des Konvergenzverhaltens.
Nehmen wir an, unsere interessierende Matrix ist symmetrisch und positiv definit. Dies macht unsere Argumentation für die obige CG-Methode besonders relevant. Wir machen hier keine Annahmen über die Sparsamkeit; wir behaupten auch nicht, dass hermitesch sein muss (was es sein muss, wenn es ein Hamiltonian ist).
Normalerweise wollen wir ein Problem der folgenden Form lösen
Man könnte sich vorstellen, dass , wobei eine Konstante ist, wie bei einem Eigenwertproblem. Aber unsere Problemstellung bleibt vorerst allgemeiner.
Wir beginnen mit einem Vektor , der eine Näherungslösung darstellt. Obwohl es Parallelen zwischen dieser Vermutung und im Abschnitt 1.1 gibt, ziehen wir hier keinen Nutzen daraus. Unser Vermutungs enthält einen Fehler, den wir als „ “ bezeichnen:
Außerdem definieren wir die Rest wie folgt:
Hier verwenden wir die Großbuchstaben , um das Residuum von der Dimension unseres Krylov-Unterraums zu unterscheiden.
Wir wollen nun einen Korrekturschritt der Form
was hoffentlich unsere Annäherung verbessert. Hier ist ein noch zu bestimmender Vektor. sei der Fehler nach der Korrektur. Dann
Wir sind daran interessiert, wie sich unser Fehler verhält, wenn er durch unsere Matrix transformiert wird. Berechnen wir also die -norm des Fehlers. Das heißt
Dabei haben wir die Symmetrie von sowie die Tatsache, dass , genutzt. Dabei ist eine von unabhängige Konstante. Wie in Abschnitt 1.2 erwähnt, ist die -Norm des Fehlers nicht die einzige Größe, die wir zur Minimierung wählen könnten, aber sie ist eine gute Wahl. Wir möchten untersuchen, wie sich diese Größe in Abhängigkeit von der Wahl unserer Korrekturvektoren verändert. Dazu definieren wir die Funktion durch Setzen von
ist einfach der Fehler in Abhängigkeit von der Korrektur gemessen in der -Norm. Daher wollen wir so wählen, dass so klein wie möglich ist. Zu diesem Zweck berechnen wir den Gradienten von . Unter Verwendung der Symmetrie von ergibt sich
Die Steigung zeigt in die Richtung des steilsten Anstiegs, was bedeutet, dass ihre entgegengesetzte Richtung die Richtung angibt, in der die Funktion am stärksten abfällt: die Richtung des steilsten Abfalls. Bei unserer anfänglichen Vermutung , wobei gilt, ergibt sich . Somit nimmt die Funktion in Richtung des Residuals am stärksten ab. Daher würde unsere anfängliche Wahl am meisten von der Addition des Vektors mit einem bestimmten Skalar profitieren.
Im nächsten Schritt wählen wir wieder einen Vektor und addieren seinen Wert zur aktuellen Approximation. Mit demselben Argument wie zuvor wählen wir für einen Skalar . Wir fahren auf diese Weise fort, so dass die Iteration unseres Vektors ist
Entsprechend wollen wir den Raum, aus dem wir unsere verbesserten Schätzungen auswählen, durch Hinzufügen von , , usw. in der Reihenfolge aufbauen. Der geschätzte Vektor liegt in
Unter Verwendung der Beziehung, dass
wir sehen, dass
Das heißt, der Raum, den wir aufbauen und der die richtige Lösung am effizientesten approximiert, ist genau der Raum, der durch die aufeinanderfolgende Anwendung der Matrix auf entsteht. Ein Krylov-Unterraum ist der Raum, der durch die Vektoren der aufeinanderfolgenden Richtungen des steilsten Abstiegs aufgespannt wird.
Abschließend möchten wir noch einmal darauf hinweisen, dass wir keine numerischen Aussagen über die Skalierung dieses Ansatzes gemacht haben, noch haben wir den vergleichbaren Nutzen für dünn besetzte Matrizen diskutiert. Dies soll nur dazu dienen, die Verwendung von Krylov-Unterraum-Methoden zu motivieren und ihnen einen gewissen intuitiven Sinn zu verleihen. Wir werden nun das Verhalten dieser Methoden numerisch untersuchen.
Überprüfen Sie Ihr Verständnis
In dem obigen Arbeitsablauf haben wir vorgeschlagen, die -Norm des Fehlers zu minimieren. Welche anderen Größen könnte man bei der Suche nach dem Grundzustand und seinem Eigenwert minimieren?
Man könnte sich vorstellen, den Residualvektor anstelle der -Norm des Fehlers zu verwenden. Es kann Fälle geben, in denen die Betrachtung des Fehlervektors selbst sinnvoll ist.
2. Krylov-Methoden in der klassischen Berechnung
In diesem Abschnitt implementieren wir Arnoldi-Iterationen rechnerisch, so dass wir einen Krylov-Unterraum bei der Lösung von Eigenwertproblemen nutzen können. Wir werden dies zunächst auf ein kleines Beispiel anwenden und dann untersuchen, wie die Berechnungszeit mit zunehmender Größe der interessierenden Matrix steigt. Ein Schlüsselgedanke dabei ist, dass die Erzeugung der Vektoren, die den Krylov-Raum aufspannen, einen großen Anteil an der benötigten Gesamtrechenzeit hat. Der benötigte Speicherplatz variiert zwischen den einzelnen Krylov-Methoden. Allerdings können Speicherbeschränkungen die Verwendung traditioneller Krylov-Methoden einschränken.
2.1 Einfaches Beispiel im kleinen Maßstab
Bei der Erstellung eines Krylov-Unterraums müssen wir die Vektoren in unserem Unterraum orthonormalisieren. Definieren wir eine Funktion, die einen etablierten Vektor aus unserem Unterraum vknown (der nicht normalisiert sein soll) und einen Kandidatenvektor nimmt, den wir zu unserem Unterraum vnext hinzufügen und vnext orthogonal zu vknown und normalisiert machen. Definieren wir ferner eine Funktion, die diesen Prozess für alle etablierten Vektoren in unserem Krylov-Unterraum durchläuft, um eine vollständig orthonormale Menge zu gewährleisten.
# 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 vDefinieren wir nun eine Funktion, die iterativ einen immer größeren Krylov-Unterraum aufbaut, bis der Raum der Krylov-Vektoren den gesamten Raum der ursprünglichen Matrix umfasst. So können wir sehen, wie gut die mit unserer Krylov-Unterraum-Methode erhaltenen Eigenwerte mit den exakten Werten übereinstimmen, und zwar in Abhängigkeit von der Krylov-Unterraum-Dimension. Wichtig ist, dass unsere Funktion krylov_full_build die Krylov-Vektoren, die projizierten Hamiltonianer, die Eigenwerte und die benötigte Zeit liefert.
# 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)Wir werden dies an einer Matrix testen, die zwar immer noch recht klein ist, aber größer, als wir sie vielleicht von Hand erstellen wollen.
# 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
)Wir können unsere Funktionen überprüfen, indem wir sicherstellen, dass im letzten Schritt (wenn der Krylov-Raum der vollständige Vektorraum der ursprünglichen Matrix ist) die Eigenwerte der Krylov-Methode genau mit denen der exakten numerischen Diagonalisierung übereinstimmen:
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]
Das war erfolgreich. Entscheidend ist natürlich, wie gut unsere Annäherung in Abhängigkeit von der Dimension unseres Krylov-Unterraums ist. Da wir uns häufig mit der Suche nach Grundzuständen und anderen minimalen Eigenwerten beschäftigen (und aus anderen, eher algebraischen Gründen, die weiter unten erläutert werden), betrachten wir unsere Schätzung des niedrigsten Eigenwerts als Funktion der Dimension des Krylov-Unterraums. Das heißt
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:
Wir sehen, dass der minimale Eigenwert ziemlich genau erreicht wird, sobald der Krylov-Unterraum auf angewachsen ist, und gemäß perfekt ist.
2.2 Zeitskalierung mit Matrixdimension
Wir wollen uns davon überzeugen, dass die Krylov-Methode gegenüber exakten numerischen Eigenlösungen auf folgende Weise vorteilhaft sein kann:
- Konstruktion von Zufallsmatrizen (nicht spärlich, nicht die ideale Anwendung für KQD)
- Bestimmen Sie Eigenwerte mit zwei Methoden: direkt mit NumPy und mit einem Krylov-Unterraum.
- Wir wählen einen Grenzwert dafür, wie genau unsere Eigenwerte sein müssen, bevor wir die Krylov-Schätzungen akzeptieren.
- Vergleichen Sie die Wandzeit, die für diese beiden Lösungswege benötigt wird.
Vorsichtsmaßnahmen: Wie wir weiter unten ausführlich erörtern werden, lässt sich die Krylov-Quantendiagonalisierung am besten auf Operatoren anwenden, deren Matrixdarstellungen spärlich sind und/oder mit einer kleinen Anzahl von Gruppen von kommutierenden Pauli-Operatoren geschrieben werden können. Die Zufallsmatrizen, die wir hier verwenden, entsprechen nicht dieser Beschreibung. Diese sind nur nützlich, um die Skala auszuloten, bei der klassische Krylov-Methoden nützlich sein könnten. Zweitens werden bei der Anwendung der Krylov-Methode die Eigenwerte unter Verwendung vieler unterschiedlich großer Krylov-Unterräume berechnet. Wir werden die Zeit angeben, die für den minimal dimensionierten Krylov-Unterraum benötigt wird, der die von uns geforderte Genauigkeit für den Grundzustandseigenwert erreicht. Auch dies ist etwas anderes als die Lösung eines Problems, das für exakte Eigenlöser unlösbar ist, da wir die exakte Lösung verwenden, um die benötigte Dimension zu bestimmen.
Wir beginnen mit der Erstellung einer Reihe von Zufallsmatrizen.
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))Wir diagonalisieren nun jede Matrix direkt mit Numpy. Zum späteren Vergleich berechnen wir die für die Diagonalisierung benötigte Zeit.
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:
Man beachte, dass in der obigen Abbildung die anomal hohe Zeit um eine Dimension von 125 herum auf die zufällige Natur der Matrizen oder auf die Implementierung auf dem verwendeten klassischen Prozessor zurückzuführen sein könnte, aber nicht reproduzierbar ist. Wenn Sie den Code erneut ausführen, erhalten Sie ein anderes Profil mit anderen anomalen Spitzenwerten.
Nun werden wir für jede Matrix einen Krylov-Unterraum aufbauen und schrittweise Eigenwerte berechnen. Bei jedem Schritt wird geprüft, ob der niedrigste Eigenwert innerhalb des von uns angegebenen absoluten Fehlers erzielt wurde. Der Unterraum, der als erster Eigenwerte innerhalb des angegebenen Fehlers liefert, ist der Unterraum, für den wir die Berechnungszeiten aufzeichnen werden. Die Ausführung dieser Zelle kann je nach Prozessorgeschwindigkeit mehrere Minuten dauern. Es steht Ihnen frei, die Auswertung zu überspringen oder die maximale Dimension der diagonalisierten Matrizen zu verringern. Es genügt, sich die vorberechneten Ergebnisse anzusehen.
# 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 = 1Wir wollen die Zeiten, die wir für diese beiden Methoden erhalten haben, zum Vergleich aufzeichnen:
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:
Dies sind die tatsächlich benötigten Zeiten, aber für die Zwecke der Diskussion glätten wir diese Kurven, indem wir den Durchschnitt über einige benachbarte Punkte / Matrixdimensionen bilden. Dies geschieht im Folgenden:
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:
Beachten Sie, dass die Zeit, die für die Erstellung eines Krylov-Unterraums benötigt wird, zunächst die Zeit übersteigt, die für die vollständige Diagonalisierung in Numpy benötigt wird. Mit zunehmender Größe der Matrix wird die Krylov-Methode jedoch vorteilhaft. Dies gilt auch dann, wenn wir unseren akzeptablen Fehler verringern, aber der Vorteil setzt bei einer größeren Matrixgröße ein. Es lohnt sich, dies auseinander zu nehmen.
Die Zeitkomplexität der numerischen Diagonalisierung ist (mit einigen Abweichungen zwischen den Algorithmen). Die Zeitkomplexität der Erzeugung einer orthonormalen Basis von Vektoren ist ebenfalls . Der Vorteil der Krylov-Methode liegt also nicht in der Verwendung der orthonormalen Basis , sondern in der Verwendung einer bestimmten orthonormalen Basis, die effektiv die Eigenwerte von Interesse herausgreift. Wir haben dies bereits in der Skizze eines Beweises im ersten Abschnitt dieser Lektion gesehen, und dies ist entscheidend für die Konvergenzgarantien in Krylov-Verfahren.
Lassen Sie uns einen Blick auf unsere bisherigen Fortschritte werfen:
- Bei sehr großen Matrizen kann die Krylov-Unterraum-Methode schneller als herkömmliche Diagonalisierungsalgorithmen approximative Eigenwerte innerhalb der erforderlichen Toleranzen liefern.
- Bei solch großen Matrizen ist die Erzeugung eines Krylov-Unterraums der zeitaufwändigste Teil der Krylov-Unterraum-Methode.
- Daher wäre ein effizientes Verfahren zur Erzeugung eines Krylov-Unterraums von großem Nutzen. Hier kommt endlich der Quantencomputer ins Spiel.
Überprüfen Sie Ihr Verständnis
Siehe die obenstehende geglättete Darstellung der Diagonalisierungszeiten in Abhängigkeit von der Matrixdimension.
(a) Ab welcher Matrixdimension wurde die Krylov-Methode laut dieser Grafik schneller?
(b) Welche Aspekte der Berechnung könnten den Bereich beeinflussen, ab dem die Krylov-Methode schneller wird?
(a) Die Ergebnisse können variieren, wenn man die Berechnung erneut durchführt, doch die Krylov-Methode wird ab einer Dimension von etwa 80–85 schneller.
(b) Es gibt viele mögliche Antworten. Wichtige Faktoren sind die von uns geforderte Genauigkeit und die Sparsität der zu diagonalizierenden Matrizen.
3. Krylov über Zeitentwicklung
Alles, was wir bisher beschrieben haben, kann klassisch durchgeführt werden. Wie und wann würden wir also einen Quantencomputer verwenden? Bei sehr großen Matrizen kann die Krylov-Methode lange Rechenzeiten und große Mengen an Speicherplatz erfordern. Die für die Matrixoperation von auf benötigte Zeit skaliert im schlimmsten Fall wie . Selbst die Multiplikation dünn besetzter Matrizen auf einem Vektor (der typische Fall für klassische Krylov-Löser) hat eine Zeitkomplexität, die wie skaliert. Dies wird für jeden gewünschten Vektor in unserem Unterraum durchgeführt. Die Unterraumdimension ist normalerweise kein signifikanter Bruchteil von und skaliert oft wie . Die Erzeugung aller Vektoren skaliert also im schlimmsten Fall wie . Obwohl es noch andere Schritte gibt, wie z. B. die Orthogonalisierung, ist dies die wichtigste Skalierung, die man im Auge behalten sollte.
Das Quantencomputing ermöglicht es uns, die Eigenschaften des Problems zu ändern, die die Skalierung der benötigten Zeit und Ressourcen bestimmen. Anstelle der generellen Abhängigkeit von der Matrixgröße werden wir Dinge wie die Anzahl der Schüsse und die Anzahl der nicht-kommutierenden Pauli-Terme sehen, aus denen sich der Hamiltonian zusammensetzt. Sehen wir uns an, wie das funktioniert.
3.1 Zeitliche Entwicklung
Der Operator, der einen Quantenzustand zeitlich entwickelt, ist (und es ist sehr üblich, insbesondere in der Quanteninformatik, aus der Notation zu streichen). Eine Möglichkeit, eine solche Exponentialfunktion eines Operators zu verstehen und sogar zu realisieren, ist die Betrachtung ihrer Taylorreihenentwicklung. Man beachte, dass diese Operation, die auf einen Anfangsvektor wirkt, eine Summe von Termen mit zunehmenden Potenzen von ergibt, die auf den Anfangszustand angewendet werden. Es sieht so aus, als könnten wir unseren Krylov-Unterraum einfach durch Zeitentwicklung unseres anfänglichen Schätzzustandes erstellen!
Die Schwierigkeit besteht darin, die Zeitentwicklung auf einem echten Quantencomputer zu realisieren. Viele der Terme in der Hamiltonformel sind nicht miteinander vertauschbar. Während also einige einfache exponentielle Operatoren wie einfachen Schaltkreisen entsprechen, tun dies allgemeine Hamiltonianer nicht. Und da sie nicht-kommutierende Terme enthalten, können wir den Exponentialwert nicht einfach in ein Produkt einfacher Terme zerlegen, wie wir es bei Zahlen können.
Dies ist also nicht trivial, aber ein gut untersuchter Prozess in der Quanteninformatik. Wir führen die Zeitevolution auf Quantencomputern mit Hilfe eines Prozesses durch, der Trotterisierung genannt wird, was an sich schon ein umfangreiches Thema ist [10]. Aber auf einer sehr hohen Ebene begrenzen wir die Auswirkungen der Nicht-Kommutativität von Termen, indem wir die zeitliche Entwicklung in sehr kleine Schritte unterteilen, z. B. mit der Größe .
wobei .
Wir nennen einen Krylov-Unterraum der Ordnung r, den wir im klassischen Kontext mit Potenzen von H direkt erzeugt haben, einen "Potenz-Krylov-Unterraum".
Jetzt erzeugen wir einen ähnlichen Raum mit dem unitären Zeit-Evolutions-Operator ; wir nennen ihn den "unitären Krylov-Raum" . Der Power-Krylov-Unterraum , den wir klassischerweise verwenden, kann nicht direkt auf einem Quantencomputer erzeugt werden, da kein unitärer Operator ist. Es kann gezeigt werden, dass die Verwendung des unitären Krylov-Unterraums ähnliche Konvergenzgarantien bietet wie der Potenz-Krylov-Unterraum, nämlich dass der Grundzustandsfehler effizient konvergiert, solange der Anfangszustand eine Überlappung mit dem wahren Grundzustand hat, die nicht exponentiell verschwindet, und solange eine ausreichende Lücke zwischen den Eigenwerten besteht. Für eine genauere Diskussion der Konvergenz siehe [1].
Hier werden die Potenzen von zu verschiedenen Zeitschritten (die Potenz von schreitet um eine Zeit voran). Wir können das Element des Unterraums, das sich für die Gesamtzeit zeitlich entwickelt, als bezeichnen.
Wir können unseren Hamiltonian H auf den unitären Krylov-Unterraum projizieren. Mit anderen Worten: Wir berechnen jedes Matrixelement von in der Basis . Wir bezeichnen diese projizierte Matrix als .
3.2 Wie man es auf einem Quantencomputer implementiert
Die Matrixelemente von sind durch die Erwartungswerte gegeben, die mit dem Quantencomputer geschätzt werden können. Beachten Sie, dass als eine Summe von Pauli-Operatoren auf verschiedenen Qubits geschrieben werden kann und dass nicht alle Pauli-Operatoren gleichzeitig gemessen werden können. Wir können die Pauli-Terme in Gruppen von kommutierenden Termen sortieren und alle diese Terme auf einmal messen. Aber wir brauchen vielleicht viele solcher Gruppen, um alle Begriffe abzudecken. Daher wird die Anzahl der verschiedenen Pendelgruppen, in die die Begriffe unterteilt werden können, , wichtig.
Dabei ist ein Pauli-String der Form oder eine Menge solcher Pauli-Strings, die miteinander kommutieren. Da wir „ “ als eine solche Summe messbarer Operatoren darstellen können, lassen sich die folgenden Ausdrücke für Matrixelemente von „ “ mithilfe des primitiven Schätzers „ IBM Quantum “ realisieren.
Dabei sind die Vektoren des unitären Krylov-Raums und die Vielfachen des gewählten Zeitschritts . Auf einem Quantencomputer kann die Berechnung der einzelnen Matrixelemente mit jedem beliebigen Algorithmus erfolgen, der es ermöglicht, eine Überlappung zwischen Quantenzuständen zu erzielen. In dieser Lektion werden wir uns mit dem Hadamard-Test beschäftigen. Da der Hamilton-Operator „ “ die Dimension „ “ hat, hat der auf den Unterraum projizierte Hamilton-Operator die Dimension „ “. Ist „ “ klein genug (im Allgemeinen reicht „ “ aus, um die Konvergenz der Schätzungen der Eigenwerte zu gewährleisten), können wir den projizierten Hamilton-Operator „ “ klassisch problemlos diagonalisieren. Allerdings können wir nicht direkt diagonalisieren, da die Vektoren des Krylov-Raums nicht orthogonal sind. Wir müssen ihre Überschneidungen messen und eine Matrix erstellen:
Dies ermöglicht es uns, das Eigenwertproblem in einem nicht-orthogonalen Raum zu lösen (auch verallgemeinertes Eigenwertproblem genannt)
Anhand der Lösungen dieses verallgemeinerten Eigenwertproblems können dann die Eigenwerte und Eigenzustände von geschätzt werden. Die Schätzung der Energie des Grundzustands wird beispielsweise durch die Ermittlung des kleinsten Eigenwerts und des Grundzustands aus dem entsprechenden Eigenvektor gewonnen. Die Koeffizienten in bestimmen den Beitrag der verschiedenen Vektoren, die aufspannen.
Verallgemeinertes Eigenwertproblem
Warum können wir nicht einfach diagonalisieren? Da die Information über die Geometrie der Krylov-Basis enthält (die in allen außer in sehr speziellen Fällen nicht orthogonal ist), beschreibt für sich genommen keine Projektion des vollständigen Hamiltonianers, so dass seine Eigenwerte keine besondere Beziehung zu denen des vollständigen Hamiltonianers haben - sie könnten beliebige Werte sein. Die Lösung des verallgemeinerten Eigenwertproblems ist erforderlich, um die approximativen Eigenwerte und Eigenvektoren zu erhalten, die der Projektion des vollständigen Hamiltonianers in den Krylov-Raum entsprechen.
Die Abbildung zeigt eine Schaltungsdarstellung des modifizierten Hadamard-Tests, einer Methode, die zur Berechnung der Überlappung zwischen verschiedenen Quantenzuständen verwendet wird. Für jedes Matrixelement wird ein Hadamard-Test zwischen den Zuständen , durchgeführt. Dies wird in der Abbildung durch das Farbschema für die Matrixelemente und die entsprechenden Operationen , hervorgehoben. Daher ist eine Reihe von Hadamard-Tests für alle möglichen Kombinationen von Krylov-Raum-Vektoren erforderlich, um alle Matrixelemente des projizierten Hamiltonian zu berechnen. Der oberste Draht in der Hadamard-Testschaltung ist ein Ancilla-Qubit, das entweder in der X- oder Y-Basis gemessen wird; sein Erwartungswert bestimmt den Wert der Überlappung zwischen den Zuständen. Der untere Draht stellt alle Qubits des System-Hamiltonianers dar. Die Operation bereitet das System-Qubit auf den Zustand vor, der durch den Zustand des Ancilla-Qubits gesteuert wird (ähnlich für ), und die Operation stellt die Pauli-Zerlegung des System-Hamiltonschen dar. Die Umsetzung dieser Operation auf einem Quantencomputer wird im Folgenden näher erläutert.
4. Krylov-Quanten-Diagonalisierung auf einem Quantencomputer
Wir werden nun die Krylov-Quanten-Diagonalisierung auf einem echten Quantencomputer implementieren. Beginnen wir damit, einige nützliche Pakete zu importieren.
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")Wir definieren die folgende Funktion, um das oben erläuterte verallgemeinerte Eigenwertproblem zu lösen.
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]Zumindest für ein erstes Benchmarking ist es nützlich, eine exakte klassische Lösung zu kennen, um das Konvergenzverhalten zu überprüfen. Die folgende Funktion berechnet die Grundzustandsenergie eines Hamiltonianers, indem sie den Hamiltonianer und die Anzahl der Qubits als Argumente verwendet.
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 Schritt 1: Problem auf Quantenschaltungen und Operatoren abbilden
Jetzt werden wir einen Hamiltonianer definieren. Sie unterscheidet sich von der obigen Funktion dadurch, dass die obige Funktion einen Hamiltonian als Argument nimmt und nur den Grundzustand zurückgibt, und zwar auf klassische Weise. Dieser Hamiltonian, den wir hier definieren, bestimmt die Energieniveaus aller Energieeigenzustände, und dieser Hamiltonian kann mit Pauli-Operatoren konstruiert und auf einem Quantencomputer implementiert werden.
Wir wählen einen Hamiltonian, der einer Kette von Spins entspricht, die eine beliebige Orientierung im Raum haben können, eine sogenannte "Heisenberg-Kette". Wir gehen davon aus, dass der Spin von seinen nächsten Nachbarn (den Spins und ) beeinflusst werden kann, nicht aber von weiter entfernten Nachbarspins. Wir berücksichtigen auch die Möglichkeit, dass die Wechselwirkung zwischen Spins unterschiedlich ist, wenn die Spins entlang verschiedener Achsen zeigen. Diese Asymmetrie kann beispielsweise durch die Struktur des Kristallgitters entstehen, in das die Spins eingebettet sind.
# 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)]
Der folgende Code beschränkt den Hamiltonian auf Einzelteilchenzustände und verwendet die Spektralnorm, um eine gute Größe für unseren Zeitschritt festzulegen. Wir wählen heuristisch einen Wert für den Zeitschritt dt (basierend auf Obergrenzen für die Hamilton-Norm). In [9] wurde gezeigt, dass ein hinreichend kleiner Zeitschritt ist und dass es bis zu einem gewissen Punkt vorzuziehen ist, diesen Wert zu unterschätzen, anstatt ihn zu überschätzen, da eine Überschätzung dazu führen kann, dass Beiträge von hochenergetischen Zuständen sogar den optimalen Zustand im Krylov-Raum verfälschen. Andererseits führt eine zu kleine Wahl von zu einer schlechteren Konditionierung des Krylov-Unterraums, da sich die Krylov-Basisvektoren von Zeitschritt zu Zeitschritt weniger unterscheiden.
# 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)
Wir legen die Anzahl der Trotter-Schritte fest, die in der Zeitentwicklung verwendet werden sollen. Wir legen außerdem eine maximale Krylov-Dimension von 4 fest. Diese Krylov-Dimension ist für realistische Anwendungen nicht groß genug. Aber für dieses Beispiel reicht es aus. Darüber hinaus werden wir die Konvergenz bei noch kleineren Dimensionen überprüfen. In späteren Lektionen werden wir Methoden untersuchen, mit denen wir unsere Hamilton-Operatoren auf größere Unterräume skalieren und projizieren können.
# Set parameters for quantum Krylov algorithm
krylov_dim = 4 # size of krylov subspace
num_trotter_steps = 4
dt_circ = dt / num_trotter_stepsStaatliche Vorbereitung
Wählen Sie einen Referenzzustand , der sich mit dem Grundzustand überschneidet. Für diesen Hamiltonian verwenden wir den a-Zustand mit einer Anregung im mittleren Qubit als unseren Referenzzustand.
qc_state_prep = QuantumCircuit(n_qubits)
qc_state_prep.x(int(n_qubits / 2) + 1)
qc_state_prep.draw("mpl", scale=0.5)Output:
Zeitliche Entwicklung
Wir können den Zeitentwicklungsoperator, der durch einen gegebenen Hamiltonian erzeugt wird, über die Lie-Trotter-Approximation realisieren: . Der Einfachheit halber verwenden wir die integrierte PauliEvolutionGate in der Zeitentwicklungsschaltung. Die allgemeine Syntax hierfür lautet wie folgt.
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>
Wir werden eine Version davon unten im Hadamard-Test verwenden, aber für die Zeiten vorwärts schreiten.
Hadamard-Test
Zur Erinnerung: Wir möchten die Matrixelemente sowohl von als auch von der Gram-Matrix mithilfe des Hadamard-Tests berechnen. Schauen wir uns einmal an, wie das in diesem Zusammenhang funktioniert, und konzentrieren wir uns dabei zunächst auf den Aufbau von „ “. Der gesamte Prozess ist im folgenden Diagramm dargestellt. Die Schichten aus farbigen Zustandsvorbereitungsblöcken dienen als Hinweis darauf, dass dieser Vorgang für alle Kombinationen von und in unserem Unterraum durchgeführt wird.
Die Zustände des Systems bei den angegebenen Schritten sind:
Hier ist ein Pauli-Term in der Zerlegung des Hamiltonianers (beachten Sie, dass es sich nicht um eine Linearkombination von mehreren kommutierenden Pauli-Termen handeln kann, da dies nicht unitär wäre - die Gruppierung ist mit einer anderen Konstruktion möglich, die wir später zeigen werden) , sind kontrollierte Operationen, die , Vektoren des unitären Krylov-Raums mit vorbereiten. Die Anwendung von Messungen von und auf diese Schaltung berechnet den Real- bzw. Imaginärteil der benötigten Matrixelemente.
Beginnend mit Schritt 4 oben, wenden Sie das Hadamard-Tor auf das nullte Qubit an.
Messen Sie dann entweder oder .
Aus der Identität . In ähnlicher Weise ergibt die Messung von
Wenn wir diese Schritte zu der Zeitentwicklung hinzufügen, die wir zuvor festgelegt haben, ergibt sich folgendes.
## 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
Wir haben bereits vor der Tiefe der Trotter-Kreise gewarnt. Die Durchführung des Hadamard-Tests unter diesen Bedingungen kann zu einer noch tieferen Schaltung führen, vor allem, wenn wir in native Gatter zerlegen. Dieser Wert erhöht sich noch weiter, wenn man die Topologie des Geräts berücksichtigt. Bevor wir also Zeit auf dem Quantencomputer verbringen, sollten wir die 2-Qubit-Tiefe unserer Schaltung überprüfen.
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
Ein Schaltkreis dieser Komplexität kann auf modernen Quantencomputern keine verwertbaren Ergebnisse liefern. Wenn wir und erstellen wollen, brauchen wir einen besseren Ansatz. Dies ist der Grund für den im Folgenden vorgestellten effizienten Hadamard-Test.
4. 2 Schritt 2. Optimieren Sie Schaltungen und Operatoren für die Zielhardware
Effizienter Hadamard-Test
Wir können die tiefen Schaltkreise für den Hadamard-Test, die wir erhalten haben, optimieren, indem wir einige Näherungen einführen und uns auf einige Annahmen über den Modell-Hamiltonian stützen. Betrachten Sie zum Beispiel die folgende Schaltung für den Hadamard-Test:
Angenommen, wir können , den Eigenwert von unter dem Hamiltonian klassisch berechnen. Dies ist erfüllt, wenn der Hamiltonian die U(1)-Symmetrie bewahrt. Obwohl dies eine starke Annahme zu sein scheint, gibt es viele Fälle, in denen man davon ausgehen kann, dass es einen Vakuumzustand gibt (in diesem Fall entspricht er dem Zustand ), der von der Wirkung des Hamiltonianers unbeeinflusst ist. Dies gilt zum Beispiel für chemische Hamiltonianer, die stabile Moleküle beschreiben (bei denen die Anzahl der Elektronen erhalten bleibt). Da das Tor den gewünschten Referenzzustand herstellt, wäre beispielsweise die Herstellung des HF-Zustands für die Chemie ein Produkt von Ein-Qubit-NOTs, so dass kontrolliert- nur ein Produkt von CNOTs ist. Die obige Schaltung implementiert dann den folgenden Zustand vor der Messung:
wobei wir die klassische simulierbare Phasenverschiebung von Schritt 2 bis 3 verwendet haben. Daher sind die Erwartungswerte
Mit diesen Annahmen konnten wir die Erwartungswerte der interessierenden Operatoren mit weniger kontrollierten Operationen schreiben. In der Tat müssen wir nur die kontrollierte Zustandsvorbereitung und nicht die kontrollierte Zeitentwicklung implementieren. Wenn wir unsere Berechnung wie oben beschrieben umgestalten, können wir die Tiefe der resultierenden Schaltkreise erheblich reduzieren.
Da der Pauli-Operator nun als Messwert am Ende der Schaltung erscheint und nicht mehr als kontrolliertes Tor in der Mitte, kann er als Bonus zusammen mit anderen pendelnden Pauli-Operatoren gemessen werden, wie in der oben angegebenen Zerlegung .
Zeitentwicklungsoperator mit Trotter-Zerlegung zerlegen
Anstatt den Zeitentwicklungsoperator exakt zu implementieren, können wir die Trotter-Zerlegung verwenden, um eine Näherung davon zu implementieren. Durch mehrmaliges Wiederholen einer bestimmten Reihenfolge der Trotter-Zerlegung lässt sich der durch die Näherung verursachte Fehler weiter reduzieren. Im Folgenden erstellen wir direkt die Trotter-Implementierung auf die effizienteste Weise für den Wechselwirkungsgraphen des von uns betrachteten Hamilton-Operators (ausschließlich Wechselwirkungen zwischen nächsten Nachbarn). In der Praxis fügen wir Pauli-Rotationen ein: , , mit Kopplungsstärken , und sowie einem parametrisierten Winkel , die der näherungsweisen Implementierung von entsprechen. Angesichts der unterschiedlichen Definition der Pauli-Rotationen und der Zeitentwicklung, die wir implementieren möchten, müssen wir den Parameter verwenden, um eine Zeitentwicklung gemäß zu erzielen. Darüber hinaus kehren wir bei einer ungeraden Anzahl von Wiederholungen der Trotter-Schritte die Reihenfolge der Operationen um, was funktional äquivalent ist, aber die Synthese benachbarter Operationen in einem einzigen unitaren „ “ ermöglicht. Dadurch ergibt sich eine wesentlich flachere Schaltung als bei Verwendung der generischen Funktionalität PauliEvolutionGate() .
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:
Für diesen effizienten Hadamard-Test bereiten wir erneut einen Ausgangszustand vor.
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:
Vorlagen-Schaltungen zur Berechnung von Matrixelementen von und über den Hadamard-Test
Der einzige Unterschied zwischen den beim Hadamard-Test verwendeten Schaltungen besteht in der Phase des Zeitentwicklungsoperators und den gemessenen Messgrößen. Daher können wir eine Schablonenschaltung erstellen, die die allgemeine Schaltung für den Hadamard-Test darstellt, mit Platzhaltern für die Gatter, die vom Zeitentwicklungsoperator abhängen.
# 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
Diese Tiefe ist im Vergleich zum ursprünglichen Hadamard-Test wesentlich geringer. Diese Tiefe ist auf modernen Quantencomputern überschaubar, wenngleich sie immer noch recht hoch ist. Um brauchbare Ergebnisse zu erhalten, müssen wir den neuesten Stand der Technik bei der Fehlerminderung anwenden.
Wählen Sie ein Backend aus, auf dem unsere Quanten-Krylov-Berechnung ausgeführt werden soll, damit wir unsere Schaltung für die Ausführung auf diesem Quantencomputer übersetzen können.
# 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")Wir setzen nun unsere Schaltungen und Operatoren um.
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)])
Nach der Optimierung wird unsere transpilierte Zwei-Qubit-Tiefe weiter reduziert.
4.3 Schritt 3. Mit der Primitive „ IBM Quantum “ ausführen
Wir erstellen nun PUBs zur Ausführung mit 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)Die Schaltungen für sind klassisch berechenbar. Wir führen dies durch, bevor wir zum Fall mit einem Quantencomputer übergehen.
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)
Obwohl wir unsere Gattertiefe mit dem effizienten Hadamard-Test um Größenordnungen reduzieren konnten, ist die Tiefe immer noch ausreichend, um eine moderne Fehlerbegrenzung zu erfordern. Im Folgenden werden die Attribute der verwendeten Abschwächung aufgeführt. Alle verwendeten Methoden sind wichtig, aber es lohnt sich, speziell die probabilistische Fehlerverstärkung (PEA) zu erwähnen. Diese leistungsstarke Technik ist mit einem großen Quanten-Overhead verbunden. Die hier durchgeführte Berechnung kann auf einem echten Quantencomputer 20 Minuten oder mehr dauern. Sie können mit den nachstehenden Parametern spielen, um die Genauigkeit und den damit verbundenen Aufwand zu erhöhen oder zu verringern. Die folgenden Standardeinstellungen führen zu sehr realitätsnahen Ergebnissen.
# 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,
}
},
},
}Schließlich führen wir die Schaltungen für und mit Estimator aus.
# 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 Schritt 4. Nachbearbeitung und Analyse der Ergebnisse
Was wir vom Quantencomputer erhalten haben, sind die einzelnen Matrixelemente von und die kommutierenden Pauli-Gruppen, aus denen sich die Matrixelemente von zusammensetzen.
# Store the outputs as 'results'.
results = job.result()[0]Berechnen Sie den effektiven Hamilton-Operator und die Überlappungsmatrizen
Berechnen Sie zunächst die Phase, die durch den Zustand während der unkontrollierten Zeitentwicklung akkumuliert wird
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)
]Sobald wir die Ergebnisse der Schaltkreisausführungen haben, können wir die Daten nachbearbeiten, um die Matrixelemente von
# 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:
Und die Matrixelemente von
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:
Schließlich können wir das verallgemeinerte Eigenwertproblem für lösen:
und erhalten eine Schätzung der Grundzustandsenergie
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
Für einen Ein-Teilchen-Sektor können wir den Grundzustand dieses Sektors des Hamiltonianers effizient und klassisch berechnen
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. Diskussion und Erweiterung
Zusammenfassend lässt sich sagen, dass wir mit einem Referenzzustand beginnen und diesen dann über verschiedene Zeiträume hinweg entwickeln, um den unitären Krylov-Unterraum zu erzeugen. Wir projizieren unseren Hamiltonianer auf diesen Unterraum. Wir schätzen auch die Überschneidungen der Unterraum-Vektoren. Schließlich lösen wir das niederdimensionale, verallgemeinerte Eigenwertproblem auf klassische Weise.
Vergleichen wir, was die Rechenkosten der klassischen und der quantenmechanischen Krylov-Technik bestimmt. Nicht für alle Schritte gibt es perfekte Analogien zwischen klassischen und Quantenansätzen. In dieser Tabelle sind einige Skalierungen der verschiedenen Schritte zusammengestellt.
Man beachte, dass Hamilton-Operatoren im Allgemeinen Terme enthalten, die nicht gleichzeitig gemessen werden können (da sie nicht miteinander kommutieren). Wir ordnen die Terme im Hamilton-Operator in Gruppen kommutierender Pauli-Operatoren ein, die alle gleichzeitig gemessen werden können, und benötigen möglicherweise viele solcher Gruppen, um alle Terme zu berücksichtigen, die nicht miteinander kommutieren. Um „ “ auf einem Quantencomputer zu berechnen, sind für jede Gruppe kommutierender Pauli-Strings im Hamilton-Operator separate Messungen erforderlich, und jede dieser Messungen erfordert viele Durchläufe. Wir müssen dies für verschiedene Matrixelemente durchführen, die den Kombinationen verschiedener Zeitentwicklungsfaktoren entsprechen. Es gibt zwar manchmal Möglichkeiten, dies zu reduzieren, doch bei dieser groben Behandlung skaliert der Zeitaufwand wie „ “. Die Elemente von „ “ müssen geschätzt werden, was wie „ “ skaliert. Schließlich erfordert die Lösung des verallgemeinerten Eigenwertproblems im projizierten Raum klassischerweise „ “.
Wir sehen, dass die Quanten-Krylov-Diagonalisierung in Fällen nützlich sein kann, in denen die Anzahl der kommutierenden Pauli-Gruppen im Hamiltonian relativ klein ist. Diese Skalierungsabhängigkeiten deuten auf einige Anwendungen hin, bei denen die Krylov-Methode nützlich sein kann, und andere, bei denen dies wahrscheinlich nicht der Fall sein wird. Einige Hamiltonianer haben eine hohe Komplexität, wenn sie auf Qubits abgebildet werden, da sie viele nicht-kommutierende Pauli-Strings beinhalten, die nicht einfach in einige wenige kommutierende Gruppen unterteilt werden können. Dies ist zum Beispiel bei Problemen der Quantenchemie häufig der Fall. Diese Komplexität stellt die Quantencomputer der nahen Zukunft vor zwei große Herausforderungen:
- Die Schätzung der einzelnen Elemente von wird aufgrund der großen Anzahl von Termen rechenintensiv.
- Die erforderlichen Trotter-Schaltkreise werden unerschwinglich tief.
Beide oben genannten Punkte werden weniger problematisch sein, wenn Quantencomputer Fehlertoleranz erreichen, aber sie müssen in naher Zukunft berücksichtigt werden. Selbst Systeme mit "einfacheren" Abbildungen als in der Quantenchemie können auf die gleichen Hindernisse stoßen, wenn die Hamiltonianer zu viele nichtkommutierende Terme haben. Die Krylov-Methode ist am nützlichsten, wenn der Hamiltonian in relativ wenige kommutierende Pauli-Gruppen unterteilt werden kann und wenn leicht in Trotter-Schaltungen zu implementieren ist. Beide Bedingungen sind zum Beispiel für viele Gittermodelle, die in der Physik von Interesse sind, erfüllt. KQD ist besonders nützlich, wenn sehr wenig über den Grundzustand bekannt ist. Dies ist auf die inhärenten Konvergenzgarantien und die Anwendbarkeit in Szenarien zurückzuführen, in denen alternative Methoden aufgrund unzureichender Grundzustandskenntnisse unhaltbar sind.
KQD ist zwar ein leistungsfähiges Werkzeug, aber die zeitaufwändigen Aspekte des Protokolls, insbesondere die Schätzung der einzelnen Elemente des projizierten Hamiltonian und die Überlappung der Krylov-Zustände, bieten Möglichkeiten zur Verbesserung. Ein alternativer Ansatz ist die Nutzung von Krylov-Methoden in Verbindung mit stichprobenbasierten Methoden, die Gegenstand der nächsten Lektion sind.
6. Anhänge
Anhang I: Krylov-Unterraum aus Echtzeit-Entwicklungen
Der unitäre Krylov-Raum ist definiert als
für einen bestimmten Zeitschritt , den wir später festlegen werden. Nehmen wir vorläufig an, dass gerade ist: Definieren Sie dann . Beachten Sie, dass der Hamiltonian, wenn wir ihn in den obigen Krylov-Raum projizieren, nicht vom Krylov-Raum zu unterscheiden ist
d. h., alle Zeitverläufe sind um Zeitschritte nach hinten verschoben. Der Grund für die Ununterscheidbarkeit ist, dass die Matrixelemente
sind invariant bei allgemeinen Verschiebungen der Evolutionszeit, da die Zeitentwicklungen mit dem Hamiltonian pendeln. Für ungerade können wir die Analyse für verwenden.
Wir wollen zeigen, dass es irgendwo in diesem Krylov-Raum garantiert einen energiearmen Zustand gibt. Wir tun dies mit Hilfe des folgenden Ergebnisses, das aus Theorem 3.1 in [3] abgeleitet ist:
Behauptung 1: Es gibt eine Funktion , so dass für Energien im Spektralbereich des Hamiltonian (d.h. zwischen der Grundzustandsenergie und der maximalen Energie)...
- für alle Werte von , die von entfernt liegen, d. h. sie wird exponentiell unterdrückt
- ist eine lineare Kombination von für
Im Folgenden geben wir einen Beweis an, den man aber getrost überspringen kann, es sei denn, man möchte das vollständige, strenge Argument verstehen. Im Moment konzentrieren wir uns auf die Auswirkungen der obigen Behauptung. Aus der obigen Eigenschaft 3 geht hervor, dass der obige verschobene Krylov-Raum den Zustand enthält. Dies ist unser energiearmer Zustand. Um zu sehen, warum, schreiben Sie in die Energie-Eigenbasis:
wobei der k-te Energieeigenzustand und seine Amplitude im Ausgangszustand ist. Ausgedrückt in diesem Sinne ist gegeben durch
unter Ausnutzung der Tatsache, dass wir durch ersetzen können, wenn sie auf den Eigenzustand wirkt. Der Energiefehler dieses Zustands ist daher
Um dies in eine obere Schranke umzuwandeln, die leichter zu verstehen ist, trennen wir zunächst die Summe im Zähler in Terme mit und Terme mit :
Wir können den ersten Term durch nach oben begrenzen,
wobei der erste Schritt folgt, weil für jede in der Summe gilt, und der zweite Schritt folgt, weil die Summe im Zähler eine Teilmenge der Summe im Nenner ist. Für den zweiten Term schränken wir zunächst den Nenner durch ein, da : Addiert man alles wieder zusammen, so ergibt sich
Um den Rest zu vereinfachen, sei darauf hingewiesen, dass wir für alle diese durch die Definition von wissen, dass . Zusätzlich ergibt die obere Begrenzung von und die obere Begrenzung von
Dies gilt für jede . Wenn wir also gleich unserem Zielfehler setzen, dann konvergiert die obige Fehlergrenze exponentiell mit der Krylov-Dimension gegen diese. Beachten Sie auch, dass bei der Term in der obigen Schranke vollständig verschwindet.
Um das Argument zu vervollständigen, stellen wir zunächst fest, dass es sich bei der obigen Angabe nur um den Energiefehler des jeweiligen Zustands handelt und nicht um den Energiefehler des Zustands mit der niedrigsten Energie im Krylov-Raum. Nach dem (Rayleigh-Ritz-)Variationsprinzip ist der Energiefehler des Zustands mit der niedrigsten Energie im Krylov-Raum jedoch durch den Energiefehler eines beliebigen Zustands im Krylov-Raum nach oben hin begrenzt, so dass die obige Aussage auch eine Obergrenze für den Energiefehler des Zustands mit der niedrigsten Energie, d. h. des Ergebnisses des Krylov-Quantendiagonalisierungsalgorithmus, darstellt.
Es kann eine ähnliche Analyse wie die obige durchgeführt werden, die zusätzlich das Rauschen und das im Notizbuch besprochene Schwellenwertverfahren berücksichtigt. Siehe [2] und [4] für diese Analyse.
Anhang II: Nachweis für Anspruch 1
Das Folgende ist größtenteils aus [3], Theorem 3.1: abgeleitet. Sei und sei der Raum der Restpolynome (Polynome, deren Wert bei 0 gleich 1 ist) vom Grad höchstens . Die Lösung von
ist
und der entsprechende Minimalwert ist
Wir wollen dies in eine Funktion umwandeln, die natürlich in Form von komplexen Exponentialen ausgedrückt werden kann, denn das sind die realen Zeitentwicklungen, die den Quanten-Krylov-Raum erzeugen. Zu diesem Zweck ist es zweckmäßig, die folgende Transformation der Energien im Spektralbereich des Hamiltonian in Zahlen im Bereich einzuführen: definieren
wobei ein solcher Zeitschritt ist, dass . Beachten Sie, dass und wachsen, wenn sich von entfernt.
Mit dem Polynom und den Parametern a, b, d, die auf , und d = int( r/2 ) gesetzt sind, definieren wir die Funktion:
wobei die Grundzustandsenergie ist. Durch Einsetzen von können wir sehen, dass ein trigonometrisches Polynom vom Grad ist, d. h. eine Linearkombination von für . Außerdem ergibt sich aus der obigen Definition von , dass und für jedes im Spektralbereich, das ist, gilt
Quellenangaben:
[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_Unterraum
[6] Krylov-Unterraum-Methoden: Grundlagen und Analyse, Jörg Liesen, Zdenek Strakos https://academic.oup.com/book/36426
[7] Iterative Methoden für spärliche lineare Systeme" von Yousef Saad
[8] "MINRES-QLP: A Krylov Subspace Method for Indefinite or Singular Symmetric Systems" von Sou-Cheng Choi, Christopher Paige, und Michael Saunders ( https://epubs.siam.org/doi/10.1137/100787921 )
[9] Ethan N. Epperly, Lin Lin, und Yuji Nakatsukasa. "Eine Theorie der Quantenunterraumdiagonalisierung". SIAM Journal on Matrix Analysis and Applications 43, 1263-1290 (2022).