Apply re-formatting to C++ in src/ tree.
[alexxy/gromacs.git] / src / gromacs / mdrunutility / multisim.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2018,2019,2020, 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.
8  *
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.
13  *
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.
18  *
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.
23  *
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.
31  *
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.
34  */
35 /*! \internal \file
36  *
37  * \brief Implements the multi-simulation support routines.
38  *
39  * \author Mark Abraham <mark.j.abraham@gmail.com>
40  * \ingroup module_mdrunutility
41  */
42 #include "gmxpre.h"
43
44 #include "multisim.h"
45
46 #include "config.h"
47
48 #include "gromacs/gmxlib/network.h"
49 #include "gromacs/mdtypes/commrec.h"
50 #include "gromacs/utility/exceptions.h"
51 #include "gromacs/utility/fatalerror.h"
52 #include "gromacs/utility/futil.h"
53 #include "gromacs/utility/gmxassert.h"
54 #include "gromacs/utility/logger.h"
55 #include "gromacs/utility/smalloc.h"
56
57 std::unique_ptr<gmx_multisim_t> buildMultiSimulation(MPI_Comm                         worldComm,
58                                                      gmx::ArrayRef<const std::string> multidirs)
59 {
60     if (multidirs.empty())
61     {
62         return nullptr;
63     }
64
65     if (!GMX_LIB_MPI && !multidirs.empty())
66     {
67         GMX_THROW(gmx::NotImplementedError(
68                 "Multi-simulations are only supported when GROMACS has been "
69                 "configured with a proper external MPI library."));
70     }
71
72     if (multidirs.size() == 1)
73     {
74         /* NOTE: It would be nice if this special case worked, but this requires checks/tests. */
75         GMX_THROW(gmx::NotImplementedError(
76                 "To run mdrun in multi-simulation mode, more then one "
77                 "actual simulation is required. The single simulation case is not supported."));
78     }
79
80 #if GMX_LIB_MPI
81     int numRanks;
82     MPI_Comm_size(worldComm, &numRanks);
83     if (numRanks % multidirs.size() != 0)
84     {
85         auto message = gmx::formatString(
86                 "The number of ranks (%d) is not a multiple of the number of simulations (%td)",
87                 numRanks,
88                 multidirs.ssize());
89         GMX_THROW(gmx::InconsistentInputError(message));
90     }
91
92     int numRanksPerSimulation = numRanks / multidirs.size();
93     int rankWithinWorldComm;
94     MPI_Comm_rank(worldComm, &rankWithinWorldComm);
95
96     if (debug)
97     {
98         fprintf(debug,
99                 "We have %td simulations, %d ranks per simulation, local simulation is %d\n",
100                 multidirs.ssize(),
101                 numRanksPerSimulation,
102                 rankWithinWorldComm / numRanksPerSimulation);
103     }
104
105     int numSimulations = multidirs.size();
106     // Create a communicator for the master ranks of each simulation
107     std::vector<int> ranksOfMasters(numSimulations);
108     for (int i = 0; i < numSimulations; i++)
109     {
110         ranksOfMasters[i] = i * numRanksPerSimulation;
111     }
112     MPI_Group worldGroup;
113     // No need to free worldGroup later, we didn't create it.
114     MPI_Comm_group(worldComm, &worldGroup);
115
116     MPI_Group mastersGroup = MPI_GROUP_NULL;
117     MPI_Group_incl(worldGroup, numSimulations, ranksOfMasters.data(), &mastersGroup);
118     MPI_Comm mastersComm = MPI_COMM_NULL;
119     MPI_Comm_create(worldComm, mastersGroup, &mastersComm);
120     if (mastersGroup != MPI_GROUP_NULL)
121     {
122         MPI_Group_free(&mastersGroup);
123     }
124
125     int      simulationIndex = rankWithinWorldComm / numRanksPerSimulation;
126     MPI_Comm simulationComm  = MPI_COMM_NULL;
127     MPI_Comm_split(worldComm, simulationIndex, rankWithinWorldComm, &simulationComm);
128
129     try
130     {
131         gmx_chdir(multidirs[simulationIndex].c_str());
132     }
133     catch (gmx::GromacsException& e)
134     {
135         e.prependContext("While changing directory for multi-simulation to " + multidirs[simulationIndex]);
136         throw;
137     }
138     return std::make_unique<gmx_multisim_t>(numSimulations, simulationIndex, mastersComm, simulationComm);
139 #else
140     GMX_UNUSED_VALUE(worldComm);
141     return nullptr;
142 #endif
143 }
144
145 gmx_multisim_t::gmx_multisim_t(int numSimulations, int simulationIndex, MPI_Comm mastersComm, MPI_Comm simulationComm) :
146     numSimulations_(numSimulations),
147     simulationIndex_(simulationIndex),
148     mastersComm_(mastersComm),
149     simulationComm_(simulationComm)
150 {
151 }
152
153 gmx_multisim_t::~gmx_multisim_t()
154 {
155 #if GMX_LIB_MPI
156     // TODO This would work better if the result of MPI_Comm_split was
157     // put into an RAII-style guard, such as gmx::unique_cptr.
158     if (mastersComm_ != MPI_COMM_NULL && mastersComm_ != MPI_COMM_WORLD)
159     {
160         MPI_Comm_free(&mastersComm_);
161     }
162     if (simulationComm_ != MPI_COMM_NULL && simulationComm_ != MPI_COMM_WORLD)
163     {
164         MPI_Comm_free(&simulationComm_);
165     }
166 #endif
167 }
168
169 #if GMX_MPI
170 static void gmx_sumd_comm(int nr, double r[], MPI_Comm mpi_comm)
171 {
172 #    if MPI_IN_PLACE_EXISTS
173     MPI_Allreduce(MPI_IN_PLACE, r, nr, MPI_DOUBLE, MPI_SUM, mpi_comm);
174 #    else
175     /* this function is only used in code that is not performance critical,
176        (during setup, when comm_rec is not the appropriate communication
177        structure), so this isn't as bad as it looks. */
178     double* buf;
179     int     i;
180
181     snew(buf, nr);
182     MPI_Allreduce(r, buf, nr, MPI_DOUBLE, MPI_SUM, mpi_comm);
183     for (i = 0; i < nr; i++)
184     {
185         r[i] = buf[i];
186     }
187     sfree(buf);
188 #    endif
189 }
190 #endif
191
192 #if GMX_MPI
193 static void gmx_sumf_comm(int nr, float r[], MPI_Comm mpi_comm)
194 {
195 #    if MPI_IN_PLACE_EXISTS
196     MPI_Allreduce(MPI_IN_PLACE, r, nr, MPI_FLOAT, MPI_SUM, mpi_comm);
197 #    else
198     /* this function is only used in code that is not performance critical,
199        (during setup, when comm_rec is not the appropriate communication
200        structure), so this isn't as bad as it looks. */
201     float* buf;
202     int    i;
203
204     snew(buf, nr);
205     MPI_Allreduce(r, buf, nr, MPI_FLOAT, MPI_SUM, mpi_comm);
206     for (i = 0; i < nr; i++)
207     {
208         r[i] = buf[i];
209     }
210     sfree(buf);
211 #    endif
212 }
213 #endif
214
215 void gmx_sumd_sim(int gmx_unused nr, double gmx_unused r[], const gmx_multisim_t gmx_unused* ms)
216 {
217 #if !GMX_MPI
218     GMX_RELEASE_ASSERT(false, "Invalid call to gmx_sumd_sim");
219 #else
220     gmx_sumd_comm(nr, r, ms->mastersComm_);
221 #endif
222 }
223
224 void gmx_sumf_sim(int gmx_unused nr, float gmx_unused r[], const gmx_multisim_t gmx_unused* ms)
225 {
226 #if !GMX_MPI
227     GMX_RELEASE_ASSERT(false, "Invalid call to gmx_sumf_sim");
228 #else
229     gmx_sumf_comm(nr, r, ms->mastersComm_);
230 #endif
231 }
232
233 void gmx_sumi_sim(int gmx_unused nr, int gmx_unused r[], const gmx_multisim_t gmx_unused* ms)
234 {
235 #if !GMX_MPI
236     GMX_RELEASE_ASSERT(false, "Invalid call to gmx_sumi_sim");
237 #else
238 #    if MPI_IN_PLACE_EXISTS
239     MPI_Allreduce(MPI_IN_PLACE, r, nr, MPI_INT, MPI_SUM, ms->mastersComm_);
240 #    else
241     /* this is thread-unsafe, but it will do for now: */
242     ms->intBuffer.resize(nr);
243     MPI_Allreduce(r, ms->intBuffer.data(), ms->intBuffer.size(), MPI_INT, MPI_SUM, ms->mastersComm_);
244     std::copy(std::begin(ms->intBuffer), std::end(ms->intBuffer), r);
245 #    endif
246 #endif
247 }
248
249 void gmx_sumli_sim(int gmx_unused nr, int64_t gmx_unused r[], const gmx_multisim_t gmx_unused* ms)
250 {
251 #if !GMX_MPI
252     GMX_RELEASE_ASSERT(false, "Invalid call to gmx_sumli_sim");
253 #else
254 #    if MPI_IN_PLACE_EXISTS
255     MPI_Allreduce(MPI_IN_PLACE, r, nr, MPI_INT64_T, MPI_SUM, ms->mastersComm_);
256 #    else
257     /* this is thread-unsafe, but it will do for now: */
258     ms->int64Buffer.resize(nr);
259     MPI_Allreduce(r, ms->int64Buffer.data(), ms->int64Buffer.size(), MPI_INT64_T, MPI_SUM, ms->mastersComm_);
260     std::copy(std::begin(ms->int64Buffer), std::end(ms->int64Buffer), r);
261 #    endif
262 #endif
263 }
264
265 std::vector<int> gatherIntFromMultiSimulation(const gmx_multisim_t* ms, const int localValue)
266 {
267     std::vector<int> valuesFromAllRanks;
268 #if GMX_MPI
269     if (ms != nullptr)
270     {
271         valuesFromAllRanks.resize(ms->numSimulations_);
272         valuesFromAllRanks[ms->simulationIndex_] = localValue;
273         gmx_sumi_sim(ms->numSimulations_, valuesFromAllRanks.data(), ms);
274     }
275     else
276     {
277         valuesFromAllRanks.emplace_back(localValue);
278     }
279 #else
280     GMX_UNUSED_VALUE(ms);
281     valuesFromAllRanks.emplace_back(localValue);
282 #endif
283     return valuesFromAllRanks;
284 }
285
286 void check_multi_int(FILE* log, const gmx_multisim_t* ms, int val, const char* name, gmx_bool bQuiet)
287 {
288     int *    ibuf, p;
289     gmx_bool bCompatible;
290
291     if (nullptr != log && !bQuiet)
292     {
293         fprintf(log, "Multi-checking %s ... ", name);
294     }
295
296     if (ms == nullptr)
297     {
298         gmx_fatal(FARGS, "check_multi_int called with a NULL communication pointer");
299     }
300
301     snew(ibuf, ms->numSimulations_);
302     ibuf[ms->simulationIndex_] = val;
303     gmx_sumi_sim(ms->numSimulations_, ibuf, ms);
304
305     bCompatible = TRUE;
306     for (p = 1; p < ms->numSimulations_; p++)
307     {
308         bCompatible = bCompatible && (ibuf[p - 1] == ibuf[p]);
309     }
310
311     if (bCompatible)
312     {
313         if (nullptr != log && !bQuiet)
314         {
315             fprintf(log, "OK\n");
316         }
317     }
318     else
319     {
320         if (nullptr != log)
321         {
322             fprintf(log, "\n%s is not equal for all subsystems\n", name);
323             for (p = 0; p < ms->numSimulations_; p++)
324             {
325                 fprintf(log, "  subsystem %d: %d\n", p, ibuf[p]);
326             }
327         }
328         gmx_fatal(FARGS, "The %d subsystems are not compatible\n", ms->numSimulations_);
329     }
330
331     sfree(ibuf);
332 }
333
334 void check_multi_int64(FILE* log, const gmx_multisim_t* ms, int64_t val, const char* name, gmx_bool bQuiet)
335 {
336     int64_t* ibuf;
337     int      p;
338     gmx_bool bCompatible;
339
340     if (nullptr != log && !bQuiet)
341     {
342         fprintf(log, "Multi-checking %s ... ", name);
343     }
344
345     if (ms == nullptr)
346     {
347         gmx_fatal(FARGS, "check_multi_int called with a NULL communication pointer");
348     }
349
350     snew(ibuf, ms->numSimulations_);
351     ibuf[ms->simulationIndex_] = val;
352     gmx_sumli_sim(ms->numSimulations_, ibuf, ms);
353
354     bCompatible = TRUE;
355     for (p = 1; p < ms->numSimulations_; p++)
356     {
357         bCompatible = bCompatible && (ibuf[p - 1] == ibuf[p]);
358     }
359
360     if (bCompatible)
361     {
362         if (nullptr != log && !bQuiet)
363         {
364             fprintf(log, "OK\n");
365         }
366     }
367     else
368     {
369         // TODO Part of this error message would also be good to go to
370         // stderr (from one rank of one sim only)
371         if (nullptr != log)
372         {
373             fprintf(log, "\n%s is not equal for all subsystems\n", name);
374             for (p = 0; p < ms->numSimulations_; p++)
375             {
376                 char strbuf[255];
377                 /* first make the format string */
378                 snprintf(strbuf, 255, "  subsystem %%d: %s\n", "%" PRId64);
379                 fprintf(log, strbuf, p, ibuf[p]);
380             }
381         }
382         gmx_fatal(FARGS, "The %d subsystems are not compatible\n", ms->numSimulations_);
383     }
384
385     sfree(ibuf);
386 }
387
388 bool findIsSimulationMasterRank(const gmx_multisim_t* ms, MPI_Comm communicator)
389 {
390     if (GMX_LIB_MPI)
391     {
392         // Ranks of multi-simulations know whether they are a master
393         // rank. Ranks of non-multi simulation do not know until a
394         // t_commrec is available.
395         if ((ms != nullptr) && (ms->numSimulations_ > 1))
396         {
397             return ms->mastersComm_ != MPI_COMM_NULL;
398         }
399         else
400         {
401             int rank = 0;
402 #if GMX_LIB_MPI
403             MPI_Comm_rank(communicator, &rank);
404 #endif
405             return (rank == 0);
406         }
407     }
408     else if (GMX_THREAD_MPI)
409     {
410         GMX_RELEASE_ASSERT(communicator == MPI_COMM_NULL || communicator == MPI_COMM_WORLD,
411                            "Invalid communicator");
412         // Spawned threads have MPI_COMM_WORLD upon creation, so if
413         // the communicator is MPI_COMM_NULL this is not a spawned thread,
414         // ie is the master thread
415         return (communicator == MPI_COMM_NULL);
416     }
417     else
418     {
419         // No MPI means it must be the master (and only) rank.
420         return true;
421     }
422 }
423
424 bool isMasterSim(const gmx_multisim_t* ms)
425 {
426     return !isMultiSim(ms) || ms->simulationIndex_ == 0;
427 }
428
429 bool isMasterSimMasterRank(const gmx_multisim_t* ms, const bool isMaster)
430 {
431     return (isMaster && isMasterSim(ms));
432 }
433
434 static bool multisim_int_all_are_equal(const gmx_multisim_t* ms, int64_t value)
435 {
436     bool     allValuesAreEqual = true;
437     int64_t* buf;
438
439     GMX_RELEASE_ASSERT(ms, "Invalid use of multi-simulation pointer");
440
441     snew(buf, ms->numSimulations_);
442     /* send our value to all other master ranks, receive all of theirs */
443     buf[ms->simulationIndex_] = value;
444     gmx_sumli_sim(ms->numSimulations_, buf, ms);
445
446     for (int s = 0; s < ms->numSimulations_; s++)
447     {
448         if (buf[s] != value)
449         {
450             allValuesAreEqual = false;
451             break;
452         }
453     }
454
455     sfree(buf);
456
457     return allValuesAreEqual;
458 }
459
460 void logInitialMultisimStatus(const gmx_multisim_t* ms,
461                               const t_commrec*      cr,
462                               const gmx::MDLogger&  mdlog,
463                               const bool            simulationsShareState,
464                               const int             numSteps,
465                               const int             initialStep)
466 {
467     if (!multisim_int_all_are_equal(ms, numSteps))
468     {
469         GMX_LOG(mdlog.warning)
470                 .appendText(
471                         "Note: The number of steps is not consistent across multi "
472                         "simulations,\n"
473                         "but we are proceeding anyway!");
474     }
475     if (!multisim_int_all_are_equal(ms, initialStep))
476     {
477         if (simulationsShareState)
478         {
479             if (MASTER(cr))
480             {
481                 gmx_fatal(FARGS,
482                           "The initial step is not consistent across multi simulations which "
483                           "share the state");
484             }
485             gmx_barrier(cr->mpi_comm_mygroup);
486         }
487         else
488         {
489             GMX_LOG(mdlog.warning)
490                     .appendText(
491                             "Note: The initial step is not consistent across multi "
492                             "simulations,\n"
493                             "but we are proceeding anyway!");
494         }
495     }
496 }