Probenbasierte Krylov-Quantendiagonalisierung eines fermionischen Gittermodells
Geschätzte Nutzungsdauer: Neun Sekunden auf einem Heron r2 Prozessor (HINWEIS: Dies ist nur eine Schätzung. Ihre Laufzeit kann variieren.)
Lernziele
- Wie man das SQD-Qiskit-Add-on verwendet, um die Grundzustandsenergie eines Gittermodells anhand von Bitstrings zu approximieren, die von einer Quantenverarbeitungseinheit (QPU) abgetastet wurden.
- Wie man mit ffsim Zeitentwicklungsschaltungen für die Fermionensimulation erstellt.
- Wie man Signale aus mehreren Schaltkreisen für die Weiterverarbeitung mit dem algorithmischen Verfahren der samplebasierten Krylov-Diagonalisierung (SKQD) zusammenführt.
Voraussetzungen
- Quantendiagonalisierung eines Chemie-Hamiltonians auf der Grundlage von Stichproben
- Krylov-Quanten-Diagonalisierung von Gitter-Hamilton-Operatoren
- Qiskit primitives
Hintergrund
Dieses Tutorial zeigt, wie man die stichprobenbasierte Quantendiagonalisierung (SQD) verwendet, um die Grundzustandsenergie eines fermionischen Gittermodells abzuschätzen. Konkret untersuchen wir das eindimensionale Single-Impurity-Anderson-Modell (SIAM), das zur Beschreibung von in Metallen eingebetteten magnetischen Verunreinigungen verwendet wird.
Dieses Tutorial folgt einem ähnlichen Arbeitsablauf wie das verwandte Tutorial Probenbasierte Quantendiagonalisierung eines Chemie-Hamiltonians. Ein wesentlicher Unterschied besteht jedoch darin, wie die Quantenschaltungen aufgebaut sind. Das andere Tutorial verwendet einen heuristischen Variationsansatz, der für chemische Hamiltonianer mit potenziell Millionen von Wechselwirkungstermen interessant ist. Andererseits werden in diesem Tutorium Schaltungen verwendet, die die Zeitentwicklung durch den Hamiltonian annähern. Solche Schaltkreise können tief sein, was diesen Ansatz für Anwendungen auf Gittermodelle besser geeignet macht. Die von diesen Schaltkreisen vorbereiteten Zustandsvektoren bilden die Grundlage für einen Krylov-Unterraum, so dass der Algorithmus unter geeigneten Annahmen nachweislich und effizient gegen den Grundzustand konvergiert.
Der in diesem Tutorium verwendete Ansatz kann als eine Kombination aus den Techniken der SQD und der Krylov-Quanten-Diagonalisierung (KQD ) betrachtet werden. Der kombinierte Ansatz wird manchmal als stichprobenbasierte Krylov-Quantendiagonalisierung (SQKD) bezeichnet. Siehe Krylov-Quanten-Diagonalisierung von Gitter-Hamiltonianern für eine Einführung in die KQD-Methode.
Dieses Tutorial basiert auf der Arbeit "Quantum-Centric Algorithm for Sample-Based Krylov Diagonalization", die für weitere Details herangezogen werden kann.
Anderson-Modell mit einer einzigen Verunreinigung (SIAM)
Der eindimensionale SIAM-Hamiltonian ist eine Summe aus drei Termen:
wo
Hier sind die fermionischen Erzeugungs-/Annihilationsoperatoren für die Badstelle mit dem Spin , sind Erzeugungs-/Annihilationsoperatoren für den Störstellenmodus und . , und sind reelle Zahlen, die die Sprung-, Vor-Ort- und Hybridisierungswechselwirkungen beschreiben, und ist eine reelle Zahl, die das chemische Potenzial angibt.
Es ist zu beachten, dass der Hamiltonian eine spezifische Instanz des allgemeinen Wechselwirkungs-Elektronen-Hamiltonians ist,
wobei aus Einkörpertermen besteht, die quadratisch in den fermionischen Erzeugungs- und Annihilationsoperatoren sind, und aus Zweikörpertermen besteht, die quartisch sind. Für die SIAM,
und enthält den Rest der Terme im Hamiltonian. Um den Hamiltonian programmatisch darstellen zu können, speichern wir die Matrix und den Tensor .
Positions- und Impulsbasen
Aufgrund der annähernden Translationssymmetrie in erwarten wir nicht, dass der Grundzustand in der Positionsbasis (der Orbitalbasis, in der der Hamiltonian oben angegeben ist) spärlich ist. Die Leistung von SQD ist nur dann gewährleistet, wenn der Grundzustand spärlich ist, d. h. nur eine kleine Anzahl von Basiszuständen eine signifikante Bedeutung hat. Um die Sparsamkeit des Grundzustands zu verbessern, führen wir die Simulation in der Orbitalbasis durch, in der diagonal ist. Wir nennen diese Basis die Impulsbasis. Da ein quadratischer fermionischer Hamiltonian ist, kann er durch eine Orbitalrotation effizient diagonalisiert werden.
Ungefähre zeitliche Entwicklung durch den Hamilton-Operator
Zur Annäherung der Zeitentwicklung durch den Hamiltonian verwenden wir eine Trotter-Suzuki-Zerlegung zweiter Ordnung,
Unter der Jordan-Wigner-Transformation läuft die Zeitentwicklung durch auf ein einziges CPhase-Gate zwischen den Spin-up- und Spin-down-Orbitalen an der Verunreinigungsstelle hinaus. Da es sich bei um einen quadratischen fermionischen Hamiltonian handelt, kommt die Zeitentwicklung über einer Orbitalrotation gleich.
Die Krylov-Basiszustände , wobei die Dimension des Krylov-Unterraums ist, werden durch wiederholte Anwendung eines einzigen Trotter-Schritts gebildet, so dass
Im folgenden SQD-basierten Arbeitsablauf werden wir aus diesem Satz von Schaltkreisen Stichproben nehmen und den kombinierten Satz von Bitstrings mit SQD nachbearbeiten. Diese Herangehensweise steht im Gegensatz zu der im verwandten Tutorial Sample-based quantum diagonalization of a chemistry Hamiltonian verwendeten, bei der die Proben aus einem einzigen heuristischen Variationskreis gezogen wurden.
Anforderungen
Vergewissern Sie sich vor Beginn dieses Tutorials, dass Sie die folgenden Komponenten installiert haben:
- Qiskit SDK v1.0 oder später, mit Visual isierungsunterstützung
- Qiskit Runtime v0.22 oder später (
pip install qiskit-ibm-runtime) - SQD Qiskit-Add-on v0.11 oder höher (
pip install qiskit-addon-sqd) - ffsim v0.0.72 oder höher (
pip install ffsim)
Beispiel für einen Simulator im kleinen Maßstab
Schritt 1: Problem auf einen Quantenschaltkreis abbilden
Zunächst erzeugen wir den SIAM-Hamiltonschen in der Positionsbasis. Der Hamiltonian wird durch die Matrix und den Tensor dargestellt. Dann drehen wir ihn in die Impulsbasis um. In der Positionsbasis setzen wir die Verunreinigung an die erste Stelle. Wenn wir jedoch zur Impulsbasis wechseln, verschieben wir die Verunreinigung an einen zentralen Ort, um Wechselwirkungen mit anderen Orbitalen zu erleichtern.
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)Als Nächstes erzeugen wir die Schaltkreise, um die Krylov-Basiszustände zu erzeugen. Für jede Spinspezies ist der Ausgangszustand durch die Überlagerung aller möglichen Anregungen der drei dem Fermi-Niveau am nächsten liegenden Elektronen in die 4 nächstgelegenen leeren Moden, ausgehend vom Zustand , gegeben und wird durch die Anwendung von sieben XXPlusYYGates realisiert. Die zeitlich veränderten Zustände werden durch aufeinanderfolgende Anwendungen eines Trotter-Schrittes zweiter Ordnung erzeugt.
Eine ausführlichere Beschreibung dieses Modells und des Entwurfs der Schaltkreise finden Sie unter "Quantum-Centric Algorithm for Sample-Based Krylov Diagonalization".
from typing 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)Output:
circuits[-1].draw("mpl", scale=0.4, fold=-1)Output:
Schritt 2: Optimierung des Problems für die Quantenausführung
Als Nächstes optimieren wir die Schaltung für eine bestimmte Hardware. Zunächst erstellen wir ein generisches Backend mit einer bestimmten Anzahl von Qubits und einem Gatter-Satz, in den sich die Zeitentwicklungsschaltungen auf natürliche Weise zerlegen lassen.
from qiskit.providers.fake_provider import GenericBackendV2
backend = GenericBackendV2(
2 * norb, basis_gates=["cp", "xx_plus_yy", "p", "x"]
)Jetzt verwenden wir Qiskit, um die Schaltkreise in das Ziel-Backend zu transpilieren.
from qiskit.transpiler import generate_preset_pass_manager
pass_manager = generate_preset_pass_manager(
optimization_level=3, backend=backend
)
isa_circuits = pass_manager.run(circuits)Schritt 3: Führen Sie den Befehl „ Qiskit primitives “ aus
Nach der Optimierung der Schaltungen für die Hardware-Ausführung können wir sie auf der Zielhardware ausführen und Proben für die Abschätzung der Grundzustandsenergie sammeln. Nachdem wir mit dem Sampler-Primitiv Bitstrings aus jedem Schaltkreis gesampelt haben, fassen wir alle Ergebnisse in einem einzigen Zählwörterbuch zusammen und zeichnen die 20 am häufigsten gesampelten Bitstrings auf.
from qiskit.visualization import plot_histogram
from qiskit.primitives import StatevectorSampler
# Sample from the circuits
sampler = StatevectorSampler()
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)Output:
Schritt 4: Nachbearbeitung und Rückgabe des Ergebnisses im gewünschten klassischen Format
Nun führen wir den SQD-Algorithmus mit der Funktion diagonalize_fermionic_hamiltonian aus. Erläuterungen zu den Argumenten für diese Funktion finden Sie in der API-Dokumentation.
from qiskit_addon_sqd.fermion import (
SCIResult,
diagonalize_fermionic_hamiltonian,
)
# 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"\tSubsample {i}")
print(f"\t\tEnergy: {result.energy}")
print(
f"\t\tSubspace dimension: {np.prod(result.sci_state.amplitudes.shape)}"
)
rng = np.random.default_rng(24)
result = diagonalize_fermionic_hamiltonian(
h1e_momentum,
h2e_momentum,
bit_array,
samples_per_batch=100,
norb=norb,
nelec=nelec,
num_batches=3,
max_iterations=5,
symmetrize_spin=True,
callback=callback,
seed=rng,
)Output:
Iteration 1
Subsample 0
Energy: -13.4222953188441
Subspace dimension: 529
Subsample 1
Energy: -13.42237556285828
Subspace dimension: 784
Subsample 2
Energy: -13.422045397387413
Subspace dimension: 529
Iteration 2
Subsample 0
Energy: -13.422379583305478
Subspace dimension: 900
Subsample 1
Energy: -13.422376197704326
Subspace dimension: 841
Subsample 2
Energy: -13.422421162849295
Subspace dimension: 1089
Iteration 3
Subsample 0
Energy: -13.422421164670345
Subspace dimension: 1156
Subsample 1
Energy: -13.422421492737689
Subspace dimension: 1156
Subsample 2
Energy: -13.422421205869572
Subspace dimension: 1156
Iteration 4
Subsample 0
Energy: -13.422421494558726
Subspace dimension: 1225
Subsample 1
Energy: -13.422421492737689
Subspace dimension: 1156
Subsample 2
Energy: -13.422421492737689
Subspace dimension: 1156
Die folgende Codezelle stellt die Ergebnisse grafisch dar. Die erste Grafik zeigt die berechnete Energie als Funktion der Anzahl der Iterationen zur Konfigurationswiederherstellung, und die zweite Grafik zeigt die durchschnittliche Besetzung jedes räumlichen Orbitals nach der letzten Iteration. Da es sich um ein so kleines Problem handelt, kommen wir bereits mit der ersten Iteration sehr nahe an den exakten Energiewert heran (beachte die Skalierung der y-Achse).
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))
# 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, min_es, label="energy", marker="o")
axs[0].set_xticks(x1)
axs[0].set_xticklabels(x1)
axs[0].axhline(
y=reference_energy,
color="#BF5700",
linestyle="--",
label="reference energy",
)
axs[0].set_title("Approximated Ground State Energy vs SQD Iterations")
axs[0].set_xlabel("Iteration Index", fontdict={"fontsize": 12})
axs[0].set_ylabel("Energy", 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()Output:
Reference energy: -13.42249
SQD energy: -13.42242
Absolute error: 0.00007
Überprüfen Sie den Energieverbrauch
Die von SQD zurückgegebene Energie ist garantiert eine Obergrenze der tatsächlichen Grundzustandsenergie. Der Wert der Energie lässt sich überprüfen, da SQD auch die Koeffizienten des Zustandsvektors zurückgibt, der den Grundzustand approximiert. Sie können die Energie aus dem Zustandsvektor anhand seiner reduzierten Dichtematrizen für ein und zwei Teilchen berechnen, wie in der folgenden Codezelle gezeigt.
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}")Output:
Recomputed energy: -13.42242
Beispiel für groß angelegte Hardware
Nun führen wir ein umfangreicheres Beispiel auf einer echten QPU aus. Als Referenzenergie verwenden wir die Ergebnisse einer separat durchgeführten DMRG -Berechnung.
from qiskit_ibm_runtime import SamplerV2 as Sampler
from qiskit_ibm_runtime import QiskitRuntimeService
# 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"\tSubsample {i}")
print(f"\t\tEnergy: {result.energy}")
print(
f"\t\tSubspace dimension: {np.prod(result.sci_state.amplitudes.shape)}"
)
rng = np.random.default_rng(24)
result = diagonalize_fermionic_hamiltonian(
h1e_momentum,
h2e_momentum,
bit_array,
samples_per_batch=100,
norb=norb,
nelec=nelec,
num_batches=3,
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))
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, min_es, label="energy", marker="o")
axs[0].set_xticks(x1)
axs[0].set_xticklabels(x1)
axs[0].axhline(
y=reference_energy,
color="#BF5700",
linestyle="--",
label="reference energy",
)
axs[0].set_title("Approximated Ground State Energy vs SQD Iterations")
axs[0].set_xlabel("Iteration Index", fontdict={"fontsize": 12})
axs[0].set_ylabel("Energy", 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()Output:
Using backend ibm_boston
Iteration 1
Subsample 0
Energy: -28.63965951544449
Subspace dimension: 9801
Subsample 1
Energy: -28.625588929202006
Subspace dimension: 9409
Subsample 2
Energy: -28.647371834135498
Subspace dimension: 8281
Iteration 2
Subsample 0
Energy: -28.67213260849567
Subspace dimension: 29584
Subsample 1
Energy: -28.670340686158816
Subspace dimension: 27225
Subsample 2
Energy: -28.669976379525988
Subspace dimension: 31329
Iteration 3
Subsample 0
Energy: -28.68622875601382
Subspace dimension: 36100
Subsample 1
Energy: -28.698569623143126
Subspace dimension: 34225
Subsample 2
Energy: -28.694848533971882
Subspace dimension: 33856
Iteration 4
Subsample 0
Energy: -28.69883392844593
Subspace dimension: 42025
Subsample 1
Energy: -28.701289495200996
Subspace dimension: 38025
Subsample 2
Energy: -28.699319594978245
Subspace dimension: 45369
Iteration 5
Subsample 0
Energy: -28.701936886834154
Subspace dimension: 51076
Subsample 1
Energy: -28.702468711812013
Subspace dimension: 53824
Subsample 2
Energy: -28.702298147575938
Subspace dimension: 52900
Reference energy: -28.70660
SQD energy: -28.70247
Absolute error: 0.00413
Nächste Schritte
Wenn Sie diese Arbeit interessant fanden, könnten Sie auch an folgenden Materialien interessiert sein:
- Beispielbasierte Quanten-Diagonalisierung eines chemischen Hamilton-Operators – ein dazugehöriges Tutorial, das anstelle von Trotter-Schaltkreisen einen heuristischen Variationsansatz verwendet
- Krylov-Quanten-Diagonalisierung von Gitter-Hamiltonianen – eine Einführung in die KQD-Methode
- SQD-Addon-API-Dokumentation – Referenz zur Funktion
diagonalize_fermionic_hamiltonian - „Quantum-Centric Algorithm for Sample-Based Krylov Diagonalization “ – der Artikel, auf dem dieses Tutorial basiert