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