Sari la conținutul principal
Acest proiect este în versiune beta. Ai găsit o eroare? Deschide un issue pe GitHub. Ai o întrebare sau o idee? Întreabă în Discussions — ne-ar plăcea să aflăm părerea ta!
Cum este calitatea traducerii pe această pagină?

Folosește trim SQD pentru o selecție îmbunătățită a configurațiilor

Comportamentul implicit al diagonalize_fermionic_hamiltonian este să diagonalizeze fiecare lot subeșantionat și să păstreze cel mai bun rezultat. Trim SQD tratează în schimb loturile ca pe niște candidați de filtrat: fiecare este diagonalizat, tăiat (trimmed) până la configurațiile cu cea mai mare pondere, iar supraviețuitoarele din toate loturile sunt unite și diagonalizate împreună. Acea diagonalizare unită este cea a cărei energie o raportează iterația, iar rezultatul ei tăiat inițializează iterația următoare.

Implementarea trim SQD se bazează pe cercetări comune ale Cleveland Clinic Foundation, RIKEN și IBM®. Dacă folosești această funcționalitate în cercetarea ta, citează și lucrarea. Găsești citarea în fișierul CITATION.bib, cu cheia de citare merz2026crossing.

Pentru mai multe detalii despre codul SQD folosit în acest exemplu, consultă tutorialul despre hamiltonianul chimic.

Configurează molecula​

# 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

Rulează bucla​

O iterație trim SQD face următoarele:

  1. Runda de filtrare. Împarte șirurile de biți în loturi. Diagonalizează fiecare lot și păstrează fracția trim_ratio din șirurile sale CI, pe sector de spin, ordonate după pondere.

  2. Runda unită. Diagonalizează o singură dată supraviețuitoarele unite. Acesta este rezultatul pe care îl raportează iterația, iar șirurile sale tăiate inițializează iterația următoare.

sci_solver al buclei efectuează ambele runde și folosește implicit solverul PySCF al acestui pachet.

Activează trim SQD transmițând TrimPolicy astfel:

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

Verifică dacă filtrarea ajută​

Filtrarea are cel mai mult de oferit atunci când configurațiile eșantionate depășesc cu mult ce poate fi diagonalizat deodată, astfel încât ordonarea unui grup mai mare găsește cu adevărat configurații pe care un singur lot le-ar fi ratat. Are cel mai puțin de oferit atunci când un subspațiu de dimensiune accesibilă captează deja cea mai mare parte a funcției de undă.

Filtrarea depinde de faptul că loturile diferă între ele. TrimPolicy gestionează acest lucru modificând comportamentul lui diagonalize_fermionic_hamiltonian astfel încât să extragă un singur grup de samples_per_batch * num_batches șiruri de biți și să îl partiționeze între loturi, astfel încât niciun șir de biți să nu apară în mai mult de un lot.

Delimitează subspațiul​

Un subspațiu de aici este generat de produsul cartezian dintre un tablou de șiruri CI de spin alfa și unul de spin beta.

Tăierea ordonează configurațiile, dar ceea ce se transmite mai departe sunt două liste de șiruri, câte una pe spin. Păstrarea celor 150 de configurații cu cea mai mare pondere dintr-un subspațiu implică de obicei aproape 150 de șiruri alfa distincte și 150 de șiruri beta distincte, al căror produs generează aproximativ 22.500 de configurații, nu 150. Așadar, subspațiul transmis iterației următoare este în general mult mai mare decât mulțimea care a fost ordonată și crește pătratic cu ceea ce păstrezi.

Tăierea și transmiterea sunt delimitate separat, deoarece au loc în momente diferite: tăierea reduce fiecare lot al rundei de filtrare, în timp ce transmiterea este ceea ce trece în iterația următoare.

Patru argumente o delimitează și este util să setezi cel puțin unul:

  • trim_ratio este fracția din șirurile fiecărui lot, pe sector de spin, care supraviețuiește filtrării. Valorile mai mici reduc subspațiul unit, dar elimină configurații pe care iterația următoare trebuie să le redescopere prin eșantionare.

  • max_strings_per_trim plafonează șirurile cu care contribuie fiecare lot, pe sector, indiferent de raport. Util împreună cu symmetrize_spin, care unește ambele sectoare de spin într-o singură listă și astfel dublează aproximativ ce oferă fiecare lot. Această limită este pe lot: subspațiul unit poate conține până la de num_batches ori mai multe șiruri, minus ceea ce aveau loturile în comun, ceea ce în practică nu e mult.

  • carryover_threshold, carryover_ratio și max_carryover delimitează șirurile care inițializează iterația următoare, aplicate subspațiului unit în ansamblu, nu pe lot. carryover_threshold este pragul de amplitudine pe care îl folosește StandardPolicy, cu aceeași valoare implicită; celelalte două delimitează numărul, lucru pe care pragul singur nu îl face.

  • max_dim, la diagonalize_fermionic_hamiltonian însuși, delimitează fiecare sector de spin al fiecărui subspațiu pe care îl diagonalizează bucla, inclusiv cel unit. Acționează ca o plasă de siguranță și se aplică la ceea ce returnează politica, după ce transmiterea este unită cu eșantioane noi.

Rularea de mai sus setează împreună max_strings_per_trim=250 și max_dim=250, astfel încât nici contribuția fiecărui lot, nici subspațiul construit din ea să nu poată crește necontrolat.

Folosește un solver de valori proprii extern​

Poți folosi trim SQD cu un solver de valori proprii extern transmițând argumentul 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,
)

Fragmentul anterior necesită instalarea pachetului sbd-eigensolver din PyPI.

Distribuie lucrul​

Cu TrimPolicy, bucla apelează sci_solver de două ori pe iterație: o dată cu toate loturile și o dată cu subspațiul unit. Ambele apeluri trec prin interfața obișnuită sci_solver, pe care suportul multi-proces al acestui pachet o invocă colectiv pe fiecare rang MPI.

Sunt potențial disponibile două niveluri de paralelism și sunt independente:

  • În cadrul unei diagonalizări. Un solver precum SBD poate distribui o singură diagonalizare pe mai multe ranguri.

  • Între loturi. Un solver ar putea, în principiu, să diagonalizeze loturile concurent.

Dat fiind un set de loturi, solverul stabilește cum delegă munca proceselor sale lucrătoare.