주 콘텐츠로 건너뛰기
이 프로젝트는 베타 단계입니다. 버그를 발견하셨나요? GitHub 이슈 만들기. 질문이나 아이디어가 있으신가요? Discussions에서 질문하기 — 피드백을 기다립니다!
이 페이지의 번역 품질은 어떻습니까?

trim SQD를 활용해 구성 선택 개선하기

diagonalize_fermionic_hamiltonian의 기본 동작은 부분 샘플링된 각 배치를 대각화하고 가장 좋은 결과를 유지하는 것입니다. 반면 Trim SQD는 배치를 선별할 후보로 취급합니다. 각 배치를 대각화한 뒤 가중치가 가장 큰 구성만 남기도록 trim하고, 모든 배치에서 살아남은 구성을 병합하여 함께 대각화합니다. 이 병합된 대각화의 에너지가 해당 반복에서 보고되는 에너지이며, 그 trim된 출력이 다음 반복의 시드가 됩니다.

trim SQD 구현은 Cleveland Clinic Foundation, RIKEN, IBM®의 공동 연구를 기반으로 합니다. 이 기능을 연구에 사용한다면 해당 논문도 인용해 주세요. 인용 정보는 CITATION.bib 파일에서 인용 키 merz2026crossing으로 찾을 수 있습니다.

이 예제에 사용된 SQD 코드에 대한 자세한 내용은 화학 해밀토니안 튜토리얼을 참고하세요.

분자 설정하기​

# 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

루프 실행하기​

trim SQD의 한 번의 반복은 다음을 수행합니다.

  1. 선별 라운드. 비트열을 배치로 나눕니다. 각 배치를 대각화하고, 스핀 섹터별로 가중치 순으로 순위를 매긴 CI 문자열 중 trim_ratio 비율만 유지합니다.

  2. 병합 라운드. 살아남은 구성을 병합하여 한 번 대각화합니다. 이것이 해당 반복에서 보고하는 결과이며, 여기서 trim된 문자열이 다음 반복의 시드가 됩니다.

루프의 sci_solver가 두 라운드를 모두 수행하며, 기본값은 이 패키지의 PySCF 솔버입니다.

다음과 같이 TrimPolicy를 전달하여 trim SQD를 활성화합니다.

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

선별이 도움이 되는지 확인하기​

선별은 샘플링된 구성이 한 번에 대각화할 수 있는 양보다 훨씬 많을 때 가장 큰 효과를 냅니다. 더 큰 풀의 순위를 매기면 단일 배치가 놓쳤을 구성을 실제로 찾아낼 수 있기 때문입니다. 반대로 감당할 수 있는 크기의 부분공간이 이미 파동함수의 대부분을 포착하고 있다면 효과가 가장 적습니다.

선별은 배치들이 서로 다르다는 데 의존합니다. TrimPolicy는 diagonalize_fermionic_hamiltonian의 동작을 수정하여 samples_per_batch * num_batches개의 비트열로 이루어진 하나의 풀을 뽑아 배치들에 나누어 줌으로써 이를 관리하며, 따라서 어떤 비트열도 둘 이상의 배치에 나타나지 않습니다.

부분공간 제한하기​

여기서 부분공간은 스핀 알파와 스핀 베타 CI 문자열 배열의 데카르트 곱이 span합니다.

trim은 구성의 순위를 매기지만, 이월되는 것은 스핀별 문자열 목록 두 개입니다. 부분공간에서 가중치가 가장 높은 구성 150개를 유지하면 보통 서로 다른 알파 문자열 약 150개와 베타 문자열 약 150개가 관여하며, 이들의 곱은 150이 아니라 약 22,500개의 구성을 span합니다. 따라서 다음 반복에 전달되는 부분공간은 일반적으로 순위를 매긴 집합보다 훨씬 크며, 유지하는 양에 대해 제곱으로 증가합니다.

trim과 이월은 서로 다른 시점에 일어나므로 별도로 제한합니다. trim은 선별 라운드의 각 배치를 줄이고, 이월은 다음 반복으로 넘어가는 것입니다.

네 개의 인수로 이를 제한하며, 최소한 하나는 설정하는 것이 좋습니다.

  • **trim_ratio**는 스핀 섹터별로 각 배치의 문자열 중 선별을 통과하는 비율입니다. 값이 낮을수록 병합된 부분공간은 줄어들지만, 다음 반복에서 샘플링으로 다시 찾아내야 하는 구성이 버려집니다.

  • **max_strings_per_trim**은 비율과 관계없이 각 배치가 섹터별로 기여하는 문자열 수의 상한을 정합니다. 두 스핀 섹터를 하나의 목록으로 병합하여 각 배치가 제공하는 양이 대략 두 배가 되는 symmetrize_spin과 함께 사용하면 유용합니다. 이 제한은 배치별입니다. 병합된 부분공간은 최대 num_batches배의 문자열을 담을 수 있으며, 배치들이 공통으로 가진 문자열만큼은 줄어듭니다. 실제로는 그 양이 많지 않습니다.

  • carryover_threshold, carryover_ratio, **max_carryover**는 다음 반복의 시드가 되는 문자열을 제한하며, 배치별이 아니라 병합된 부분공간 전체에 적용됩니다. carryover_threshold는 StandardPolicy가 사용하는 진폭 임계값이며 기본값도 같습니다. 나머지 둘은 개수를 제한하는데, 임계값만으로는 개수를 제한할 수 없습니다.

  • diagonalize_fermionic_hamiltonian 자체의 **max_dim**은 병합된 부분공간을 포함해 루프가 대각화하는 모든 부분공간의 각 스핀 섹터를 제한합니다. 이는 안전장치 역할을 하며, 이월된 구성이 새 샘플과 병합된 후 정책이 반환하는 모든 것에 적용됩니다.

위 실행에서는 max_strings_per_trim=250과 max_dim=250을 함께 설정하므로, 각 배치가 기여하는 양도 그로부터 만들어지는 부분공간도 통제 불능으로 커질 수 없습니다.

외부 eigensolver 사용하기​

sci_solver 인수를 전달하여 trim SQD를 외부 eigensolver와 함께 사용할 수 있습니다.

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,
)

앞의 코드 조각을 실행하려면 PyPI에서 sbd-eigensolver 패키지를 설치해야 합니다.

작업 분산하기​

TrimPolicy를 사용하면 루프는 반복마다 sci_solver를 두 번 호출합니다. 한 번은 모든 배치와 함께, 한 번은 병합된 부분공간과 함께 호출합니다. 두 호출 모두 일반 sci_solver 인터페이스를 거치며, 이 패키지의 다중 프로세스 지원은 이를 모든 MPI 랭크에서 집합적으로 호출합니다.

두 수준의 병렬성을 사용할 수 있으며, 둘은 서로 독립적입니다.

  • 하나의 대각화 내부. SBD와 같은 솔버는 하나의 대각화를 여러 랭크에 걸쳐 분산할 수 있습니다.

  • 배치 간. 솔버는 원칙적으로 배치들을 동시에 대각화할 수 있습니다.

배치 집합이 주어지면, 솔버가 작업자 프로세스에 작업을 어떻게 위임할지 결정합니다.