2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 2021, by the GROMACS development team, led by
5 * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
6 * and including many others, as listed in the AUTHORS file in the
7 * top-level source directory and at http://www.gromacs.org.
9 * GROMACS is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public License
11 * as published by the Free Software Foundation; either version 2.1
12 * of the License, or (at your option) any later version.
14 * GROMACS is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with GROMACS; if not, see
21 * http://www.gnu.org/licenses, or write to the Free Software Foundation,
22 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24 * If you want to redistribute modifications to GROMACS, please
25 * consider that scientific software is very special. Version
26 * control is crucial - bugs must be traceable. We will be happy to
27 * consider code for inclusion in the official distribution, but
28 * derived work must not be called official GROMACS. Details are found
29 * in the README & COPYING files - if they are missing, get the
30 * official version at http://www.gromacs.org.
32 * To help us fund GROMACS development, we humbly ask that you cite
33 * the research papers on the package. Check out http://www.gromacs.org.
38 * \brief This file defines functions used by the domdec module
39 * while managing the construction, use and error checking for
40 * topologies local to a DD rank.
42 * \ingroup module_domdec
50 #include "gromacs/domdec/localtopologychecker.h"
52 #include "gromacs/domdec/domdec_internal.h"
53 #include "gromacs/domdec/reversetopology.h"
54 #include "gromacs/gmxlib/network.h"
55 #include "gromacs/mdtypes/commrec.h"
56 #include "gromacs/topology/idef.h"
57 #include "gromacs/topology/ifunc.h"
58 #include "gromacs/topology/mtop_util.h"
59 #include "gromacs/utility/arrayref.h"
60 #include "gromacs/utility/fatalerror.h"
61 #include "gromacs/utility/logger.h"
62 #include "gromacs/utility/stringstream.h"
63 #include "gromacs/utility/textwriter.h"
70 /*! \brief Checks whether interactions have been assigned for one function type
72 * Loops over a list of interactions in the local topology of one function type
73 * and flags each of the interactions as assigned in the global \p isAssigned list.
74 * Exits with an inconsistency error when an interaction is assigned more than once.
76 static void flagInteractionsForType(const int ftype,
77 const InteractionList& il,
78 const reverse_ilist_t& ril,
79 const gmx::Range<int>& atomRange,
80 const int numAtomsPerMolecule,
81 ArrayRef<const int> globalAtomIndices,
82 ArrayRef<int> isAssigned)
84 const int nril_mol = ril.index[numAtomsPerMolecule];
85 const int nral = NRAL(ftype);
87 for (int i = 0; i < il.size(); i += 1 + nral)
89 // ia[0] is the interaction type, ia[1, ...] the atom indices
90 const int* ia = il.iatoms.data() + i;
91 // Extract the global atom index of the first atom in this interaction
92 const int a0 = globalAtomIndices[ia[1]];
93 /* Check if this interaction is in
94 * the currently checked molblock.
96 if (atomRange.isInRange(a0))
98 // The molecule index in the list of this molecule type
99 const int moleculeIndex = (a0 - atomRange.begin()) / numAtomsPerMolecule;
100 const int atomOffset = (a0 - atomRange.begin()) - moleculeIndex * numAtomsPerMolecule;
101 const int globalAtomStartInMolecule = atomRange.begin() + moleculeIndex * numAtomsPerMolecule;
102 int j_mol = ril.index[atomOffset];
104 while (j_mol < ril.index[atomOffset + 1] && !found)
106 const int j = moleculeIndex * nril_mol + j_mol;
107 const int ftype_j = ril.il[j_mol];
108 /* Here we need to check if this interaction has
109 * not already been assigned, since we could have
110 * multiply defined interactions.
112 if (ftype == ftype_j && ia[0] == ril.il[j_mol + 1] && isAssigned[j] == 0)
114 /* Check the atoms */
116 for (int a = 0; a < nral; a++)
118 if (globalAtomIndices[ia[1 + a]]
119 != globalAtomStartInMolecule + ril.il[j_mol + 2 + a])
129 j_mol += 2 + nral_rt(ftype_j);
133 gmx_incons("Some interactions seem to be assigned multiple times");
139 /*! \brief Help print error output when interactions are missing in a molblock
141 * \note This function needs to be called on all ranks (contains a global summation)
143 static std::string printMissingInteractionsMolblock(t_commrec* cr,
144 const gmx_reverse_top_t& rt,
145 const char* moltypename,
146 const reverse_ilist_t& ril,
147 const gmx::Range<int>& atomRange,
148 const int numAtomsPerMolecule,
149 const int numMolecules,
150 const InteractionDefinitions& idef)
152 const int nril_mol = ril.index[numAtomsPerMolecule];
153 std::vector<int> isAssigned(numMolecules * nril_mol, 0);
154 gmx::StringOutputStream stream;
155 gmx::TextWriter log(&stream);
157 for (int ftype = 0; ftype < F_NRE; ftype++)
159 if (dd_check_ftype(ftype, rt.options()))
161 flagInteractionsForType(
162 ftype, idef.il[ftype], ril, atomRange, numAtomsPerMolecule, cr->dd->globalAtomIndices, isAssigned);
166 gmx_sumi(isAssigned.size(), isAssigned.data(), cr);
168 const int numMissingToPrint = 10;
170 for (int mol = 0; mol < numMolecules; mol++)
173 while (j_mol < nril_mol)
175 int ftype = ril.il[j_mol];
176 int nral = NRAL(ftype);
177 int j = mol * nril_mol + j_mol;
178 if (isAssigned[j] == 0 && !(interaction_function[ftype].flags & IF_VSITE))
180 if (DDMASTER(cr->dd))
184 log.writeLineFormatted("Molecule type '%s'", moltypename);
185 log.writeLineFormatted(
186 "the first %d missing interactions, except for exclusions:",
189 log.writeStringFormatted("%20s atoms", interaction_function[ftype].longname);
191 for (; a < nral; a++)
193 log.writeStringFormatted("%5d", ril.il[j_mol + 2 + a] + 1);
197 log.writeString(" ");
200 log.writeString(" global");
201 for (int a = 0; a < nral; a++)
203 log.writeStringFormatted("%6d",
204 atomRange.begin() + mol * numAtomsPerMolecule
205 + ril.il[j_mol + 2 + a] + 1);
207 log.ensureLineBreak();
210 if (i >= numMissingToPrint)
215 j_mol += 2 + nral_rt(ftype);
219 return stream.toString();
222 /*! \brief Help print error output when interactions are missing */
223 static void printMissingInteractionsAtoms(const gmx::MDLogger& mdlog,
225 const gmx_mtop_t& mtop,
226 const InteractionDefinitions& idef)
228 const gmx_reverse_top_t& rt = *cr->dd->reverse_top;
230 /* Print the atoms in the missing interactions per molblock */
232 for (const gmx_molblock_t& molb : mtop.molblock)
234 const gmx_moltype_t& moltype = mtop.moltype[molb.type];
235 const int a_start = a_end;
236 a_end = a_start + molb.nmol * moltype.atoms.nr;
237 const gmx::Range<int> atomRange(a_start, a_end);
239 auto warning = printMissingInteractionsMolblock(
243 cr->dd->reverse_top->interactionListForMoleculeType(molb.type),
249 GMX_LOG(mdlog.warning).appendText(warning);
253 /*! \brief Print error output when interactions are missing */
254 [[noreturn]] static void dd_print_missing_interactions(const gmx::MDLogger& mdlog,
256 const int numBondedInteractionsOverAllDomains,
257 const int expectedNumGlobalBondedInteractions,
258 const gmx_mtop_t& top_global,
259 const gmx_localtop_t* top_local,
260 ArrayRef<const RVec> x,
264 gmx_domdec_t* dd = cr->dd;
266 GMX_LOG(mdlog.warning)
268 "Not all bonded interactions have been properly assigned to the domain "
269 "decomposition cells");
271 const int ndiff_tot = numBondedInteractionsOverAllDomains - expectedNumGlobalBondedInteractions;
273 for (int ftype = 0; ftype < F_NRE; ftype++)
275 const int nral = NRAL(ftype);
276 cl[ftype] = top_local->idef.il[ftype].size() / (1 + nral);
279 gmx_sumi(F_NRE, cl, cr);
283 GMX_LOG(mdlog.warning).appendText("A list of missing interactions:");
284 int rest_global = expectedNumGlobalBondedInteractions;
285 int rest = numBondedInteractionsOverAllDomains;
286 for (int ftype = 0; ftype < F_NRE; ftype++)
288 /* In the reverse and local top all constraints are merged
289 * into F_CONSTR. So in the if statement we skip F_CONSTRNC
290 * and add these constraints when doing F_CONSTR.
292 if (dd_check_ftype(ftype, dd->reverse_top->options()) && ftype != F_CONSTRNC)
294 int n = gmx_mtop_ftype_count(top_global, ftype);
295 if (ftype == F_CONSTR)
297 n += gmx_mtop_ftype_count(top_global, F_CONSTRNC);
299 int ndiff = cl[ftype] - n;
302 GMX_LOG(mdlog.warning)
303 .appendTextFormatted("%20s of %6d missing %6d",
304 interaction_function[ftype].longname,
313 int ndiff = rest - rest_global;
316 GMX_LOG(mdlog.warning).appendTextFormatted("%20s of %6d missing %6d", "exclusions", rest_global, -ndiff);
320 printMissingInteractionsAtoms(mdlog, cr, top_global, top_local->idef);
321 write_dd_pdb("dd_dump_err", 0, "dump", top_global, cr, -1, as_rvec_array(x.data()), box);
323 std::string errorMessage;
328 "One or more interactions were assigned to multiple domains of the domain "
329 "decompostion. Please report this bug.";
333 errorMessage = gmx::formatString(
334 "%d of the %d bonded interactions could not be calculated because some atoms "
335 "involved moved further apart than the multi-body cut-off distance (%g nm) or the "
336 "two-body cut-off distance (%g nm), see option -rdd, for pairs and tabulated bonds "
337 "also see option -ddcheck",
339 expectedNumGlobalBondedInteractions,
340 dd_cutoff_multibody(dd),
341 dd_cutoff_twobody(dd));
343 gmx_fatal_collective(FARGS, cr->mpi_comm_mygroup, MASTER(cr), "%s", errorMessage.c_str());
349 /*! \brief Compute the total bonded interaction count
351 * \param[in] mtop The global system topology
352 * \param[in] useUpdateGroups Whether update groups are in use
354 * When using domain decomposition without update groups,
355 * constraint-type interactions can be split across domains, and so we
356 * do not consider them in this correctness check. Otherwise, we
359 static int computeExpectedNumGlobalBondedInteractions(const gmx_mtop_t& mtop, const bool useUpdateGroups)
361 int expectedNumGlobalBondedInteractions = gmx_mtop_interaction_count(mtop, IF_BOND);
364 expectedNumGlobalBondedInteractions += gmx_mtop_interaction_count(mtop, IF_CONSTRAINT);
366 return expectedNumGlobalBondedInteractions;
369 LocalTopologyChecker::LocalTopologyChecker(const gmx_mtop_t& mtop, const bool useUpdateGroups) :
370 expectedNumGlobalBondedInteractions(computeExpectedNumGlobalBondedInteractions(mtop, useUpdateGroups))
376 void scheduleCheckOfLocalTopology(gmx_domdec_t* dd, const int numBondedInteractionsToReduce)
378 dd->localTopologyChecker->numBondedInteractionsToReduce = numBondedInteractionsToReduce;
379 // Note that it's possible for this to still be true from the last
380 // time it was set, e.g. if repartitioning was triggered before
381 // global communication that would have acted on the true
382 // value. This could happen for example when replica exchange took
383 // place soon after a partition.
384 dd->localTopologyChecker->shouldCheckNumberOfBondedInteractions = true;
385 // Clear the old global value, which is now invalid
386 dd->localTopologyChecker->numBondedInteractionsOverAllDomains.reset();
389 bool shouldCheckNumberOfBondedInteractions(const gmx_domdec_t& dd)
391 return dd.localTopologyChecker->shouldCheckNumberOfBondedInteractions;
394 int numBondedInteractions(const gmx_domdec_t& dd)
396 return dd.localTopologyChecker->numBondedInteractionsToReduce;
399 void setNumberOfBondedInteractionsOverAllDomains(gmx_domdec_t* dd, int newValue)
401 GMX_RELEASE_ASSERT(!dd->localTopologyChecker->numBondedInteractionsOverAllDomains.has_value(),
402 "Cannot set number of bonded interactions because it is already set");
403 dd->localTopologyChecker->numBondedInteractionsOverAllDomains.emplace(newValue);
406 void checkNumberOfBondedInteractions(const gmx::MDLogger& mdlog,
408 const gmx_mtop_t& top_global,
409 const gmx_localtop_t* top_local,
410 ArrayRef<const RVec> x,
415 "No need to check number of bonded interactions when not using domain decomposition");
416 if (cr->dd->localTopologyChecker->shouldCheckNumberOfBondedInteractions)
418 GMX_RELEASE_ASSERT(cr->dd->localTopologyChecker->numBondedInteractionsOverAllDomains.has_value(),
419 "The check for the total number of bonded interactions requires the "
420 "value to have been reduced across all domains");
421 if (cr->dd->localTopologyChecker->numBondedInteractionsOverAllDomains.value()
422 != cr->dd->localTopologyChecker->expectedNumGlobalBondedInteractions)
424 dd_print_missing_interactions(
427 cr->dd->localTopologyChecker->numBondedInteractionsOverAllDomains.value(),
428 cr->dd->localTopologyChecker->expectedNumGlobalBondedInteractions,
432 box); // Does not return
434 // Now that the value is set and the check complete, future
435 // global communication should not compute the value until
436 // after the next partitioning.
437 cr->dd->localTopologyChecker->shouldCheckNumberOfBondedInteractions = false;