Diagonalizzazione quantistica di Krylov di Hamiltoniane su reticolo
Stima di utilizzo: 20 minuti su un Heron r2 (NOTA: Questa è solo una stima. Il vostro tempo di esecuzione potrebbe variare.)
# 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
Contesto
Questo tutorial dimostra come implementare l'Algoritmo di Diagonalizzazione Quantistica di Krylov (KQD) nel contesto dei pattern Qiskit. Imparerete prima la teoria alla base dell'algoritmo e poi vedrete una dimostrazione della sua esecuzione su una QPU.
In diverse discipline, siamo interessati a conoscere le proprietà dello stato fondamentale dei sistemi quantistici. Gli esempi includono la comprensione della natura fondamentale delle particelle e delle forze, la previsione e la comprensione del comportamento di materiali complessi e la comprensione delle interazioni e reazioni biochimiche. A causa della crescita esponenziale dello spazio di Hilbert e della correlazione che si manifesta nei sistemi entangled, gli algoritmi classici faticano a risolvere questo problema per sistemi quantistici di dimensioni crescenti. A un'estremità dello spettro vi è l'approccio esistente che sfrutta l'hardware quantistico concentrandosi sui metodi quantistici variazionali (ad esempio, variational quantum eigensolver). Queste tecniche affrontano sfide con i dispositivi attuali a causa dell'elevato numero di chiamate di funzione richieste nel processo di ottimizzazione, che aggiunge un grande overhead in risorse una volta introdotte tecniche avanzate di mitigazione degli errori, limitando così la loro efficacia a sistemi di piccole dimensioni. All'altra estremità dello spettro, vi sono metodi quantistici fault-tolerant con garanzie di prestazioni (ad esempio, quantum phase estimation), che richiedono circuiti profondi che possono essere eseguiti solo su un dispositivo fault-tolerant. Per questi motivi, introduciamo qui un algoritmo quantistico basato su metodi di sottospazio (come descritto in questo review paper), l'algoritmo di diagonalizzazione quantistica di Krylov (KQD). Questo algoritmo funziona bene su larga scala [1] sull'hardware quantistico esistente, condivide garanzie di prestazioni simili alla stima di fase, è compatibile con tecniche avanzate di mitigazione degli errori e potrebbe fornire risultati che sono classicamente inaccessibili.
Requisiti
Prima di iniziare questo tutorial, assicuratevi di avere installato quanto segue:
- Qiskit SDK v2.0 o successivo, con supporto per la visualizzazione
- Qiskit Runtime v0.22 o successivo (
pip install qiskit-ibm-runtime)
Configurazione
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
Step 1: Mappare gli input classici a un problema quantistico
Lo spazio di Krylov
Lo spazio di Krylov di ordine è lo spazio generato dai vettori ottenuti moltiplicando potenze più elevate di una matrice , fino a , con un vettore di riferimento .
Se la matrice è l'Hamiltoniana , faremo riferimento allo spazio corrispondente come spazio di Krylov di potenza . Nel caso in cui sia l'operatore di evoluzione temporale generato dall'Hamiltoniana , faremo riferimento allo spazio come spazio di Krylov unitario . Il sottospazio di Krylov di potenza che utilizziamo classicamente non può essere generato direttamente su un computer quantistico poiché non è un operatore unitario. Invece, possiamo usare l'operatore di evoluzione temporale che può essere dimostrato fornire garanzie di convergenza simili al metodo delle potenze. Le potenze di diventano quindi diversi passi temporali .
Consultate l'Appendice per una derivazione dettagliata di come lo spazio di Krylov unitario permetta di rappresentare accuratamente gli autostati a bassa energia.
Algoritmo di diagonalizzazione quantistica di Krylov
Data un'Hamiltoniana che desideriamo diagonalizzare, consideriamo prima il corrispondente spazio di Krylov unitario . L'obiettivo è trovare una rappresentazione compatta dell'Hamiltoniana in , che chiameremo . Gli elementi di matrice di , la proiezione dell'Hamiltoniana nello spazio di Krylov, possono essere calcolati calcolando i seguenti valori di aspettazione
Dove sono i vettori dello spazio di Krylov unitario e sono i multipli del passo temporale scelto. Su un computer quantistico, il calcolo di ciascun elemento di matrice può essere effettuato con qualsiasi algoritmo che permetta di ottenere la sovrapposizione tra stati quantistici. Questo tutorial si concentra sul test di Hadamard. Dato che ha dimensione , l'Hamiltoniana proiettata nel sottospazio avrà dimensioni . Con sufficientemente piccolo (generalmente è sufficiente per ottenere la convergenza delle stime delle autoenegie) possiamo quindi facilmente diagonalizzare l'Hamiltoniana proiettata . Tuttavia, non possiamo diagonalizzare direttamente a causa della non-ortogonalità dei vettori dello spazio di Krylov. Dovremo misurare le loro sovrapposizioni e costruire una matrice
Questo ci permette di risolvere il problema agli autovalori in uno spazio non ortogonale (chiamato anche problema agli autovalori generalizzato)
Si possono quindi ottenere stime degli autovalori e degli autostati di osservando quelli di . Ad esempio, la stima dell'energia dello stato fondamentale si ottiene prendendo l'autovalore più piccolo e lo stato fondamentale dal corrispondente autovettore . I coefficienti in determinano il contributo dei diversi vettori che generano .

La figura mostra una rappresentazione circuitale del test di Hadamard modificato, un metodo utilizzato per calcolare la sovrapposizione tra diversi stati quantistici. Per ogni elemento di matrice , viene eseguito un test di Hadamard tra lo stato , . Questo è evidenziato nella figura dallo schema di colori per gli elementi di matrice e le corrispondenti operazioni , . Pertanto, è necessario un insieme di test di Hadamard per tutte le possibili combinazioni di vettori dello spazio di Krylov per calcolare tutti gli elementi di matrice dell'Hamiltoniana proiettata . Il filo superiore nel circuito del test di Hadamard è un qubit ancilla che viene misurato nella base X o Y, il suo valore di aspettazione determina il valore della sovrapposizione tra gli stati. Il filo inferiore rappresenta tutti i qubit dell'Hamiltoniana del sistema. L'operazione prepara il qubit del sistema nello stato controllato dallo stato del qubit ancilla (similmente per ) e l'operazione rappresenta la decomposizione di Pauli dell'Hamiltoniana del sistema . Una derivazione più dettagliata delle operazioni calcolate dal test di Hadamard è fornita di seguito.
Definire l'Hamiltoniana
Consideriamo l'Hamiltoniana di Heisenberg per qubit su una catena lineare:
# 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)]
Impostare i parametri per l'algoritmo
Scegliamo euristicamente un valore per il passo temporale dt (basato su limiti superiori della norma dell'Hamiltoniana). Il riferimento [2] ha dimostrato che un passo temporale sufficientemente piccolo è , e che è preferibile fino a un certo punto sottostimare questo valore piuttosto che sovrastimarlo, poiché sovrastimarlo può permettere ai contributi degli stati ad alta energia di corrompere anche lo stato ottimale nello spazio di Krylov. D'altra parte, scegliere troppo piccolo porta a un peggiore condizionamento del sottospazio di Krylov, poiché i vettori di base di Krylov differiscono meno da un passo temporale all'altro.
# Get Hamiltonian restricted to single-particle states
single_particle_H = np.zeros((n_qubits, n_qubits))
for i in range(n_qubits):
for j in range(i + 1):
for p, coeff in H_op.to_list():
p_x = Pauli(p).x
p_z = Pauli(p).z
if all(
p_x[k] == ((i == k) + (j == k)) % 2 for k in range(n_qubits)
):
sgn = (
(-1j) ** sum(p_z[k] and p_x[k] for k in range(n_qubits))
) * ((-1) ** p_z[i])
else:
sgn = 0
single_particle_H[i, j] += sgn * coeff
for i in range(n_qubits):
for j in range(i + 1, n_qubits):
single_particle_H[i, j] = np.conj(single_particle_H[j, i])
# Set dt according to spectral norm
dt = np.pi / np.linalg.norm(single_particle_H, ord=2)
dt
np.float64(0.10833078115826875)
E impostiamo altri parametri dell'algoritmo. Per il bene di questo tutorial, ci limiteremo a utilizzare uno spazio di Krylov con solo cinque dimensioni, il che è abbastanza limitante.
# Set parameters for quantum Krylov algorithm
krylov_dim = 5 # size of Krylov subspace
num_trotter_steps = 6
dt_circ = dt / num_trotter_steps
Preparazione dello stato
Scegliete uno stato di riferimento che abbia una certa sovrapposizione con lo stato fondamentale. Per questa Hamiltoniana, utilizziamo uno stato con un'eccitazione nel qubit centrale come nostro stato di riferimento.
qc_state_prep = QuantumCircuit(n_qubits)
qc_state_prep.x(int(n_qubits / 2) + 1)
qc_state_prep.draw("mpl", scale=0.5)
Evoluzione temporale
Possiamo realizzare l'operatore di evoluzione temporale generato da una data Hamiltoniana: tramite l'approssimazione di Lie-Trotter.
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>
Test di Hadamard

Dove è uno dei termini nella decomposizione dell'Hamiltoniana e , sono operazioni controllate che preparano , vettori dello spazio di Krylov unitario, con . Per misurare , applicare prima ...
... poi misurare:
Dall'identità . Analogamente, misurare produce
## 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)
Circuit for calculating the real part of the overlap in S via Hadamard test
Il circuito del test di Hadamard può essere un circuito profondo una volta decomposto in porte native (che aumenterà ancora di più se teniamo conto della topologia del dispositivo)
print(
"Number of layers of 2Q operations",
qc_real.decompose(reps=2).depth(lambda x: x[0].num_qubits == 2),
)
Number of layers of 2Q operations 112753
Step 2: Ottimizzare il problema per l'esecuzione su hardware quantistico
Test di Hadamard efficiente
Possiamo ottimizzare i circuiti profondi per il test di Hadamard che abbiamo ottenuto introducendo alcune approssimazioni e facendo affidamento su alcune assunzioni sull'Hamiltoniana del modello. Ad esempio, considerate il seguente circuito per il test di Hadamard:

Supponiamo di poter calcolare classicamente , l'autovalore di sotto l'Hamiltoniana . Questa condizione è soddisfatta quando l'Hamiltoniana preserva la simmetria U(1). Sebbene questa possa sembrare un'assunzione forte, ci sono molti casi in cui è sicuro assumere che esista uno stato di vuoto (in questo caso corrisponde allo stato ) che non è influenzato dall'azione dell'Hamiltoniana. Questo è vero ad esempio per le Hamiltoniane di chimica che descrivono molecole stabili (dove il numero di elettroni è conservato). Dato che il gate prepara lo stato di riferimento desiderato , ad esempio, per preparare lo stato HF per la chimica sarebbe un prodotto di NOT a singolo qubit, quindi controlled- è semplicemente un prodotto di CNOT. Allora il circuito sopra implementa il seguente stato prima della misura:
dove abbiamo usato la fase shift classicamente simulabile nella terza riga. Pertanto i valori di aspettazione si ottengono come