Fix circular neighbor list copy dependencies and re-enable trimming (issue #4529) - #51
Closed
stanmoore1 wants to merge 4 commits into
Closed
Fix circular neighbor list copy dependencies and re-enable trimming (issue #4529)#51stanmoore1 wants to merge 4 commits into
stanmoore1 wants to merge 4 commits into
Conversation
Builds on the parent commit, which distinguishes a cutoff that is the maximum across atom types from one that applies uniformly to all of them. That distinction is what lets the circular copy dependency of issue lammps#4529 be fixed without reintroducing the truncated analysis lists of issue lammps#4437. Finish it so that it compiles, covers every requestor, and cannot be omitted. Define the missing setter. fix nonaffine/displacement called set_cutoff_min(), which was never defined, so EXTRA-FIX did not compile. Its cutoff applies to all types, so it now uses set_cutoff_fixed(). Make the interpretation impossible to omit. Requesting a non-standard cutoff used to be a bare set_cutoff() whose meaning defaulted to "maximum across types", with an optional second set_cut_fixed(1) call to mean "uniform across types". Forgetting that second call silently truncates the list for the short-cutoff type pairs, with no error, and it was in fact missing at several call sites. Replace both with two single-call setters that each state the intent, and remove the bare set_cutoff(): set_cutoff_max(C) C is the maximum across types (pair styles) set_cutoff_fixed(C) C applies uniformly to every type pair (analysis) All 17 call sites are converted, so omitting the interpretation is now a compile error rather than a wrong answer. This also removes a latent case in compute composition/atom, which set the uniform flag unconditionally but the cutoff only conditionally, leaving the flag set on a list with no custom cutoff. Clear cut_fixed whenever morph_unique() drops the cut flag. A list reverting to the default cutoff kept a stale interpretation, which then fed the cutoff comparison that collapses copy chains in morph_copy_trim() and could point a copy at an intermediate list instead of its root, the same shape of problem as the circular chains in issue lammps#4529. Restructure the decision in morph_unique() into one explicit predicate. The previous else-if chain let a uniform-cutoff request that failed its own test fall through to the maximum-cutoff test. That fallthrough is deliberate, it is how a shorter uniform cutoff still gets its own list so it can be trimmed rather than silently widened, but nothing said so. Behavior is unchanged. Re-enable the one_trim_half_list_{newton,nonewton} tests, which pass again now that trimming is detected, and add a regression test for a uniform-cutoff list whose cutoff equals cutneighmax in a system with heterogeneous per-type pair cutoffs. It asserts that such a list's contents do not depend on the pair style's per-type cutoffs, by comparing neighbor counts against the same system with uniform pair cutoffs.
morph_copy_trim() orders copies by list index to keep three or more perpetual lists from being turned into copies of each other, which is a circular dependency that reordering the build order cannot resolve. That ordering was skipped for any list carrying a custom cutoff: if (!irq->occasional && !irq->cut && j > i) continue; The !irq->cut term stood in for "both lists use the default cutoff", which was close enough while few lists kept a custom cutoff. Now that a request keeps its cutoff whenever it differs from cutneighmax, several sub-styles sharing one cutoff all skip the ordering and can form a cycle. With pair_style hybrid/overlay lj/cut 2.5 lj/cut 1.2 lj/cut 1.2 lj/cut 1.2 initialization failed with "Failed to reorder neighbor lists to satisfy constraints", the very error of issue lammps#4529, on an input that develop handles. Two equal cutoffs stayed fine, since the existing jrq->copylist == i check catches a 2-cycle, so this only appeared with three or more. Order by cutoff instead, which is what actually bounds the chain: a copy or trim always points at a list whose cutoff is greater or equal, so a strictly longer cutoff makes the chain increase and it can never close. Only equal cutoffs need the index as a tie break: if (!irq->occasional && (icut == jcut) && j > i) continue; For lists on the default cutoff icut and jcut are both cutneighmax, so their behavior is unchanged, and trimming from a longer list is still allowed regardless of index. Add a regression test for three sub-styles sharing a cutoff. It checks the resulting lists through the library interface rather than the printed summary, and uses the HIDE_OUTPUT lambda so a failure is reported as a failed test instead of aborting the whole test binary.
stanmoore1
force-pushed
the
bugfix_4529_split
branch
from
August 24, 2026 22:48
83b6ef2 to
c41eb45
Compare
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.
Summary
NeighRequest::cutoffhad two possible meanings and no way to tell them apart:Because the default neighbor list is built with a cutoff per pair of atom types, guessing wrong either truncates the list for the type pairs with a shorter cutoff, or forces lists to be duplicated where they could be shared. Resolving that ambiguity is what allows the circular copy dependency of issue lammps#4529 to be fixed without reintroducing the truncated analysis lists of issue lammps#4437, and re-enables neighbor list trimming.
This is not a hard pair vs non-pair distinction, nor a perpetual vs occasional one; those are just useful ways to describe the typical case. See Implementation Notes.
The first commit is @jtclemm's, introducing the distinction. The remaining commits complete it:
set_cutoff_min().fix nonaffine/displacementcalled it but it was never defined, soEXTRA-FIXdid not compile. Its cutoff applies to all types, so it now usesset_cutoff_fixed().set_cutoff()andset_cut_fixed()with two single-call setters that each state the intent,set_cutoff_max()andset_cutoff_fixed(), and remove the bareset_cutoff(). All 17 call sites are converted, so leaving the interpretation unstated is a compile error rather than a silently wrong answer. Previously it was an optional second call that was in fact missing at several call sites.cut_fixedwhenmorph_unique()drops thecutflag, so a stale interpretation cannot leak into the cutoff comparison that collapses copy chains inmorph_copy_trim().morph_unique()into one explicit predicate. Behavior is unchanged; the deliberate fallthrough that lets a shorter uniform cutoff be trimmed rather than silently widened is now documented instead of implied.cutflag inmorph_copy_trim(), fixing a circular dependency between three or more perpetual lists that share a custom cutoff.Related Issue(s)
fixes lammps#4529
Also relevant: lammps#4437, whose fix is what this distinction preserves.
Author(s)
Stan Moore (SNL), stanmoore1@gmail.com (corresponding author).
The first commit in this branch is authored by Joel Clemmer (SNL) and is included unchanged.
Licensing
By submitting this pull request, I agree, that my contribution will be included in LAMMPS and redistributed under either the GNU General Public License version 2 (GPL v2) or the GNU Lesser General Public License version 2.1 (LGPL v2.1).
Artificial Intelligence (AI) Tools Usage
Portions of this pull request were generated with Claude (Anthropic) under my direction. The
cut_fixeddistinction inmorph_unique()originates from @jtclemm's commit, which is included unchanged and is not AI generated. The AI generated parts are: theset_cutoff_max()/set_cutoff_fixed()API refactor and its call site conversions, themorph_unique()restructuring and flag clearing, the copy cycle fix inmorph_copy_trim(), the added unit tests, and the documentation updates. All of it was reviewed by me and verified as described below.Backward Compatibility
No changes to input scripts or their behavior, other than the bug fixes themselves.
There is an API change for code outside the distribution:
NeighRequest::set_cutoff()andNeighRequest::set_cut_fixed()are removed in favor ofset_cutoff_max()andset_cutoff_fixed(). This is deliberate, since the removed call had no way to express the interpretation and defaulted to the one that silently truncates. A new section inDeveloper_updating.rstdocuments the migration, and the failure mode is a compile error rather than incorrect results.This is the one point worth an explicit decision before a stable release. A deprecated
set_cutoff()alias would preserve source compatibility, at the cost of restoring a default that is silently wrong for uniform-cutoff requests.Implementation Notes
The two interpretations are the whole point, and the divider is neither pair vs non-pair nor perpetual vs occasional.
set_cutoff_fixed()is used bycompute rdf,adf,hbond/local,ave/sphere/atom,composition/atom,efield/wolf/atom,fix rheo/oxidation,fix rheo/thermal,fix hyper/local,fix nonaffine/displacement,fix lambda/la/csp/apip, and the library request path, spanning fixes and computes, perpetual and occasional.set_cutoff_max()is used bypair hybrid,pair hybrid/molecular,pair kim, andpair lj/cut/tip4p/long/gpu.fix groupis the clearest illustration, requesting perpetual or occasional depending onevery, from one call site with one interpretation:The pair vs non-pair framing appears only as the explanatory typical case, in
neigh_request.handDeveloper_notes.rst.This also replaces the earlier
|| irq->occasionaltest: that clause is gone, and lammps#4437 is handled by the interpretation itself rather than by inferring intent from the occasional flag. A perpetual fix with a uniform cutoff, which the occasional proxy got wrong, is now correct and has a regression test.Copy cycle fix.
morph_copy_trim()orders copies by list index to keep three or more perpetual lists from becoming copies of each other:The
!irq->cutterm stood in for "both lists use the default cutoff", which held while few lists kept a custom cutoff. Once a request keeps its cutoff whenever it differs fromcutneighmax, several sub-styles sharing one cutoff all skip the ordering and can form a cycle. Withinitialization failed with "Failed to reorder neighbor lists to satisfy constraints", the very error of lammps#4529, on an input that
develophandles. Two equal cutoffs stayed fine, since the existingjrq->copylist == icheck catches a 2-cycle, so this only appeared with three or more.The chain is bounded by cutoff, not by the
cutflag: a copy or trim always points at a list whose cutoff is greater or equal, so a strictly longer cutoff makes the cutoff increase along the chain and it can never close. Only equal cutoffs need the index as a tie break:For lists on the default cutoff
icutandjcutare bothcutneighmax, so their behavior is unchanged, and trimming from a longer list is still allowed regardless of index.Verification, on serial, MPI (OpenMPI 4.1.6, up to 8 ranks) and KOKKOS (Serial backend) builds:
paircontains different cutoff for different type. lammps/lammps#4437, run unmodified, gives radial distribution functions that reach the requested cutoffs (6.0 and 8.0) instead of being truncated at the 3.6 pair cutoff.hybrid/overlaysub-style matches a standalone pair style at the same cutoff to all printed digits.cutneighmaxin a system with heterogeneous per-type cutoffs is no longer truncated, and matchesdevelopand an untruncated reference.comm_style tiledandfix balance rcbon 8 ranks, with trimming active, conserves atom count and reproduces the unbalanced energies exactly.test_neighbor_classpasses on all three builds.Three tests are added to
unittest/cplusplus/test_neighbor_class.cpp, and the two previously disabledone_trim_half_list_{newton,nonewton}tests are re-enabled. The new tests assert through the library interface rather than the printed neighbor list summary, so they do not depend on that formatting. Each was checked to fail without its corresponding fix.Out of scope: occasional lists influencing perpetual list optimization
Raised in review, and worth recording, but not addressed here because it is pre-existing and independent of the two interpretations above.
cutneighminfolds in the cutoffs of occasional requests (neighbor.cpp, "update cutneighmin based on individual neighbor list requests") and then feedsmorph_unique(),morph_copy_trim(), and bin sizing. So an occasional list can change how the perpetual lists are built. With a uniformlj/cut 6.0andgroup ... dynamic ... within 6.0 every 1, the perpetualfix GROUPlist shares the default binning on its own, but adding an unrelatedcompute rdf ... cutoff 1.0gives itcut 6.3andunique = 1, which skips the bin sharing loop inNeighbor::init()and instantiates a secondNBin, binning every atom a second time per reneighboring step.developdoes the same thing, so this branch neither introduces nor fixes it. Fixing it means deriving those decisions from a minimum taken over perpetual requests only, which is a behavior change well beyond this bug fix and belongs in its own pull request.Post Submission Checklist
Further Information, Files, and Links
Two items are not verified and are left unchecked above: the documentation build was not run, and only the CMake build system was used, not the traditional make build. Both are worth confirming in CI or by a reviewer.
A follow-up branch,
neigh_cutoff_min_reuse, builds on this one and adds a third interpretation,set_cutoff_min(), for styles that filter neighbors by distance themselves. It lets such a request reuse the default list when that list already covers the cutoff, and fixes a pre-existing per-type truncation incompute ave/sphere/atom. It is kept separate so this bug fix can be reviewed and backported on its own.