Vzorkování-based Krylovova kvantová diagonalizace fermionického mřížkového modelu
Odhad využití: Devět sekund na procesoru Heron r2 (POZNÁMKA: Toto je pouze odhad. Tvůj skutečný čas se může lišit.)
Výsledky učení
-
Jak používat SQD Qiskit addon k aproximaci energie základního stavu mřížkového modelu pomocí bitstrings vzorkovaných z kvantového procesoru (QPU).
-
Jak používat ffsim k sestavení okruhů časového vývoje pro fermionickou simulaci.
-
Jak kombinovat vzorky z více okruhů pro post-processing pomocí algoritmu vzorkování-based Krylovovy diagonalizace (SKQD).
Předpoklady
Pozadí
Tento tutoriál ukazuje, jak použít vzorkování-based kvantovou diagonalizaci (SQD) k odhadnutí energie základního stavu fermionického mřížkového modelu. Konkrétně studujeme jednorozměrný model single-impurity Anderson (SIAM), který se používá k popisu magnetických nečistot zakotvených v kovech.
Tento tutoriál sleduje podobný postup jako příbuzný tutoriál Kvantová diagonalizace chemického hamiltoniánu založená na vzorkování. Klíčový rozdíl spočívá v tom, jak jsou sestaveny kvantové Circuit. Druhý tutoriál používá heuristický variační ansatz, který je atraktivní pro chemické hamiltoniány s potenciálně miliony interakčních členů. Na druhou stranu, tento tutoriál používá Circuit, které aproximují časový vývoj podle hamiltoniánu. Takovéto Circuit mohou být hluboké, a proto se tento přístup lépe hodí pro aplikace na mřížkové modely. Stavové vektory připravené těmito Circuit tvoří základ pro Krylovův podprostor, a v důsledku toho algoritmus prokazatelně a efektivně konverguje k základnímu stavu za vhodných předpokladů.
Přístup použitý v tomto tutoriálu lze chápat jako kombinaci technik použitých v SQD a Krylovově kvantové diagonalizaci (KQD). Kombinovaný přístup se někdy označuje jako vzorkování-based Krylovova kvantová diagonalizace (SQKD). Viz Krylovova kvantová diagonalizace mřížkových hamiltoniánů pro tutoriál o metodě KQD.
Tento tutoriál vychází z práce "Quantum-Centric Algorithm for Sample-Based Krylov Diagonalization", na kterou se můžeš odkázat pro více podrobností.
Model single-impurity Anderson (SIAM)
Jednorozměrný hamiltonián SIAM je součtem tří členů:
kde
Zde jsou fermionské operátory vytváření/anihilace pro místo lázně se spinem , jsou operátory vytváření/anihilace pro módu nečistoty, a . , a jsou reálná čísla popisující hopping, on-site a hybridizační interakce, a je reálné číslo specifikující chemický potenciál.
Všimni si, že hamiltonián je konkrétní instance obecného hamiltoniánu interagujících elektronů,
kde sestává z jednočásticových členů, které jsou kvadratické ve fermionských operátorech vytváření a anihilace, a sestává z dvoučásticových členů, které jsou kvartické. Pro SIAM,
a obsahuje zbytek členů hamiltoniánu. Abychom hamiltonián reprezentovali programově, ukládáme matici a tenzor .
Polohová a hybnostní báze
Kvůli přibližné translační symetrii v neočekáváme, že základní stav bude řídký v polohové bázi (orbitální báze, v níž je hamiltonián výše specifikován). Výkon SQD je zaručen pouze tehdy, pokud je základní stav řídký, tj. má výraznou váhu pouze na malém počtu stavů výpočetní báze. Abychom zlepšili řídkost základního stavu, provádíme simulaci v orbitální bázi, ve které je diagonální. Tuto bázi nazýváme hybnostní báze. Protože je kvadratický fermionský hamiltonián, lze jej efektivně diagonalizovat orbitální rotací.
Přibližný časový vývoj podle hamiltoniánu
K aproximaci časového vývoje podle hamiltoniánu používáme Trotter-Suzukiho rozklad druhého řádu,
Pomocí Jordanovy-Wignerovy transformace se časový vývoj podle rovná jednomu Gate CPhase mezi spin-up a spin-down orbitaly na místě nečistoty. Protože je kvadratický fermionský hamiltonián, časový vývoj podle odpovídá orbitální rotaci.
Krylovovy bázové stavy , kde je dimenze Krylovova podprostoru, jsou tvořeny opakovanou aplikací jednoho Trotterova kroku, takže
V následujícím pracovním postupu založeném na SQD budeme vzorkovat z této sady Circuit a post-procesovat kombinovanou sadu bitstrings pomocí SQD. Tento přístup je v kontrastu s přístupem používaným v příbuzném tutoriálu Vzorkování-based kvantová diagonalizace chemického hamiltoniánu, kde byly vzorky odebírány z jednoho heuristického variačního Circuit.
Požadavky
Před zahájením tohoto tutoriálu se ujisti, že máš nainstalováno následující:
-
Qiskit SDK v1.0 nebo novější, s podporou vizualizace
-
Qiskit Runtime v0.22 nebo novější (
pip install qiskit-ibm-runtime) -
SQD Qiskit addon v0.14 nebo novější (
pip install qiskit-addon-sqd) -
ffsim v0.0.72 nebo novější (
pip install ffsim)
Příklad s malým simulátorem
Krok 1: Namapuj problém na kvantový Circuit
Nejprve vygenerujeme hamiltonián SIAM v polohové bázi. Hamiltonián je reprezentován maticí a tenzorem . Poté jej rotujeme do impulzové báze. V polohové bázi umístíme nečistotu na první místo. Když však rotujeme do impulzové báze, přesuneme nečistotu na centrální místo, abychom usnadnili interakce s ostatními orbitaly.
# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util
_needed = {"ffsim": "ffsim", "matplotlib": "matplotlib", "numpy": "numpy", "pyscf": "pyscf", "qiskit": "qiskit", "qiskit_addon_sqd": "qiskit-addon-sqd", "qiskit_ibm_runtime": "qiskit-ibm-runtime", "scipy": "scipy"}
_missing = [pip for module, pip in _needed.items()
if importlib.util.find_spec(module) is None]
# One at a time, so a package that fails to install does not block the others
for _pip in _missing:
%pip install -q {_pip}
if not _missing:
print("\u2713 All required packages are installed")
import numpy as np
import pyscf.fci
def siam_hamiltonian(
norb: int,
hopping: float,
onsite: float,
hybridization: float,
chemical_potential: float,
) -> tuple[np.ndarray, np.ndarray]:
"""Hamiltonian for the single-impurity Anderson model."""
# Place the impurity on the first site
impurity_orb = 0
# One body matrix elements in the "position" basis
h1e = np.zeros((norb, norb))
np.fill_diagonal(h1e[:, 1:], -hopping)
np.fill_diagonal(h1e[1:, :], -hopping)
h1e[impurity_orb, impurity_orb + 1] = -hybridization
h1e[impurity_orb + 1, impurity_orb] = -hybridization
h1e[impurity_orb, impurity_orb] = chemical_potential
# Two body matrix elements in the "position" basis
h2e = np.zeros((norb, norb, norb, norb))
h2e[impurity_orb, impurity_orb, impurity_orb, impurity_orb] = onsite
return h1e, h2e
def momentum_basis(norb: int) -> np.ndarray:
"""Get the orbital rotation to change from the position to the momentum basis."""
n_bath = norb - 1
# Orbital rotation that diagonalizes the bath (non-interacting system)
hopping_matrix = np.zeros((n_bath, n_bath))
np.fill_diagonal(hopping_matrix[:, 1:], -1)
np.fill_diagonal(hopping_matrix[1:, :], -1)
_, vecs = np.linalg.eigh(hopping_matrix)
# Expand to include impurity
orbital_rotation = np.zeros((norb, norb))
# Impurity is on the first site
orbital_rotation[0, 0] = 1
orbital_rotation[1:, 1:] = vecs
# Move the impurity to the center
new_index = n_bath // 2
perm = np.r_[1 : (new_index + 1), 0, (new_index + 1) : norb]
orbital_rotation = orbital_rotation[:, perm]
return orbital_rotation
def rotated(
h1e: np.ndarray, h2e: np.ndarray, orbital_rotation: np.ndarray
) -> tuple[np.ndarray, np.ndarray]:
"""Rotate the orbital basis of a Hamiltonian."""
h1e_rotated = np.einsum(
"ab,Aa,Bb->AB",
h1e,
orbital_rotation,
orbital_rotation.conj(),
optimize="greedy",
)
h2e_rotated = np.einsum(
"abcd,Aa,Bb,Cc,Dd->ABCD",
h2e,
orbital_rotation,
orbital_rotation.conj(),
orbital_rotation,
orbital_rotation.conj(),
optimize="greedy",
)
return h1e_rotated, h2e_rotated
# Total number of spatial orbitals, including the bath sites and the impurity
# This should be an even number
norb = 8
# System is half-filled
nelec = (norb // 2, norb // 2)
# One orbital is the impurity, the rest are bath sites
n_bath = norb - 1
# Hamiltonian parameters
hybridization = 1.0
hopping = 1.0
onsite = 10.0
chemical_potential = -0.5 * onsite
# Generate Hamiltonian in position basis
h1e, h2e = siam_hamiltonian(
norb=norb,
hopping=hopping,
onsite=onsite,
hybridization=hybridization,
chemical_potential=chemical_potential,
)
# Rotate to momentum basis
orbital_rotation = momentum_basis(norb)
h1e_momentum, h2e_momentum = rotated(h1e, h2e, orbital_rotation.T.conj())
# In the momentum basis, the impurity is placed in the center
impurity_index = n_bath // 2
# Use PySCF to compute the exact ground state energy
reference_energy, _ = pyscf.fci.direct_spin1.kernel(h1e, h2e, norb, nelec)
Dále vygenerujeme Circuit pro přípravu Krylovových bázových stavů. Pro každý spinový druh je počáteční stav dán superpozicí všech možných excitací tří elektronů nejbližších Fermiho hladině do 4 nejbližších prázdných módů, vycházejících ze stavu , realizovaných aplikací sedmi XXPlusYYGate. Časově vyvinuté stavy jsou produkovány postupnými aplikacemi Trotterova kroku druhého řádu.
Pro podrobnější popis tohoto modelu a způsobu, jakým jsou Circuit navrženy, viz "Quantum-Centric Algorithm for Sample-Based Krylov Diagonalization".
from collections.abc import Sequence
import ffsim
import scipy
from qiskit import QuantumCircuit, QuantumRegister
from qiskit.circuit import CircuitInstruction, Qubit
from qiskit.circuit.library import CPhaseGate, XGate, XXPlusYYGate
def prepare_initial_state(qubits: Sequence[Qubit], norb: int, nocc: int):
"""Prepare initial state."""
assert norb >= 8
x_gate = XGate()
rot = XXPlusYYGate(0.5 * np.pi, -0.5 * np.pi)
for i in range(nocc):
yield CircuitInstruction(x_gate, [qubits[i]])
yield CircuitInstruction(x_gate, [qubits[norb + i]])
for i in range(3):
for j in range(nocc - i - 1, nocc + i, 2):
yield CircuitInstruction(rot, [qubits[j], qubits[j + 1]])
yield CircuitInstruction(
rot, [qubits[norb + j], qubits[norb + j + 1]]
)
yield CircuitInstruction(rot, [qubits[j + 1], qubits[j + 2]])
yield CircuitInstruction(
rot, [qubits[norb + j + 1], qubits[norb + j + 2]]
)
def trotter_step(
qubits: Sequence[Qubit],
time_step: float,
one_body_evolution: np.ndarray,
h2e: np.ndarray,
impurity_index: int,
norb: int,
):
"""A Trotter step."""
# Assume the two-body interaction is just the on-site interaction of the impurity
onsite = h2e[
impurity_index, impurity_index, impurity_index, impurity_index
]
# Two-body evolution for half the time
yield CircuitInstruction(
CPhaseGate(-0.5 * time_step * onsite),
[qubits[impurity_index], qubits[norb + impurity_index]],
)
# One-body evolution for the full time
yield CircuitInstruction(
ffsim.qiskit.OrbitalRotationJW(norb, one_body_evolution), qubits
)
# Two-body evolution for half the time
yield CircuitInstruction(
CPhaseGate(-0.5 * time_step * onsite),
[qubits[impurity_index], qubits[norb + impurity_index]],
)
# Time step
time_step = 0.2
# Number of Krylov basis states
krylov_dim = 8
# Initialize circuit
qubits = QuantumRegister(2 * norb, name="q")
circuit = QuantumCircuit(qubits)
# Generate initial state
for instruction in prepare_initial_state(qubits, norb=norb, nocc=norb // 2):
circuit.append(instruction)
circuit.measure_all()
# Create list of circuits, starting with the initial state circuit
circuits = [circuit.copy()]
# Add time evolution circuits to the list
one_body_evolution = scipy.linalg.expm(-1j * time_step * h1e_momentum)
for i in range(krylov_dim - 1):
# Remove measurements
circuit.remove_final_measurements()
# Append another Trotter step
for instruction in trotter_step(
qubits,
time_step,
one_body_evolution,
h2e_momentum,
impurity_index,
norb,
):
circuit.append(instruction)
# Measure qubits
circuit.measure_all()
# Add a copy of the circuit to the list
circuits.append(circuit.copy())
circuits[0].draw("mpl", scale=0.4, fold=-1)
circuits[-1].draw("mpl", scale=0.4, fold=-1)

Krok 2: Optimalizace problému pro kvantové spuštění
Dále optimalizujeme Circuit pro cílový hardware. Prozatím vytvoříme generický backend s určeným počtem qubitů a sadou Gate, do které se Circuit časového vývoje přirozeně rozkládají.
from qiskit.providers.fake_provider import GenericBackendV2
rng = np.random.default_rng(12345)
backend = GenericBackendV2(
2 * norb, basis_gates=["cp", "xx_plus_yy", "p", "x"], seed=rng
)
Nyní použijeme Qiskit k transpilaci circuits na cílový backend.
from qiskit.transpiler import generate_preset_pass_manager
pass_manager = generate_preset_pass_manager(
optimization_level=3,
backend=backend,
seed_transpiler=int(rng.integers(2**31)),
)
isa_circuits = pass_manager.run(circuits)
Krok 3: Spuštění pomocí primitiv Qiskit
Po optimalizaci okruhů pro spuštění na hardware jsme připraveni spustit je na cílovém hardware a shromáždit vzorky pro odhad energie základního stavu. Po použití primitivy Sampler k vzorkování bitových řetězců z každého okruhu zkombinujeme všechny výsledky do jediného slovníku počtů a vykreslíme 20 nejčastěji vzorkovaných bitových řetězců.
from qiskit.primitives import StatevectorSampler
from qiskit.visualization import plot_histogram
# Sample from the circuits
sampler = StatevectorSampler(seed=rng)
job = sampler.run(isa_circuits, shots=500)
from qiskit.primitives import BitArray
# Combine the shots from the individual Trotter circuits
bit_array = BitArray.concatenate_shots(
[result.data.meas for result in job.result()]
)
plot_histogram(bit_array.get_counts(), number_to_keep=20)

Krok 4: Následné zpracování a vrácení výsledku do požadovaného klasického formátu
Teď spustíme algoritmus SQD pomocí funkce diagonalize_fermionic_hamiltonian. Vysvětlení argumentů této funkce najdeš v dokumentaci API.
Používáme trim SQD, které zapneme předáním TrimPolicy jako argumentu policy. Každá iterace obnovy konfigurace v trim SQD má následující kroky:
-
Diagonalizuj každou dávku bitových řetězců.
-
Ořízni každou dávku tak, aby zůstaly pouze řetězce konfigurační interakce (CI) s největší váhou v odhadu základního stavu dané dávky. CI řetězce jsou části bitových řetězců se spinem a spinem .
-
Sluč zbylé CI řetězce ze všech dávek a diagonalizuj Hamiltonián znovu ve sloučeném podprostoru.
Při dvou dávkách stojí každá iterace tři diagonalizace. Callback dostává pouze sloučený výsledek, takže každá iterace vypíše jedinou energii. Volba trim_ratio určuje podíl CI řetězců každé dávky, které ořezem přežijí. Argument max_dim omezuje počet CI řetězců na spinový sektor v každém podprostoru, včetně sloučeného. Další podrobnosti najdeš v příručce k trim SQD.
from qiskit_addon_sqd.fermion import (
SCIResult,
diagonalize_fermionic_hamiltonian,
)
from qiskit_addon_sqd.trim import TrimPolicy
# List to capture intermediate results
result_history = []
def callback(results: list[SCIResult]):
result_history.append(results)
iteration = len(result_history)
print(f"Iteration {iteration}")
for i, result in enumerate(results):
print(f"\tEnergy: {result.energy}")
print(
f"\tSubspace dimension: {np.prod(result.sci_state.amplitudes.shape)}"
)
result = diagonalize_fermionic_hamiltonian(
h1e_momentum,
h2e_momentum,
bit_array,
samples_per_batch=1000,
norb=norb,
nelec=nelec,
num_batches=2,
policy=TrimPolicy(
trim_ratio=0.8,
carryover_threshold=1e-4,
carryover_ratio=0.5,
),
max_dim=100,
max_iterations=5,
symmetrize_spin=True,
callback=callback,
seed=rng,
)
Iteration 1
Energy: -13.422366648114345
Subspace dimension: 729
Iteration 2
Energy: -13.422366646833664
Subspace dimension: 729
Následující buňka kódu vykresluje výsledky. První graf zobrazuje chybu vypočtené energie jako funkci počtu iterací obnovy konfigurace, druhý graf zobrazuje průměrnou obsazenost každého prostorového orbitalu po poslední iteraci. Protože je to takto malý problém, první iterace nás již přivede velmi blízko přesné energie.
import matplotlib.pyplot as plt
min_es = [
min(result, key=lambda res: res.energy).energy
for result in result_history
]
min_id, min_e = min(enumerate(min_es), key=lambda x: x[1])
# Data for energies plot
x1 = range(len(result_history))
e_diff = [abs(e - reference_energy) for e in min_es]
# Data for avg spatial orbital occupancy
y2 = np.sum(result.orbital_occupancies, axis=0)
x2 = range(len(y2))
fig, axs = plt.subplots(1, 2, figsize=(12, 6))
# Plot energies
axs[0].plot(x1, e_diff, label="energy error", marker="o")
axs[0].set_xticks(x1)
axs[0].set_xticklabels(x1)
axs[0].set_yscale("log")
axs[0].set_ylim(1e-5, 1e-2)
axs[0].set_title("Approximated Ground State Energy Error vs SQD Iterations")
axs[0].set_xlabel("Iteration Index", fontdict={"fontsize": 12})
axs[0].set_ylabel("Energy Error", fontdict={"fontsize": 12})
axs[0].legend()
# Plot orbital occupancy
axs[1].bar(x2, y2, width=0.8)
axs[1].set_xticks(x2)
axs[1].set_xticklabels(x2)
axs[1].set_title("Avg Occupancy per Spatial Orbital")
axs[1].set_xlabel("Orbital Index", fontdict={"fontsize": 12})
axs[1].set_ylabel("Avg Occupancy", fontdict={"fontsize": 12})
print(f"Reference energy: {reference_energy:.5f}")
print(f"SQD energy: {min_e:.5f}")
print(f"Absolute error: {abs(min_e - reference_energy):.5f}")
plt.tight_layout()
plt.show()
Reference energy: -13.42249
SQD energy: -13.42237
Absolute error: 0.00012

Ověření energie
Energie vrácená algoritmem SQD je zaručeně horní mezí skutečné energie základního stavu. Hodnotu energie lze ověřit, protože SQD také vrací koeficienty vektoru stavu aproximujícího základní stav. Energii z vektoru stavu můžeš vypočítat pomocí jeho jednočásticových a dvoučásticových redukovaných hustotních matic, jak ukazuje následující buňka kódu.
rdm1 = result.sci_state.rdm(rank=1, spin_summed=True)
rdm2 = result.sci_state.rdm(rank=2, spin_summed=True)
energy = np.sum(h1e_momentum * rdm1) + 0.5 * np.sum(h2e_momentum * rdm2)
print(f"Recomputed energy: {energy:.5f}")
Recomputed energy: -13.42237
Příklad ve velkém měřítku na hardware
Nyní spustíme větší příklad na reálném QPU.
Jako referenční energii používáme výsledky výpočtu metodou DMRG, který byl proveden samostatně.
Protože tento systém má mnohem více možných CI řetězců než malý příklad, snížíme trim_ratio na 0.5 a zvýšíme max_dim na 200.
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import SamplerV2 as Sampler
# Model parameters
norb = 20
nelec = (norb // 2, norb // 2)
n_bath = norb - 1
hybridization = 1.0
hopping = 1.0
onsite = 10.0
chemical_potential = -0.5 * onsite
# Generate Hamiltonian and orbital rotation
h1e, h2e = siam_hamiltonian(
norb=norb,
hopping=hopping,
onsite=onsite,
hybridization=hybridization,
chemical_potential=chemical_potential,
)
orbital_rotation = momentum_basis(norb)
h1e_momentum, h2e_momentum = rotated(h1e, h2e, orbital_rotation.T.conj())
impurity_index = n_bath // 2
# Set reference energy to DMRG value computed separately
reference_energy = -28.70659686
# Algorithm parameters
time_step = 0.2
krylov_dim = 8
# Construct circuits
qubits = QuantumRegister(2 * norb, name="q")
circuit = QuantumCircuit(qubits)
for instruction in prepare_initial_state(qubits, norb=norb, nocc=norb // 2):
circuit.append(instruction)
circuit.measure_all()
circuits = [circuit.copy()]
one_body_evolution = scipy.linalg.expm(-1j * time_step * h1e_momentum)
for i in range(krylov_dim - 1):
circuit.remove_final_measurements()
for instruction in trotter_step(
qubits,
time_step,
one_body_evolution,
h2e_momentum,
impurity_index,
norb,
):
circuit.append(instruction)
circuit.measure_all()
circuits.append(circuit.copy())
# Initialize hardware backend
service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=127
)
print(f"Using backend {backend.name}")
# Transpile to backend
pass_manager = generate_preset_pass_manager(
optimization_level=3, backend=backend
)
isa_circuits = pass_manager.run(circuits)
# Sample from the circuits
sampler = Sampler(backend)
sampler.options.environment.job_tags = ["TUT_SKQD"]
job = sampler.run(isa_circuits, shots=500)
# Combine the shots from the individual Trotter circuits
bit_array = BitArray.concatenate_shots(
[result.data.meas for result in job.result()]
)
# Run configuration recovery and diagonalization
result_history = []
def callback(results: list[SCIResult]):
result_history.append(results)
iteration = len(result_history)
print(f"Iteration {iteration}")
for i, result in enumerate(results):
print(f"\tEnergy: {result.energy}")
print(
f"\tSubspace dimension: {np.prod(result.sci_state.amplitudes.shape)}"
)
result = diagonalize_fermionic_hamiltonian(
h1e_momentum,
h2e_momentum,
bit_array,
samples_per_batch=1000,
norb=norb,
nelec=nelec,
num_batches=2,
policy=TrimPolicy(
trim_ratio=0.5,
carryover_threshold=1e-4,
carryover_ratio=0.5,
),
max_dim=200,
max_iterations=5,
symmetrize_spin=True,
callback=callback,
seed=rng,
)
# Plot results
min_es = [
min(result, key=lambda res: res.energy).energy
for result in result_history
]
min_id, min_e = min(enumerate(min_es), key=lambda x: x[1])
x1 = range(len(result_history))
e_diff = [abs(e - reference_energy) for e in min_es]
y2 = np.sum(result.orbital_occupancies, axis=0)
x2 = range(len(y2))
fig, axs = plt.subplots(1, 2, figsize=(12, 6))
axs[0].plot(x1, e_diff, label="energy error", marker="o")
axs[0].set_xticks(x1)
axs[0].set_xticklabels(x1)
axs[0].set_yscale("log")
axs[0].set_ylim(1e-3, 1e-1)
axs[0].set_title("Approximated Ground State Energy Error vs SQD Iterations")
axs[0].set_xlabel("Iteration Index", fontdict={"fontsize": 12})
axs[0].set_ylabel("Energy Error", fontdict={"fontsize": 12})
axs[0].legend()
axs[1].bar(x2, y2, width=0.8)
axs[1].set_xticks(x2)
axs[1].set_xticklabels(x2)
axs[1].set_title("Avg Occupancy per Spatial Orbital")
axs[1].set_xlabel("Orbital Index", fontdict={"fontsize": 12})
axs[1].set_ylabel("Avg Occupancy", fontdict={"fontsize": 12})
print(f"Reference energy: {reference_energy:.5f}")
print(f"SQD energy: {min_e:.5f}")
print(f"Absolute error: {abs(min_e - reference_energy):.5f}")
plt.tight_layout()
plt.show()
Using backend ibm_fez
Iteration 1
Energy: -28.632642067246973
Subspace dimension: 13689
Iteration 2
Energy: -28.7030144883651
Subspace dimension: 18769
Iteration 3
Energy: -28.70519592695907
Subspace dimension: 13456
Iteration 4
Energy: -28.705288597420616
Subspace dimension: 15129
Iteration 5
Energy: -28.70502224787334
Subspace dimension: 15129
Reference energy: -28.70660
SQD energy: -28.70529
Absolute error: 0.00131

Další kroky
Pokud tě tato práce zaujala, mohl by tě zajímat následující materiál:
- Vzorkování-based kvantová diagonalizace chemického hamiltoniánu – příbuzný tutoriál používající heuristický variační ansatz místo Trotterových okruhů
- Krylovova kvantová diagonalizace mřížkových hamiltoniánů – tutoriál o metodě KQD
- Dokumentace API addonu SQD – reference pro funkci
diagonalize_fermionic_hamiltonian - Quantum-Centric Algorithm for Sample-Based Krylov Diagonalization – článek, na němž je tento tutoriál založen