Repository navigation
GeneralizedDimer: use the C++ LBFGS for the rotation step (fixes flaky test_generalized_dimer) - #48
Merged
Merged
Conversation
The dimer's rotation step minimized the curvature with MYLBFGS, which accepts a step only if the relative change in curvature is below maxErise. Near zero curvature that rejects every step, and after 10 failed line searches it raised LineSearchError without its required argument, i.e. a TypeError. test_generalized_dimer::test1 failed in 71 of 2000 held-out random starts (seeds 10000-11999); with this change it fails in 3 (an atom breaks off, see the TODO in the test). - LBFGS_CPP: get_state/set_state for the LBFGS memory (C++ and Python) - FindLowestEigenVector(cpp_lbfgs=True) uses LBFGS_CPP and keeps its memory across update_coords; GeneralizedDimer turns it on. findTransitionState keeps MYLBFGS. - LineSearchError is raised with a message - TODO on LowestEigPot's Python overhead (~34 us per call vs 2.5 us for the LJ18 gradient) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
test_generalized_dimer::test1starts from an unseeded random configuration and failed intermittently (e.g. in CI on the meson branch) with:Cause
The dimer's rotation step (
FindLowestEigenVector) minimizes the curvature along a direction withMYLBFGS(rel_energy=True), which accepts a step only if(E_new - E_old) / |E_old| <= maxErise(1e-4). When the lowest curvature approaches zero (seen at ~1e-7), ordinary finite-difference noise (~0.1% relative) exceeds that, every step is rejected, and after 10 failed line searches_lbfgs_py.pydoesraise LineSearchErrorwithout its required argument, hence theTypeError.Change
LBFGS_CPP:get_state()/set_state()for the LBFGS memory (s,y,rho,H0,k), in C++ and Cython, with a size checkFindLowestEigenVector(cpp_lbfgs=True): useLBFGS_CPPand keep its memory acrossupdate_coords(asMYLBFGSdid).GeneralizedDimerturns it on.findTransitionStateis unchanged (stillMYLBFGS); it hasn't been evaluated the same way.LineSearchErroris raised with a message.LowestEigPoton its Python overhead: with the C++ LBFGS each curvature evaluation costs ~34 us, of which 2.5 us is the LJ18 gradient; the rest is Python/numpy/orthogopt. A C++LowestEigPotentialexists insource/pele/lowest_eig_potential.hppbut lacks rotational orthogonalization and a Cython wrapper.Evaluation
To avoid tuning on the seeds used to choose the optimizer (0-299), everything below uses held-out seeds, paired (same seeds for both rotators). A result counts as a transition state only if the exact Hessian has exactly one negative eigenvalue (rigid-body modes excluded) and rms gradient < 1e-3; "test fails" means
res.successis False.Not tuned on: LJ13, LJ38, Morse13 (rho=6) and binary LJ18, 500 seeds per start each. No crashes anywhere with LBFGS_CPP (301 → 0 over all 8000 runs), test-failure rate 334 → 67. Verified-TS rates are equal or higher within the confidence intervals, except binary LJ18 from a random box (89.8% vs 91.4%, overlapping CIs).
Known limitations, unchanged by this PR:
res.successonly checks that the lowest curvature found is negative: 56 of the 2000 LJ18 runs "succeed" at points that are not first-order saddles (mostly two negative modes). With MYLBFGS it was 22, largely because runs that crashed now continue.Testing
pytest pele/: 591 passed (built with the currentsetup.py), including 2 new tests: restartingLBFGS_CPPfromget_state()takes the same step as the original, andset_statewith the wrong size raises.🤖 Generated with Claude Code