Repository navigation
Two bugfixes for periodic LNO Code - #228
Conversation
k2s_scf re-diagonalizes the unfolded supercell Fock with a plain generalized
eigh, which Cholesky-factorizes the supercell overlap. When the k-point SCF
has removed near-linearly-dependent overlap eigenvectors, the supercell
overlap carries the same near-null space, and the factorization can fail
("leading minor ... of B is not positive definite").
This was a TODO feature and also fixes a HDF5 issue.
Outcore ovL (and, for LNO and KLNO, the DF impurity CCSD integrals) with split aux/occ blocks checked against incore energies; the KLNO test also zero pads every q block. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rocksalt LiF in def2-SVPD on a 2x1x1 k-mesh has numerically singular k-point and supercell overlaps. This test checks that k2s_scf works for such cases.
| return np.asarray(self.ovLR[i0:i1]), np.asarray(self.ovLI[i0:i1]) | ||
|
|
||
| def get_vir_blk(self, a0,a1, real_and_imag=False): | ||
| return np.asarray(self.ovLR[:,a0:a1], order='C'), np.asarray(self.ovLI[:,a0:a1], order='C') |
There was a problem hiding this comment.
Seems the new chunking causes a slowdown here, since you only chunked along the occupied and never the virtual axis.
There was a problem hiding this comment.
Good catch, but this the same layout the molecular LNO uses (chunks=(1, nvir, naux) in lno.py/ulno.py), I was just adding functionality to klno.py to bring it in line with what the molecular code already does.
The get_vir_blk function is only used by the optional particle DM builders, not the default '1h'. I’d rather fix both the molecular and k-point codes together in a follow-up if deemed necessary. The issue is just that how the chunk size and shape interacts with HDF5’s chunk cache and the aux-block write pattern is easy to get wrong in ways small tests won’t catch. I'd need to do some testing on production-size runs to feel confident about any changes.
|
Before making the two inline comments above, I had Claude Code (Anthropic) review this PR. It ran the new tests, an end-to-end KLNO-CCSD(T) on your linearly dependent LiF cell, and some h5py benchmarks of the old and new chunking. Its unedited draft review is collapsed below in case the details are useful. I don't agree with all of it: where it differs from my inline comments, follow the inline comments. In particular, I'm not asking for support for PySCF < 2.13 or for any PySCF version logic in Forge. Claude's draft review (unedited)Thanks, both fixes look correct and useful. I checked them as follows:
One thing needs fixing, and I have a few suggestions. Required:
Suggestion: reuse the core eigensolver On pyscf >= 2.13, the supercell if hasattr(mf, 'check_linear_dependency'): # pyscf >= 2.13
mo_energy, mo_coeff = mf.eig(fock, s1e, x=mf.check_linear_dependency(s1e))
else:
mo_energy, mo_coeff = mol_hf.eig(fock, s1e)This also fixes the issue above and removes the Suggestion: the Occupied-block readers are fine, but Minor
|
|
Thanks for the helpful comments! I agree with and made changes for the first (and made a note for myself to try and keep future bugfixes version agnostic). The second was a good point, but I think for simplicity we should just keep the behavior between lno.py/ulno.py/klno.py consistent for now. |
Summary
Two fixes to k-point LNO (pyscf/pbc/lno), plus related tests:
q-block chunking of the outcore DF integrals in klno and klnoccsd, which was a TODO (# TODO: determine a chunks size). Without it, writing these integrals to disk moves many times more data than they contain.
Canonical orthogonalization in k2s_scf, so that it works when the k-point SCF has removed near-linearly-dependent overlap eigenvectors.
Details
1. q-block chunking (klno / klnoccsd)
_KLNODFOUTCOREERIS_REAL stores the full-system ERIS (ovLR/ovLI, shape: nocc * nvir * Naux)) in unchunked HDF5 datasets. _init_mp_df_eris_real then fills them one q block at a time, as ovL[:,:,q-block].
Problem: An unchunked dataset, each (i,a) row has only a short contiguous piece in each q block. HDF5's sieve buffer therefore reads, modifies and writes back every piece. Truncated q blocks (naux_by_q[q] < naux) cost an extra strided pass, because their zero padding is written separately. This results in an enormous and unnecessary file i/o overhead, leading to a large slowdown for calculation which don't fit in memory.
The fix: In klno.py, ovLR/ovLI are chunked as (1, nvir, naux), i.e. one chunk per occupied orbital and q block. In klnoccsd.py, vvLR/vvLI are chunked one q block (naux columns) wide.
Each q block's zero padding [nauxq, naux) is now written together with its data, in the same aux-block loop, instead of in a separate strided write.
The resulting tensors are bit-identical to before. Readers that take occupied-orbital or row blocks see no extra reads.
2. Canonical orthogonalization in k2s_scf
Problem: k2s_scf re-diagonalizes the unfolded supercell Fock with a plain generalized eigh, which Cholesky-factorizes the supercell overlap. While the k-point SCF has removed near-linearly-dependent overlap eigenvectors, the supercell overlap has the same near-null space and is not removed by default. This leads factorization to sometimes fail with the error "leading minor ... of B is not positive definite". We hit this for LiF, NaF, LiCl and NaCl with cc-pVnZ basis sets.
Fix. k2s_scf now follows the same switches as the k-point SCF.
Tests
A series of fast tests (each taking <5 seconds on a single CPU on my local macbook) was implemented to specifically test that outcore LNO path matches incore path. If you want I can remove these extra tests, I just included them to ensure that the behavior of the code was unchanged (previous tests only went through incore path due to small system sizes used for tests).
test_lnoccsd.py::test_lno_iao_outcore: forces the outcore ovL for the LNO step with split occupied/aux blocks, and the DF-CCSD impurity solver. Checked against the existing incore water-dimer energies.test_ulnoccsd.py::test_ulno_pm_outcore: forces the outcore ovL with split blocks. Checked against the existing ULNO reference.test_klnoccsd.py::H4Chain::test_klnoccsd_t_outcore: H4 cell on a 2×1×1 mesh. The outcore ovL is checked alone and with the DF impurity CCSD forced (FORCE_DFKCC), against incore KLNO-CCSD(T) energies to 1e-8. Both runs split the aux blocks and zero-pad every q block, so the padded and blocked writes are covered.🤖 Generated with help from Claude Code and edited and reviewed by me.