Sari la conținutul principal

Estimarea energiei stării fundamentale a lanțului Heisenberg cu VQE

Estimare de utilizare: 37 de minute pe un procesor Heron (NOTĂ: Aceasta este doar o estimare. Timpul tău de execuție poate varia.)

Rezultate ale învățării

După finalizarea acestui tutorial, te poți aștepta să înțelegi următoarele informații:

  • Cum să modelezi un lanț de spinuri Heisenberg ca Hamiltonian cuantic folosind Qiskit
  • Cum să folosești optimizatorul SPSA pentru a estima energia stării fundamentale a unui sistem cuantic
  • Cum să execuți fluxuri de lucru variaționali pe hardware cuantic IBM® folosind primitivele și sesiunile Qiskit Runtime

Cerințe prealabile

Se recomandă să te familiarizezi cu aceste subiecte:

Context

Lanțul de spinuri Heisenberg este unul dintre cele mai studiate modele din fizica materiei condensate și magnetismul cuantic. Descrie o rețea unidimensională de spinuri cuantice interacționante, unde spinurile vecine sunt cuplate prin interacțiuni de schimb. Hamiltonianul pentru modelul Heisenberg izotrop cu un câmp magnetic extern este dat de:

H=i,j(JxXiXj+JyYiYj+JzZiZj)+ihiZi,H = \sum_{\langle i,j \rangle} \left( J_x X_i X_j + J_y Y_i Y_j + J_z Z_i Z_j \right) + \sum_{i} h_i Z_i,

unde XiX_i, YiY_i și ZiZ_i sunt operatorii Pauli care acționează pe site-ul ii, suma i,j\langle i,j \rangle rulează peste perechile de vecini apropiați, Jx=Jy=Jz=0.5J_x = J_y = J_z = 0.5 sunt constantele de cuplare de schimb (izotrope în acest tutorial), iar hih_i reprezintă un câmp magnetic extern dependent de site. În acest tutorial, valorile câmpului magnetic sunt eșantionate aleator din intervalul [1,1][-1, 1]. Rețineți că în implementarea de mai jos, setul de perechi „vecini apropiați" este determinat de cuplarea nativă a hardware-ului backend între primii NN qubiți, care ar putea să nu formeze un lanț liniar strict în funcție de topologia dispozitivului.

Înțelegerea energiei stării fundamentale a acestui Hamiltonian este de o importanță fundamentală în fizică. Starea fundamentală codifică informații despre tranzițiile de fază cuantice, structura entanglementului și ordonarea magnetică. Clasic, calculul exact al energiei stării fundamentale devine intractabil pe măsură ce numărul de spinuri crește, deoarece dimensiunea spațiului Hilbert scalează exponențial ca 2N2^N pentru NN spinuri. Aceasta îl face un candidat natural pentru simularea cuantică.

Eigensolver-ul Cuantic Variațional (VQE) este un algoritm hibrid cuantic-clasic conceput pentru a estima energia stării fundamentale a unui Hamiltonian. Funcționează prin pregătirea unei stări cuantice parametrizate ψ(θ)|\psi(\theta)\rangle (numită ansatz) pe un calculator cuantic și măsurarea valorii de așteptare ψ(θ)Hψ(θ)\langle \psi(\theta) | H | \psi(\theta) \rangle. Un optimizator clasic ajustează apoi iterativ parametrii θ\theta pentru a minimiza această energie, valorificând principiul variațional care garantează că energia măsurată este întotdeauna o limită superioară pentru energia adevărată a stării fundamentale.

În acest tutorial, folosim ansatz-ul efficient_su2 din biblioteca de circuite Qiskit, care construiește straturi de rotații pe un singur qubit și porți de entanglement. Optimizarea este realizată folosind algoritmul Simultaneous Perturbation Stochastic Approximation (SPSA), care este bine adaptat pentru hardware cuantic zgomotos deoarece estimează gradienții folosind doar două evaluări de funcție pe iterație, indiferent de numărul de parametri.

Cerințe

Înainte de a începe acest tutorial, asigură-te că ai instalate următoarele:

  • Qiskit SDK v2.0 sau o versiune mai recentă, cu suport pentru vizualizare
  • Qiskit Runtime v0.44 sau o versiune mai recentă (pip install qiskit-ibm-runtime)

Configurare

# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-ibm-runtime
import numpy as np
import matplotlib.pyplot as plt
from typing import Sequence

from qiskit import QuantumCircuit
from qiskit.quantum_info import SparsePauliOp
from qiskit.primitives import BaseEstimatorV2
from qiskit.circuit.library import XGate
from qiskit.circuit.library import efficient_su2
from qiskit.transpiler import PassManager
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit.transpiler.passes.scheduling import (
ALAPScheduleAnalysis,
PadDynamicalDecoupling,
)
from qiskit_ibm_runtime import QiskitRuntimeService, Session, EstimatorV2

def visualize_results(results):
plt.plot(results["cost_history"], lw=2)
plt.xlabel("Number of function evaluations")
plt.ylabel("Energy")
plt.show()

Exemplu la scară mică

În această secțiune, parcurgem fiecare pas al tiparului Qiskit la scară mică, explicând componentele cheie pe măsură ce construim fluxul de lucru.

Pasul 1: Maparea intrărilor clasice la o problemă cuantică

  • Intrare: Numărul de spinuri
  • Ieșire: Ansatz și Hamiltonian care modelează lanțul Heisenberg

Construiește un ansatz și un Hamiltonian care modelează un lanț Heisenberg cu 10 spinuri. În acest pas, vom construi un Hamiltonian Heisenberg cu 10 spinuri peste harta de cuplare a backend-ului cel mai puțin aglomerat și vom pregăti ansatz-ul efficient_su2.

num_spins = 10
ansatz = efficient_su2(num_qubits=num_spins, reps=2)

service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, min_num_qubits=num_spins, simulator=False
)

coupling = backend.target.build_coupling_map()
reduced_coupling = coupling.reduce(list(range(num_spins)))

edge_list = reduced_coupling.graph.edge_list()
ham_list = []

for edge in edge_list:
ham_list.append(("ZZ", edge, 0.5))
ham_list.append(("YY", edge, 0.5))
ham_list.append(("XX", edge, 0.5))

for qubit in reduced_coupling.physical_qubits:
ham_list.append(("Z", [qubit], np.random.random() * 2 - 1))

hamiltonian = SparsePauliOp.from_sparse_list(ham_list, num_qubits=num_spins)

ansatz.draw("mpl", style="iqp")

Output of the previous code cell

Pasul 2: Optimizarea problemei pentru execuția pe hardware cuantic

  • Intrare: Circuit abstract, observabilă
  • Ieșire: Circuit și observabilă țintă, optimizate pentru QPU selectat

Folosește funcția generate_preset_pass_manager din Qiskit pentru a genera automat o rutină de optimizare pentru circuitul nostru în raport cu QPU selectat. Alegem optimization_level=3, care oferă cel mai înalt nivel de optimizare al managerilor de pase presetate. Includem, de asemenea, pase de planificare ALAPScheduleAnalysis și PadDynamicalDecoupling pentru a suprima erorile de decoerență.

target = backend.target
pm = generate_preset_pass_manager(optimization_level=3, target=target)
pm.scheduling = PassManager(
[
ALAPScheduleAnalysis(durations=target.durations()),
PadDynamicalDecoupling(
durations=target.durations(),
dd_sequence=[XGate(), XGate()],
pulse_alignment=target.pulse_alignment,
),
]
)
isa_ansatz = pm.run(ansatz)
isa_observable = hamiltonian.apply_layout(isa_ansatz.layout)
isa_ansatz.draw("mpl", scale=0.6, style="iqp", fold=-1, idle_wires=False)

Output of the previous code cell

Pasul 3: Execuția folosind primitivele Qiskit

  • Intrare: Circuit și observabilă țintă
  • Ieșire: Rezultatele optimizării

Minimizează energia estimată a stării fundamentale a sistemului prin optimizarea parametrilor circuitului. Folosește primitiva Estimator din Qiskit Runtime pentru a evalua funcția de cost în timpul optimizării.

Deoarece am optimizat circuitul pentru backend în Pasul 2, putem evita transpilarea pe serverul Runtime setând skip_transpilation=True și transmițând circuitul optimizat. Pentru această demonstrație, vom rula pe un QPU folosind primitivele qiskit-ibm-runtime. Pentru a rula cu primitivele bazate pe statevector din qiskit, înlocuiește blocul de cod care folosește primitivele Qiskit Runtime cu blocul comentat.

În acest tutorial folosim Simultaneous Perturbation Stochastic Approximation (SPSA), care este un optimizator bazat pe gradient. În continuare oferim o scurtă introducere în el și codul pentru a implementa SPSA folosind Qiskit v2.0.

Introducere în SPSA

Simultaneous Perturbation Stochastic Approximation (SPSA) [1] este un algoritm de optimizare care aproximează întregul vector gradient folosind doar două apeluri de funcție la fiecare iterație. Fie f:RpRf:\mathbb{R}^p\rightarrow \mathbb{R} funcția de cost cu pp parametri de optimizat, și xiRpx_i\in \mathbb{R}^p vectorul de parametri la pasul ithi^{th} al iterației. Pentru a calcula gradientul, se creează un vector aleator Δi\Delta_i de dimensiune pp, unde fiecare element Δij\Delta_{ij}, \forall j{1,2,...,p}j\in \{1,2,...,p\}, este eșantionat uniform din {1,1}\{-1, 1\}. Apoi, fiecare element al vectorului aleator Δi\Delta_i este înmulțit cu o valoare mică cic_i pentru a crea o perturbare aleatoare. Gradientul este estimat astfel:

[f(xi)]jf(xi+ciΔi)f(xiciΔi)2ciΔij.[\nabla f(x_i)]_j \approx \frac{f(x_i + c_i \Delta_i) - f(x_i - c_i \Delta_i)}{2c_i\Delta_{ij}}.

Intuitiv, deoarece o perturbare aleatoare este aplicată în timpul estimării gradientului, se preconizează că mici deviații în valorile exacte ale ff provenite din zgomot pot fi tolerate și compensate. De fapt, SPSA este cunoscut în special pentru robustețea sa față de zgomot și necesită doar două apeluri hardware pentru fiecare iterație. Prin urmare, este unul dintre optimizatoarele preferate pentru implementarea algoritmilor variaționali.

În acest tutorial, hiperparametrii pentru iterația ithi^{th}, aia_i și cic_i, sunt calculați ca

ai=a(A+i+1)αandci=c(i+1)γ,a_i = \frac{a}{(A + i + 1)^\alpha} \quad \text{and} \quad c_i = \frac{c}{(i+1)^\gamma},

unde valorile constante sunt A=30A = 30, α=0.9\alpha = 0.9, a=0.3a = 0.3, c=0.1c = 0.1 și γ=0.4\gamma = 0.4. Aceste valori sunt selectate din [2]. Ajustarea adecvată a hiperparametrilor este necesară pentru a extrage o performanță bună din SPSA.

def spsa(
fun, x0, args=(), A=30, alpha=0.9, a=0.3, c=0.1, gamma=0.4, maxiter=100
):
nparams = len(x0)
x = np.copy(x0)

for i in range(maxiter):
a_i = a / (A + i + 1) ** alpha
c_i = c / (i + 1) ** gamma
delta_i = np.random.choice([-1, 1], nparams)

# two hardware calls
eval_1 = fun(x + c_i * delta_i, *args)
eval_2 = fun(x - c_i * delta_i, *args)

# compute the gradient and update the parameters
grad = (eval_1 - eval_2) / (2 * c_i) * np.reciprocal(delta_i)
x = x - a_i * grad

return x
def cost_func(
params: Sequence,
ansatz: QuantumCircuit,
hamiltonian: SparsePauliOp,
estimator: BaseEstimatorV2,
cost_history_dict: dict,
) -> float:
"""Ground state energy evaluation."""
energy = (
estimator.run([(ansatz, hamiltonian, [params])]).result()[0].data.evs
)

cost_history_dict["iters"] += 1
cost_history_dict["prev_vector"] = list(params)
cost_history_dict["cost_history"].append(float(energy[0]))

print(
f"Fx Iters. done: {cost_history_dict['iters']} [Current cost: {round(energy[0], 5)}]",
end="\r",
)

return energy

def solve(x0, isa_ansatz, isa_observable, maxiter=150):
cost_history_dict = {
"prev_vector": None,
"iters": 0,
"cost_history": [],
"y_min": None,
}

# Evaluate the problem using a QPU via Qiskit IBM Runtime
with Session(backend=backend) as session:
estimator = EstimatorV2(mode=session)
estimator.skip_transpilation = True
estimator.options.environment.job_tags = ["TUT_HSVQE"]
x_opt = spsa(
cost_func,
x0=x0,
args=(isa_ansatz, isa_observable, estimator, cost_history_dict),
maxiter=maxiter,
)

y_min = cost_func(
x_opt, isa_ansatz, isa_observable, estimator, cost_history_dict
)

return y_min, cost_history_dict
np.random.seed(42)
num_params = ansatz.num_parameters
params = 2 * np.pi * np.random.random(num_params)

Aici setăm maxiter = 50. Rețineți că deoarece fiecare iterație necesită două apeluri la funcție pentru a calcula gradientul, numărul total de apeluri de funcție va fi 2×maxiter2 \times \text{maxiter}. maxiter poate fi crescut la orice valoare mai mare pentru o estimare mai bună a energiei.

maxiter = 50
spsa_min, spsa_history = solve(
params, isa_ansatz, isa_observable, maxiter=maxiter
)
Fx Iters. done: 101 [Current cost: -3.03843]

Pasul 4: Post-procesarea și returnarea rezultatului în formatul clasic dorit

  • Intrare: Estimările energiei stării fundamentale în timpul optimizării
  • Ieșire: Energia estimată a stării fundamentale
print(f"Estimated ground state energy: {spsa_min}")
Estimated ground state energy: [-3.03842968]
results = {
"spsa": spsa_history,
}

visualize_results(spsa_history)

Output of the previous code cell

Exemplu hardware la scară largă

Un exemplu hardware la scară largă nu este inclus în acest tutorial. Pe măsură ce numărul de qubiți crește, VQE întâmpină provocări semnificative din cauza fenomenului barren plateau: gradientul funcției de cost dispare exponențial cu dimensiunea sistemului, făcând optimizarea practic imposibilă pentru circuite mari. Combinat cu zgomotul hardware, aceasta înseamnă că scalarea VQE la lanțuri de spinuri mai mari nu produce rezultate reproductibile fiabil. Pentru abordări care depășesc aceste limitări, consultă secțiunea Pași următori de mai jos.

Provocare

Acum că ai o implementare VQE funcțională pentru lanțul Heisenberg, încearcă următoarele:

  1. Experimentează cu adâncimea ansatz: Modifică parametrul reps în efficient_su2 (de exemplu, încearcă reps=1 și reps=3). Cum afectează adâncimea ansatz energia estimată a stării fundamentale și viteza de convergență? La ce punct observi randamente descrescătoare sau instabilitate?
  2. Ajustează hiperparametrii SPSA: Ajustează parametrii programului de rată de învățare (a, c, alpha, gamma, A) și observă cum îi afectează convergența. Poți găsi o configurație care converge mai rapid decât valorile implicite folosite aici?
  3. Compară topologiile de cuplare: În loc să folosești harta de cuplare nativă a backend-ului, încearcă să construiești un lanț liniar simplu cu vecini apropiați și compară rezultatele. Cum afectează conectivitatea hardware-ului fizic adâncimea circuitului transpilat și estimarea finală a energiei?

Referințe

[1] Spall, J. C. (2002). Implementation of the simultaneous perturbation algorithm for stochastic optimization. IEEE Transactions on Aerospace and Electronic Systems, 34(3), 817-823.

[2] Sahin, M. Emre, et al. (2025). Qiskit Machine Learning: an open-source library for quantum machine learning tasks at scale on quantum hardware and classical simulators. arXiv:2505.17756.

Pași următori

Recomandări

Dacă ți s-a părut interesant acest material, poate fi util și:

  • Încearcă Diagonalizarea Cuantică Bazată pe Eșantionare (SQD): Așa cum s-a demonstrat în acest tutorial, VQE se confruntă cu provocări la scară din cauza barren plateau-urilor și a supraîncărcării ridicate de măsurători. IBM a dezvoltat Diagonalizarea Cuantică Bazată pe Eșantionare (SQD) ca o alternativă mai scalabilă. Spre deosebire de VQE, SQD evită complet optimizarea variaționalistă; în schimb, un calculator cuantic generează eșantioane, iar un calculator clasic proiectează Hamiltonianul pe un subspațiu generat de acele eșantioane și îl diagonalizează. Aceasta oferă o limită superioară pentru energia stării fundamentale cu semnificativ mai puține măsurători și fără susceptibilitate la barren plateau-uri. Urmează tutorialul SQD pentru a vedea această abordare în acțiune.
  • Explorează cursul Algoritmi de Diagonalizare Cuantică: Aprofundează înțelegerea atât a VQE, cât și a SQD, inclusiv compromisurile lor, în cursul Algoritmi de diagonalizare cuantică de pe IBM Quantum Learning.