Krylov-Quantendiagonalisierung von Gitter-Hamiltonoperatoren
Geschätzte Nutzungsdauer: 20 Minuten auf einem Heron r2 (HINWEIS: Dies ist nur eine Schätzung. Deine tatsächliche Laufzeit kann abweichen.)
# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-ibm-runtime scipy sympy
# This cell is hidden from users – it disables some lint rules
# ruff: noqa: E402 E722 F601
Hintergrund
Dieses Tutorial zeigt, wie du den Krylov-Quantendiagonalisierungsalgorithmus (KQD) im Rahmen von Qiskit-Mustern implementierst. Du wirst zunächst die Theorie hinter dem Algorithmus kennenlernen und anschließend eine Demonstration seiner Ausführung auf einer QPU sehen.
In vielen Disziplinen sind wir daran interessiert, Grundzustandseigenschaften von Quantensystemen zu bestimmen. Beispiele umfassen das Verständnis der fundamentalen Natur von Teilchen und Kräften, die Vorhersage und das Verständnis des Verhaltens komplexer Materialien sowie das Verständnis biochemischer Wechselwirkungen und Reaktionen. Aufgrund des exponentiellen Wachstums des Hilbertraums und der Korrelationen, die in verschränkten Systemen auftreten, haben klassische Algorithmen Schwierigkeiten, dieses Problem für Quantensysteme zunehmender Größe zu lösen. An einem Ende des Spektrums steht der bestehende Ansatz, der die Quantenhardware nutzt und sich auf variationelle Quantenmethoden konzentriert (zum Beispiel den variationellen Quanten-Eigenwertlöser). Diese Techniken stehen bei aktuellen Geräten vor Herausforderungen, da der Optimierungsprozess eine hohe Anzahl von Funktionsaufrufen erfordert, was einen großen Overhead an Ressourcen verursacht, sobald fortgeschrittene Fehlerminderungstechniken eingeführt werden, und somit ihre Wirksamkeit auf kleine Systeme beschränkt. Am anderen Ende des Spektrums stehen fehlertolerante Quantenmethoden mit Leistungsgarantien (zum Beispiel die Quantenphasenschätzung), die tiefe Schaltkreise erfordern, die nur auf einem fehlertoleranten Gerät ausgeführt werden können. Aus diesen Gründen führen wir hier einen Quantenalgorithmus ein, der auf Unterraummethoden basiert (wie in diesem Übersichtsartikel beschrieben), den Krylov-Quantendiagonalisierungsalgorithmus (KQD). Dieser Algorithmus funktioniert im großen Maßstab gut [1] auf bestehender Quantenhardware, teilt ähnliche Leistungsgarantien wie die Phasenschätzung, ist mit fortgeschrittenen Fehlerminderungstechniken kompatibel und könnte Ergebnisse liefern, die klassisch nicht erreichbar sind.
Voraussetzungen
Bevor du mit diesem Tutorial beginnst, stelle sicher, dass Folgendes installiert ist:
- Qiskit SDK v2.0 oder neuer, mit Visualisierungs-Unterstützung
- Qiskit Runtime v0.22 oder neuer (
pip install qiskit-ibm-runtime)
Einrichtung
import numpy as np
import scipy as sp
import matplotlib.pylab as plt
from typing import Union, List
import itertools as it
import copy
from sympy import Matrix
import warnings
warnings.filterwarnings("ignore")
from qiskit.quantum_info import SparsePauliOp, Pauli, StabilizerState
from qiskit.circuit import Parameter, IfElseOp
from qiskit import QuantumCircuit, QuantumRegister
from qiskit.circuit.library import PauliEvolutionGate
from qiskit.synthesis import LieTrotter
from qiskit.transpiler import Target, CouplingMap
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import (
QiskitRuntimeService,
EstimatorV2 as Estimator,
)
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 the Krylov subspace
s (numpy.ndarray):
The matrix of overlaps between vectors of the 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]
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_en
Schritt 1: Klassische Eingaben auf ein Quantenproblem abbilden
Der Krylov-Raum
Der Krylov-Raum der Ordnung ist der Raum, der von Vektoren aufgespannt wird, die durch Multiplikation höherer Potenzen einer Matrix , bis zu , mit einem Referenzvektor erhalten werden.
Wenn die Matrix der Hamiltonoperator ist, bezeichnen wir den entsprechenden Raum als Potenz-Krylov-Raum . Im Fall, dass der Zeitentwicklungsoperator ist, der vom Hamiltonoperator erzeugt wird , bezeichnen wir den Raum als unitären Krylov-Raum . Der klassisch verwendete Potenz-Krylov-Unterraum kann auf einem Quantencomputer nicht direkt erzeugt werden, da kein unitärer Operator ist. Stattdessen können wir den Zeitentwicklungsoperator verwenden, für den gezeigt werden kann, dass er ähnliche Konvergenzgarantien wie die Potenzmethode bietet. Potenzen von werden dann zu verschiedenen Zeitschritten .
Siehe den Anhang für eine detaillierte Herleitung, wie der unitäre Krylov-Raum es ermöglicht, niederenergetische Eigenzustände genau darzustellen.
Krylov-Quantendiagonalisierungsalgorithmus
Gegeben sei ein Hamiltonoperator , den wir diagonalisieren möchten. Zunächst betrachten wir den entsprechenden unitären Krylov-Raum . Das Ziel ist es, eine kompakte Darstellung des Hamiltonoperators in zu finden, die wir als bezeichnen. Die Matrixelemente von , der Projektion des Hamiltonoperators in den Krylov-Raum, können durch Berechnung der folgenden Erwartungswerte bestimmt werden
Dabei sind die Vektoren des unitären Krylov-Raums und die Vielfachen des gewählten Zeitschritts . Auf einem Quantencomputer kann die Berechnung jedes Matrixelements mit jedem Algorithmus durchgeführt werden, der es ermöglicht, Überlappungen zwischen Quantenzuständen zu bestimmen. Dieses Tutorial konzentriert sich auf den Hadamard-Test. Da der die Dimension hat, wird der in den Unterraum projizierte Hamiltonoperator die Dimensionen haben. Bei hinreichend kleinem (im Allgemeinen reicht aus, um Konvergenz der Eigenenergieabschätzungen zu erhalten) können wir den projizierten Hamiltonoperator dann leicht diagonalisieren. Allerdings können wir nicht direkt diagonalisieren, da die Krylov-Raum-Vektoren nicht orthogonal sind. Wir muessen ihre Überlappungen messen und eine Matrix konstruieren
Dies ermöglicht es uns, das Eigenwertproblem in einem nicht-orthogonalen Raum zu lösen (auch als verallgemeinertes Eigenwertproblem bezeichnet)
Man kann dann Abschätzungen der Eigenwerte und Eigenzustände von erhalten, indem man diejenigen von betrachtet. Beispielsweise erhält man die Abschätzung der Grundzustandsenergie, indem man den kleinsten Eigenwert nimmt, und den Grundzustand aus dem entsprechenden Eigenvektor . Die Koeffizienten in bestimmen den Beitrag der verschiedenen Vektoren, die aufspannen.

Die Abbildung zeigt eine Schaltkreisdarstellung des modifizierten Hadamard-Tests, einer Methode zur Berechnung der Überlappung zwischen verschiedenen Quantenzuständen. 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. Somit ist ein Satz von Hadamard-Tests für alle möglichen Kombinationen von Krylov-Raum-Vektoren erforderlich, um alle Matrixelemente des projizierten Hamiltonoperators zu berechnen. Der obere Draht im Hadamard-Test-Schaltkreis 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 repräsentiert alle Qubits des System-Hamiltonoperators. Die -Operation bereitet das System-Qubit im Zustand vor, gesteuert durch den Zustand des Ancilla-Qubits (analog für ), und die Operation repräsentiert die Pauli-Zerlegung des System-Hamiltonoperators . Eine detailliertere Herleitung der vom Hadamard-Test berechneten Operationen wird nachfolgend gegeben.
Definition des Hamiltonoperators
Betrachten wir den Heisenberg-Hamiltonoperator für Qubits auf einer linearen Kette:
# Define problem Hamiltonian.
n_qubits = 30
J = 1 # coupling strength for ZZ interaction
# 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, J) if term.count("Z") == 2 else (term, 1) for term in H_int]
# Get operator
H_op = SparsePauliOp.from_list(H_tot)
print(H_tot)
[('ZZIIIIIIIIIIIIIIIIIIIIIIIIIIII', 1), ('IZZIIIIIIIIIIIIIIIIIIIIIIIIIII', 1), ('IIZZIIIIIIIIIIIIIIIIIIIIIIIIII', 1), ('IIIZZIIIIIIIIIIIIIIIIIIIIIIIII', 1), ('IIIIZZIIIIIIIIIIIIIIIIIIIIIIII', 1), ('IIIIIZZIIIIIIIIIIIIIIIIIIIIIII', 1), ('IIIIIIZZIIIIIIIIIIIIIIIIIIIIII', 1), ('IIIIIIIZZIIIIIIIIIIIIIIIIIIIII', 1), ('IIIIIIIIZZIIIIIIIIIIIIIIIIIIII', 1), ('IIIIIIIIIZZIIIIIIIIIIIIIIIIIII', 1), ('IIIIIIIIIIZZIIIIIIIIIIIIIIIIII', 1), ('IIIIIIIIIIIZZIIIIIIIIIIIIIIIII', 1), ('IIIIIIIIIIIIZZIIIIIIIIIIIIIIII', 1), ('IIIIIIIIIIIIIZZIIIIIIIIIIIIIII', 1), ('IIIIIIIIIIIIIIZZIIIIIIIIIIIIII', 1), ('IIIIIIIIIIIIIIIZZIIIIIIIIIIIII', 1), ('IIIIIIIIIIIIIIIIZZIIIIIIIIIIII', 1), ('IIIIIIIIIIIIIIIIIZZIIIIIIIIIII', 1), ('IIIIIIIIIIIIIIIIIIZZIIIIIIIIII', 1), ('IIIIIIIIIIIIIIIIIIIZZIIIIIIIII', 1), ('IIIIIIIIIIIIIIIIIIIIZZIIIIIIII', 1), ('IIIIIIIIIIIIIIIIIIIIIZZIIIIIII', 1), ('IIIIIIIIIIIIIIIIIIIIIIZZIIIIII', 1), ('IIIIIIIIIIIIIIIIIIIIIIIZZIIIII', 1), ('IIIIIIIIIIIIIIIIIIIIIIIIZZIIII', 1), ('IIIIIIIIIIIIIIIIIIIIIIIIIZZIII', 1), ('IIIIIIIIIIIIIIIIIIIIIIIIIIZZII', 1), ('IIIIIIIIIIIIIIIIIIIIIIIIIIIZZI', 1), ('IIIIIIIIIIIIIIIIIIIIIIIIIIIIZZ', 1), ('XXIIIIIIIIIIIIIIIIIIIIIIIIIIII', 1), ('IXXIIIIIIIIIIIIIIIIIIIIIIIIIII', 1), ('IIXXIIIIIIIIIIIIIIIIIIIIIIIIII', 1), ('IIIXXIIIIIIIIIIIIIIIIIIIIIIIII', 1), ('IIIIXXIIIIIIIIIIIIIIIIIIIIIIII', 1), ('IIIIIXXIIIIIIIIIIIIIIIIIIIIIII', 1), ('IIIIIIXXIIIIIIIIIIIIIIIIIIIIII', 1), ('IIIIIIIXXIIIIIIIIIIIIIIIIIIIII', 1), ('IIIIIIIIXXIIIIIIIIIIIIIIIIIIII', 1), ('IIIIIIIIIXXIIIIIIIIIIIIIIIIIII', 1), ('IIIIIIIIIIXXIIIIIIIIIIIIIIIIII', 1), ('IIIIIIIIIIIXXIIIIIIIIIIIIIIIII', 1), ('IIIIIIIIIIIIXXIIIIIIIIIIIIIIII', 1), ('IIIIIIIIIIIIIXXIIIIIIIIIIIIIII', 1), ('IIIIIIIIIIIIIIXXIIIIIIIIIIIIII', 1), ('IIIIIIIIIIIIIIIXXIIIIIIIIIIIII', 1), ('IIIIIIIIIIIIIIIIXXIIIIIIIIIIII', 1), ('IIIIIIIIIIIIIIIIIXXIIIIIIIIIII', 1), ('IIIIIIIIIIIIIIIIIIXXIIIIIIIIII', 1), ('IIIIIIIIIIIIIIIIIIIXXIIIIIIIII', 1), ('IIIIIIIIIIIIIIIIIIIIXXIIIIIIII', 1), ('IIIIIIIIIIIIIIIIIIIIIXXIIIIIII', 1), ('IIIIIIIIIIIIIIIIIIIIIIXXIIIIII', 1), ('IIIIIIIIIIIIIIIIIIIIIIIXXIIIII', 1), ('IIIIIIIIIIIIIIIIIIIIIIIIXXIIII', 1), ('IIIIIIIIIIIIIIIIIIIIIIIIIXXIII', 1), ('IIIIIIIIIIIIIIIIIIIIIIIIIIXXII', 1), ('IIIIIIIIIIIIIIIIIIIIIIIIIIIXXI', 1), ('IIIIIIIIIIIIIIIIIIIIIIIIIIIIXX', 1), ('YYIIIIIIIIIIIIIIIIIIIIIIIIIIII', 1), ('IYYIIIIIIIIIIIIIIIIIIIIIIIIIII', 1), ('IIYYIIIIIIIIIIIIIIIIIIIIIIIIII', 1), ('IIIYYIIIIIIIIIIIIIIIIIIIIIIIII', 1), ('IIIIYYIIIIIIIIIIIIIIIIIIIIIIII', 1), ('IIIIIYYIIIIIIIIIIIIIIIIIIIIIII', 1), ('IIIIIIYYIIIIIIIIIIIIIIIIIIIIII', 1), ('IIIIIIIYYIIIIIIIIIIIIIIIIIIIII', 1), ('IIIIIIIIYYIIIIIIIIIIIIIIIIIIII', 1), ('IIIIIIIIIYYIIIIIIIIIIIIIIIIIII', 1), ('IIIIIIIIIIYYIIIIIIIIIIIIIIIIII', 1), ('IIIIIIIIIIIYYIIIIIIIIIIIIIIIII', 1), ('IIIIIIIIIIIIYYIIIIIIIIIIIIIIII', 1), ('IIIIIIIIIIIIIYYIIIIIIIIIIIIIII', 1), ('IIIIIIIIIIIIIIYYIIIIIIIIIIIIII', 1), ('IIIIIIIIIIIIIIIYYIIIIIIIIIIIII', 1), ('IIIIIIIIIIIIIIIIYYIIIIIIIIIIII', 1), ('IIIIIIIIIIIIIIIIIYYIIIIIIIIIII', 1), ('IIIIIIIIIIIIIIIIIIYYIIIIIIIIII', 1), ('IIIIIIIIIIIIIIIIIIIYYIIIIIIIII', 1), ('IIIIIIIIIIIIIIIIIIIIYYIIIIIIII', 1), ('IIIIIIIIIIIIIIIIIIIIIYYIIIIIII', 1), ('IIIIIIIIIIIIIIIIIIIIIIYYIIIIII', 1), ('IIIIIIIIIIIIIIIIIIIIIIIYYIIIII', 1), ('IIIIIIIIIIIIIIIIIIIIIIIIYYIIII', 1), ('IIIIIIIIIIIIIIIIIIIIIIIIIYYIII', 1), ('IIIIIIIIIIIIIIIIIIIIIIIIIIYYII', 1), ('IIIIIIIIIIIIIIIIIIIIIIIIIIIYYI', 1), ('IIIIIIIIIIIIIIIIIIIIIIIIIIIIYY', 1)]
Parameter für den Algorithmus festlegen
Wir wählen heuristisch einen Wert für den Zeitschritt dt (basierend auf oberen Schranken für die Hamiltonoperator-Norm). Ref [2] zeigte, dass ein ausreichend kleiner Zeitschritt ist, und dass es bis zu einem gewissen Punkt vorzuziehen ist, diesen Wert eher zu unterschätzen als zu überschätzen, da eine Überschätzung es Beitraegen von hochenergetischen Zuständen erlauben kann, selbst den optimalen Zustand im Krylov-Raum zu verfälschen. Andererseits führt die Wahl eines zu kleinen 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)
dt
np.float64(0.10833078115826875)
Und lege weitere Parameter des Algorithmus fest. Im Rahmen dieses Tutorials beschränken wir uns auf einen Krylov-Raum mit nur fünf Dimensionen, was recht einschränkend ist.
# Set parameters for quantum Krylov algorithm
krylov_dim = 5 # size of Krylov subspace
num_trotter_steps = 6
dt_circ = dt / num_trotter_steps
Zustandsvorbereitung
Wähle einen Referenzzustand , der eine gewisse Überlappung mit dem Grundzustand aufweist. Für diesen Hamiltonoperator verwenden wir einen 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)
Zeitentwicklung
Wir können den Zeitentwicklungsoperator, der von einem gegebenen Hamiltonoperator erzeugt wird: , über die Lie-Trotter-Approximation realisieren.
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)
<qiskit.circuit.instructionset.InstructionSet at 0x11eef9be0>
Hadamard-Test
