diff --git a/doc/src/Developer_notes.rst b/doc/src/Developer_notes.rst index 8b255daa0e7..cff792bc930 100644 --- a/doc/src/Developer_notes.rst +++ b/doc/src/Developer_notes.rst @@ -192,13 +192,33 @@ internally by :doc:`Peridynamics pair styles `: It is also possible to request a neighbor list that uses a different cutoff than what is usually inferred from the pair style settings (largest cutoff of -all pair styles plus neighbor list skin). The following is used in the -:doc:`compute rdf ` command implementation: +all pair styles plus neighbor list skin). Since the default neighbor list is +built with a cutoff *per pair of atom types*, such a request must also state +how its cutoff is to be interpreted, by calling exactly one of: + +.. list-table:: + :header-rows: 1 + :widths: 25 75 + + * - Function + - Interpretation of the requested cutoff + * - ``set_cutoff_max(cutoff)`` + - the maximum across atom types, individual type pairs may use a shorter + cutoff. This is the typical case for pair styles. + * - ``set_cutoff_fixed(cutoff)`` + - applies uniformly to every pair of atom types. This is the typical + case for a fix or compute analyzing a fixed range, e.g. an RDF. + +Using ``set_cutoff_max()`` where a uniform cutoff is meant will silently +truncate the list for those type pairs that use a shorter pair style cutoff. + +The following is used in the :doc:`compute rdf ` command +implementation, where the requested cutoff applies to all atom types: .. code-block:: c++ if (cutflag) - neighbor->add_request(this, NeighConst::REQ_OCCASIONAL)->set_cutoff(mycutneigh); + neighbor->add_request(this, NeighConst::REQ_OCCASIONAL)->set_cutoff_fixed(mycutneigh); else neighbor->add_request(this, NeighConst::REQ_OCCASIONAL); diff --git a/doc/src/Developer_updating.rst b/doc/src/Developer_updating.rst index 364c0e6ea0c..0f41e06390f 100644 --- a/doc/src/Developer_updating.rst +++ b/doc/src/Developer_updating.rst @@ -31,6 +31,7 @@ Available topics in mostly chronological order are: - `Refactored grid communication using Grid3d/Grid2d classes instead of GridComm`_ - `FLERR as first argument to minimum image functions in Domain class`_ - `Use utils::logmesg() instead of error->warning()`_ +- `Explicit interpretation for custom neighbor list cutoffs`_ ---- @@ -681,3 +682,37 @@ New: if (comm->me == 0) utils::logmesg(lmp, "INFO: About to read data file: {}\n", filename); This change is **required** or else the code will not compile. + +Explicit interpretation for custom neighbor list cutoffs +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The default neighbor list is built with a cutoff *per pair of atom types*, so +a request for a custom cutoff is ambiguous on its own: the same number can +mean "the largest cutoff used by any type pair" or "this cutoff for every type +pair". Assuming the former where the latter was meant silently truncates the +list for the type pairs that use a shorter pair style cutoff. + +``NeighRequest::set_cutoff()`` has therefore been replaced by two functions +that each state the intended interpretation, and must be called instead: + +- ``set_cutoff_max(cutoff)`` -- the maximum across atom types, individual type + pairs may use a shorter cutoff. This is the typical case for pair styles. +- ``set_cutoff_fixed(cutoff)`` -- applies uniformly to every pair of atom + types. This is the typical case for a fix or compute analyzing a fixed + range, e.g. an RDF. + +Old: + +.. code-block:: c++ + + auto req = neighbor->add_request(this, NeighConst::REQ_OCCASIONAL); + if (cutflag) req->set_cutoff(mycutneigh); + +New: + +.. code-block:: c++ + + auto req = neighbor->add_request(this, NeighConst::REQ_OCCASIONAL); + if (cutflag) req->set_cutoff_fixed(mycutneigh); + +This change is **required** or else the code will not compile. diff --git a/src/APIP/fix_lambda_la_csp_apip.cpp b/src/APIP/fix_lambda_la_csp_apip.cpp index 9a7b041737a..5914f8e17c1 100644 --- a/src/APIP/fix_lambda_la_csp_apip.cpp +++ b/src/APIP/fix_lambda_la_csp_apip.cpp @@ -251,7 +251,7 @@ void FixLambdaLACSPAPIP::init() { // full neighbour list for thermostating auto *req = neighbor->add_request(this, NeighConst::REQ_FULL); - req->set_cutoff(sqrt(cutsq_combined)); + req->set_cutoff_fixed(sqrt(cutsq_combined)); if (atom->tag_enable == 0) error->all(FLERR, "fix lambda/la/csp/apip requires atom IDs"); diff --git a/src/EXTRA-COMPUTE/compute_adf.cpp b/src/EXTRA-COMPUTE/compute_adf.cpp index 9575838e42a..d99e8d651af 100644 --- a/src/EXTRA-COMPUTE/compute_adf.cpp +++ b/src/EXTRA-COMPUTE/compute_adf.cpp @@ -332,7 +332,7 @@ void ComputeADF::init() if (neighbor->style == Neighbor::MULTI) error->all(FLERR, "Compute adf with custom cutoffs requires neighbor style 'bin' or 'nsq'"); - req->set_cutoff(mycutneigh); + req->set_cutoff_fixed(mycutneigh); } } diff --git a/src/EXTRA-COMPUTE/compute_ave_sphere_atom.cpp b/src/EXTRA-COMPUTE/compute_ave_sphere_atom.cpp index b3b6bd97ff2..24cd062d69f 100644 --- a/src/EXTRA-COMPUTE/compute_ave_sphere_atom.cpp +++ b/src/EXTRA-COMPUTE/compute_ave_sphere_atom.cpp @@ -116,7 +116,8 @@ void ComputeAveSphereAtom::init() // need an occasional full neighbor list auto *req = neighbor->add_request(this, NeighConst::REQ_FULL | NeighConst::REQ_OCCASIONAL); - if (cutflag) req->set_cutoff(cutoff); + // the analysis cutoff applies to all atom types + if (cutflag) req->set_cutoff_fixed(cutoff); } /* ---------------------------------------------------------------------- */ diff --git a/src/EXTRA-COMPUTE/compute_composition_atom.cpp b/src/EXTRA-COMPUTE/compute_composition_atom.cpp index 346ced33734..e4e3dc3f83d 100644 --- a/src/EXTRA-COMPUTE/compute_composition_atom.cpp +++ b/src/EXTRA-COMPUTE/compute_composition_atom.cpp @@ -108,7 +108,8 @@ void ComputeCompositionAtom::init() // need an occasional full neighbor list auto *req = neighbor->add_request(this, NeighConst::REQ_FULL | NeighConst::REQ_OCCASIONAL); - if (cutflag) req->set_cutoff(cutoff); + + if (cutflag) req->set_cutoff_fixed(cutoff); } /* ---------------------------------------------------------------------- */ diff --git a/src/EXTRA-COMPUTE/compute_efield_wolf_atom.cpp b/src/EXTRA-COMPUTE/compute_efield_wolf_atom.cpp index 50f8fef02be..750cacc37c1 100644 --- a/src/EXTRA-COMPUTE/compute_efield_wolf_atom.cpp +++ b/src/EXTRA-COMPUTE/compute_efield_wolf_atom.cpp @@ -98,7 +98,9 @@ void ComputeEfieldWolfAtom::init() // request an occasional full neighbor list auto *req = neighbor->add_request(this, NeighConst::REQ_FULL | NeighConst::REQ_OCCASIONAL); - if (cutoff_flag) req->set_cutoff(cutoff); + if (cutoff_flag) { + req->set_cutoff_fixed(cutoff); + } jgroup = group->find(group2); if (jgroup < 0) error->all(FLERR, "Compute efield/atom/wolf group {} does not exist", group2); diff --git a/src/EXTRA-COMPUTE/compute_hbond_local.cpp b/src/EXTRA-COMPUTE/compute_hbond_local.cpp index ef09ffa82d7..8006f4b0533 100644 --- a/src/EXTRA-COMPUTE/compute_hbond_local.cpp +++ b/src/EXTRA-COMPUTE/compute_hbond_local.cpp @@ -149,7 +149,8 @@ void ComputeHBondLocal::init() error->all(FLERR, Error::NOLASTLINE, "Compute hbond/local requires neighbor style 'bin' or 'nsq'"); auto *req = neighbor->add_request(this, NeighConst::REQ_FULL | NeighConst::REQ_OCCASIONAL); - req->set_cutoff(distcutoff); + + req->set_cutoff_fixed(distcutoff); // do initial memory allocation assuming all donors have two hydrogen bonds diff --git a/src/EXTRA-FIX/fix_nonaffine_displacement.cpp b/src/EXTRA-FIX/fix_nonaffine_displacement.cpp index e7a7efb53e9..ee61ffb962b 100644 --- a/src/EXTRA-FIX/fix_nonaffine_displacement.cpp +++ b/src/EXTRA-FIX/fix_nonaffine_displacement.cpp @@ -242,7 +242,8 @@ void FixNonaffineDisplacement::init() if (mycutneigh > cutghost) error->all(FLERR,"Fix nonaffine/displacement D2Min option cutoff exceeds ghost atom range - use comm_modify cutoff command"); - req->set_cutoff(mycutneigh); + // the cutoff is required for all types + req->set_cutoff_fixed(mycutneigh); } } } diff --git a/src/GPU/pair_lj_cut_tip4p_long_gpu.cpp b/src/GPU/pair_lj_cut_tip4p_long_gpu.cpp index 6268d8e4945..4ccee7ebfba 100644 --- a/src/GPU/pair_lj_cut_tip4p_long_gpu.cpp +++ b/src/GPU/pair_lj_cut_tip4p_long_gpu.cpp @@ -201,7 +201,7 @@ void PairLJCutTIP4PLongGPU::init_style() if (gpu_mode == GPU_FORCE) { auto *req = neighbor->add_request(this, NeighConst::REQ_FULL); // NOTE: we must not add the neighbor list skin here. It is already added automatically. - req->set_cutoff(cut_coulplus); + req->set_cutoff_max(cut_coulplus); } } diff --git a/src/KIM/pair_kim.cpp b/src/KIM/pair_kim.cpp index a4ba99b29a8..3dfb0d26902 100644 --- a/src/KIM/pair_kim.cpp +++ b/src/KIM/pair_kim.cpp @@ -594,7 +594,7 @@ void PairKIM::init_style() if (kim_cutoff_values[i] <= neighbor->skin) error->all(FLERR,"Illegal neighbor request (force cutoff {:.3} <= skin {:.3})", kim_cutoff_values[i], neighbor->skin); - req->set_cutoff(kim_cutoff_values[i]); + req->set_cutoff_max(kim_cutoff_values[i]); } // increment instance_me in case of need to change the neighbor list // request settings diff --git a/src/REPLICA/fix_hyper_local.cpp b/src/REPLICA/fix_hyper_local.cpp index 0bd875d6f24..bd0278f4b33 100644 --- a/src/REPLICA/fix_hyper_local.cpp +++ b/src/REPLICA/fix_hyper_local.cpp @@ -313,7 +313,8 @@ void FixHyperLocal::init() auto *req = neighbor->add_request(this, NeighConst::REQ_FULL | NeighConst::REQ_OCCASIONAL); req->set_id(1); - req->set_cutoff(dcut); + + req->set_cutoff_fixed(dcut); // also need occasional half neighbor list derived from pair style // used for building local bond list diff --git a/src/RHEO/fix_rheo_oxidation.cpp b/src/RHEO/fix_rheo_oxidation.cpp index 5acf8a133ed..a74a56f1f43 100644 --- a/src/RHEO/fix_rheo_oxidation.cpp +++ b/src/RHEO/fix_rheo_oxidation.cpp @@ -109,7 +109,8 @@ void FixRHEOOxidation::init() // need a half neighbor list auto *req = neighbor->add_request(this, NeighConst::REQ_FULL); - req->set_cutoff(cut); + + req->set_cutoff_fixed(cut); } /* ---------------------------------------------------------------------- */ diff --git a/src/RHEO/fix_rheo_thermal.cpp b/src/RHEO/fix_rheo_thermal.cpp index 02419da5873..a86cba8f0a9 100644 --- a/src/RHEO/fix_rheo_thermal.cpp +++ b/src/RHEO/fix_rheo_thermal.cpp @@ -294,7 +294,8 @@ void FixRHEOThermal::init() // need a half neighbor list, built only when particles freeze auto *req = neighbor->add_request(this, NeighConst::REQ_OCCASIONAL); - req->set_cutoff(cut_kernel); + + req->set_cutoff_fixed(cut_kernel); // find instances of bond history to delete/shift data histories = modify->get_fix_by_style("BOND_HISTORY"); diff --git a/src/compute_rdf.cpp b/src/compute_rdf.cpp index 28ba8b22bed..06c38973498 100644 --- a/src/compute_rdf.cpp +++ b/src/compute_rdf.cpp @@ -219,7 +219,8 @@ void ComputeRDF::init() if (neighbor->style == Neighbor::MULTI) error->all(FLERR, Error::NOLASTLINE, "Compute rdf with custom cutoff requires neighbor style 'bin' or 'nsq'"); - req->set_cutoff(mycutneigh); + + req->set_cutoff_fixed(mycutneigh); } } diff --git a/src/fix_group.cpp b/src/fix_group.cpp index 8074054151e..cf4b853dbcb 100644 --- a/src/fix_group.cpp +++ b/src/fix_group.cpp @@ -210,7 +210,8 @@ void FixGroup::init() } else { req = neighbor->add_request(this, NeighConst::REQ_FULL | NeighConst::REQ_OCCASIONAL); } - req->set_cutoff(cutoff); + + req->set_cutoff_fixed(cutoff); } if (excludeflag) excludebit = group->get_bitmask_by_id(FLERR, idexclude, "group dynamic exclude"); diff --git a/src/library.cpp b/src/library.cpp index 0d59caadf8e..3c7cab54a38 100644 --- a/src/library.cpp +++ b/src/library.cpp @@ -6485,7 +6485,10 @@ void NeighProxy::command(int narg, char **arg) int flags = utils::inumeric(FLERR, arg[1], false, lmp); double cutoff = utils::numeric(FLERR, arg[2], false, lmp); req->apply_flags(flags); - if (cutoff > 0.0) req->set_cutoff(cutoff); + if (cutoff > 0.0) { + // library-requested cutoff applies to all types + req->set_cutoff_fixed(cutoff); + } lmp->init(); // setup domain, communication and neighboring diff --git a/src/neigh_request.cpp b/src/neigh_request.cpp index d7281aca402..1862b335cc3 100644 --- a/src/neigh_request.cpp +++ b/src/neigh_request.cpp @@ -54,6 +54,7 @@ NeighRequest::NeighRequest(LAMMPS *_lmp) : Pointers(_lmp), requestor(nullptr) // default is no Kokkos neighbor list build // default is no Shardlow Splitting Algorithm (SSA) neighbor list build // default is no list-specific cutoff + // default is no fixed cutoff for all atom types // default is no storage of auxiliary floating point values occasional = 0; @@ -69,6 +70,7 @@ NeighRequest::NeighRequest(LAMMPS *_lmp) : Pointers(_lmp), requestor(nullptr) kokkos_host = kokkos_device = 0; ssa = 0; cut = 0; + cut_fixed = 0; cutoff = 0.0; // skip info, default is no skipping @@ -170,6 +172,7 @@ int NeighRequest::identical(NeighRequest *other) if (ssa != other->ssa) same = 0; if (copy != other->copy) same = 0; if (cutoff != other->cutoff) same = 0; + if (cut_fixed != other->cut_fixed) same = 0; if (skip != other->skip) same = 0; if (same && skip && other->skip) same = same_skip(other); @@ -235,6 +238,7 @@ void NeighRequest::copy_request(NeighRequest *other, int skipflag) kokkos_device = other->kokkos_device; ssa = other->ssa; cut = other->cut; + cut_fixed = other->cut_fixed; cutoff = other->cutoff; iskip = nullptr; @@ -283,10 +287,28 @@ void NeighRequest::apply_flags(int flags) /* ---------------------------------------------------------------------- */ -void NeighRequest::set_cutoff(double _cutoff) +// a requestor with a non-standard cutoff must state how to interpret it, +// by calling exactly one of the two methods below +// there is deliberately no plain set_cutoff(): the interpretation cannot be +// guessed, and getting it wrong silently truncates the list for some type pairs + +// _cutoff is the MAXIMUM cutoff across atom types, individual type pairs may +// use a shorter cutoff. This is the usual case for pair styles + +void NeighRequest::set_cutoff_max(double _cutoff) +{ + cut = 1; + cutoff = _cutoff; +} + +// _cutoff applies UNIFORMLY to every atom type pair. This is the usual case +// for fixes/computes analyzing a fixed range (e.g. an RDF) + +void NeighRequest::set_cutoff_fixed(double _cutoff) { cut = 1; cutoff = _cutoff; + cut_fixed = 1; } void NeighRequest::set_id(int _id) diff --git a/src/neigh_request.h b/src/neigh_request.h index 52d633de219..78b708bb804 100644 --- a/src/neigh_request.h +++ b/src/neigh_request.h @@ -82,9 +82,17 @@ class NeighRequest : protected Pointers { int intel; // set by INTEL package int kokkos_host; // set by KOKKOS package int kokkos_device; - int ssa; // set by DPD-REACT package, for Shardlow lists - int cut; // 1 if use a non-standard cutoff length - double cutoff; // special cutoff distance for this list + int ssa; // set by DPD-REACT package, for Shardlow lists + + // non-standard cutoffs + // By default, the cutoff corresponds to the maximum cutoff across all types, + // does not imply all types have the same cutoff. + // This is typical of requests for pair styles + // If set, cut_fixed implies the cutoff is uniform across all atom types. + // This is typical of fixes/computes with a fixed range of analysis (e.g. an RDF) + int cut_fixed; // toggles cutoff interpretation, whether fixed across types + int cut; // 1 if use a non-standard cutoff length + double cutoff; // special cutoff distance for this list // flags set by pair hybrid @@ -136,7 +144,11 @@ class NeighRequest : protected Pointers { void copy_request(NeighRequest *, int); void apply_flags(int); - void set_cutoff(double); + // a non-standard cutoff requires stating its interpretation, exactly one of: + // max - cutoff is the maximum across types (typical of pair styles) + // fixed - cutoff applies uniformly to all types (typical of analysis) + void set_cutoff_max(double); + void set_cutoff_fixed(double); void set_id(int); void set_kokkos_device(int); void set_kokkos_host(int); diff --git a/src/neighbor.cpp b/src/neighbor.cpp index a66c96ddb02..ab83965587f 100644 --- a/src/neighbor.cpp +++ b/src/neighbor.cpp @@ -1214,20 +1214,40 @@ void Neighbor::morph_unique() for (int i = 0; i < nrequest; i++) { irq = requests[i]; - // if cut flag set by requestor and cutoff is larger than minimum for default, - // and the list is not a skip list, set unique flag; otherwise unset cut flag - // this forces Pair,Stencil,Bin styles to be instantiated separately + // decide whether a requested non-standard cutoff needs a list of its own + // (unique = its own Bin,Stencil,Pair styles are instantiated) or whether + // the default cutoffs already serve it, in which case the cut flag is + // unset and the request falls back on the default list // also add skin to cutoff of perpetual lists if (irq->cut) { if (!irq->occasional) irq->cutoff += skin; - if ((irq->cutoff > cutneighmin) && !irq->skip) { + int needs_own_cutoff; + if (irq->skip) { + // skip lists inherit the cutoff of the parent they skip from + needs_own_cutoff = 0; + } else if (irq->cut_fixed && (irq->cutoff > cutneighmin)) { + // uniform cutoff that some type pair of the default list does not + // reach, so the default list would be truncated for those pairs + needs_own_cutoff = 1; + } else { + // remaining cases, for either interpretation of the cutoff, only need + // their own list if the requested value differs from the default one + // they would otherwise inherit. A shorter cutoff still lands here so + // that morph_copy_trim() can trim it down instead of widening it + needs_own_cutoff = (irq->cutoff != cutneighmax); + } + + if (needs_own_cutoff) { irq->unique = 1; } else { + // reverts to the default cutoff, so drop its interpretation as well, + // otherwise the stale flags leak into later cutoff comparisons irq->cut = 0; irq->cutoff = 0.0; + irq->cut_fixed = 0; } } @@ -1561,11 +1581,14 @@ void Neighbor::morph_copy_trim() // other list (jrq) to copy from must be perpetual // list that becomes a copy list (irq) can be perpetual or occasional - // if both lists are perpetual, require j < i + // if both lists are perpetual and have the same cutoff, require j < i // to prevent circular dependence with 3 or more copies of a list + // lists with a shorter cutoff are exempt: they are trimmed from a strictly + // longer one, so the cutoff increases along the chain and it cannot close + // into a cycle. Only equal cutoffs need the index ordering as tie break if (jrq->occasional) continue; - if (!irq->occasional && !irq->cut && j > i) continue; + if (!irq->occasional && (icut == jcut) && j > i) continue; // both lists must be half, or both full @@ -1624,7 +1647,8 @@ void Neighbor::morph_copy_trim() if (jj < nrequest) { irq->copy = 1; irq->trim = trim_flag; - if (jrq->copy && irq->cutoff == requests[jrq->copylist]->cutoff) + if (jrq->copy && irq->cut_fixed == requests[jrq->copylist]->cut_fixed && + irq->cutoff == requests[jrq->copylist]->cutoff) irq->copylist = jrq->copylist; else irq->copylist = j; @@ -1912,7 +1936,10 @@ void Neighbor::print_pairwise_info() if (rq->kokkos_device) out += ", kokkos_device"; if (rq->kokkos_host) out += ", kokkos_host"; if (rq->ssa) out += ", ssa"; - if (rq->cut) out += fmt::format(", cut {}", rq->cutoff); + if (rq->cut) { + out += fmt::format(", cut {}", rq->cutoff); + if (rq->cut_fixed) out += fmt::format(", cut fixed {}", rq->cut_fixed); + } if (rq->off2on) out += ", off2on"; out += "\n"; diff --git a/src/pair_hybrid.cpp b/src/pair_hybrid.cpp index ebe8060efa6..f8f93d08416 100644 --- a/src/pair_hybrid.cpp +++ b/src/pair_hybrid.cpp @@ -762,7 +762,7 @@ double PairHybrid::init_one(int i, int j) for (const auto &request : neighbor->get_pair_requests()) { if (styles[istyle] == request->get_requestor()) { - request->set_cutoff(cutmax_style[istyle]); + request->set_cutoff_max(cutmax_style[istyle]); break; } } diff --git a/src/pair_hybrid_molecular.cpp b/src/pair_hybrid_molecular.cpp index 3635b770507..85149ee6f61 100644 --- a/src/pair_hybrid_molecular.cpp +++ b/src/pair_hybrid_molecular.cpp @@ -119,7 +119,7 @@ double PairHybridMolecular::init_one(int i, int j) for (const auto &request : neighbor->get_pair_requests()) { if (styles[istyle] == request->get_requestor()) { - request->set_cutoff(cutmax_style[istyle]); + request->set_cutoff_max(cutmax_style[istyle]); break; } } diff --git a/unittest/cplusplus/test_neighbor_class.cpp b/unittest/cplusplus/test_neighbor_class.cpp index 9facc38f048..6ba318439ac 100644 --- a/unittest/cplusplus/test_neighbor_class.cpp +++ b/unittest/cplusplus/test_neighbor_class.cpp @@ -184,6 +184,20 @@ class NeighborListsBin : public LAMMPSTest { } return 0; } + + // total number of neighbors stored in a neighbor list, -1 if there is none + int count_neighbors(int nlidx) + { + if (nlidx < 0) return -1; + int total = 0, numneigh = -1, iatom = -1; + int *neighbors = nullptr; + int inum = lammps_neighlist_num_elements(lmp, nlidx); + for (int i = 0; i < inum; ++i) { + lammps_neighlist_element_neighbors(lmp, nlidx, i, &iatom, &numneigh, &neighbors); + total += numneigh; + } + return total; + } }; class NeighborListsNsq : public NeighborListsBin { @@ -560,8 +574,6 @@ TEST_F(NeighborListsBin, one_hybrid_half_list_nonewton) } } -#if 0 -// FIXME: currently trim is not detected and this test will thus fail TEST_F(NeighborListsBin, one_trim_half_list_newton) { create_system("charge", "real", "on"); @@ -615,7 +627,6 @@ TEST_F(NeighborListsBin, one_trim_half_list_newton) } } -// FIXME: currently trim is not detected and this test will thus fail TEST_F(NeighborListsBin, one_trim_half_list_nonewton) { create_system("charge", "real", "off"); @@ -668,7 +679,67 @@ TEST_F(NeighborListsBin, one_trim_half_list_nonewton) GTEST_FAIL() << "No suitable neighbor list info found"; } } -#endif + +// three or more perpetual lists that share the same custom cutoff must not be +// turned into copies of each other, which would be a circular dependency that +// the reordering of the build order cannot resolve (issue #4529). Sub-styles +// with a shorter cutoff are trimmed from the longest one instead +TEST_F(NeighborListsBin, several_sublists_equal_cutoff) +{ + create_system("atomic", "real", "on"); + // the lambda form releases the output capture if setting up the lists fails, + // so that a circular dependency shows up as a failed test and not an abort + ASSERT_NO_THROW(HIDE_OUTPUT([&] { + command("pair_style hybrid/overlay lj/cut 3.5 lj/cut 2.0 lj/cut 2.0 lj/cut 2.0"); + for (int i = 1; i <= 4; ++i) + command(fmt::format("pair_coeff * * lj/cut {} 0.01 2.0", i)); + command("run 0 post no"); + })); + + // every sub-style must end up with a list, the three that share a cutoff + // must hold the same neighbors, and the longer ranged one must hold more + int n1 = count_neighbors(lammps_find_pair_neighlist(lmp, "lj/cut", 1, 1, 0)); + int n2 = count_neighbors(lammps_find_pair_neighlist(lmp, "lj/cut", 1, 2, 0)); + int n3 = count_neighbors(lammps_find_pair_neighlist(lmp, "lj/cut", 1, 3, 0)); + int n4 = count_neighbors(lammps_find_pair_neighlist(lmp, "lj/cut", 1, 4, 0)); + EXPECT_GT(n2, 0); + EXPECT_EQ(n2, n3); + EXPECT_EQ(n2, n4); + EXPECT_GT(n1, n2); +} + +// a list requested with a uniform cutoff must reach that cutoff for every atom +// type, also when the pair style uses a shorter cutoff for some type pairs and +// the default list is therefore truncated for them. The contents of such a +// list may not depend on the pair style's per-type cutoffs at all +TEST_F(NeighborListsBin, perpetual_fixed_cutoff_uniform) +{ + create_system("atomic", "real", "on"); + BEGIN_HIDE_OUTPUT(); + // small skin, so that a truncated per-type cutoff really does lose pairs + command("neighbor 0.3 bin"); + command("pair_style lj/cut 3.5"); + // 1-1 pairs use a shorter cutoff, so the default list is truncated for them + command("pair_coeff 1 1 0.01 2.0 2.5"); + command("pair_coeff 1 2 0.01 2.0 3.5"); + command("pair_coeff 2 2 0.01 2.0 3.5"); + // perpetual full list with a uniform cutoff of 3.5 + skin, equal to cutneighmax + command("group dyn dynamic all within 3.5 every 1"); + command("run 0 post no"); + END_HIDE_OUTPUT(); + int nneigh_mixed = count_neighbors(lammps_find_fix_neighlist(lmp, "GROUP_dyn", 0)); + + // same uniform group cutoff, but now no type pair uses a shorter cutoff, so + // the default list cannot be truncated and yields the reference answer + BEGIN_HIDE_OUTPUT(); + command("pair_coeff 1 1 0.01 2.0 3.5"); + command("run 0 post no"); + END_HIDE_OUTPUT(); + int nneigh_uniform = count_neighbors(lammps_find_fix_neighlist(lmp, "GROUP_dyn", 0)); + + EXPECT_GT(nneigh_uniform, 0); + EXPECT_EQ(nneigh_mixed, nneigh_uniform); +} TEST_F(NeighborListsBin, one_atomic_full) {