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
47 #include "gromacs/domdec/localtopologychecker.h"
53 #include "gromacs/domdec/domdec_internal.h"
54 #include "gromacs/domdec/reversetopology.h"
55 #include "gromacs/gmxlib/network.h"
56 #include "gromacs/mdtypes/commrec.h"
57 #include "gromacs/topology/idef.h"
58 #include "gromacs/topology/ifunc.h"
59 #include "gromacs/topology/mtop_util.h"
60 #include "gromacs/utility/arrayref.h"
61 #include "gromacs/utility/fatalerror.h"
62 #include "gromacs/utility/logger.h"
63 #include "gromacs/utility/stringstream.h"
64 #include "gromacs/utility/textwriter.h"
71 /*! \brief Checks whether interactions have been assigned for one function type
73 * Loops over a list of interactions in the local topology of one function type
74 * and flags each of the interactions as assigned in the global \p isAssigned list.
75 * Exits with an inconsistency error when an interaction is assigned more than once.
77 static void flagInteractionsForType(const int ftype,
78 const InteractionList& il,
79 const reverse_ilist_t& ril,
80 const Range<int>& atomRange,
81 const int numAtomsPerMolecule,
82 ArrayRef<const int> globalAtomIndices,
83 ArrayRef<int> isAssigned)
85 const int nril_mol = ril.index[numAtomsPerMolecule];
86 const int nral = NRAL(ftype);
88 for (int i = 0; i < il.size(); i += 1 + nral)
90 // ia[0] is the interaction type, ia[1, ...] the atom indices
91 const int* ia = il.iatoms.data() + i;
92 // Extract the global atom index of the first atom in this interaction
93 const int a0 = globalAtomIndices[ia[1]];
94 /* Check if this interaction is in
95 * the currently checked molblock.
97 if (atomRange.isInRange(a0))
99 // The molecule index in the list of this molecule type
100 const int moleculeIndex = (a0 - atomRange.begin()) / numAtomsPerMolecule;
101 const int atomOffset = (a0 - atomRange.begin()) - moleculeIndex * numAtomsPerMolecule;
102 const int globalAtomStartInMolecule = atomRange.begin() + moleculeIndex * numAtomsPerMolecule;
103 int j_mol = ril.index[atomOffset];
105 while (j_mol < ril.index[atomOffset + 1] && !found)
107 const int j = moleculeIndex * nril_mol + j_mol;
108 const int ftype_j = ril.il[j_mol];
109 /* Here we need to check if this interaction has
110 * not already been assigned, since we could have
111 * multiply defined interactions.
113 if (ftype == ftype_j && ia[0] == ril.il[j_mol + 1] && isAssigned[j] == 0)
115 /* Check the atoms */
117 for (int a = 0; a < nral; a++)
119 if (globalAtomIndices[ia[1 + a]]
120 != globalAtomStartInMolecule + ril.il[j_mol + 2 + a])
130 j_mol += 2 + nral_rt(ftype_j);
134 gmx_incons("Some interactions seem to be assigned multiple times");
140 /*! \brief Help print error output when interactions are missing in a molblock
142 * \note This function needs to be called on all ranks (contains a global summation)
144 static std::string printMissingInteractionsMolblock(const t_commrec* cr,
145 const gmx_reverse_top_t& rt,
146 const char* moltypename,
147 const reverse_ilist_t& ril,
148 const Range<int>& atomRange,
149 const int numAtomsPerMolecule,
150 const int numMolecules,
151 const InteractionDefinitions& idef)
153 const int nril_mol = ril.index[numAtomsPerMolecule];
154 std::vector<int> isAssigned(numMolecules * nril_mol, 0);
155 StringOutputStream stream;
156 TextWriter log(&stream);
158 for (int ftype = 0; ftype < F_NRE; ftype++)
160 if (dd_check_ftype(ftype, rt.options()))
162 flagInteractionsForType(
163 ftype, idef.il[ftype], ril, atomRange, numAtomsPerMolecule, cr->dd->globalAtomIndices, isAssigned);
167 gmx_sumi(isAssigned.size(), isAssigned.data(), cr);
169 const int numMissingToPrint = 10;
171 for (int mol = 0; mol < numMolecules; mol++)
174 while (j_mol < nril_mol)
176 int ftype = ril.il[j_mol];
177 int nral = NRAL(ftype);
178 int j = mol * nril_mol + j_mol;
179 if (isAssigned[j] == 0 && !(interaction_function[ftype].flags & IF_VSITE))
181 if (DDMASTER(cr->dd))
185 log.writeLineFormatted("Molecule type '%s'", moltypename);
186 log.writeLineFormatted(
187 "the first %d missing interactions, except for exclusions:",
190 log.writeStringFormatted("%20s atoms", interaction_function[ftype].longname);
192 for (; a < nral; a++)
194 log.writeStringFormatted("%5d", ril.il[j_mol + 2 + a] + 1);
198 log.writeString(" ");
201 log.writeString(" global");
202 for (int a = 0; a < nral; a++)
204 log.writeStringFormatted("%6d",
205 atomRange.begin() + mol * numAtomsPerMolecule
206 + ril.il[j_mol + 2 + a] + 1);
208 log.ensureLineBreak();
211 if (i >= numMissingToPrint)
216 j_mol += 2 + nral_rt(ftype);
220 return stream.toString();
223 /*! \brief Help print error output when interactions are missing */
224 static void printMissingInteractionsAtoms(const MDLogger& mdlog,
226 const gmx_mtop_t& mtop,
227 const InteractionDefinitions& idef)
229 const gmx_reverse_top_t& rt = *cr->dd->reverse_top;
231 /* Print the atoms in the missing interactions per molblock */
233 for (const gmx_molblock_t& molb : mtop.molblock)
235 const gmx_moltype_t& moltype = mtop.moltype[molb.type];
236 const int a_start = a_end;
237 a_end = a_start + molb.nmol * moltype.atoms.nr;
238 const Range<int> atomRange(a_start, a_end);
240 auto warning = printMissingInteractionsMolblock(
244 cr->dd->reverse_top->interactionListForMoleculeType(molb.type),
250 GMX_LOG(mdlog.warning).appendText(warning);
254 /*! \brief Print error output when interactions are missing */
255 [[noreturn]] static void dd_print_missing_interactions(const MDLogger& mdlog,
257 const int numBondedInteractionsOverAllDomains,
258 const int expectedNumGlobalBondedInteractions,
259 const gmx_mtop_t& top_global,
260 const gmx_localtop_t* top_local,
261 ArrayRef<const RVec> x,
265 gmx_domdec_t* dd = cr->dd;
267 GMX_LOG(mdlog.warning)
269 "Not all bonded interactions have been properly assigned to the domain "
270 "decomposition cells");
272 const int ndiff_tot = numBondedInteractionsOverAllDomains - expectedNumGlobalBondedInteractions;
274 for (int ftype = 0; ftype < F_NRE; ftype++)
276 const int nral = NRAL(ftype);
277 cl[ftype] = top_local->idef.il[ftype].size() / (1 + nral);
280 gmx_sumi(F_NRE, cl, cr);
284 GMX_LOG(mdlog.warning).appendText("A list of missing interactions:");
285 int rest_global = expectedNumGlobalBondedInteractions;
286 int rest = numBondedInteractionsOverAllDomains;
287 for (int ftype = 0; ftype < F_NRE; ftype++)
289 /* In the reverse and local top all constraints are merged
290 * into F_CONSTR. So in the if statement we skip F_CONSTRNC
291 * and add these constraints when doing F_CONSTR.
293 if (dd_check_ftype(ftype, dd->reverse_top->options()) && ftype != F_CONSTRNC)
295 int n = gmx_mtop_ftype_count(top_global, ftype);
296 if (ftype == F_CONSTR)
298 n += gmx_mtop_ftype_count(top_global, F_CONSTRNC);
300 int ndiff = cl[ftype] - n;
303 GMX_LOG(mdlog.warning)
304 .appendTextFormatted("%20s of %6d missing %6d",
305 interaction_function[ftype].longname,
314 int ndiff = rest - rest_global;
317 GMX_LOG(mdlog.warning).appendTextFormatted("%20s of %6d missing %6d", "exclusions", rest_global, -ndiff);
321 printMissingInteractionsAtoms(mdlog, cr, top_global, top_local->idef);
322 write_dd_pdb("dd_dump_err", 0, "dump", top_global, cr, -1, as_rvec_array(x.data()), box);
324 std::string errorMessage;
329 "One or more interactions were assigned to multiple domains of the domain "
330 "decompostion. Please report this bug.";
334 errorMessage = formatString(
335 "%d of the %d bonded interactions could not be calculated because some atoms "
336 "involved moved further apart than the multi-body cut-off distance (%g nm) or the "
337 "two-body cut-off distance (%g nm), see option -rdd, for pairs and tabulated bonds "
338 "also see option -ddcheck",
340 expectedNumGlobalBondedInteractions,
341 dd_cutoff_multibody(dd),
342 dd_cutoff_twobody(dd));
344 gmx_fatal_collective(FARGS, cr->mpi_comm_mygroup, MASTER(cr), "%s", errorMessage.c_str());
347 /*! \brief Data to help check local topology construction
349 * Partitioning could incorrectly miss a bonded interaction.
350 * However, checking for that requires a global communication
351 * stage, which does not otherwise happen during partitioning. So,
352 * for performance, we do that alongside the first global energy
353 * reduction after a new DD is made. These variables handle
354 * whether the check happens, its input for this domain, output
355 * across all domains, and the expected value it should match. */
356 class LocalTopologyChecker::Impl
360 Impl(const MDLogger& mdlog, const t_commrec* cr, const gmx_mtop_t& mtop, bool useUpdateGroups);
362 //! Objects used when reporting that interactions are missing
365 const MDLogger& mdlog_;
366 //! Communication record
367 const t_commrec* cr_;
368 //! Global system topology
369 const gmx_mtop_t& mtop_;
372 /*! \brief Number of bonded interactions found in the local
373 * topology for this domain. */
374 int numBondedInteractionsToReduce_ = 0;
375 /*! \brief Whether to check at the next global communication
376 * stage the total number of bonded interactions found.
378 * Cleared after that number is found. */
379 bool shouldCheckNumberOfBondedInteractions_ = false;
380 /*! \brief The total number of bonded interactions found in
381 * the local topology across all domains.
383 * Only has a value after reduction across all ranks, which is
384 * removed when it is again time to check after a new
386 std::optional<int> numBondedInteractionsOverAllDomains_;
387 //! The number of bonded interactions computed from the full system topology
388 int expectedNumGlobalBondedInteractions_ = 0;
392 /*! \brief Compute the total bonded interaction count
394 * \param[in] mtop The global system topology
395 * \param[in] useUpdateGroups Whether update groups are in use
397 * When using domain decomposition without update groups,
398 * constraint-type interactions can be split across domains, and so we
399 * do not consider them in this correctness check. Otherwise, we
402 static int computeExpectedNumGlobalBondedInteractions(const gmx_mtop_t& mtop, const bool useUpdateGroups)
404 int expectedNumGlobalBondedInteractions = gmx_mtop_interaction_count(mtop, IF_BOND);
407 expectedNumGlobalBondedInteractions += gmx_mtop_interaction_count(mtop, IF_CONSTRAINT);
409 return expectedNumGlobalBondedInteractions;
412 LocalTopologyChecker::Impl::Impl(const MDLogger& mdlog,
414 const gmx_mtop_t& mtop,
415 const bool useUpdateGroups) :
419 expectedNumGlobalBondedInteractions_(computeExpectedNumGlobalBondedInteractions(mtop, useUpdateGroups))
423 LocalTopologyChecker::LocalTopologyChecker(const MDLogger& mdlog,
425 const gmx_mtop_t& mtop,
426 const bool useUpdateGroups) :
427 impl_(std::make_unique<Impl>(mdlog, cr, mtop, useUpdateGroups))
431 LocalTopologyChecker::~LocalTopologyChecker() = default;
433 LocalTopologyChecker::LocalTopologyChecker(LocalTopologyChecker&&) noexcept = default;
435 LocalTopologyChecker& LocalTopologyChecker::operator=(LocalTopologyChecker&& other) noexcept
437 impl_ = std::move(other.impl_);
441 void LocalTopologyChecker::scheduleCheckOfLocalTopology(const int numBondedInteractionsToReduce)
443 impl_->numBondedInteractionsToReduce_ = numBondedInteractionsToReduce;
444 // Note that it's possible for this to still be true from the last
445 // time it was set, e.g. if repartitioning was triggered before
446 // global communication that would have acted on the true
447 // value. This could happen for example when replica exchange took
448 // place soon after a partition.
449 impl_->shouldCheckNumberOfBondedInteractions_ = true;
450 // Clear the old global value, which is now invalid
451 impl_->numBondedInteractionsOverAllDomains_.reset();
454 bool LocalTopologyChecker::shouldCheckNumberOfBondedInteractions() const
456 return impl_->shouldCheckNumberOfBondedInteractions_;
459 int LocalTopologyChecker::numBondedInteractions() const
461 return impl_->numBondedInteractionsToReduce_;
464 void LocalTopologyChecker::setNumberOfBondedInteractionsOverAllDomains(const int newValue)
466 GMX_RELEASE_ASSERT(!impl_->numBondedInteractionsOverAllDomains_.has_value(),
467 "Cannot set number of bonded interactions because it is already set");
468 impl_->numBondedInteractionsOverAllDomains_.emplace(newValue);
471 void LocalTopologyChecker::checkNumberOfBondedInteractions(const gmx_localtop_t* top_local,
472 ArrayRef<const RVec> x,
475 if (impl_->shouldCheckNumberOfBondedInteractions_)
477 GMX_RELEASE_ASSERT(impl_->numBondedInteractionsOverAllDomains_.has_value(),
478 "The check for the total number of bonded interactions requires the "
479 "value to have been reduced across all domains");
480 if (impl_->numBondedInteractionsOverAllDomains_.value() != impl_->expectedNumGlobalBondedInteractions_)
482 dd_print_missing_interactions(impl_->mdlog_,
484 impl_->numBondedInteractionsOverAllDomains_.value(),
485 impl_->expectedNumGlobalBondedInteractions_,
489 box); // Does not return
491 // Now that the value is set and the check complete, future
492 // global communication should not compute the value until
493 // after the next partitioning.
494 impl_->shouldCheckNumberOfBondedInteractions_ = false;