Merge branch release-2018
[alexxy/gromacs.git] / src / gromacs / mdlib / calc_verletbuf.cpp
index 47fc1c9580c15a04b2dc6db6f1c82f5afdd8a5c4..f0a48401fc798d746bae2b7a19b8916f42663746 100644 (file)
@@ -204,8 +204,24 @@ static void add_at(verletbuf_atomtype_t **att_p, int *natt_p,
     }
 }
 
+/* Returns the mass of atom atomIndex or 1 when setMassesToOne=true */
+static real getMass(const t_atoms &atoms,
+                    int            atomIndex,
+                    bool           setMassesToOne)
+{
+    if (!setMassesToOne)
+    {
+        return atoms.atom[atomIndex].m;
+    }
+    else
+    {
+        return 1;
+    }
+}
+
 static void get_vsite_masses(const gmx_moltype_t  *moltype,
                              const gmx_ffparams_t *ffparams,
+                             bool                  setMassesToOne,
                              real                 *vsite_m,
                              int                  *n_nonlin_vsite)
 {
@@ -225,7 +241,7 @@ static void get_vsite_masses(const gmx_moltype_t  *moltype,
             {
                 const t_iparams *ip;
                 real             inv_mass, coeff, m_aj;
-                int              a1, aj;
+                int              a1;
 
                 ip = &ffparams->iparams[il->iatoms[i]];
 
@@ -243,17 +259,18 @@ static void get_vsite_masses(const gmx_moltype_t  *moltype,
                     assert(maxj <= 5);
                     for (j = 1; j < maxj; j++)
                     {
-                        cam[j] = moltype->atoms.atom[il->iatoms[i+1+j]].m;
+                        int aj = il->iatoms[i + 1 + j];
+                        cam[j] = getMass(moltype->atoms, aj, setMassesToOne);
                         if (cam[j] == 0)
                         {
-                            cam[j] = vsite_m[il->iatoms[i+1+j]];
+                            cam[j] = vsite_m[aj];
                         }
                         if (cam[j] == 0)
                         {
                             gmx_fatal(FARGS, "In molecule type '%s' %s construction involves atom %d, which is a virtual site of equal or high complexity. This is not supported.",
                                       *moltype->name,
                                       interaction_function[ft].longname,
-                                      il->iatoms[i+1+j]+1);
+                                      aj + 1);
                         }
                     }
 
@@ -300,8 +317,8 @@ static void get_vsite_masses(const gmx_moltype_t  *moltype,
                     inv_mass = 0;
                     for (j = 0; j < 3*ffparams->iparams[il->iatoms[i]].vsiten.n; j += 3)
                     {
-                        aj    = il->iatoms[i+j+2];
-                        coeff = ffparams->iparams[il->iatoms[i+j]].vsiten.a;
+                        int aj = il->iatoms[i + j + 2];
+                        coeff  = ffparams->iparams[il->iatoms[i+j]].vsiten.a;
                         if (moltype->atoms.atom[aj].ptype == eptVSite)
                         {
                             m_aj = vsite_m[aj];
@@ -331,6 +348,7 @@ static void get_vsite_masses(const gmx_moltype_t  *moltype,
 }
 
 static void get_verlet_buffer_atomtypes(const gmx_mtop_t      *mtop,
+                                        bool                   setMassesToOne,
                                         verletbuf_atomtype_t **att_p,
                                         int                   *natt_p,
                                         int                   *n_nonlin_vsite)
@@ -374,14 +392,16 @@ static void get_verlet_buffer_atomtypes(const gmx_mtop_t      *mtop,
                 ip         = &mtop->ffparams.iparams[il->iatoms[i]];
                 a1         = il->iatoms[i+1];
                 a2         = il->iatoms[i+2];
-                if (atoms->atom[a2].m > prop[a1].con_mass)
+                real mass1 = getMass(*atoms, a1, setMassesToOne);
+                real mass2 = getMass(*atoms, a2, setMassesToOne);
+                if (mass2 > prop[a1].con_mass)
                 {
-                    prop[a1].con_mass = atoms->atom[a2].m;
+                    prop[a1].con_mass = mass2;
                     prop[a1].con_len  = ip->constr.dA;
                 }
-                if (atoms->atom[a1].m > prop[a2].con_mass)
+                if (mass1 > prop[a2].con_mass)
                 {
-                    prop[a2].con_mass = atoms->atom[a1].m;
+                    prop[a2].con_mass = mass1;
                     prop[a2].con_len  = ip->constr.dA;
                 }
             }
@@ -399,18 +419,19 @@ static void get_verlet_buffer_atomtypes(const gmx_mtop_t      *mtop,
              * If this is not the case, we overestimate the displacement,
              * which leads to a larger buffer (ok since this is an exotic case).
              */
-            prop[a1].con_mass = atoms->atom[a2].m;
+            prop[a1].con_mass = getMass(*atoms, a2, setMassesToOne);
             prop[a1].con_len  = ip->settle.doh;
 
-            prop[a2].con_mass = atoms->atom[a1].m;
+            prop[a2].con_mass = getMass(*atoms, a1, setMassesToOne);
             prop[a2].con_len  = ip->settle.doh;
 
-            prop[a3].con_mass = atoms->atom[a1].m;
+            prop[a3].con_mass = getMass(*atoms, a1, setMassesToOne);
             prop[a3].con_len  = ip->settle.doh;
         }
 
         get_vsite_masses(&moltype,
                          &mtop->ffparams,
+                         setMassesToOne,
                          vsite_m,
                          &n_nonlin_vsite_mol);
         if (n_nonlin_vsite != nullptr)
@@ -426,7 +447,7 @@ static void get_verlet_buffer_atomtypes(const gmx_mtop_t      *mtop,
             }
             else
             {
-                prop[a].mass = atoms->atom[a].m;
+                prop[a].mass = getMass(*atoms, a, setMassesToOne);
             }
             prop[a].type     = atoms->atom[a].type;
             prop[a].q        = atoms->atom[a].q;
@@ -567,8 +588,8 @@ static void approx_2dof(real s2, real x, real *shift, real *scale)
 
 // Returns an (over)estimate of the energy drift for a single atom pair,
 // given the kinetic properties, displacement variances and list buffer.
-static real energyDriftAtomPair(const atom_nonbonded_kinetic_prop_t *prop_i,
-                                const atom_nonbonded_kinetic_prop_t *prop_j,
+static real energyDriftAtomPair(bool isConstrained_i,
+                                bool isConstrained_j,
                                 real s2, real s2i_2d, real s2j_2d,
                                 real r_buffer,
                                 const pot_derivatives_t *der)
@@ -602,7 +623,7 @@ static real energyDriftAtomPair(const atom_nonbonded_kinetic_prop_t *prop_i,
     else
     {
         /* For constraints: adapt r and scaling for the Gaussian */
-        if (prop_i->bConstr)
+        if (isConstrained_i)
         {
             real sh, sc;
 
@@ -610,7 +631,7 @@ static real energyDriftAtomPair(const atom_nonbonded_kinetic_prop_t *prop_i,
             rsh    += sh;
             sc_fac *= sc;
         }
-        if (prop_j->bConstr)
+        if (isConstrained_j)
         {
             real sh, sc;
 
@@ -690,7 +711,7 @@ static real energyDrift(const verletbuf_atomtype_t *att, int natt,
             lj.d2  = c6*ljDisp->d2  + c12*ljRep->d2;
             lj.md3 = c6*ljDisp->md3 + c12*ljRep->md3;
 
-            real pot_lj = energyDriftAtomPair(prop_i, prop_j,
+            real pot_lj = energyDriftAtomPair(prop_i->bConstr, prop_j->bConstr,
                                               s2, s2i_2d, s2j_2d,
                                               rlist - rlj,
                                               &lj);
@@ -701,7 +722,7 @@ static real energyDrift(const verletbuf_atomtype_t *att, int natt,
             elec_qq.d2  = elec->d2 *prop_i->q*prop_j->q;
             elec_qq.md3 = 0;
 
-            real pot_q  = energyDriftAtomPair(prop_i, prop_j,
+            real pot_q  = energyDriftAtomPair(prop_i->bConstr, prop_j->bConstr,
                                               s2, s2i_2d, s2j_2d,
                                               rlist - rcoulomb,
                                               &elec_qq);
@@ -802,6 +823,89 @@ static real md3_force_switch(real p, real rswitch, real rc)
     return md3_pot + md3_sw;
 }
 
+/* Returns the maximum reference temperature over all coupled groups */
+static real maxReferenceTemperature(const t_inputrec &ir)
+{
+    if (EI_MD(ir.eI) && ir.etc == etcNO)
+    {
+        /* This case should be handled outside calc_verlet_buffer_size */
+        gmx_incons("calc_verlet_buffer_size called with an NVE ensemble and reference_temperature < 0");
+    }
+
+    real maxTemperature = 0;
+    for (int i = 0; i < ir.opts.ngtc; i++)
+    {
+        if (ir.opts.tau_t[i] >= 0)
+        {
+            maxTemperature = std::max(maxTemperature, ir.opts.ref_t[i]);
+        }
+    }
+
+    return maxTemperature;
+}
+
+/* Returns the variance of the atomic displacement over timePeriod.
+ *
+ * Note: When not using BD with a non-mass dependendent friction coefficient,
+ *       the return value still needs to be divided by the particle mass.
+ */
+static real displacementVariance(const t_inputrec &ir,
+                                 real              temperature,
+                                 real              timePeriod)
+{
+    real kT_fac;
+
+    if (ir.eI == eiBD)
+    {
+        /* Get the displacement distribution from the random component only.
+         * With accurate integration the systematic (force) displacement
+         * should be negligible (unless nstlist is extremely large, which
+         * you wouldn't do anyhow).
+         */
+        kT_fac = 2*BOLTZ*temperature*timePeriod;
+        if (ir.bd_fric > 0)
+        {
+            /* This is directly sigma^2 of the displacement */
+            kT_fac /= ir.bd_fric;
+        }
+        else
+        {
+            /* Per group tau_t is not implemented yet, use the maximum */
+            real tau_t = ir.opts.tau_t[0];
+            for (int i = 1; i < ir.opts.ngtc; i++)
+            {
+                tau_t = std::max(tau_t, ir.opts.tau_t[i]);
+            }
+
+            kT_fac *= tau_t;
+            /* This kT_fac needs to be divided by the mass to get sigma^2 */
+        }
+    }
+    else
+    {
+        kT_fac = BOLTZ*temperature*gmx::square(timePeriod);
+    }
+
+    return kT_fac;
+}
+
+/* Returns the largest sigma of the Gaussian displacement over all particle
+ * types. This ignores constraints, so is an overestimate.
+ */
+static real maxSigma(real                        kT_fac,
+                     int                         natt,
+                     const verletbuf_atomtype_t *att)
+{
+    assert(att);
+    real smallestMass = att[0].prop.mass;
+    for (int i = 1; i < natt; i++)
+    {
+        smallestMass = std::min(smallestMass, att[i].prop.mass);
+    }
+
+    return 2*std::sqrt(kT_fac/smallestMass);
+}
+
 void calc_verlet_buffer_size(const gmx_mtop_t *mtop, real boxvol,
                              const t_inputrec *ir,
                              int               nstlist,
@@ -818,9 +922,8 @@ void calc_verlet_buffer_size(const gmx_mtop_t *mtop, real boxvol,
     real                  nb_clust_frac_pairs_not_in_list_at_cutoff;
 
     verletbuf_atomtype_t *att  = nullptr;
-    int                   natt = -1, i;
+    int                   natt = -1;
     real                  elfac;
-    real                  kT_fac, mass_min;
     int                   ib0, ib1, ib;
     real                  rb, rl;
     real                  drift;
@@ -836,25 +939,11 @@ void calc_verlet_buffer_size(const gmx_mtop_t *mtop, real boxvol,
 
     if (reference_temperature < 0)
     {
-        if (EI_MD(ir->eI) && ir->etc == etcNO)
-        {
-            /* This case should be handled outside calc_verlet_buffer_size */
-            gmx_incons("calc_verlet_buffer_size called with an NVE ensemble and reference_temperature < 0");
-        }
-
         /* We use the maximum temperature with multiple T-coupl groups.
          * We could use a per particle temperature, but since particles
          * interact, this might underestimate the buffer size.
          */
-        reference_temperature = 0;
-        for (i = 0; i < ir->opts.ngtc; i++)
-        {
-            if (ir->opts.tau_t[i] >= 0)
-            {
-                reference_temperature = std::max(reference_temperature,
-                                                 ir->opts.ref_t[i]);
-            }
-        }
+        reference_temperature = maxReferenceTemperature(*ir);
     }
 
     /* Resolution of the buffer size */
@@ -893,7 +982,11 @@ void calc_verlet_buffer_size(const gmx_mtop_t *mtop, real boxvol,
     /* Worst case assumption: HCP packing of particles gives largest distance */
     particle_distance = std::cbrt(boxvol*std::sqrt(2)/mtop->natoms);
 
-    get_verlet_buffer_atomtypes(mtop, &att, &natt, n_nonlin_vsite);
+    /* TODO: Obtain masses through (future) integrator functionality
+     *       to avoid scattering the code with (or forgetting) checks.
+     */
+    const bool setMassesToOne = (ir->eI == eiBD && ir->bd_fric > 0);
+    get_verlet_buffer_atomtypes(mtop, setMassesToOne, &att, &natt, n_nonlin_vsite);
     assert(att != nullptr && natt >= 0);
 
     if (debug)
@@ -1013,52 +1106,8 @@ void calc_verlet_buffer_size(const gmx_mtop_t *mtop, real boxvol,
      * For inertial dynamics (not Brownian dynamics) the mass factor
      * is not included in kT_fac, it is added later.
      */
-    if (ir->eI == eiBD)
-    {
-        /* Get the displacement distribution from the random component only.
-         * With accurate integration the systematic (force) displacement
-         * should be negligible (unless nstlist is extremely large, which
-         * you wouldn't do anyhow).
-         */
-        kT_fac = 2*BOLTZ*reference_temperature*list_lifetime*ir->delta_t;
-        if (ir->bd_fric > 0)
-        {
-            /* This is directly sigma^2 of the displacement */
-            kT_fac /= ir->bd_fric;
-
-            /* Set the masses to 1 as kT_fac is the full sigma^2,
-             * but we divide by m in ener_drift().
-             */
-            for (i = 0; i < natt; i++)
-            {
-                att[i].prop.mass = 1;
-            }
-        }
-        else
-        {
-            real tau_t;
-
-            /* Per group tau_t is not implemented yet, use the maximum */
-            tau_t = ir->opts.tau_t[0];
-            for (i = 1; i < ir->opts.ngtc; i++)
-            {
-                tau_t = std::max(tau_t, ir->opts.tau_t[i]);
-            }
-
-            kT_fac *= tau_t;
-            /* This kT_fac needs to be divided by the mass to get sigma^2 */
-        }
-    }
-    else
-    {
-        kT_fac = BOLTZ*reference_temperature*gmx::square(list_lifetime*ir->delta_t);
-    }
-
-    mass_min = att[0].prop.mass;
-    for (i = 1; i < natt; i++)
-    {
-        mass_min = std::min(mass_min, att[i].prop.mass);
-    }
+    const real kT_fac = displacementVariance(*ir, reference_temperature,
+                                             list_lifetime*ir->delta_t);
 
     if (debug)
     {
@@ -1067,13 +1116,12 @@ void calc_verlet_buffer_size(const gmx_mtop_t *mtop, real boxvol,
         fprintf(debug, "LJ rep.  -V' %9.2e V'' %9.2e -V''' %9.2e\n", ljRep.md1, ljRep.d2, ljRep.md3);
         fprintf(debug, "Electro. -V' %9.2e V'' %9.2e\n", elec.md1, elec.d2);
         fprintf(debug, "sqrt(kT_fac) %f\n", std::sqrt(kT_fac));
-        fprintf(debug, "mass_min %f\n", mass_min);
     }
 
     /* Search using bisection */
     ib0 = -1;
     /* The drift will be neglible at 5 times the max sigma */
-    ib1 = static_cast<int>(5*2*std::sqrt(kT_fac/mass_min)/resolution) + 1;
+    ib1 = static_cast<int>(5*maxSigma(kT_fac, natt, att)/resolution) + 1;
     while (ib1 - ib0 > 1)
     {
         ib = (ib0 + ib1)/2;
@@ -1126,3 +1174,117 @@ void calc_verlet_buffer_size(const gmx_mtop_t *mtop, real boxvol,
 
     *rlist = std::max(ir->rvdw, ir->rcoulomb) + ib1*resolution;
 }
+
+/* Returns the pairlist buffer size for use as a minimum buffer size
+ *
+ * Note that this is a rather crude estimate. It is ok for a buffer
+ * set for good energy conservation or RF electrostatics. But it is
+ * too small with PME and the buffer set with the default tolerance.
+ */
+static real minCellSizeFromPairlistBuffer(const t_inputrec &ir)
+{
+    return ir.rlist - std::max(ir.rvdw, ir.rcoulomb);
+}
+
+real minCellSizeForAtomDisplacement(const gmx_mtop_t &mtop,
+                                    const t_inputrec &ir,
+                                    real              chanceRequested)
+{
+    if (!EI_DYNAMICS(ir.eI) || (EI_MD(ir.eI) && ir.etc == etcNO))
+    {
+        return minCellSizeFromPairlistBuffer(ir);
+    }
+
+    /* We use the maximum temperature with multiple T-coupl groups.
+     * We could use a per particle temperature, but since particles
+     * interact, this might underestimate the displacements.
+     */
+    const real            temperature = maxReferenceTemperature(ir);
+
+    const bool            setMassesToOne = (ir.eI == eiBD && ir.bd_fric > 0);
+
+    verletbuf_atomtype_t *att  = nullptr;
+    int                   natt = -1;
+    get_verlet_buffer_atomtypes(&mtop, setMassesToOne, &att, &natt, nullptr);
+
+    const real kT_fac = displacementVariance(ir, temperature,
+                                             ir.nstlist*ir.delta_t);
+
+    /* Resolution of the cell size */
+    real resolution = 0.001;
+
+    /* Search using bisection, avoid 0 and start at 1 */
+    int  ib0      = 0;
+    /* The chance will be neglible at 10 times the max sigma */
+    int  ib1      = int(10*maxSigma(kT_fac, natt, att)/resolution) + 1;
+    real cellSize = 0;
+    while (ib1 - ib0 > 1)
+    {
+        int ib = (ib0 + ib1)/2;
+        cellSize = ib*resolution;
+
+        /* We assumes atom are distributed uniformly over the cell width.
+         * Once an atom has moved by more than the cellSize (as passed
+         * as the buffer argument to energyDriftAtomPair() below),
+         * the chance of crossing the boundary of the neighbor cell
+         * thus increases as 1/cellSize with the additional displacement
+         * on to of cellSize. We thus create a linear interaction with
+         * derivative = -1/cellSize. Using this in the energyDriftAtomPair
+         * function will return the chance of crossing the next boundary.
+         */
+        const pot_derivatives_t boundaryInteraction = { 1/cellSize, 0, 0 };
+
+        real                    chance = 0;
+        for (int i = 0; i < natt; i++)
+        {
+            const atom_nonbonded_kinetic_prop_t &propAtom = att[i].prop;
+            real s2_2d;
+            real s2_3d;
+            get_atom_sigma2(kT_fac, &propAtom, &s2_2d, &s2_3d);
+
+            real chancePerAtom = energyDriftAtomPair(propAtom.bConstr, false,
+                                                     s2_2d + s2_3d, s2_2d, 0,
+                                                     cellSize,
+                                                     &boundaryInteraction);
+
+            if (propAtom.bConstr)
+            {
+                /* energyDriftAtomPair() uses an unlimited Gaussian displacement
+                 * distribution for constrained atoms, whereas they can
+                 * actually not move more than the COM of the two constrained
+                 * atoms plus twice the distance from the COM.
+                 * Use this maximum, limited displacement when this results in
+                 * a smaller chance (note that this is still an overestimate).
+                 */
+                real massFraction = propAtom.con_mass/(propAtom.mass + propAtom.con_mass);
+                real comDistance  = propAtom.con_len*massFraction;
+
+                real chanceWithMaxDistance =
+                    energyDriftAtomPair(false, false,
+                                        s2_3d, 0, 0,
+                                        cellSize - 2*comDistance,
+                                        &boundaryInteraction);
+                chancePerAtom = std::min(chancePerAtom, chanceWithMaxDistance);
+            }
+
+            /* Take into account the line density of the boundary */
+            chancePerAtom /= cellSize;
+
+            chance        += att[i].n*chancePerAtom;
+        }
+
+        /* Note: chance is for every nstlist steps */
+        if (chance > chanceRequested*ir.nstlist)
+        {
+            ib0 = ib;
+        }
+        else
+        {
+            ib1 = ib;
+        }
+    }
+
+    sfree(att);
+
+    return cellSize;
+}