Three parameters, from molecular integrals to a converged ground-state energy. 三个参数,从分子积分到收敛的量子化学基态能量
Have a FCIDUMP file? Three lines: 有 FCIDUMP 文件?三行代码:
Python
import trimci
result = trimci.ground_state("FCIDUMP")
print(result.energy) # variational energy (Ha)
print(result.energy_pt2) # + PT2 correction
No FCIDUMP, just h1 and eri arrays? Also three lines:
没有 FCIDUMP,只有 h1、eri 数组?也三行:
Python
result = trimci.ground_state((h1, eri, n_elec))
print(result.summary())
MS2 headerFCIDUMP 文件路径(字符串)— 最常用;自旋由文件 MS2 字段决定(h1, eri, n_elec) tuple — pass spin via ms2; e_nuc defaults to 0(h1, eri, n_elec) 元组 — 用 ms2 指定自旋;e_nuc 默认 0(h1, eri, n_elec, e_nuc) tuple — fully specified, ms2 kwarg sets spin(h1, eri, n_elec, e_nuc) 元组 — 完整指定,ms2 kwarg 设自旋mf.mol.spin; pass active=(n_e, n_o) for an active spacePySCF mean-field 对象 — 自旋取自 mf.mol.spin;用 active=(n_e, n_o) 指定活性空间n_dets=10⁸ → 1k / 1M / 10⁸).
。决定最终精度 — 越大越准。内部按 log scale 自动切成 Phase 0/1/2 三段(例如 n_dets=10⁸ 切成 1k / 1M / 10⁸)。
100_000默认 100_000effort=0.4 is the sweet spot — leave it alone (based on Fe4S4 measurements)effort=0.4 是 sweet spot — 不用调(基于 Fe4S4 实测)effort=1.0 matches TrimCI_skill defaults (num_runs=64, BFGS=50 iter)effort=1.0 对齐 TrimCI_skill 默认(num_runs=64, BFGS=50 iter)effort=0.05 for a quick smoke testeffort=0.05 快速 smoke testnum_runs ≈ 64×effort (BFGS maxiter fixed at 100 — exits early when converged).具体 scaling: num_runs ≈ 64×effort(BFGS maxiter 固定 100,收敛即提早退出)。
(h1, eri, n_elec) path: pass ms2=1 for doublet, ms2=2 for triplet, etc. For FCIDUMP path: the file's MS2 field is authoritative (passing ms2 here warns and is ignored).
默认 0(闭壳层单重态)。(h1, eri, n_elec) 路径:ms2=1 doublet,ms2=2 triplet 等。FCIDUMP 路径:以文件 MS2 字段为准(此 kwarg 会被忽略并 warn)。
effort=0.4 is already good enough; for higher accuracy, scale n_dets, not effort. If your n_dets request exceeds the Hilbert dim, it's auto-clamped (see res.diagnostics.n_fci).
实测发现 n_dets 是主导轴,effort 次之。effort=0.4 已经够好;要更高精度优先扩 n_dets,不是堆 effort。n_dets 超 Hilbert 维度时自动 clamp(查 res.diagnostics.n_fci)。
Values below come from a 9-point n_dets sweep on a 32-core x86 node (effort=0.4 fixed). Wall times will scale with your hardware. 下面这张表来自跨 9 个 n_dets 的实测扫描,跑在 32 核 x86 节点上(effort=0.4 固定)。不同硬件 wall 时间会不同。
| Goal目标 | Call调用 | Expected E_PT2 (Ha)期望 E_PT2 (Ha) | Typical wall典型 wall |
|---|---|---|---|
| Smoke test (just confirm it runs)烟雾测试(确认能跑) | ground_state(p, n_dets=100, effort=0.05) |
-327.17 | ~2 min |
| Quick estimate快速估算 | ground_state(p, n_dets=3000) |
-327.18 | ~4 min |
| Balanced accuracy/cost平衡精度/成本recommended推荐 | ground_state(p, n_dets=100_000) |
-327.224 | ~5 min |
| High accuracy高精度 | ground_state(p, n_dets=1_000_000) |
-327.231 | ~10 min |
| Larger scale更大规模 | ground_state(p, n_dets=3_000_000) |
-327.235 | ~16 min |
The returned Result has three layers. The 5 most commonly used fields are at the top:
返回的 Result 分三层。最常用的字段在顶层(5 个):
Top-level shortcuts
res = trimci.ground_state("FCIDUMP")
res.energy # float — variational total energy (Ha)
res.energy_pt2 # float | None — variational + PT2
res.n_dets # int — final det count
res.wall_time # float — total wall (seconds)
To judge whether the result is "converged enough" for your purpose, look at: 想判断结果是不是"够收敛",看:
res.energies.pt2_correction — small (e.g. < 1 mHa) ⇒ variational space is saturated很小(< 1 mHa)⇒ variational 空间足够res.energies.history[-3:] — last few rounds flat ⇒ converged plateau末几轮平 ⇒ 收敛平台res.n_dets == res.diagnostics.n_fci — expansion reached the Hilbert dim ⇒ exact (FCI)达到 Hilbert 维度 ⇒ FCI 精确.energies / .wavefunction / .diagnostics
子对象:.energies / .wavefunction / .diagnostics
Energies — energy decompositionEnergies — 能量分解
res.energies.var # variational energy
res.energies.pt2_correction # ΔE_PT2 (≤ 0)
res.energies.nuclear # nuclear repulsion
res.energies.electronic # = var - nuclear
res.energies.history # energy trajectory across all rounds [list]
res.energies.pt2_history # per-round ΔPT2 [list]
Wavefunction
wf = res.wavefunction
wf.coeffs # (N,) ndarray — amplitudes
wf.dets # (N, 2K) ndarray — α+β bitstrings stacked
wf.alpha_bits # (N, K) uint64
wf.beta_bits # (N, K) uint64
wf.n_orb
wf.is_normalized # normalization check
# advanced extraction
wf.leading(5) # top-5 dets: [(α_bits, β_bits, c), ...]
wf.occupation_numbers() # per-orbital occupation (n_orb, 2) [α, β]
# dict-like access (backward compat)
wf["110011|110011"] # lookup amplitude by bitstring
for bits, c in wf.items():
...
Diagnostics — performance / Hilbert dim / configDiagnostics — 性能 / Hilbert 维度 / 配置
d = res.diagnostics
d.wall_phase0 # Phase 0 (orbopt) time
d.wall_expansion # Phase 1+2 expansion time
d.n_dets_phase0 # det count after Phase 0
d.n_dets_final # final det count
d.expansion_rounds # total Phase 1+2 rounds
d.n_fci # full Hilbert dim = C(n_orb, n_α)·C(n_orb, n_β)
# requested n_dets is auto-clamped to this
d.orbopt_dir # Phase 0 orbopt npz dir (or None)
d.checkpoint_dir # Phase 1+2 checkpoint dir (or None)
d.config # the Config dataclass used for this run
# Integrals in the FINAL basis (after both Phase 0 and Phase 1 orbopt):
res.final_h1 # (n_orb, n_orb)
res.final_eri # (n_orb, n_orb, n_orb, n_orb)
# Cumulative rotation: C_final = C_input @ U
res.orbital_rotation # (n_orb, n_orb) orthogonal matrix
# Sanity check: U^T · h1_input · U should equal final_h1
assert np.allclose(
res.orbital_rotation.T @ h1_input @ res.orbital_rotation,
res.final_h1,
)
Human-readable summary人类可读 summary
print(res.summary())
# Output (example):
# TrimCI Calculation Summary
# Energy (var): -327.1937 Ha
# Energy (PT2): -327.1957 Ha (ΔPT2 = -0.002)
# Determinants: 800 (Phase 0: 100)
# Wall (Phase 0): 3.1 min
# Wall (Expansion): 0.7 min (12 rounds)
# Wall (total): 3.8 min
res = trimci.ground_state("path/to/FCIDUMP", n_dets=100000)
(h1, eri, n_elec) tuple方式 B: (h1, eri, n_elec) 元组import numpy as np
# 4-site Hubbard ring, U=4, t=1
n = 4
h1 = np.zeros((n, n))
eri = np.zeros((n, n, n, n))
for i in range(n):
j = (i + 1) % n
h1[i, j] = h1[j, i] = -1.0
eri[i, i, i, i] = 4.0
res = trimci.ground_state((h1, eri, 4), n_dets=30)
# E_var = -2.1027484 Ha — matches exact FCI
# Open-shell: pass ms2 = 2·S_z = n_alpha - n_beta
res = trimci.ground_state((h1, eri, 3), ms2=1) # doublet
res = trimci.ground_state((h1, eri, 4), ms2=2) # triplet
The 4-tuple form (h1, eri, n_elec, e_nuc) lets you specify nuclear repulsion explicitly.
四元组形式 (h1, eri, n_elec, e_nuc) 让你显式指定核排斥。
from pyscf import gto, scf
mol = gto.M(atom="N 0 0 0; N 0 0 1.1", basis="sto-3g")
mf = scf.RHF(mol).run()
# Unified entry — same as Mode A/B
res = trimci.ground_state(mf, n_dets=10000)
# For an active space: pass active=(n_e, n_o)
# res = trimci.ground_state(mf, n_dets=10000, active=(8, 8))
For large-n_dets calculations, save your results. Default is NPZ (compressed binary): 大 n_dets 跑出来的结果,务必存盘。默认 NPZ 格式(压缩二进制):
res.save("my_calc.npz") # NPZ — recommended
res.save("my_calc") # auto-appends .npz
res.save("my_calc.json") # JSON — only for small n_dets / debug
# load back
res2 = trimci.Result.load("my_calc.npz")
print(res2.energy) # exact round-trip across processes
uint64/complex128 are preserved exactly. Always use NPZ unless you need to read it by eye.
N=1M dets 的 JSON 大约 100 MB,NPZ + zlib 压缩约 5-15 MB(缩 8-20×),还保留 uint64/complex128 等 dtype。除非要肉眼检视,都用 NPZ。
trimci.ground_state(p, n_dets=100, effort=0.05)
# ~2 min, absolute value off by ~100 mHa, but you'll know if import + workflow are healthy
trimci.ground_state(p, n_dets=10_000)
# Usually nails FCI. Cross-check with pyscf FCI: diff < 0.1 mHa
trimci.ground_state(p, n_dets=100_000)
# Fe4S4: -327.224 Ha in 5 min, ~7 mHa above theoretical limit
trimci.ground_state(p, n_dets=1_000_000)
# Fe4S4 single run: ~-327.23 Ha in ~10 min
# For tighter bounds, run multiple seeds and take the best/mean.
trimci.ground_state(p, n_dets=3_000_000)
# Measured: N2 r=2.5 Å (823 mHa correlation) → FCI at n=2000
# Fe4S4-scale clusters: 1M-3M recommended
n_dets to the 10k-100k range; leave effort alone降 n_dets 到 10k-100k 区间,effort 不动
When 3 parameters aren't enough, construct a Config and pass it via the config= kwarg of ground_state:
当三参数不够用时,构造 Config 对象,通过 ground_state 的 config= 关键字传入:
from trimci import Config
cfg = Config(
# Phase 0
num_runs=128,
orbopt_cycles=8,
orbopt=True,
max_dets_phase0=500,
tracking_dets=True,
loaded_dets_randomness=0.1,
# Phase 1 (orbopt + expansion)
max_dets_phase1=100_000,
growth_factor_phase1=1.1,
orbopt_phase1=True,
use_connection_cache_phase1=True,
# Phase 2 (final expansion)
max_dets_phase2=1_000_000,
growth_factor_phase2=2.0,
pt2_correction=True,
pt2_only_last=True, # PT2 only at the final round; saves time
# Reproducibility (None = fresh entropy per call; int = deterministic)
seed=42,
# Performance
n_cpu_workers=8, # Phase 0 parallel subprocesses
n_gpu_workers=0,
backend_phase1="cpu", # or "gpu", "auto"
# Advanced PT2 override
phase2_overrides={"pt2": {"sketch_width": 500_000}},
)
# config= works with any input type:
res = trimci.ground_state("FCIDUMP", config=cfg)
res = trimci.ground_state((h1, eri, 14, 0.0), config=cfg)
res = trimci.ground_state(mf, config=cfg)
n_dets exceeds the Hilbert dim C(n_orb, n_α) × C(n_orb, n_β), the Phase 0/1/2 caps are silently right-sized at the dispatcher (no warning needed — full FCI is the correct ceiling). Available as res.diagnostics.n_fci.
用户请求的 n_dets 超过 Hilbert 维度 C(n_orb, n_α) × C(n_orb, n_β) 时,dispatcher 静默把 Phase 0/1/2 caps 切到正确大小(不警告 — full FCI 本来就是天花板)。值在 res.diagnostics.n_fci。
Config(seed=42) drives Phase 0 TrimCI Core Search + PT2 sketches + loaded-det random walks from a master seed. Same seed → bitwise-identical Result (modulo OMP non-determinism, ~1e-12 Ha). Default seed=None draws fresh entropy on each call.
Config(seed=42) 用 master seed 驱动 Phase 0 TrimCI Core Search + PT2 sketch + 继承 det 的 random walk。相同 seed → bitwise 一致 Result(除 OMP 非确定性的 ~1e-12 Ha 抖动)。默认 seed=None 每次取新熵。
ground_state)
默认参数(ground_state 自动用)
| Parameter参数 | Default默认值 | Meaning含义 |
|---|---|---|
orbopt_cycles |
5 (fixed)5 固定 | Phase 0 orbital optimization passes.Phase 0 轨道优化轮数 |
tracking_dets |
True | Carry dets between cycles instead of fresh random restarts.cycle 间继承 dets,不每次随机重启 |
loaded_dets_randomness |
0.1 | ~5 random single-excitations on carried dets to avoid basin lock-in.继承的 det 上做 ~5 步随机激发,避免 basin 锁死 |
pt2_only_last |
True | Skip per-round PT2 during Phase 2 expansion; evaluate only at final det count.Phase 2 expansion 不做 per-round PT2,只在最终评估 |
num_runs |
round(64 × effort) | Phase 0 stochastic exploration count.Phase 0 stochastic 探索次数 |
orbopt_max_iter |
100 (fixed)100 (固定) | Phase 1 BFGS step cap. Exits early when converged — cap rarely binds.Phase 1 BFGS 单 cycle 上限。收敛即提早退出,cap 几乎不触底 |
seed |
None | None ⇒ fresh entropy. Set int for reproducible runs / multi-seed variance studies.None ⇒ 每次新随机。设 int 用于复现 / multi-seed 方差研究 |
The variance of a single stochastic realization can exceed the expected gap between different effort levels. Pass config=Config(seed=42, ...) to ground_state to make a run reproducible, then sweep seeds to estimate variance. Or just scale up n_dets instead of pushing effort.
原因:单次 stochastic 实现的方差大于不同 effort 之间的期望差。给 ground_state 传 config=Config(seed=42, ...) 可复现一次运行;扫多个 seed 可估方差。或者直接扩 n_dets 而不是堆 effort。
This is expected. Phase 0 (orbital optimization) dominates wall time and barely scales with n_dets; Phase 1+2 expansion is relatively cheap under the default settings. 这是正常现象。Phase 0(轨道优化)是主要 wall 来源,几乎不随 n_dets 变化;Phase 1+2 expansion 在默认配置下成本相对较低。
res.energy_pt2 is None
问题:res.energy_pt2 is None
Either pt2_correction is disabled, or n_dets has covered the full Hilbert space (FCI-saturated — no external dets for PT2 to add). Read res.energy directly.
说明 pt2_correction 被关了,或者 n_dets 已经覆盖了完整 Hilbert 空间(FCI saturated,PT2 没有外部 det 可加)。看 res.energy。
No. Wavefunction.alpha_bits has shape (N, K) where K = ceil(n_orb / 64) uint64 segments. All API surfaces (leading(), to_dict(), save/load) handle multi-segment dets correctly.
没问题。Wavefunction.alpha_bits 是 (N, K) 形状,K = ceil(n_orb / 64) 个 uint64 segments。所有 API(leading()、to_dict()、save/load)都正确处理多 segment。
PySCF is required to write FCIDUMP
问题:报错 PySCF is required to write FCIDUMP
The (h1, eri) tuple path calls pyscf.tools.fcidump.from_integrals under the hood. Either install PySCF or pre-write the FCIDUMP file yourself and pass the path.
用 (h1, eri) 元组路径会调用 pyscf.tools.fcidump.from_integrals。装 PySCF 或者自己用 FCIDUMP 文件路径替代。
Three options, depending on input form: 视输入形式,三种方式:
MS2 header (just make sure your FCIDUMP writer set it correctly).FCIDUMP 路径 — 自旋从文件 MS2 字段读取(确保 FCIDUMP 写出时设对了)。ms2 kwarg: ground_state((h1, eri, n_elec), n_dets=..., ms2=2) for a triplet, ms2=1 for a doublet, etc.积分元组 — 传 ms2 关键字:三态 ground_state((h1, eri, n_elec), n_dets=..., ms2=2),二态 ms2=1,依此类推。mf.mol.spin automatically (e.g. gto.M(..., spin=2) + scf.ROHF/UHF).PySCF mf — 自旋自动取自 mf.mol.spin(用 gto.M(..., spin=2) + scf.ROHF/UHF)。
Check stdout for Phase 0 progress before res.diagnostics — Phase 0 prints continuously. If stuck in Cycle 1, orbopt_max_iter may be too high or the system is hard to converge. Try effort=0.05 first to confirm the workflow runs.
查 res.diagnostics 之前先看 stdout — Phase 0 会 print progress。若卡在 Phase 0 cycle 1,可能是 orbopt_max_iter 太大 / 体系难收敛。试 effort=0.05 先验证 workflow 通。