Diagonalisation quantique de Krylov pour les hamiltoniens de réseau
Estimation d'utilisation : 20 minutes sur un Heron r2 (REMARQUE : ceci est une estimation uniquement. Votre temps d'exécution peut varier.)
# This cell is hidden from users – it disables some lint rules
# ruff: noqa: E402 E722 F601
Contexte
Ce tutoriel montre comment implémenter l'algorithme de diagonalisation quantique de Krylov (KQD) dans le cadre des modèles Qiskit. Vous apprendrez d'abord la théorie sous-jacente à l'algorithme, puis vous verrez une démonstration de son exécution sur un QPU.
Dans de nombreuses disciplines, nous cherchons à connaître les propriétés de l'état fondamental des systèmes quantiques. Parmi les exemples, on trouve la compréhension de la nature fondamentale des particules et des forces, la prédiction et la compréhension du comportement de matériaux complexes, ainsi que la compréhension des interactions et réactions biochimiques. En raison de la croissance exponentielle de l'espace de Hilbert et des corrélations qui apparaissent dans les systèmes intriqués, les algorithmes classiques peinent à résoudre ce problème pour des systèmes quantiques de taille croissante. À une extrémité du spectre, l'approche existante qui tire parti du matériel quantique se concentre sur les méthodes quantiques variationnelles (par exemple, le solveur quantique variationnel des valeurs propres). Ces techniques font face à des défis avec les dispositifs actuels en raison du grand nombre d'appels de fonction requis dans le processus d'optimisation, ce qui ajoute une surcharge importante en ressources une fois que des techniques avancées d'atténuation des erreurs sont introduites, limitant ainsi leur efficacité aux petits systèmes. À l'autre extrémité du spectre, il existe des méthodes quantiques tolérantes aux fautes avec des garanties de performance (par exemple, l'estimation de phase quantique), qui nécessitent des circuits profonds ne pouvant être exécutés que sur un dispositif tolérant aux fautes. Pour ces raisons, nous introduisons ici un algorithme quantique basé sur les méthodes de sous-espace (tel que décrit dans cet article de synthèse), l'algorithme de diagonalisation quantique de Krylov (KQD). Cet algorithme fonctionne bien à grande échelle [1] sur le matériel quantique existant, partage des garanties de performance similaires à celles de l'estimation de phase, est compatible avec les techniques avancées d'atténuation des erreurs et pourrait fournir des résultats classiquement inaccessibles.
Prérequis
Avant de commencer ce tutoriel, assurez-vous d'avoir installé les éléments suivants :
- Qiskit SDK v2.0 ou version ultérieure, avec le support de la visualisation
- Qiskit Runtime v0.22 ou version ultérieure (
pip install qiskit-ibm-runtime)
Configuration
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
Étape 1 : Transposer les entrées classiques en un problème quantique
L'espace de Krylov
L'espace de Krylov d'ordre est l'espace engendré par les vecteurs obtenus en multipliant des puissances croissantes d'une matrice , jusqu'à , avec un vecteur de référence .
Si la matrice est le hamiltonien , nous désignerons l'espace correspondant comme l'espace de Krylov en puissances . Dans le cas où est l'opérateur d'évolution temporelle généré par le hamiltonien , nous désignerons l'espace comme l'espace de Krylov unitaire . Le sous-espace de Krylov en puissances que nous utilisons classiquement ne peut pas être généré directement sur un ordinateur quantique car n'est pas un opérateur unitaire. En revanche, nous pouvons utiliser l'opérateur d'évolution temporelle qui offre des garanties de convergence similaires à celles de la méthode des puissances. Les puissances de deviennent alors différents pas de temps .
Consultez l'annexe pour une dérivation détaillée montrant comment l'espace de Krylov unitaire permet de représenter avec précision les états propres de basse énergie.
Algorithme de diagonalisation quantique de Krylov
Étant donné un hamiltonien que nous souhaitons diagonaliser, nous considérons d'abord l'espace de Krylov unitaire correspondant . L'objectif est de trouver une représentation compacte du hamiltonien dans , que nous désignerons par . Les éléments de matrice de , la projection du hamiltonien dans l'espace de Krylov, peuvent être calculés en évaluant les valeurs moyennes suivantes
Où sont les vecteurs de l'espace de Krylov unitaire et sont les multiples du pas de temps choisi. Sur un ordinateur quantique, le calcul de chaque élément de matrice peut être effectué avec tout algorithme permettant d'obtenir le recouvrement entre états quantiques. Ce tutoriel se concentre sur le test de Hadamard. Étant donné que a une dimension , le hamiltonien projeté dans le sous-espace aura des dimensions . Avec suffisamment petit (généralement suffit pour obtenir la convergence des estimations des énergies propres), nous pouvons alors facilement diagonaliser le hamiltonien projeté . Cependant, nous ne pouvons pas diagonaliser directement en raison de la non-orthogonalité des vecteurs de l'espace de Krylov. Nous devrons mesurer leurs recouvrements et construire une matrice
Cela nous permet de résoudre le problème aux valeurs propres dans un espace non orthogonal (également appelé problème aux valeurs propres généralisé)
On peut alors obtenir des estimations des valeurs propres et des états propres de en examinant ceux de . Par exemple, l'estimation de l'énergie de l'état fondamental est obtenue en prenant la plus petite valeur propre et l'état fondamental à partir du vecteur propre correspondant . Les coefficients dans déterminent la contribution des différents vecteurs qui engendrent .

La figure montre une représentation en circuit du test de Hadamard modifié, une méthode utilisée pour calculer le recouvrement entre différents états quantiques. Pour chaque élément de matrice , un test de Hadamard entre l'état et est effectué. Ceci est mis en évidence dans la figure par le code couleur des éléments de matrice et les opérations correspondantes , . Ainsi, un ensemble de tests de Hadamard pour toutes les combinaisons possibles de vecteurs de l'espace de Krylov est nécessaire pour calculer tous les éléments de matrice du hamiltonien projeté . Le fil supérieur dans le circuit du test de Hadamard est un qubit ancillaire qui est mesuré soit dans la base X, soit dans la base Y ; sa valeur moyenne détermine la valeur du recouvrement entre les états. Le fil inférieur représente tous les qubits du hamiltonien du système. L'opération prépare le qubit du système dans l'état de manière contrôlée par l'état du qubit ancillaire (de même pour ), et l'opération représente la décomposition de Pauli du hamiltonien du système . Une dérivation plus détaillée des opérations calculées par le test de Hadamard est donnée ci-dessous.
Définition du hamiltonien
Considérons le hamiltonien de Heisenberg pour qubits sur une chaîne linéaire :
# 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)]
Définition des paramètres de l'algorithme
Nous choisissons heuristiquement une valeur pour le pas de temps dt (basée sur des bornes supérieures de la norme du hamiltonien). La référence [2] a montré qu'un pas de temps suffisamment petit est , et qu'il est préférable, jusqu'à un certain point, de sous-estimer cette valeur plutôt que de la surestimer, car une surestimation peut permettre aux contributions des états de haute énergie de corrompre même l'état optimal dans l'espace de Krylov. En revanche, choisir un trop petit conduit à un moins bon conditionnement du sous-espace de Krylov, puisque les vecteurs de la base de Krylov diffèrent moins d'un pas de temps à l'autre.
# 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)
Nous définissons ensuite les autres paramètres de l'algorithme. Dans le cadre de ce tutoriel, nous nous limiterons à utiliser un espace de Krylov de seulement cinq dimensions, ce qui est assez restrictif.
# Set parameters for quantum Krylov algorithm
krylov_dim = 5 # size of Krylov subspace
num_trotter_steps = 6
dt_circ = dt / num_trotter_steps