Wykorzystaj trim SQD do lepszego doboru konfiguracji
Domyślne zachowanie diagonalize_fermionic_hamiltonian polega na diagonalizacji każdej podpróbkowanej partii i zachowaniu najlepszego wyniku. Trim SQD zamiast tego traktuje partie jako kandydatów do przesiania: każda jest diagonalizowana, przycinana do konfiguracji o największej wadze, a ocalałe konfiguracje ze wszystkich partii są scalane i diagonalizowane razem. Ta scalona diagonalizacja jest tą, której energię raportuje iteracja, a jej własne przycięte wyjście inicjuje następną iterację.
Implementacja trim SQD opiera się na wspólnych badaniach Cleveland Clinic Foundation, RIKEN i IBM®. Jeśli używasz tej funkcji w swoich badaniach, zacytuj również ten artykuł. Cytowanie znajdziesz w pliku CITATION.bib, pod kluczem cytowania merz2026crossing.
Więcej szczegółów o kodzie SQD użytym w tym przykładzie znajdziesz w samouczku o hamiltonianie chemicznym.
Skonfiguruj cząsteczkę
# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util
_needed = {"numpy": "numpy", "pyscf": "pyscf", "qiskit_addon_sqd": "qiskit-addon-sqd"}
_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
import pyscf.mcscf
from qiskit_addon_sqd.counts import generate_bit_array_uniform
from qiskit_addon_sqd.fermion import diagonalize_fermionic_hamiltonian
from qiskit_addon_sqd.trim import TrimPolicy
# Build N2 molecule
mol = pyscf.gto.Mole()
mol.build(
atom=[["N", (0, 0, 0)], ["N", (1.0, 0, 0)]],
basis="6-31g",
symmetry="Dooh",
)
# Define active space
n_frozen = 2
active_space = range(n_frozen, mol.nao_nr())
# Get molecular integrals
scf = pyscf.scf.RHF(mol).run()
num_orbitals = len(active_space)
n_electrons = int(sum(scf.mo_occ[active_space]))
num_elec_a = (n_electrons + mol.spin) // 2
num_elec_b = (n_electrons - mol.spin) // 2
cas = pyscf.mcscf.CASCI(scf, num_orbitals, (num_elec_a, num_elec_b))
mo = cas.sort_mo(active_space, base=0)
hcore, nuclear_repulsion_energy = cas.get_h1cas(mo)
eri = pyscf.ao2mo.restore(1, cas.get_h2cas(mo), num_orbitals)
# Compute exact energy
exact_energy = cas.run().e_tot
# Create a seed to control randomness throughout this workflow
rng = np.random.default_rng(24)
# Generate random samples
bit_array = generate_bit_array_uniform(
10_000, num_orbitals * 2, rand_seed=rng
)
WARN: Unable to to identify input symmetry using original axes.
Different symmetry axes will be used.
converged SCF energy = -108.835236570774
CASCI E = -109.046671778080 E(CI) = -32.8155692383188 S^2 = 0.0000000
Uruchom pętlę
Jedna iteracja trim SQD wykonuje następujące kroki:
-
Runda przesiewania. Podziel ciągi bitów na partie. Zdiagonalizuj każdą partię i zachowaj ułamek
trim_ratiojej ciągów CI, osobno dla każdego sektora spinowego, uszeregowanych według wagi. -
Runda scalona. Zdiagonalizuj scalone ocalałe konfiguracje jednokrotnie. To jest wynik raportowany przez iterację, a jej własne przycięte ciągi inicjują następną iterację.
sci_solver pętli wykonuje obie rundy i domyślnie używa solwera PySCF z tego pakietu.
Włącz trim SQD, przekazując TrimPolicy w następujący sposób:
result = diagonalize_fermionic_hamiltonian(
hcore,
eri,
bit_array,
samples_per_batch=300,
norb=num_orbitals,
nelec=(num_elec_a, num_elec_b),
num_batches=5,
max_iterations=5,
policy=TrimPolicy(trim_ratio=0.5, max_strings_per_trim=250),
symmetrize_spin=True,
max_dim=250,
seed=rng,
)
sci_state = result.sci_state
print(
f"Merged subspace: {len(sci_state.ci_strs_a)} x {len(sci_state.ci_strs_b)} CI strings"
)
print(f"Exact energy: {exact_energy}")
print(f"Estimated energy: {result.energy + nuclear_repulsion_energy}")
Merged subspace: 148 x 148 CI strings
Exact energy: -109.04667177808024
Estimated energy: -109.034369147063
Sprawdź, czy przesiewanie pomaga
Przesiewanie ma najwięcej do zaoferowania, gdy spróbkowanych konfiguracji jest znacznie więcej, niż można zdiagonalizować naraz, dzięki czemu uszeregowanie większej puli rzeczywiście znajduje konfiguracje, które pojedyncza partia by pominęła. Ma najmniej do zaoferowania, gdy podprzestrzeń o osiągalnym rozmiarze już ujmuje większość funkcji falowej.
Przesiewanie zależy od tego, że partie różnią się między sobą. TrimPolicy zapewnia to, zmieniając zachowanie diagonalize_fermionic_hamiltonian tak, aby losowało jedną pulę samples_per_batch * num_batches ciągów bitów i dzieliło ją między partie, dzięki czemu żaden ciąg bitów nie występuje w więcej niż jednej partii.
Ogranicz podprzestrzeń
Podprzestrzeń jest tu rozpięta przez iloczyn kartezjański tablicy ciągów CI spinu alfa i tablicy ciągów CI spinu beta.
Przycinanie szereguje konfiguracje, ale przenoszone dalej są dwie listy ciągów dla poszczególnych spinów. Zachowanie 150 konfiguracji o największej wadze w podprzestrzeni zwykle obejmuje blisko 150 różnych ciągów alfa i 150 różnych ciągów beta, których iloczyn rozpina około 22 500 konfiguracji, a nie 150. Dlatego podprzestrzeń przekazana do następnej iteracji jest zazwyczaj znacznie większa niż uszeregowany zbiór i rośnie kwadratowo wraz z liczbą zachowanych konfiguracji.
Przycinanie i przenoszenie są ograniczane osobno, ponieważ zachodzą w różnych momentach: przycinanie zmniejsza każdą partię rundy przesiewania, a przeniesienie to to, co przechodzi do następnej iteracji.
Ogranicza ją cztery argumenty i warto ustawić przynajmniej jeden z nich:
-
trim_ratioto ułamek ciągów każdej partii, osobno dla każdego sektora spinowego, które przeżywają przesiewanie. Niższe wartości zmniejszają scaloną podprzestrzeń, ale odrzucają konfiguracje, które następna iteracja musi ponownie odkryć przez próbkowanie. -
max_strings_per_trimogranicza liczbę ciągów wnoszonych przez każdą partię, osobno dla każdego sektora, niezależnie od ułamka. Przydatne zsymmetrize_spin, który scala oba sektory spinowe w jedną listę i tym samym mniej więcej podwaja to, co oferuje każda partia. To ograniczenie jest na partię: scalona podprzestrzeń może zawierać donum_batchesrazy tyle ciągów, pomniejszone o to, co partie miały wspólne, czego w praktyce nie jest dużo. -
carryover_threshold,carryover_ratioimax_carryoverograniczają ciągi inicjujące następną iterację i są stosowane do scalonej podprzestrzeni jako całości, a nie na partię.carryover_thresholdto próg amplitudy używany przezStandardPolicy, z tą samą wartością domyślną; pozostałe dwa ograniczają liczbę, czego sam próg nie robi. -
max_dim, w samej funkcjidiagonalize_fermionic_hamiltonian, ogranicza każdy sektor spinowy każdej podprzestrzeni diagonalizowanej w pętli, w tym scalonej. Działa jako zabezpieczenie i dotyczy tego, co zwróci polityka, po scaleniu przeniesienia ze świeżymi próbkami.
Powyższe uruchomienie ustawia razem max_strings_per_trim=250 i max_dim=250, dzięki czemu ani to, co wnosi każda partia, ani zbudowana z tego podprzestrzeń nie mogą wymknąć się spod kontroli.
Użyj zewnętrznego solwera własnego
Możesz używać trim SQD z zewnętrznym solwerem własnym, przekazując argument sci_solver:
from sbd.sbd_solver import create_sbd_solver
result = diagonalize_fermionic_hamiltonian(
hcore,
eri,
bit_array,
samples_per_batch=300,
norb=num_orbitals,
nelec=(num_elec_a, num_elec_b),
num_batches=5,
policy=TrimPolicy(trim_ratio=0.1),
sci_solver=create_sbd_solver(sbd_config={"method": 0, "eps": 1e-10}),
seed=rng,
)
Powyższy fragment kodu wymaga zainstalowania pakietu sbd-eigensolver z PyPI.
Rozdziel pracę
Z TrimPolicy pętla wywołuje sci_solver dwa razy na iterację: raz ze wszystkimi partiami i raz ze scaloną podprzestrzenią. Oba wywołania przechodzą przez zwykły interfejs sci_solver, który obsługa wieloprocesowości tego pakietu wywołuje zbiorczo na każdej randze MPI.
Potencjalnie dostępne są dwa poziomy równoległości i są one niezależne:
-
W obrębie diagonalizacji. Solwer taki jak SBD może rozłożyć pojedynczą diagonalizację na wiele rang.
-
Między partiami. Solwer mógłby w zasadzie diagonalizować partie równocześnie.
Mając zestaw partii, solwer decyduje, jak delegować pracę do swoich procesów roboczych.