Merge branch release-2021 into master
[alexxy/gromacs.git] / src / gromacs / domdec / domdec_constraints.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2006,2007,2008,2009,2010 by the GROMACS development team.
5  * Copyright (c) 2012,2013,2014,2015,2016 by the GROMACS development team.
6  * Copyright (c) 2017,2018,2019,2020,2021, by the GROMACS development team, led by
7  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
8  * and including many others, as listed in the AUTHORS file in the
9  * top-level source directory and at http://www.gromacs.org.
10  *
11  * GROMACS is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU Lesser General Public License
13  * as published by the Free Software Foundation; either version 2.1
14  * of the License, or (at your option) any later version.
15  *
16  * GROMACS is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
19  * Lesser General Public License for more details.
20  *
21  * You should have received a copy of the GNU Lesser General Public
22  * License along with GROMACS; if not, see
23  * http://www.gnu.org/licenses, or write to the Free Software Foundation,
24  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
25  *
26  * If you want to redistribute modifications to GROMACS, please
27  * consider that scientific software is very special. Version
28  * control is crucial - bugs must be traceable. We will be happy to
29  * consider code for inclusion in the official distribution, but
30  * derived work must not be called official GROMACS. Details are found
31  * in the README & COPYING files - if they are missing, get the
32  * official version at http://www.gromacs.org.
33  *
34  * To help us fund GROMACS development, we humbly ask that you cite
35  * the research papers on the package. Check out http://www.gromacs.org.
36  */
37
38 /*! \internal \file
39  *
40  * \brief This file implements functions for domdec to use
41  * while managing inter-atomic constraints.
42  *
43  * \author Berk Hess <hess@kth.se>
44  * \ingroup module_domdec
45  */
46
47 #include "gmxpre.h"
48
49 #include "domdec_constraints.h"
50
51 #include <cassert>
52
53 #include <algorithm>
54 #include <memory>
55
56 #include "gromacs/domdec/dlbtiming.h"
57 #include "gromacs/domdec/domdec.h"
58 #include "gromacs/domdec/domdec_struct.h"
59 #include "gromacs/domdec/ga2la.h"
60 #include "gromacs/domdec/hashedmap.h"
61 #include "gromacs/math/vec.h"
62 #include "gromacs/mdlib/constr.h"
63 #include "gromacs/mdlib/gmx_omp_nthreads.h"
64 #include "gromacs/mdtypes/atominfo.h"
65 #include "gromacs/mdtypes/commrec.h"
66 #include "gromacs/pbcutil/ishift.h"
67 #include "gromacs/topology/ifunc.h"
68 #include "gromacs/topology/mtop_lookup.h"
69 #include "gromacs/utility/exceptions.h"
70 #include "gromacs/utility/fatalerror.h"
71 #include "gromacs/utility/gmxassert.h"
72 #include "gromacs/utility/listoflists.h"
73
74 #include "domdec_internal.h"
75 #include "domdec_specatomcomm.h"
76
77 using gmx::ListOfLists;
78
79 /*! \brief Struct used during constraint setup with domain decomposition */
80 struct gmx_domdec_constraints_t
81 {
82     //! @cond Doxygen_Suppress
83     std::vector<int> molb_con_offset; /**< Offset in the constraint array for each molblock */
84     std::vector<int> molb_ncon_mol; /**< The number of constraints per molecule for each molblock */
85
86     int ncon; /**< The fully local and conneced constraints */
87     /* The global constraint number, only required for clearing gc_req */
88     std::vector<int> con_gl;     /**< Global constraint indices for local constraints */
89     std::vector<int> con_nlocat; /**< Number of local atoms (2/1/0) for each constraint */
90
91     std::vector<bool> gc_req; /**< Boolean that tells if a global constraint index has been requested; note: size global #constraints */
92
93     /* Hash table for keeping track of requests */
94     std::unique_ptr<gmx::HashedMap<int>> ga2la; /**< Global to local communicated constraint atom only index */
95
96     /* Multi-threading stuff */
97     int                          nthread; /**< Number of threads used for DD constraint setup */
98     std::vector<InteractionList> ils;     /**< Constraint ilist working arrays, size \p nthread */
99
100     /* Buffers for requesting atoms */
101     std::vector<std::vector<int>> requestedGlobalAtomIndices; /**< Buffers for requesting global atom indices, one per thread */
102
103     //! @endcond
104 };
105
106 void dd_move_x_constraints(gmx_domdec_t*            dd,
107                            const matrix             box,
108                            gmx::ArrayRef<gmx::RVec> x0,
109                            gmx::ArrayRef<gmx::RVec> x1,
110                            bool                     bX1IsCoord)
111 {
112     if (dd->constraint_comm)
113     {
114         dd_move_x_specat(
115                 dd, dd->constraint_comm, box, as_rvec_array(x0.data()), as_rvec_array(x1.data()), bX1IsCoord);
116
117         ddReopenBalanceRegionCpu(dd);
118     }
119 }
120
121 gmx::ArrayRef<const int> dd_constraints_nlocalatoms(const gmx_domdec_t* dd)
122 {
123     if (dd && dd->constraints)
124     {
125         return dd->constraints->con_nlocat;
126     }
127     else
128     {
129         return {};
130     }
131 }
132
133 void dd_clear_local_constraint_indices(gmx_domdec_t* dd)
134 {
135     gmx_domdec_constraints_t* dc = dd->constraints;
136
137     std::fill(dc->gc_req.begin(), dc->gc_req.end(), false);
138
139     if (dd->constraint_comm)
140     {
141         dc->ga2la->clearAndResizeHashTable();
142     }
143 }
144
145 /*! \brief Walks over the constraints out from the local atoms into the non-local atoms and adds them to a list */
146 static void walk_out(int                       con,
147                      int                       con_offset,
148                      int                       a,
149                      int                       offset,
150                      int                       nrec,
151                      gmx::ArrayRef<const int>  ia1,
152                      gmx::ArrayRef<const int>  ia2,
153                      const ListOfLists<int>&   at2con,
154                      const gmx_ga2la_t&        ga2la,
155                      gmx_bool                  bHomeConnect,
156                      gmx_domdec_constraints_t* dc,
157                      gmx_domdec_specat_comm_t* dcc,
158                      InteractionList*          il_local,
159                      std::vector<int>*         ireq)
160 {
161     if (!dc->gc_req[con_offset + con])
162     {
163         /* Add this non-home constraint to the list */
164         dc->con_gl.push_back(con_offset + con);
165         dc->con_nlocat.push_back(bHomeConnect ? 1 : 0);
166         dc->gc_req[con_offset + con]     = true;
167         const int*         iap           = constr_iatomptr(ia1, ia2, con);
168         const int          parameterType = iap[0];
169         const int          a1_gl         = offset + iap[1];
170         const int          a2_gl         = offset + iap[2];
171         std::array<int, 2> atoms;
172         /* The following indexing code can probably be optizimed */
173         if (const int* a_loc = ga2la.findHome(a1_gl))
174         {
175             atoms[0] = *a_loc;
176         }
177         else
178         {
179             /* We set this index later */
180             atoms[0] = -a1_gl - 1;
181         }
182         if (const int* a_loc = ga2la.findHome(a2_gl))
183         {
184             atoms[1] = *a_loc;
185         }
186         else
187         {
188             /* We set this index later */
189             atoms[1] = -a2_gl - 1;
190         }
191         il_local->push_back(parameterType, atoms);
192         dc->ncon++;
193     }
194     /* Check to not ask for the same atom more than once */
195     if (!dc->ga2la->find(offset + a))
196     {
197         assert(dcc);
198         /* Add this non-home atom to the list */
199         ireq->push_back(offset + a);
200         /* Temporarily mark with -2, we get the index later */
201         dc->ga2la->insert(offset + a, -2);
202     }
203
204     if (nrec > 0)
205     {
206         /* Loop over the constraint connected to atom a */
207         for (const int coni : at2con[a])
208         {
209             if (coni != con)
210             {
211                 /* Walk further */
212                 const int* iap = constr_iatomptr(ia1, ia2, coni);
213                 const int  b   = (a == iap[1]) ? iap[2] : iap[1];
214                 if (!ga2la.findHome(offset + b))
215                 {
216                     walk_out(coni, con_offset, b, offset, nrec - 1, ia1, ia2, at2con, ga2la, FALSE, dc, dcc, il_local, ireq);
217                 }
218             }
219         }
220     }
221 }
222
223 /*! \brief Looks up SETTLE constraints for a range of charge-groups */
224 static void atoms_to_settles(gmx_domdec_t*                         dd,
225                              const gmx_mtop_t&                     mtop,
226                              gmx::ArrayRef<const int64_t>          atomInfo,
227                              gmx::ArrayRef<const std::vector<int>> at2settle_mt,
228                              int                                   cg_start,
229                              int                                   cg_end,
230                              InteractionList*                      ils_local,
231                              std::vector<int>*                     ireq)
232 {
233     const gmx_ga2la_t& ga2la = *dd->ga2la;
234     int                nral  = NRAL(F_SETTLE);
235
236     int mb = 0;
237     for (int a = cg_start; a < cg_end; a++)
238     {
239         if (atomInfo[a] & gmx::sc_atomInfo_Settle)
240         {
241             int a_gl  = dd->globalAtomIndices[a];
242             int a_mol = 0;
243             mtopGetMolblockIndex(mtop, a_gl, &mb, nullptr, &a_mol);
244
245             const gmx_molblock_t* molb   = &mtop.molblock[mb];
246             int                   settle = at2settle_mt[molb->type][a_mol];
247
248             if (settle >= 0)
249             {
250                 int offset = a_gl - a_mol;
251
252                 const int* ia1 = mtop.moltype[molb->type].ilist[F_SETTLE].iatoms.data();
253
254                 int      a_gls[3];
255                 gmx_bool bAssign = FALSE;
256                 int      nlocal  = 0;
257                 for (int sa = 0; sa < nral; sa++)
258                 {
259                     int a_glsa = offset + ia1[settle * (1 + nral) + 1 + sa];
260                     a_gls[sa]  = a_glsa;
261                     if (ga2la.findHome(a_glsa))
262                     {
263                         if (nlocal == 0 && a_gl == a_glsa)
264                         {
265                             bAssign = TRUE;
266                         }
267                         nlocal++;
268                     }
269                 }
270
271                 if (bAssign)
272                 {
273                     const int          parameterType = ia1[settle * 4];
274                     std::array<int, 3> atoms;
275                     for (int sa = 0; sa < nral; sa++)
276                     {
277                         if (const int* a_loc = ga2la.findHome(a_gls[sa]))
278                         {
279                             atoms[sa] = *a_loc;
280                         }
281                         else
282                         {
283                             atoms[sa] = -a_gls[sa] - 1;
284                             /* Add this non-home atom to the list */
285                             ireq->push_back(a_gls[sa]);
286                             /* A check on double atom requests is
287                              * not required for settle.
288                              */
289                         }
290                     }
291                     ils_local->push_back(parameterType, atoms);
292                 }
293             }
294         }
295     }
296 }
297
298 /*! \brief Looks up constraint for the local atoms */
299 static void atoms_to_constraints(gmx_domdec_t*                         dd,
300                                  const gmx_mtop_t&                     mtop,
301                                  gmx::ArrayRef<const int64_t>          atomInfo,
302                                  gmx::ArrayRef<const ListOfLists<int>> at2con_mt,
303                                  int                                   nrec,
304                                  InteractionList*                      ilc_local,
305                                  std::vector<int>*                     ireq)
306 {
307     gmx_domdec_constraints_t* dc  = dd->constraints;
308     gmx_domdec_specat_comm_t* dcc = dd->constraint_comm;
309
310     const gmx_ga2la_t& ga2la = *dd->ga2la;
311
312     dc->con_gl.clear();
313     dc->con_nlocat.clear();
314
315     int mb    = 0;
316     int nhome = 0;
317     for (int a = 0; a < dd->numHomeAtoms; a++)
318     {
319         if (atomInfo[a] & gmx::sc_atomInfo_Constraint)
320         {
321             int a_gl  = dd->globalAtomIndices[a];
322             int molnr = 0;
323             int a_mol = 0;
324             mtopGetMolblockIndex(mtop, a_gl, &mb, &molnr, &a_mol);
325
326             const gmx_molblock_t& molb = mtop.molblock[mb];
327
328             gmx::ArrayRef<const int> ia1 = mtop.moltype[molb.type].ilist[F_CONSTR].iatoms;
329             gmx::ArrayRef<const int> ia2 = mtop.moltype[molb.type].ilist[F_CONSTRNC].iatoms;
330
331             /* Calculate the global constraint number offset for the molecule.
332              * This is only required for the global index to make sure
333              * that we use each constraint only once.
334              */
335             const int con_offset = dc->molb_con_offset[mb] + molnr * dc->molb_ncon_mol[mb];
336
337             /* The global atom number offset for this molecule */
338             const int offset = a_gl - a_mol;
339             /* Loop over the constraints connected to atom a_mol in the molecule */
340             const auto& at2con = at2con_mt[molb.type];
341             for (const int con : at2con[a_mol])
342             {
343                 const int* iap   = constr_iatomptr(ia1, ia2, con);
344                 int        b_mol = 0;
345                 if (a_mol == iap[1])
346                 {
347                     b_mol = iap[2];
348                 }
349                 else
350                 {
351                     b_mol = iap[1];
352                 }
353                 if (const int* a_loc = ga2la.findHome(offset + b_mol))
354                 {
355                     /* Add this fully home constraint at the first atom */
356                     if (a_mol < b_mol)
357                     {
358                         dc->con_gl.push_back(con_offset + con);
359                         dc->con_nlocat.push_back(2);
360                         const int          b_lo          = *a_loc;
361                         const int          parameterType = iap[0];
362                         std::array<int, 2> atoms;
363                         atoms[0] = (a_gl == iap[1] ? a : b_lo);
364                         atoms[1] = (a_gl == iap[1] ? b_lo : a);
365                         ilc_local->push_back(parameterType, atoms);
366                         dc->ncon++;
367                         nhome++;
368                     }
369                 }
370                 else
371                 {
372                     /* We need the nrec constraints coupled to this constraint,
373                      * so we need to walk out of the home cell by nrec+1 atoms,
374                      * since already atom bg is not locally present.
375                      * Therefore we call walk_out with nrec recursions to go
376                      * after this first call.
377                      */
378                     walk_out(con, con_offset, b_mol, offset, nrec, ia1, ia2, at2con, ga2la, TRUE, dc, dcc, ilc_local, ireq);
379                 }
380             }
381         }
382     }
383
384     GMX_ASSERT(dc->con_gl.size() == static_cast<size_t>(dc->ncon),
385                "con_gl size should match the number of constraints");
386     GMX_ASSERT(dc->con_nlocat.size() == static_cast<size_t>(dc->ncon),
387                "con_nlocat size should match the number of constraints");
388
389     if (debug)
390     {
391         fprintf(debug,
392                 "Constraints: home %3d border %3d atoms: %3zu\n",
393                 nhome,
394                 dc->ncon - nhome,
395                 dd->constraint_comm ? ireq->size() : 0);
396     }
397 }
398
399 int dd_make_local_constraints(gmx_domdec_t*                  dd,
400                               int                            at_start,
401                               const struct gmx_mtop_t&       mtop,
402                               gmx::ArrayRef<const int64_t>   atomInfo,
403                               gmx::Constraints*              constr,
404                               int                            nrec,
405                               gmx::ArrayRef<InteractionList> il_local)
406 {
407     // This code should not be called unless this condition is true,
408     // because that's the only time init_domdec_constraints is
409     // called...
410     GMX_RELEASE_ASSERT(dd->comm->systemInfo.mayHaveSplitConstraints || dd->comm->systemInfo.mayHaveSplitSettles,
411                        "dd_make_local_constraints called when there are no local constraints");
412     // ... and init_domdec_constraints always sets
413     // dd->constraint_comm...
414     GMX_RELEASE_ASSERT(
415             dd->constraint_comm,
416             "Invalid use of dd_make_local_constraints before construction of constraint_comm");
417     // ... which static analysis needs to be reassured about, because
418     // otherwise, when dd->splitSettles is
419     // true. dd->constraint_comm is unilaterally dereferenced before
420     // the call to atoms_to_settles.
421
422     gmx_domdec_constraints_t* dc = dd->constraints;
423
424     InteractionList* ilc_local = &il_local[F_CONSTR];
425     InteractionList* ils_local = &il_local[F_SETTLE];
426
427     dc->ncon = 0;
428     gmx::ArrayRef<const ListOfLists<int>> at2con_mt;
429     std::vector<int>*                     ireq = nullptr;
430     ilc_local->clear();
431     if (dd->constraint_comm)
432     {
433         // TODO Perhaps gmx_domdec_constraints_t should keep a valid constr?
434         GMX_RELEASE_ASSERT(constr != nullptr, "Must have valid constraints object");
435         at2con_mt = constr->atom2constraints_moltype();
436         ireq      = &dc->requestedGlobalAtomIndices[0];
437         ireq->clear();
438     }
439
440     gmx::ArrayRef<const std::vector<int>> at2settle_mt;
441     /* When settle works inside charge groups, we assigned them already */
442     if (dd->comm->systemInfo.mayHaveSplitSettles)
443     {
444         // TODO Perhaps gmx_domdec_constraints_t should keep a valid constr?
445         GMX_RELEASE_ASSERT(constr != nullptr, "Must have valid constraints object");
446         at2settle_mt = constr->atom2settle_moltype();
447         ils_local->clear();
448     }
449
450     if (at2settle_mt.empty())
451     {
452         atoms_to_constraints(dd, mtop, atomInfo, at2con_mt, nrec, ilc_local, ireq);
453     }
454     else
455     {
456         /* Do the constraints, if present, on the first thread.
457          * Do the settles on all other threads.
458          */
459         const int t0_set = ((!at2con_mt.empty() && dc->nthread > 1) ? 1 : 0);
460
461 #pragma omp parallel for num_threads(dc->nthread) schedule(static)
462         for (int thread = 0; thread < dc->nthread; thread++)
463         {
464             try
465             {
466                 if (!at2con_mt.empty() && thread == 0)
467                 {
468                     atoms_to_constraints(dd, mtop, atomInfo, at2con_mt, nrec, ilc_local, ireq);
469                 }
470
471                 if (thread >= t0_set)
472                 {
473                     /* Distribute the settle check+assignments over
474                      * dc->nthread or dc->nthread-1 threads.
475                      */
476                     const int cg0 = (dd->numHomeAtoms * (thread - t0_set)) / (dc->nthread - t0_set);
477                     const int cg1 = (dd->numHomeAtoms * (thread - t0_set + 1)) / (dc->nthread - t0_set);
478
479                     InteractionList* ilst = (thread == t0_set) ? ils_local : &dc->ils[thread];
480                     ilst->clear();
481
482                     std::vector<int>& ireqt = dc->requestedGlobalAtomIndices[thread];
483                     if (thread > 0)
484                     {
485                         ireqt.clear();
486                     }
487
488                     atoms_to_settles(dd, mtop, atomInfo, at2settle_mt, cg0, cg1, ilst, &ireqt);
489                 }
490             }
491             GMX_CATCH_ALL_AND_EXIT_WITH_FATAL_ERROR
492         }
493
494         /* Combine the generate settles and requested indices */
495         for (int thread = 1; thread < dc->nthread; thread++)
496         {
497             if (thread > t0_set)
498             {
499                 ils_local->append(dc->ils[thread]);
500             }
501
502             const std::vector<int>& ireqt = dc->requestedGlobalAtomIndices[thread];
503             ireq->insert(ireq->end(), ireqt.begin(), ireqt.end());
504         }
505
506         if (debug)
507         {
508             fprintf(debug, "Settles: total %3d\n", ils_local->size() / 4);
509         }
510     }
511
512     int at_end = 0;
513     if (dd->constraint_comm)
514     {
515         at_end = setup_specat_communication(dd,
516                                             ireq,
517                                             dd->constraint_comm,
518                                             dd->constraints->ga2la.get(),
519                                             at_start,
520                                             2,
521                                             "constraint",
522                                             " or lincs-order");
523
524         /* Fill in the missing indices */
525         gmx::HashedMap<int>* ga2la_specat = dd->constraints->ga2la.get();
526
527         int nral1 = 1 + NRAL(F_CONSTR);
528         for (int i = 0; i < ilc_local->size(); i += nral1)
529         {
530             int* iap = ilc_local->iatoms.data() + i;
531             for (int j = 1; j < nral1; j++)
532             {
533                 if (iap[j] < 0)
534                 {
535                     const int* a = ga2la_specat->find(-iap[j] - 1);
536                     GMX_ASSERT(a, "We have checked before that this atom index has been set");
537                     iap[j] = *a;
538                 }
539             }
540         }
541
542         nral1 = 1 + NRAL(F_SETTLE);
543         for (int i = 0; i < ils_local->size(); i += nral1)
544         {
545             int* iap = ils_local->iatoms.data() + i;
546             for (int j = 1; j < nral1; j++)
547             {
548                 if (iap[j] < 0)
549                 {
550                     const int* a = ga2la_specat->find(-iap[j] - 1);
551                     GMX_ASSERT(a, "We have checked before that this atom index has been set");
552                     iap[j] = *a;
553                 }
554             }
555         }
556     }
557     else
558     {
559         // Currently unreachable
560         at_end = at_start;
561     }
562
563     return at_end;
564 }
565
566 void init_domdec_constraints(gmx_domdec_t* dd, const gmx_mtop_t& mtop)
567 {
568     if (debug)
569     {
570         fprintf(debug, "Begin init_domdec_constraints\n");
571     }
572
573     dd->constraints              = new gmx_domdec_constraints_t;
574     gmx_domdec_constraints_t* dc = dd->constraints;
575
576     dc->molb_con_offset.resize(mtop.molblock.size());
577     dc->molb_ncon_mol.resize(mtop.molblock.size());
578
579     int ncon = 0;
580     for (size_t mb = 0; mb < mtop.molblock.size(); mb++)
581     {
582         const gmx_molblock_t* molb = &mtop.molblock[mb];
583         dc->molb_con_offset[mb]    = ncon;
584         dc->molb_ncon_mol[mb]      = mtop.moltype[molb->type].ilist[F_CONSTR].size() / 3
585                                 + mtop.moltype[molb->type].ilist[F_CONSTRNC].size() / 3;
586         ncon += molb->nmol * dc->molb_ncon_mol[mb];
587     }
588
589     if (ncon > 0)
590     {
591         dc->gc_req.resize(ncon);
592     }
593
594     /* Use a hash table for the global to local index.
595      * The number of keys is a rough estimate, it will be optimized later.
596      */
597     int numKeysEstimate = std::min(mtop.natoms / 20, mtop.natoms / (2 * dd->nnodes));
598     dc->ga2la           = std::make_unique<gmx::HashedMap<int>>(numKeysEstimate);
599
600     dc->nthread = gmx_omp_nthreads_get(ModuleMultiThread::Domdec);
601     dc->ils.resize(dc->nthread);
602
603     dd->constraint_comm = new gmx_domdec_specat_comm_t;
604
605     dc->requestedGlobalAtomIndices.resize(dc->nthread);
606 }