Diagonalização quântica de Krylov de Hamiltonianos de rede
Estimativa de uso: 20 minutos em um Heron r2 (NOTA: Esta é apenas uma estimativa. O tempo de execução pode variar.)
# 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
Contexto
Este tutorial demonstra como implementar o Algoritmo de Diagonalização Quântica de Krylov (KQD) no contexto dos padrões do Qiskit. Você aprenderá primeiro a teoria por trás do algoritmo e, em seguida, verá uma demonstração de sua execução em uma QPU.
Em diversas áreas do conhecimento, temos interesse em estudar as propriedades do estado fundamental de sistemas quânticos. Exemplos incluem a compreensão da natureza fundamental de partículas e forças, a previsão e compreensão do comportamento de materiais complexos, e o entendimento de interações e reações bioquímicas. Devido ao crescimento exponencial do espaço de Hilbert e às correlações que surgem em sistemas entrelaçados, os algoritmos clássicos têm dificuldade em resolver esse problema para sistemas quânticos de tamanho crescente. Em um extremo do espectro, há a abordagem existente que aproveita o hardware quântico com foco em métodos quânticos variacionais (por exemplo, o solver quântico variacional). Essas técnicas enfrentam desafios com os dispositivos atuais devido ao elevado número de chamadas de função exigidas no processo de otimização, o que adiciona uma grande sobrecarga de recursos quando técnicas avançadas de mitigação de erros são introduzidas, limitando assim sua eficácia a sistemas pequenos. No outro extremo do espectro, existem métodos quânticos tolerantes a falhas com garantias de desempenho (por exemplo, a estimativa de fase quântica), que requerem circuitos profundos que só podem ser executados em um dispositivo tolerante a falhas. Por essas razões, apresentamos aqui um algoritmo quântico baseado em métodos de subespaço (conforme descrito neste artigo de revisão), o algoritmo de diagonalização quântica de Krylov (KQD). Esse algoritmo apresenta bom desempenho em larga escala [1] no hardware quântico atual, compartilha garantias de desempenho semelhantes às da estimativa de fase, é compatível com técnicas avançadas de mitigação de erros e pode fornecer resultados classicamente inacessíveis.
Requisitos
Antes de iniciar este tutorial, certifique-se de ter o seguinte instalado:
- Qiskit SDK v2.0 ou posterior, com suporte a visualização
- Qiskit Runtime v0.22 ou posterior (
pip install qiskit-ibm-runtime)
Configuração
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
Etapa 1: Mapear entradas clássicas para um problema quântico
O espaço de Krylov
O espaço de Krylov de ordem é o espaço gerado pelos vetores obtidos multiplicando-se potências crescentes de uma matriz , até , por um vetor de referência .
Se a matriz for o Hamiltoniano , denominaremos o espaço correspondente de espaço de Krylov de potência . No caso em que é o operador de evolução temporal gerado pelo Hamiltoniano , denominaremos o espaço de espaço de Krylov unitário . O subespaço de Krylov de potência que utilizamos classicamente não pode ser gerado diretamente em um computador quântico, pois não é um operador unitário. Em vez disso, podemos usar o operador de evolução temporal , que pode ser demonstrado fornecer garantias de convergência semelhantes às do método de potência. Potências de tornam-se então diferentes passos de tempo .
Consulte o Apêndice para uma derivação detalhada de como o espaço de Krylov unitário permite representar com precisão os autoestados de baixa energia.
Algoritmo de diagonalização quântica de Krylov
Dado um Hamiltoniano que desejamos diagonalizar, primeiro consideramos o espaço de Krylov unitário correspondente . O objetivo é encontrar uma representação compacta do Hamiltoniano em , que denominaremos . Os elementos de matriz de , a projeção do Hamiltoniano no espaço de Krylov, podem ser calculados avaliando os seguintes valores esperados
Onde são os vetores do espaço de Krylov unitário e são os múltiplos do passo de tempo escolhido. Em um computador quântico, o cálculo de cada elemento de matriz pode ser feito com qualquer algoritmo que permita obter a sobreposição entre estados quânticos. Este tutorial foca no teste de Hadamard. Dado que tem dimensão , o Hamiltoniano projetado no subespaço terá dimensões . Com suficientemente pequeno (em geral, é suficiente para obter a convergência das estimativas de autoenergias), podemos então facilmente diagonalizar o Hamiltoniano projetado . No entanto, não podemos diagonalizar diretamente devido à não-ortogonalidade dos vetores do espaço de Krylov. Precisaremos medir suas sobreposições e construir uma matriz
Isso nos permite resolver o problema de autovalores em um espaço não ortogonal (também chamado de problema de autovalores generalizado)
Pode-se então obter estimativas dos autovalores e autoestados de a partir dos de . Por exemplo, a estimativa da energia do estado fundamental é obtida tomando-se o menor autovalor e o estado fundamental a partir do autovetor correspondente . Os coeficientes em determinam a contribuição dos diferentes vetores que geram .

A figura mostra uma representação em circuito do teste de Hadamard modificado, um método utilizado para calcular a sobreposição entre diferentes estados quânticos. Para cada elemento de matriz , realiza-se um teste de Hadamard entre os estados e . Isso é destacado na figura pelo esquema de cores dos elementos de matriz e pelas operações correspondentes e . Portanto, um conjunto de testes de Hadamard para todas as combinações possíveis de vetores do espaço de Krylov é necessário para calcular todos os elementos de matriz do Hamiltoniano projetado . O fio superior no circuito do teste de Hadamard é um qubit ancila que é medido na base X ou Y; seu valor esperado determina o valor da sobreposição entre os estados. O fio inferior representa todos os qubits do Hamiltoniano do sistema. A operação prepara o qubit do sistema no estado controlado pelo estado do qubit ancila (analogamente para ), e a operação representa a decomposição de Pauli do Hamiltoniano do sistema . Uma derivação mais detalhada das operações calculadas pelo teste de Hadamard é apresentada abaixo.
Definir o Hamiltoniano
Vamos considerar o Hamiltoniano de Heisenberg para qubits em uma cadeia linear:
# 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)]
Definir os parâmetros do algoritmo
Escolhemos heuristicamente um valor para o passo de tempo dt (com base em limitantes superiores da norma do Hamiltoniano). A Ref. [2] mostrou que um passo de tempo suficientemente pequeno é , e que é preferível, até certo ponto, subestimar esse valor em vez de superestimá-lo, pois superestimar pode permitir que contribuições de estados de alta energia corrompam até mesmo o estado ótimo no espaço de Krylov. Por outro lado, escolher muito pequeno leva a um pior condicionamento do subespaço de Krylov, uma vez que os vetores da base de Krylov diferem menos de um passo de tempo para o outro.
# 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 defina os demais parâmetros do algoritmo. Para fins deste tutorial, nos limitaremos a usar um espaço de Krylov com apenas cinco dimensões, o que é bastante restritivo.
# Set parameters for quantum Krylov algorithm
krylov_dim = 5 # size of Krylov subspace
num_trotter_steps = 6
dt_circ = dt / num_trotter_steps
Preparação do estado
Escolha um estado de referência que tenha alguma sobreposição com o estado fundamental. Para este Hamiltoniano, utilizamos um estado com uma excitação no qubit central como nosso estado de referência.
qc_state_prep = QuantumCircuit(n_qubits)
qc_state_prep.x(int(n_qubits / 2) + 1)
qc_state_prep.draw("mpl", scale=0.5)
Evolução temporal
Podemos realizar o operador de evolução temporal gerado por um dado Hamiltoniano: por meio da aproximação de 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>
Teste de Hadamard
