90e9acf9275df0eb14a4ba6a3f2791c304240a50
[alexxy/gromacs.git] / src / gromacs / mdlib / calc_verletbuf.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2012-2018, The GROMACS development team.
5  * Copyright (c) 2019,2020, by the GROMACS development team, led by
6  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
7  * and including many others, as listed in the AUTHORS file in the
8  * top-level source directory and at http://www.gromacs.org.
9  *
10  * GROMACS is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU Lesser General Public License
12  * as published by the Free Software Foundation; either version 2.1
13  * of the License, or (at your option) any later version.
14  *
15  * GROMACS is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18  * Lesser General Public License for more details.
19  *
20  * You should have received a copy of the GNU Lesser General Public
21  * License along with GROMACS; if not, see
22  * http://www.gnu.org/licenses, or write to the Free Software Foundation,
23  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
24  *
25  * If you want to redistribute modifications to GROMACS, please
26  * consider that scientific software is very special. Version
27  * control is crucial - bugs must be traceable. We will be happy to
28  * consider code for inclusion in the official distribution, but
29  * derived work must not be called official GROMACS. Details are found
30  * in the README & COPYING files - if they are missing, get the
31  * official version at http://www.gromacs.org.
32  *
33  * To help us fund GROMACS development, we humbly ask that you cite
34  * the research papers on the package. Check out http://www.gromacs.org.
35  */
36 #include "gmxpre.h"
37
38 #include "calc_verletbuf.h"
39
40 #include <cmath>
41 #include <cstdlib>
42
43 #include <algorithm>
44
45 #include "gromacs/ewald/ewald_utils.h"
46 #include "gromacs/math/functions.h"
47 #include "gromacs/math/units.h"
48 #include "gromacs/math/vec.h"
49 #include "gromacs/mdtypes/inputrec.h"
50 #include "gromacs/mdtypes/md_enums.h"
51 #include "gromacs/nbnxm/nbnxm.h"
52 #include "gromacs/nbnxm/nbnxm_geometry.h"
53 #include "gromacs/nbnxm/nbnxm_simd.h"
54 #include "gromacs/topology/block.h"
55 #include "gromacs/topology/ifunc.h"
56 #include "gromacs/topology/topology.h"
57 #include "gromacs/utility/fatalerror.h"
58 #include "gromacs/utility/real.h"
59 #include "gromacs/utility/strconvert.h"
60
61 /* The code in this file estimates a pairlist buffer length
62  * given a target energy drift per atom per picosecond.
63  * This is done by estimating the drift given a buffer length.
64  * Ideally we would like to have a tight overestimate of the drift,
65  * but that can be difficult to achieve.
66  *
67  * Significant approximations used:
68  *
69  * Uniform particle density. UNDERESTIMATES the drift by rho_global/rho_local.
70  *
71  * Interactions don't affect particle motion. OVERESTIMATES the drift on longer
72  * time scales. This approximation probably introduces the largest errors.
73  *
74  * Only take one constraint per particle into account: OVERESTIMATES the drift.
75  *
76  * For rotating constraints assume the same functional shape for time scales
77  * where the constraints rotate significantly as the exact expression for
78  * short time scales. OVERESTIMATES the drift on long time scales.
79  *
80  * For non-linear virtual sites use the mass of the lightest constructing atom
81  * to determine the displacement. OVER/UNDERESTIMATES the drift, depending on
82  * the geometry and masses of constructing atoms.
83  *
84  * Note that the formulas for normal atoms and linear virtual sites are exact,
85  * apart from the first two approximations.
86  *
87  * Note that apart from the effect of the above approximations, the actual
88  * drift of the total energy of a system can be orders of magnitude smaller
89  * due to cancellation of positive and negative drift for different pairs.
90  */
91
92
93 /* Struct for unique atom type for calculating the energy drift.
94  * The atom displacement depends on mass and constraints.
95  * The energy jump for given distance depend on LJ type and q.
96  */
97 struct VerletbufAtomtype
98 {
99     atom_nonbonded_kinetic_prop_t prop; /* non-bonded and kinetic atom prop. */
100     int                           n;    /* #atoms of this type in the system */
101 };
102
103 // Struct for derivatives of a non-bonded interaction potential
104 struct pot_derivatives_t
105 {
106     real md1; // -V' at the cutoff
107     real d2;  //  V'' at the cutoff
108     real md3; // -V''' at the cutoff
109 };
110
111 VerletbufListSetup verletbufGetListSetup(Nbnxm::KernelType nbnxnKernelType)
112 {
113     /* Note that the current buffer estimation code only handles clusters
114      * of size 1, 2 or 4, so for 4x8 or 8x8 we use the estimate for 4x4.
115      */
116     VerletbufListSetup listSetup;
117
118     listSetup.cluster_size_i = Nbnxm::IClusterSizePerKernelType[nbnxnKernelType];
119     listSetup.cluster_size_j = Nbnxm::JClusterSizePerKernelType[nbnxnKernelType];
120
121     if (!Nbnxm::kernelTypeUsesSimplePairlist(nbnxnKernelType))
122     {
123         /* The GPU kernels (except for OpenCL) split the j-clusters in two halves */
124         listSetup.cluster_size_j /= 2;
125     }
126
127     return listSetup;
128 }
129
130 VerletbufListSetup verletbufGetSafeListSetup(ListSetupType listType)
131 {
132     /* When calling this function we often don't know which kernel type we
133      * are going to use. We choose the kernel type with the smallest possible
134      * i- and j-cluster sizes, so we potentially overestimate, but never
135      * underestimate, the buffer drift.
136      */
137     Nbnxm::KernelType nbnxnKernelType;
138
139     if (listType == ListSetupType::Gpu)
140     {
141         nbnxnKernelType = Nbnxm::KernelType::Gpu8x8x8;
142     }
143     else if (GMX_SIMD && listType == ListSetupType::CpuSimdWhenSupported)
144     {
145 #ifdef GMX_NBNXN_SIMD_2XNN
146         /* We use the smallest cluster size to be on the safe side */
147         nbnxnKernelType = Nbnxm::KernelType::Cpu4xN_Simd_2xNN;
148 #else
149         nbnxnKernelType = Nbnxm::KernelType::Cpu4xN_Simd_4xN;
150 #endif
151     }
152     else
153     {
154         nbnxnKernelType = Nbnxm::KernelType::Cpu4x4_PlainC;
155     }
156
157     return verletbufGetListSetup(nbnxnKernelType);
158 }
159
160 // Returns whether prop1 and prop2 are identical
161 static bool atom_nonbonded_kinetic_prop_equal(const atom_nonbonded_kinetic_prop_t& prop1,
162                                               const atom_nonbonded_kinetic_prop_t& prop2)
163 {
164     return (prop1.mass == prop2.mass && prop1.type == prop2.type && prop1.q == prop2.q
165             && prop1.bConstr == prop2.bConstr && prop1.con_mass == prop2.con_mass
166             && prop1.con_len == prop2.con_len);
167 }
168
169 static void addAtomtype(std::vector<VerletbufAtomtype>* att, const atom_nonbonded_kinetic_prop_t& prop, int nmol)
170 {
171     if (prop.mass == 0)
172     {
173         /* Ignore massless particles */
174         return;
175     }
176
177     size_t i = 0;
178     while (i < att->size() && !atom_nonbonded_kinetic_prop_equal(prop, (*att)[i].prop))
179     {
180         i++;
181     }
182
183     if (i < att->size())
184     {
185         (*att)[i].n += nmol;
186     }
187     else
188     {
189         att->push_back({ prop, nmol });
190     }
191 }
192
193 /* Returns the mass of atom atomIndex or 1 when setMassesToOne=true */
194 static real getMass(const t_atoms& atoms, int atomIndex, bool setMassesToOne)
195 {
196     if (!setMassesToOne)
197     {
198         return atoms.atom[atomIndex].m;
199     }
200     else
201     {
202         return 1;
203     }
204 }
205
206 // Set the masses of a vsites in vsite_m and the non-linear vsite count in n_nonlin_vsite
207 static void get_vsite_masses(const gmx_moltype_t&  moltype,
208                              const gmx_ffparams_t& ffparams,
209                              bool                  setMassesToOne,
210                              gmx::ArrayRef<real>   vsite_m)
211 {
212     int numNonlinearVsites = 0;
213
214     /* Check for virtual sites, determine mass from constructing atoms */
215     for (const auto& ilist : extractILists(moltype.ilist, IF_VSITE))
216     {
217         for (size_t i = 0; i < ilist.iatoms.size(); i += ilistStride(ilist))
218         {
219             const t_iparams& ip = ffparams.iparams[ilist.iatoms[i]];
220             const int        a1 = ilist.iatoms[i + 1];
221
222             if (ilist.functionType != F_VSITEN)
223             {
224                 /* Only vsiten can have more than four
225                    constructing atoms, so NRAL(ft) <= 5 */
226                 const int         maxj = NRAL(ilist.functionType);
227                 std::vector<real> cam(maxj, 0);
228                 GMX_ASSERT(maxj <= 5, "This code expect at most 5 atoms in a vsite");
229                 for (int j = 1; j < maxj; j++)
230                 {
231                     const int aj = ilist.iatoms[i + 1 + j];
232                     cam[j]       = getMass(moltype.atoms, aj, setMassesToOne);
233                     if (cam[j] == 0)
234                     {
235                         cam[j] = vsite_m[aj];
236                     }
237                     /* A vsite should be constructed from normal atoms or
238                      * vsites of lower complexity, which we have processed
239                      * in a previous iteration.
240                      */
241                     GMX_ASSERT(cam[j] != 0, "We should have a non-zero mass");
242                 }
243
244                 switch (ilist.functionType)
245                 {
246                     case F_VSITE2:
247                         /* Exact */
248                         vsite_m[a1] = (cam[1] * cam[2])
249                                       / (cam[2] * gmx::square(1 - ip.vsite.a)
250                                          + cam[1] * gmx::square(ip.vsite.a));
251                         break;
252                     case F_VSITE3:
253                         /* Exact */
254                         vsite_m[a1] = (cam[1] * cam[2] * cam[3])
255                                       / (cam[2] * cam[3] * gmx::square(1 - ip.vsite.a - ip.vsite.b)
256                                          + cam[1] * cam[3] * gmx::square(ip.vsite.a)
257                                          + cam[1] * cam[2] * gmx::square(ip.vsite.b));
258                         break;
259                     case F_VSITEN:
260                         GMX_RELEASE_ASSERT(false, "VsiteN should not end up in this code path");
261                         break;
262                     default:
263                         /* Use the mass of the lightest constructing atom.
264                          * This is an approximation.
265                          * If the distance of the virtual site to the
266                          * constructing atom is less than all distances
267                          * between constructing atoms, this is a safe
268                          * over-estimate of the displacement of the vsite.
269                          * This condition holds for all H mass replacement
270                          * vsite constructions, except for SP2/3 groups.
271                          * In SP3 groups one H will have a F_VSITE3
272                          * construction, so even there the total drift
273                          * estimate shouldn't be far off.
274                          */
275                         vsite_m[a1] = cam[1];
276                         for (int j = 2; j < maxj; j++)
277                         {
278                             vsite_m[a1] = std::min(vsite_m[a1], cam[j]);
279                         }
280                         numNonlinearVsites++;
281                         break;
282                 }
283             }
284             else
285             {
286                 /* Exact */
287                 real inv_mass             = 0;
288                 int  numConstructingAtoms = ffparams.iparams[ilist.iatoms[i]].vsiten.n;
289                 for (int j = 0; j < 3 * numConstructingAtoms; j += 3)
290                 {
291                     int  aj    = ilist.iatoms[i + j + 2];
292                     real coeff = ffparams.iparams[ilist.iatoms[i + j]].vsiten.a;
293                     real m_aj;
294                     if (moltype.atoms.atom[aj].ptype == eptVSite)
295                     {
296                         m_aj = vsite_m[aj];
297                     }
298                     else
299                     {
300                         m_aj = moltype.atoms.atom[aj].m;
301                     }
302                     if (m_aj <= 0)
303                     {
304                         gmx_incons("The mass of a vsiten constructing atom is <= 0");
305                     }
306                     inv_mass += coeff * coeff / m_aj;
307                 }
308                 vsite_m[a1] = 1 / inv_mass;
309                 /* Correct the loop increment of i for processes more than 1 entry */
310                 i += (numConstructingAtoms - 1) * ilistStride(ilist);
311             }
312             if (gmx_debug_at)
313             {
314                 fprintf(debug, "atom %4d %-20s mass %6.3f\n", a1,
315                         interaction_function[ilist.functionType].longname, vsite_m[a1]);
316             }
317         }
318     }
319
320     if (debug && numNonlinearVsites > 0)
321     {
322         fprintf(debug, "The molecule type has %d non-linear virtual constructions\n", numNonlinearVsites);
323     }
324 }
325
326 static std::vector<VerletbufAtomtype> getVerletBufferAtomtypes(const gmx_mtop_t& mtop, const bool setMassesToOne)
327 {
328     std::vector<VerletbufAtomtype> att;
329     int                            ft, i, a1, a2, a3, a;
330     const t_iparams*               ip;
331
332     for (const gmx_molblock_t& molblock : mtop.molblock)
333     {
334         int                  nmol    = molblock.nmol;
335         const gmx_moltype_t& moltype = mtop.moltype[molblock.type];
336         const t_atoms*       atoms   = &moltype.atoms;
337
338         /* Check for constraints, as they affect the kinetic energy.
339          * For virtual sites we need the masses and geometry of
340          * the constructing atoms to determine their velocity distribution.
341          * Thus we need a list of properties for all atoms which
342          * we partially fill when looping over constraints.
343          */
344         std::vector<atom_nonbonded_kinetic_prop_t> prop(atoms->nr);
345
346         for (ft = F_CONSTR; ft <= F_CONSTRNC; ft++)
347         {
348             const InteractionList& il = moltype.ilist[ft];
349
350             for (i = 0; i < il.size(); i += 1 + NRAL(ft))
351             {
352                 ip         = &mtop.ffparams.iparams[il.iatoms[i]];
353                 a1         = il.iatoms[i + 1];
354                 a2         = il.iatoms[i + 2];
355                 real mass1 = getMass(*atoms, a1, setMassesToOne);
356                 real mass2 = getMass(*atoms, a2, setMassesToOne);
357                 if (mass2 > prop[a1].con_mass)
358                 {
359                     prop[a1].con_mass = mass2;
360                     prop[a1].con_len  = ip->constr.dA;
361                 }
362                 if (mass1 > prop[a2].con_mass)
363                 {
364                     prop[a2].con_mass = mass1;
365                     prop[a2].con_len  = ip->constr.dA;
366                 }
367             }
368         }
369
370         const InteractionList& il = moltype.ilist[F_SETTLE];
371
372         for (i = 0; i < il.size(); i += 1 + NRAL(F_SETTLE))
373         {
374             ip = &mtop.ffparams.iparams[il.iatoms[i]];
375             a1 = il.iatoms[i + 1];
376             a2 = il.iatoms[i + 2];
377             a3 = il.iatoms[i + 3];
378             /* Usually the mass of a1 (usually oxygen) is larger than a2/a3.
379              * If this is not the case, we overestimate the displacement,
380              * which leads to a larger buffer (ok since this is an exotic case).
381              */
382             prop[a1].con_mass = getMass(*atoms, a2, setMassesToOne);
383             prop[a1].con_len  = ip->settle.doh;
384
385             prop[a2].con_mass = getMass(*atoms, a1, setMassesToOne);
386             prop[a2].con_len  = ip->settle.doh;
387
388             prop[a3].con_mass = getMass(*atoms, a1, setMassesToOne);
389             prop[a3].con_len  = ip->settle.doh;
390         }
391
392         std::vector<real> vsite_m(atoms->nr);
393         get_vsite_masses(moltype, mtop.ffparams, setMassesToOne, vsite_m);
394
395         for (a = 0; a < atoms->nr; a++)
396         {
397             if (atoms->atom[a].ptype == eptVSite)
398             {
399                 prop[a].mass = vsite_m[a];
400             }
401             else
402             {
403                 prop[a].mass = getMass(*atoms, a, setMassesToOne);
404             }
405             prop[a].type = atoms->atom[a].type;
406             prop[a].q    = atoms->atom[a].q;
407             /* We consider an atom constrained, #DOF=2, when it is
408              * connected with constraints to (at least one) atom with
409              * a mass of more than 0.4x its own mass. This is not a critical
410              * parameter, since with roughly equal masses the unconstrained
411              * and constrained displacement will not differ much (and both
412              * overestimate the displacement).
413              */
414             prop[a].bConstr = (prop[a].con_mass > 0.4 * prop[a].mass);
415
416             addAtomtype(&att, prop[a], nmol);
417         }
418     }
419
420     if (gmx_debug_at)
421     {
422         for (size_t a = 0; a < att.size(); a++)
423         {
424             fprintf(debug, "type %zu: m %5.2f t %d q %6.3f con %s con_m %5.3f con_l %5.3f n %d\n",
425                     a, att[a].prop.mass, att[a].prop.type, att[a].prop.q,
426                     gmx::boolToString(att[a].prop.bConstr), att[a].prop.con_mass,
427                     att[a].prop.con_len, att[a].n);
428         }
429     }
430
431     return att;
432 }
433
434 /* This function computes two components of the estimate of the variance
435  * in the displacement of one atom in a system of two constrained atoms.
436  * Returns in sigma2_2d the variance due to rotation of the constrained
437  * atom around the atom to which it constrained.
438  * Returns in sigma2_3d the variance due to displacement of the COM
439  * of the whole system of the two constrained atoms.
440  *
441  * Note that we only take a single constraint (the one to the heaviest atom)
442  * into account. If an atom has multiple constraints, this will result in
443  * an overestimate of the displacement, which gives a larger drift and buffer.
444  */
445 void constrained_atom_sigma2(real kT_fac, const atom_nonbonded_kinetic_prop_t* prop, real* sigma2_2d, real* sigma2_3d)
446 {
447     /* Here we decompose the motion of a constrained atom into two
448      * components: rotation around the COM and translation of the COM.
449      */
450
451     /* Determine the variance of the arc length for the two rotational DOFs */
452     real massFraction = prop->con_mass / (prop->mass + prop->con_mass);
453     real sigma2_rot   = kT_fac * massFraction / prop->mass;
454
455     /* The distance from the atom to the COM, i.e. the rotational arm */
456     real comDistance = prop->con_len * massFraction;
457
458     /* The variance relative to the arm */
459     real sigma2_rel = sigma2_rot / gmx::square(comDistance);
460
461     /* For sigma2_rel << 1 we don't notice the rotational effect and
462      * we have a normal, Gaussian displacement distribution.
463      * For larger sigma2_rel the displacement is much less, in fact it can
464      * not exceed 2*comDistance. We can calculate MSD/arm^2 as:
465      *   integral_x=0-inf distance2(x) x/sigma2_rel exp(-x^2/(2 sigma2_rel)) dx
466      * where x is angular displacement and distance2(x) is the distance^2
467      * between points at angle 0 and x:
468      *   distance2(x) = (sin(x) - sin(0))^2 + (cos(x) - cos(0))^2
469      * The limiting value of this MSD is 2, which is also the value for
470      * a uniform rotation distribution that would be reached at long time.
471      * The maximum is 2.5695 at sigma2_rel = 4.5119.
472      * We approximate this integral with a rational polynomial with
473      * coefficients from a Taylor expansion. This approximation is an
474      * overestimate for all values of sigma2_rel. Its maximum value
475      * of 2.6491 is reached at sigma2_rel = sqrt(45/2) = 4.7434.
476      * We keep the approximation constant after that.
477      * We use this approximate MSD as the variance for a Gaussian distribution.
478      *
479      * NOTE: For any sensible buffer tolerance this will result in a (large)
480      * overestimate of the buffer size, since the Gaussian has a long tail,
481      * whereas the actual distribution can not reach values larger than 2.
482      */
483     /* Coeffients obtained from a Taylor expansion */
484     const real a = 1.0 / 3.0;
485     const real b = 2.0 / 45.0;
486
487     /* Our approximation is constant after sigma2_rel = 1/sqrt(b) */
488     sigma2_rel = std::min(sigma2_rel, 1 / std::sqrt(b));
489
490     /* Compute the approximate sigma^2 for 2D motion due to the rotation */
491     *sigma2_2d =
492             gmx::square(comDistance) * sigma2_rel / (1 + a * sigma2_rel + b * gmx::square(sigma2_rel));
493
494     /* The constrained atom also moves (in 3D) with the COM of both atoms */
495     *sigma2_3d = kT_fac / (prop->mass + prop->con_mass);
496 }
497
498 static void get_atom_sigma2(real kT_fac, const atom_nonbonded_kinetic_prop_t* prop, real* sigma2_2d, real* sigma2_3d)
499 {
500     if (prop->bConstr)
501     {
502         /* Complicated constraint calculation in a separate function */
503         constrained_atom_sigma2(kT_fac, prop, sigma2_2d, sigma2_3d);
504     }
505     else
506     {
507         /* Unconstrained atom: trivial */
508         *sigma2_2d = 0;
509         *sigma2_3d = kT_fac / prop->mass;
510     }
511 }
512
513 static void approx_2dof(real s2, real x, real* shift, real* scale)
514 {
515     /* A particle with 1 DOF constrained has 2 DOFs instead of 3.
516      * This code is also used for particles with multiple constraints,
517      * in which case we overestimate the displacement.
518      * The 2DOF distribution is sqrt(pi/2)*erfc(r/(sqrt(2)*s))/(2*s).
519      * We approximate this with scale*Gaussian(s,r+shift),
520      * by matching the distribution value and derivative at x.
521      * This is a tight overestimate for all r>=0 at any s and x.
522      */
523     real ex, er;
524
525     ex = std::exp(-x * x / (2 * s2));
526     er = std::erfc(x / std::sqrt(2 * s2));
527
528     *shift = -x + std::sqrt(2 * s2 / M_PI) * ex / er;
529     *scale = 0.5 * M_PI * std::exp(ex * ex / (M_PI * er * er)) * er;
530 }
531
532 // Returns an (over)estimate of the energy drift for a single atom pair,
533 // given the kinetic properties, displacement variances and list buffer.
534 static real energyDriftAtomPair(bool                     isConstrained_i,
535                                 bool                     isConstrained_j,
536                                 real                     s2,
537                                 real                     s2i_2d,
538                                 real                     s2j_2d,
539                                 real                     r_buffer,
540                                 const pot_derivatives_t* der)
541 {
542     // For relatively small arguments erfc() is so small that if will be 0.0
543     // when stored in a float. We set an argument limit of 8 (Erfc(8)=1e-29),
544     // such that we can divide by erfc and have some space left for arithmetic.
545     const real erfc_arg_max = 8.0;
546
547     real rsh    = r_buffer;
548     real sc_fac = 1.0;
549
550     real c_exp, c_erfc;
551
552     if (rsh * rsh > 2 * s2 * erfc_arg_max * erfc_arg_max)
553     {
554         // Below we calculate c_erfc = 0.5*erfc(rsh/sqrt(2*s2))
555         // When rsh/sqrt(2*s2) increases, this erfc will be the first
556         // result that underflows and becomes 0.0. To avoid this,
557         // we set c_exp=0 and c_erfc=0 for large arguments.
558         // This also avoids NaN in approx_2dof().
559         // In any relevant case this has no effect on the results,
560         // since c_exp < 6e-29, so the displacement is completely
561         // negligible for such atom pairs (and an overestimate).
562         // In nearly all use cases, there will be other atom pairs
563         // that contribute much more to the total, so zeroing
564         // this particular contribution has no effect at all.
565         c_exp  = 0;
566         c_erfc = 0;
567     }
568     else
569     {
570         /* For constraints: adapt r and scaling for the Gaussian */
571         if (isConstrained_i)
572         {
573             real sh, sc;
574
575             approx_2dof(s2i_2d, r_buffer * s2i_2d / s2, &sh, &sc);
576             rsh += sh;
577             sc_fac *= sc;
578         }
579         if (isConstrained_j)
580         {
581             real sh, sc;
582
583             approx_2dof(s2j_2d, r_buffer * s2j_2d / s2, &sh, &sc);
584             rsh += sh;
585             sc_fac *= sc;
586         }
587
588         /* Exact contribution of an atom pair with Gaussian displacement
589          * with sigma s to the energy drift for a potential with
590          * derivative -md and second derivative dd at the cut-off.
591          * The only catch is that for potentials that change sign
592          * near the cut-off there could be an unlucky compensation
593          * of positive and negative energy drift.
594          * Such potentials are extremely rare though.
595          *
596          * Note that pot has unit energy*length, as the linear
597          * atom density still needs to be put in.
598          */
599         c_exp  = std::exp(-rsh * rsh / (2 * s2)) / std::sqrt(2 * M_PI);
600         c_erfc = 0.5 * std::erfc(rsh / (std::sqrt(2 * s2)));
601     }
602     real s    = std::sqrt(s2);
603     real rsh2 = rsh * rsh;
604
605     real pot1 = sc_fac * der->md1 / 2 * ((rsh2 + s2) * c_erfc - rsh * s * c_exp);
606     real pot2 = sc_fac * der->d2 / 6 * (s * (rsh2 + 2 * s2) * c_exp - rsh * (rsh2 + 3 * s2) * c_erfc);
607     real pot3 = sc_fac * der->md3 / 24
608                 * ((rsh2 * rsh2 + 6 * rsh2 * s2 + 3 * s2 * s2) * c_erfc - rsh * s * (rsh2 + 5 * s2) * c_exp);
609
610     return pot1 + pot2 + pot3;
611 }
612
613 // Computes and returns an estimate of the energy drift for the whole system
614 static real energyDrift(gmx::ArrayRef<const VerletbufAtomtype> att,
615                         const gmx_ffparams_t*                  ffp,
616                         real                                   kT_fac,
617                         const pot_derivatives_t*               ljDisp,
618                         const pot_derivatives_t*               ljRep,
619                         const pot_derivatives_t*               elec,
620                         real                                   rlj,
621                         real                                   rcoulomb,
622                         real                                   rlist,
623                         real                                   boxvol)
624 {
625     double drift_tot = 0;
626
627     if (kT_fac == 0)
628     {
629         /* No atom displacements: no drift, avoid division by 0 */
630         return drift_tot;
631     }
632
633     // Here add up the contribution of all atom pairs in the system to
634     // (estimated) energy drift by looping over all atom type pairs.
635     for (gmx::index i = 0; i < att.ssize(); i++)
636     {
637         // Get the thermal displacement variance for the i-atom type
638         const atom_nonbonded_kinetic_prop_t* prop_i = &att[i].prop;
639         real                                 s2i_2d, s2i_3d;
640         get_atom_sigma2(kT_fac, prop_i, &s2i_2d, &s2i_3d);
641
642         for (gmx::index j = i; j < att.ssize(); j++)
643         {
644             // Get the thermal displacement variance for the j-atom type
645             const atom_nonbonded_kinetic_prop_t* prop_j = &att[j].prop;
646             real                                 s2j_2d, s2j_3d;
647             get_atom_sigma2(kT_fac, prop_j, &s2j_2d, &s2j_3d);
648
649             /* Add up the up to four independent variances */
650             real s2 = s2i_2d + s2i_3d + s2j_2d + s2j_3d;
651
652             // Set -V', V'' and -V''' at the cut-off for LJ */
653             real              c6  = ffp->iparams[prop_i->type * ffp->atnr + prop_j->type].lj.c6;
654             real              c12 = ffp->iparams[prop_i->type * ffp->atnr + prop_j->type].lj.c12;
655             pot_derivatives_t lj;
656             lj.md1 = c6 * ljDisp->md1 + c12 * ljRep->md1;
657             lj.d2  = c6 * ljDisp->d2 + c12 * ljRep->d2;
658             lj.md3 = c6 * ljDisp->md3 + c12 * ljRep->md3;
659
660             real pot_lj = energyDriftAtomPair(prop_i->bConstr, prop_j->bConstr, s2, s2i_2d, s2j_2d,
661                                               rlist - rlj, &lj);
662
663             // Set -V' and V'' at the cut-off for Coulomb
664             pot_derivatives_t elec_qq;
665             elec_qq.md1 = elec->md1 * prop_i->q * prop_j->q;
666             elec_qq.d2  = elec->d2 * prop_i->q * prop_j->q;
667             elec_qq.md3 = 0;
668
669             real pot_q = energyDriftAtomPair(prop_i->bConstr, prop_j->bConstr, s2, s2i_2d, s2j_2d,
670                                              rlist - rcoulomb, &elec_qq);
671
672             // Note that attractive and repulsive potentials for individual
673             // pairs can partially cancel.
674             real pot = pot_lj + pot_q;
675
676             /* Multiply by the number of atom pairs */
677             if (j == i)
678             {
679                 pot *= static_cast<double>(att[i].n) * (att[i].n - 1) / 2;
680             }
681             else
682             {
683                 pot *= static_cast<double>(att[i].n) * att[j].n;
684             }
685             /* We need the line density to get the energy drift of the system.
686              * The effective average r^2 is close to (rlist+sigma)^2.
687              */
688             pot *= 4 * M_PI * gmx::square(rlist + std::sqrt(s2)) / boxvol;
689
690             /* Add the unsigned drift to avoid cancellation of errors */
691             drift_tot += std::abs(pot);
692         }
693     }
694
695     return drift_tot;
696 }
697
698 // Returns the chance that a particle in a cluster is at distance rlist
699 // when the cluster is at distance rlist
700 static real surface_frac(int cluster_size, real particle_distance, real rlist)
701 {
702     real d, area_rel;
703
704     if (rlist < 0.5 * particle_distance)
705     {
706         /* We have non overlapping spheres */
707         return 1.0;
708     }
709
710     /* Half the inter-particle distance relative to rlist */
711     d = 0.5 * particle_distance / rlist;
712
713     /* Determine the area of the surface at distance rlist to the closest
714      * particle, relative to surface of a sphere of radius rlist.
715      * The formulas below assume close to cubic cells for the pair search grid,
716      * which the pair search code tries to achieve.
717      * Note that in practice particle distances will not be delta distributed,
718      * but have some spread, often involving shorter distances,
719      * as e.g. O-H bonds in a water molecule. Thus the estimates below will
720      * usually be slightly too high and thus conservative.
721      */
722     switch (cluster_size)
723     {
724         case 1:
725             /* One particle: trivial */
726             area_rel = 1.0;
727             break;
728         case 2:
729             /* Two particles: two spheres at fractional distance 2*a */
730             area_rel = 1.0 + d;
731             break;
732         case 4:
733             /* We assume a perfect, symmetric tetrahedron geometry.
734              * The surface around a tetrahedron is too complex for a full
735              * analytical solution, so we use a Taylor expansion.
736              */
737             area_rel = (1.0
738                         + 1 / M_PI
739                                   * (6 * std::acos(1 / std::sqrt(3)) * d
740                                      + std::sqrt(3) * d * d
741                                                * (1.0 + 5.0 / 18.0 * d * d + 7.0 / 45.0 * d * d * d * d
742                                                   + 83.0 / 756.0 * d * d * d * d * d * d)));
743             break;
744         default: gmx_incons("surface_frac called with unsupported cluster_size");
745     }
746
747     return area_rel / cluster_size;
748 }
749
750 /* Returns the negative of the third derivative of a potential r^-p
751  * with a force-switch function, evaluated at the cut-off rc.
752  */
753 static real md3_force_switch(real p, real rswitch, real rc)
754 {
755     /* The switched force function is:
756      * p*r^-(p+1) + a*(r - rswitch)^2 + b*(r - rswitch)^3
757      */
758     real a, b;
759     real md3_pot, md3_sw;
760
761     a = -((p + 4) * rc - (p + 1) * rswitch) / (pow(rc, p + 2) * gmx::square(rc - rswitch));
762     b = ((p + 3) * rc - (p + 1) * rswitch) / (pow(rc, p + 2) * gmx::power3(rc - rswitch));
763
764     md3_pot = (p + 2) * (p + 1) * p * pow(rc, p + 3);
765     md3_sw  = 2 * a + 6 * b * (rc - rswitch);
766
767     return md3_pot + md3_sw;
768 }
769
770 /* Returns the variance of the atomic displacement over timePeriod.
771  *
772  * Note: When not using BD with a non-mass dependendent friction coefficient,
773  *       the return value still needs to be divided by the particle mass.
774  */
775 static real displacementVariance(const t_inputrec& ir, real temperature, real timePeriod)
776 {
777     real kT_fac;
778
779     if (ir.eI == eiBD)
780     {
781         /* Get the displacement distribution from the random component only.
782          * With accurate integration the systematic (force) displacement
783          * should be negligible (unless nstlist is extremely large, which
784          * you wouldn't do anyhow).
785          */
786         kT_fac = 2 * BOLTZ * temperature * timePeriod;
787         if (ir.bd_fric > 0)
788         {
789             /* This is directly sigma^2 of the displacement */
790             kT_fac /= ir.bd_fric;
791         }
792         else
793         {
794             /* Per group tau_t is not implemented yet, use the maximum */
795             real tau_t = ir.opts.tau_t[0];
796             for (int i = 1; i < ir.opts.ngtc; i++)
797             {
798                 tau_t = std::max(tau_t, ir.opts.tau_t[i]);
799             }
800
801             kT_fac *= tau_t;
802             /* This kT_fac needs to be divided by the mass to get sigma^2 */
803         }
804     }
805     else
806     {
807         kT_fac = BOLTZ * temperature * gmx::square(timePeriod);
808     }
809
810     return kT_fac;
811 }
812
813 /* Returns the largest sigma of the Gaussian displacement over all particle
814  * types. This ignores constraints, so is an overestimate.
815  */
816 static real maxSigma(real kT_fac, gmx::ArrayRef<const VerletbufAtomtype> att)
817 {
818     GMX_ASSERT(!att.empty(), "We should have at least one type");
819     real smallestMass = att[0].prop.mass;
820     for (const auto& atomType : att)
821     {
822         smallestMass = std::min(smallestMass, atomType.prop.mass);
823     }
824
825     return 2 * std::sqrt(kT_fac / smallestMass);
826 }
827
828 real calcVerletBufferSize(const gmx_mtop_t&         mtop,
829                           const real                boxVolume,
830                           const t_inputrec&         ir,
831                           const int                 nstlist,
832                           const int                 listLifetime,
833                           real                      referenceTemperature,
834                           const VerletbufListSetup& listSetup)
835 {
836     double resolution;
837     char*  env;
838
839     real particle_distance;
840     real nb_clust_frac_pairs_not_in_list_at_cutoff;
841
842     real elfac;
843     int  ib0, ib1, ib;
844     real rb, rl;
845     real drift;
846
847     if (!EI_DYNAMICS(ir.eI))
848     {
849         gmx_incons(
850                 "Can only determine the Verlet buffer size for integrators that perform dynamics");
851     }
852     if (ir.verletbuf_tol <= 0)
853     {
854         gmx_incons("The Verlet buffer tolerance needs to be larger than zero");
855     }
856
857     if (referenceTemperature < 0)
858     {
859         /* We use the maximum temperature with multiple T-coupl groups.
860          * We could use a per particle temperature, but since particles
861          * interact, this might underestimate the buffer size.
862          */
863         referenceTemperature = maxReferenceTemperature(ir);
864
865         GMX_RELEASE_ASSERT(referenceTemperature >= 0,
866                            "Without T-coupling we should not end up here");
867     }
868
869     /* Resolution of the buffer size */
870     resolution = 0.001;
871
872     env = getenv("GMX_VERLET_BUFFER_RES");
873     if (env != nullptr)
874     {
875         sscanf(env, "%lf", &resolution);
876     }
877
878     /* In an atom wise pair-list there would be no pairs in the list
879      * beyond the pair-list cut-off.
880      * However, we use a pair-list of groups vs groups of atoms.
881      * For groups of 4 atoms, the parallelism of SSE instructions, only
882      * 10% of the atoms pairs are not in the list just beyond the cut-off.
883      * As this percentage increases slowly compared to the decrease of the
884      * Gaussian displacement distribution over this range, we can simply
885      * reduce the drift by this fraction.
886      * For larger groups, e.g. of 8 atoms, this fraction will be lower,
887      * so then buffer size will be on the conservative (large) side.
888      *
889      * Note that the formulas used here do not take into account
890      * cancellation of errors which could occur by missing both
891      * attractive and repulsive interactions.
892      *
893      * The only major assumption is homogeneous particle distribution.
894      * For an inhomogeneous system, such as a liquid-vapor system,
895      * the buffer will be underestimated. The actual energy drift
896      * will be higher by the factor: local/homogeneous particle density.
897      *
898      * The results of this estimate have been checked againt simulations.
899      * In most cases the real drift differs by less than a factor 2.
900      */
901
902     /* Worst case assumption: HCP packing of particles gives largest distance */
903     particle_distance = std::cbrt(boxVolume * std::sqrt(2) / mtop.natoms);
904
905     /* TODO: Obtain masses through (future) integrator functionality
906      *       to avoid scattering the code with (or forgetting) checks.
907      */
908     const bool setMassesToOne = (ir.eI == eiBD && ir.bd_fric > 0);
909     const auto att            = getVerletBufferAtomtypes(mtop, setMassesToOne);
910     GMX_ASSERT(!att.empty(), "We expect at least one type");
911
912     if (debug)
913     {
914         fprintf(debug, "particle distance assuming HCP packing: %f nm\n", particle_distance);
915         fprintf(debug, "energy drift atom types: %zu\n", att.size());
916     }
917
918     pot_derivatives_t ljDisp = { 0, 0, 0 };
919     pot_derivatives_t ljRep  = { 0, 0, 0 };
920     real              repPow = mtop.ffparams.reppow;
921
922     if (ir.vdwtype == evdwCUT)
923     {
924         real sw_range, md3_pswf;
925
926         switch (ir.vdw_modifier)
927         {
928             case eintmodNONE:
929             case eintmodPOTSHIFT:
930                 /* -dV/dr of -r^-6 and r^-reppow */
931                 ljDisp.md1 = -6 * std::pow(ir.rvdw, -7.0);
932                 ljRep.md1  = repPow * std::pow(ir.rvdw, -(repPow + 1));
933                 /* The contribution of the higher derivatives is negligible */
934                 break;
935             case eintmodFORCESWITCH:
936                 /* At the cut-off: V=V'=V''=0, so we use only V''' */
937                 ljDisp.md3 = -md3_force_switch(6.0, ir.rvdw_switch, ir.rvdw);
938                 ljRep.md3  = md3_force_switch(repPow, ir.rvdw_switch, ir.rvdw);
939                 break;
940             case eintmodPOTSWITCH:
941                 /* At the cut-off: V=V'=V''=0.
942                  * V''' is given by the original potential times
943                  * the third derivative of the switch function.
944                  */
945                 sw_range = ir.rvdw - ir.rvdw_switch;
946                 md3_pswf = 60.0 / gmx::power3(sw_range);
947
948                 ljDisp.md3 = -std::pow(ir.rvdw, -6.0) * md3_pswf;
949                 ljRep.md3  = std::pow(ir.rvdw, -repPow) * md3_pswf;
950                 break;
951             default: gmx_incons("Unimplemented VdW modifier");
952         }
953     }
954     else if (EVDW_PME(ir.vdwtype))
955     {
956         real b   = calc_ewaldcoeff_lj(ir.rvdw, ir.ewald_rtol_lj);
957         real r   = ir.rvdw;
958         real br  = b * r;
959         real br2 = br * br;
960         real br4 = br2 * br2;
961         real br6 = br4 * br2;
962         // -dV/dr of g(br)*r^-6 [where g(x) = exp(-x^2)(1+x^2+x^4/2),
963         // see LJ-PME equations in manual] and r^-reppow
964         ljDisp.md1 = -std::exp(-br2) * (br6 + 3.0 * br4 + 6.0 * br2 + 6.0) * std::pow(r, -7.0);
965         ljRep.md1  = repPow * pow(r, -(repPow + 1));
966         // The contribution of the higher derivatives is negligible
967     }
968     else
969     {
970         gmx_fatal(FARGS,
971                   "Energy drift calculation is only implemented for plain cut-off Lennard-Jones "
972                   "interactions");
973     }
974
975     elfac = ONE_4PI_EPS0 / ir.epsilon_r;
976
977     // Determine the 1st and 2nd derivative for the electostatics
978     pot_derivatives_t elec = { 0, 0, 0 };
979
980     if (ir.coulombtype == eelCUT || EEL_RF(ir.coulombtype))
981     {
982         real eps_rf, k_rf;
983
984         if (ir.coulombtype == eelCUT)
985         {
986             eps_rf = 1;
987             k_rf   = 0;
988         }
989         else
990         {
991             eps_rf = ir.epsilon_rf / ir.epsilon_r;
992             if (eps_rf != 0)
993             {
994                 k_rf = (eps_rf - ir.epsilon_r) / (gmx::power3(ir.rcoulomb) * (2 * eps_rf + ir.epsilon_r));
995             }
996             else
997             {
998                 /* epsilon_rf = infinity */
999                 k_rf = 0.5 / gmx::power3(ir.rcoulomb);
1000             }
1001         }
1002
1003         if (eps_rf > 0)
1004         {
1005             elec.md1 = elfac * (1.0 / gmx::square(ir.rcoulomb) - 2 * k_rf * ir.rcoulomb);
1006         }
1007         elec.d2 = elfac * (2.0 / gmx::power3(ir.rcoulomb) + 2 * k_rf);
1008     }
1009     else if (EEL_PME(ir.coulombtype) || ir.coulombtype == eelEWALD)
1010     {
1011         real b, rc, br;
1012
1013         b        = calc_ewaldcoeff_q(ir.rcoulomb, ir.ewald_rtol);
1014         rc       = ir.rcoulomb;
1015         br       = b * rc;
1016         elec.md1 = elfac * (b * std::exp(-br * br) * M_2_SQRTPI / rc + std::erfc(br) / (rc * rc));
1017         elec.d2  = elfac / (rc * rc)
1018                   * (2 * b * (1 + br * br) * std::exp(-br * br) * M_2_SQRTPI + 2 * std::erfc(br) / rc);
1019     }
1020     else
1021     {
1022         gmx_fatal(FARGS,
1023                   "Energy drift calculation is only implemented for Reaction-Field and Ewald "
1024                   "electrostatics");
1025     }
1026
1027     /* Determine the variance of the atomic displacement
1028      * over list_lifetime steps: kT_fac
1029      * For inertial dynamics (not Brownian dynamics) the mass factor
1030      * is not included in kT_fac, it is added later.
1031      */
1032     const real kT_fac = displacementVariance(ir, referenceTemperature, listLifetime * ir.delta_t);
1033
1034     if (debug)
1035     {
1036         fprintf(debug, "Derivatives of non-bonded potentials at the cut-off:\n");
1037         fprintf(debug, "LJ disp. -V' %9.2e V'' %9.2e -V''' %9.2e\n", ljDisp.md1, ljDisp.d2, ljDisp.md3);
1038         fprintf(debug, "LJ rep.  -V' %9.2e V'' %9.2e -V''' %9.2e\n", ljRep.md1, ljRep.d2, ljRep.md3);
1039         fprintf(debug, "Electro. -V' %9.2e V'' %9.2e\n", elec.md1, elec.d2);
1040         fprintf(debug, "sqrt(kT_fac) %f\n", std::sqrt(kT_fac));
1041     }
1042
1043     /* Search using bisection */
1044     ib0 = -1;
1045     /* The drift will be neglible at 5 times the max sigma */
1046     ib1 = static_cast<int>(5 * maxSigma(kT_fac, att) / resolution) + 1;
1047     while (ib1 - ib0 > 1)
1048     {
1049         ib = (ib0 + ib1) / 2;
1050         rb = ib * resolution;
1051         rl = std::max(ir.rvdw, ir.rcoulomb) + rb;
1052
1053         /* Calculate the average energy drift at the last step
1054          * of the nstlist steps at which the pair-list is used.
1055          */
1056         drift = energyDrift(att, &mtop.ffparams, kT_fac, &ljDisp, &ljRep, &elec, ir.rvdw,
1057                             ir.rcoulomb, rl, boxVolume);
1058
1059         /* Correct for the fact that we are using a Ni x Nj particle pair list
1060          * and not a 1 x 1 particle pair list. This reduces the drift.
1061          */
1062         /* We don't have a formula for 8 (yet), use 4 which is conservative */
1063         nb_clust_frac_pairs_not_in_list_at_cutoff =
1064                 surface_frac(std::min(listSetup.cluster_size_i, 4), particle_distance, rl)
1065                 * surface_frac(std::min(listSetup.cluster_size_j, 4), particle_distance, rl);
1066         drift *= nb_clust_frac_pairs_not_in_list_at_cutoff;
1067
1068         /* Convert the drift to drift per unit time per atom */
1069         drift /= nstlist * ir.delta_t * mtop.natoms;
1070
1071         if (debug)
1072         {
1073             fprintf(debug, "ib %3d %3d %3d rb %.3f %dx%d fac %.3f drift %.1e\n", ib0, ib, ib1, rb,
1074                     listSetup.cluster_size_i, listSetup.cluster_size_j,
1075                     nb_clust_frac_pairs_not_in_list_at_cutoff, drift);
1076         }
1077
1078         if (std::abs(drift) > ir.verletbuf_tol)
1079         {
1080             ib0 = ib;
1081         }
1082         else
1083         {
1084             ib1 = ib;
1085         }
1086     }
1087
1088     return std::max(ir.rvdw, ir.rcoulomb) + ib1 * resolution;
1089 }
1090
1091 /* Returns the pairlist buffer size for use as a minimum buffer size
1092  *
1093  * Note that this is a rather crude estimate. It is ok for a buffer
1094  * set for good energy conservation or RF electrostatics. But it is
1095  * too small with PME and the buffer set with the default tolerance.
1096  */
1097 static real minCellSizeFromPairlistBuffer(const t_inputrec& ir)
1098 {
1099     return ir.rlist - std::max(ir.rvdw, ir.rcoulomb);
1100 }
1101
1102 static real chanceOfAtomCrossingCell(gmx::ArrayRef<const VerletbufAtomtype> atomtypes, real kT_fac, real cellSize)
1103 {
1104     /* We assume atoms are distributed uniformly over the cell width.
1105      * Once an atom has moved by more than the cellSize (as passed
1106      * as the buffer argument to energyDriftAtomPair() below),
1107      * the chance of crossing the boundary of the neighbor cell
1108      * thus increases as 1/cellSize with the additional displacement
1109      * on top of cellSize. We thus create a linear interaction with
1110      * derivative = -1/cellSize. Using this in the energyDriftAtomPair
1111      * function will return the chance of crossing the next boundary.
1112      */
1113     const pot_derivatives_t boundaryInteraction = { 1 / cellSize, 0, 0 };
1114
1115     real chance = 0;
1116     for (const VerletbufAtomtype& att : atomtypes)
1117     {
1118         const atom_nonbonded_kinetic_prop_t& propAtom = att.prop;
1119         real                                 s2_2d;
1120         real                                 s2_3d;
1121         get_atom_sigma2(kT_fac, &propAtom, &s2_2d, &s2_3d);
1122
1123         real chancePerAtom = energyDriftAtomPair(propAtom.bConstr, false, s2_2d + s2_3d, s2_2d, 0,
1124                                                  cellSize, &boundaryInteraction);
1125
1126         if (propAtom.bConstr)
1127         {
1128             /* energyDriftAtomPair() uses an unlimited Gaussian displacement
1129              * distribution for constrained atoms, whereas they can
1130              * actually not move more than the COM of the two constrained
1131              * atoms plus twice the distance from the COM.
1132              * Use this maximum, limited displacement when this results in
1133              * a smaller chance (note that this is still an overestimate).
1134              */
1135             real massFraction = propAtom.con_mass / (propAtom.mass + propAtom.con_mass);
1136             real comDistance  = propAtom.con_len * massFraction;
1137
1138             real chanceWithMaxDistance = energyDriftAtomPair(
1139                     false, false, s2_3d, 0, 0, cellSize - 2 * comDistance, &boundaryInteraction);
1140             chancePerAtom = std::min(chancePerAtom, chanceWithMaxDistance);
1141         }
1142
1143         /* Take into account the line density of the boundary */
1144         chancePerAtom /= cellSize;
1145
1146         chance += att.n * chancePerAtom;
1147     }
1148
1149     return chance;
1150 }
1151
1152 /* Struct for storing constraint properties of atoms */
1153 struct AtomConstraintProps
1154 {
1155     void addConstraint(real length)
1156     {
1157         numConstraints += 1;
1158         sumLengths += length;
1159     }
1160
1161     int  numConstraints = 0; /* The number of constraints of an atom */
1162     real sumLengths     = 0; /* The sum of constraint length over the constraints */
1163 };
1164
1165 /* Constructs and returns a list of constraint properties per atom */
1166 static std::vector<AtomConstraintProps> getAtomConstraintProps(const gmx_moltype_t&  moltype,
1167                                                                const gmx_ffparams_t& ffparams)
1168 {
1169     const t_atoms&                   atoms = moltype.atoms;
1170     std::vector<AtomConstraintProps> props(atoms.nr);
1171
1172     for (const auto& ilist : extractILists(moltype.ilist, IF_CONSTRAINT))
1173     {
1174         // Settles are handled separately
1175         if (ilist.functionType == F_SETTLE)
1176         {
1177             continue;
1178         }
1179
1180         for (size_t i = 0; i < ilist.iatoms.size(); i += ilistStride(ilist))
1181         {
1182             int  type   = ilist.iatoms[i];
1183             int  a1     = ilist.iatoms[i + 1];
1184             int  a2     = ilist.iatoms[i + 2];
1185             real length = ffparams.iparams[type].constr.dA;
1186             props[a1].addConstraint(length);
1187             props[a2].addConstraint(length);
1188         }
1189     }
1190
1191     return props;
1192 }
1193
1194 /* Return the chance of at least one update group in a molecule crossing a cell of size cellSize */
1195 static real chanceOfUpdateGroupCrossingCell(const gmx_moltype_t&          moltype,
1196                                             const gmx_ffparams_t&         ffparams,
1197                                             const gmx::RangePartitioning& updateGrouping,
1198                                             real                          kT_fac,
1199                                             real                          cellSize)
1200 {
1201     const t_atoms& atoms = moltype.atoms;
1202     GMX_ASSERT(updateGrouping.fullRange().end() == atoms.nr,
1203                "The update groups should match the molecule type");
1204
1205     const pot_derivatives_t boundaryInteraction = { 1 / cellSize, 0, 0 };
1206
1207     const auto atomConstraintProps = getAtomConstraintProps(moltype, ffparams);
1208
1209     real chance = 0;
1210     for (int group = 0; group < updateGrouping.numBlocks(); group++)
1211     {
1212         const auto& block = updateGrouping.block(group);
1213         /* Determine the number of atoms with constraints and the mass of the COG */
1214         int  numAtomsWithConstraints = 0;
1215         real massSum                 = 0;
1216         for (const int atom : block)
1217         {
1218             if (atomConstraintProps[atom].numConstraints > 0)
1219             {
1220                 numAtomsWithConstraints++;
1221             }
1222             massSum += moltype.atoms.atom[atom].m;
1223         }
1224         /* Determine the maximum possible distance between the center of mass
1225          * and the center of geometry of the update group
1226          */
1227         real maxComCogDistance = 0;
1228         if (numAtomsWithConstraints == 2)
1229         {
1230             for (const int atom : block)
1231             {
1232                 if (atomConstraintProps[atom].numConstraints > 0)
1233                 {
1234                     GMX_ASSERT(atomConstraintProps[atom].numConstraints == 1,
1235                                "Two atoms should be connected by one constraint");
1236                     maxComCogDistance = std::abs(atoms.atom[atom].m / massSum - 0.5)
1237                                         * atomConstraintProps[atom].sumLengths;
1238                     break;
1239                 }
1240             }
1241         }
1242         else if (numAtomsWithConstraints > 2)
1243         {
1244             for (const int atom : block)
1245             {
1246                 if (atomConstraintProps[atom].numConstraints == numAtomsWithConstraints - 1)
1247                 {
1248                     real comCogDistance = atomConstraintProps[atom].sumLengths / numAtomsWithConstraints;
1249                     maxComCogDistance = std::max(maxComCogDistance, comCogDistance);
1250                 }
1251             }
1252         }
1253         else if (block.size() > 1)
1254         {
1255             // All normal atoms must be connected by SETTLE
1256             for (const int atom : block)
1257             {
1258                 const auto& ilist = moltype.ilist[F_SETTLE];
1259                 GMX_RELEASE_ASSERT(!ilist.empty(),
1260                                    "There should be at least one settle in this moltype");
1261                 for (int i = 0; i < ilist.size(); i += 1 + NRAL(F_SETTLE))
1262                 {
1263                     if (atom == ilist.iatoms[i + 1])
1264                     {
1265                         const t_iparams& iparams = ffparams.iparams[ilist.iatoms[i]];
1266                         real             dOH     = iparams.settle.doh;
1267                         real             dHH     = iparams.settle.dhh;
1268                         real             dOMidH  = std::sqrt(dOH * dOH - 0.25_real * dHH * dHH);
1269                         maxComCogDistance =
1270                                 std::abs(atoms.atom[atom].m / massSum - 1.0_real / 3.0_real) * dOMidH;
1271                     }
1272                 }
1273             }
1274         }
1275         real s2_3d = kT_fac / massSum;
1276         chance += energyDriftAtomPair(false, false, s2_3d, 0, 0, cellSize - 2 * maxComCogDistance,
1277                                       &boundaryInteraction);
1278     }
1279
1280     return chance;
1281 }
1282
1283 /* Return the chance of at least one update group in the system crossing a cell of size cellSize */
1284 static real chanceOfUpdateGroupCrossingCell(const gmx_mtop_t&      mtop,
1285                                             PartitioningPerMoltype updateGrouping,
1286                                             real                   kT_fac,
1287                                             real                   cellSize)
1288 {
1289     GMX_RELEASE_ASSERT(updateGrouping.size() == mtop.moltype.size(),
1290                        "The update groups should match the topology");
1291
1292     real chance = 0;
1293     for (const gmx_molblock_t& molblock : mtop.molblock)
1294     {
1295         const gmx_moltype_t& moltype = mtop.moltype[molblock.type];
1296         chance += molblock.nmol
1297                   * chanceOfUpdateGroupCrossingCell(moltype, mtop.ffparams,
1298                                                     updateGrouping[molblock.type], kT_fac, cellSize);
1299     }
1300
1301     return chance;
1302 }
1303
1304 real minCellSizeForAtomDisplacement(const gmx_mtop_t&      mtop,
1305                                     const t_inputrec&      ir,
1306                                     PartitioningPerMoltype updateGrouping,
1307                                     real                   chanceRequested)
1308 {
1309     if (!EI_DYNAMICS(ir.eI) || (EI_MD(ir.eI) && ir.etc == etcNO))
1310     {
1311         return minCellSizeFromPairlistBuffer(ir);
1312     }
1313
1314     /* We use the maximum temperature with multiple T-coupl groups.
1315      * We could use a per particle temperature, but since particles
1316      * interact, this might underestimate the displacements.
1317      */
1318     const real temperature = maxReferenceTemperature(ir);
1319
1320     const bool setMassesToOne = (ir.eI == eiBD && ir.bd_fric > 0);
1321
1322     const auto atomtypes = getVerletBufferAtomtypes(mtop, setMassesToOne);
1323
1324     const real kT_fac = displacementVariance(ir, temperature, ir.nstlist * ir.delta_t);
1325
1326     /* Resolution of the cell size */
1327     real resolution = 0.001;
1328
1329     /* Search using bisection, avoid 0 and start at 1 */
1330     int ib0 = 0;
1331     /* The chance will be neglible at 10 times the max sigma */
1332     int  ib1      = int(10 * maxSigma(kT_fac, atomtypes) / resolution) + 1;
1333     real cellSize = 0;
1334     while (ib1 - ib0 > 1)
1335     {
1336         int ib   = (ib0 + ib1) / 2;
1337         cellSize = ib * resolution;
1338
1339         real chance;
1340         if (updateGrouping.empty())
1341         {
1342             chance = chanceOfAtomCrossingCell(atomtypes, kT_fac, cellSize);
1343         }
1344         else
1345         {
1346             chance = chanceOfUpdateGroupCrossingCell(mtop, updateGrouping, kT_fac, cellSize);
1347         }
1348
1349         /* Note: chance is for every nstlist steps */
1350         if (chance > chanceRequested * ir.nstlist)
1351         {
1352             ib0 = ib;
1353         }
1354         else
1355         {
1356             ib1 = ib;
1357         }
1358     }
1359
1360     return cellSize;
1361 }