d301bd83d9240375263af5bed02b49b3c8978407
[alexxy/gromacs.git] / src / gromacs / applied_forces / awh / read_params.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 1991-2000, University of Groningen, The Netherlands.
5  * Copyright (c) 2001-2004, The GROMACS development team.
6  * Copyright (c) 2013,2014,2015,2016,2017 by the GROMACS development team.
7  * Copyright (c) 2018,2019,2020, by the GROMACS development team, led by
8  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
9  * and including many others, as listed in the AUTHORS file in the
10  * top-level source directory and at http://www.gromacs.org.
11  *
12  * GROMACS is free software; you can redistribute it and/or
13  * modify it under the terms of the GNU Lesser General Public License
14  * as published by the Free Software Foundation; either version 2.1
15  * of the License, or (at your option) any later version.
16  *
17  * GROMACS is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20  * Lesser General Public License for more details.
21  *
22  * You should have received a copy of the GNU Lesser General Public
23  * License along with GROMACS; if not, see
24  * http://www.gnu.org/licenses, or write to the Free Software Foundation,
25  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
26  *
27  * If you want to redistribute modifications to GROMACS, please
28  * consider that scientific software is very special. Version
29  * control is crucial - bugs must be traceable. We will be happy to
30  * consider code for inclusion in the official distribution, but
31  * derived work must not be called official GROMACS. Details are found
32  * in the README & COPYING files - if they are missing, get the
33  * official version at http://www.gromacs.org.
34  *
35  * To help us fund GROMACS development, we humbly ask that you cite
36  * the research papers on the package. Check out http://www.gromacs.org.
37  */
38 #include "gmxpre.h"
39
40 #include "read_params.h"
41
42 #include "gromacs/applied_forces/awh/awh.h"
43 #include "gromacs/fileio/readinp.h"
44 #include "gromacs/fileio/warninp.h"
45 #include "gromacs/math/units.h"
46 #include "gromacs/math/utilities.h"
47 #include "gromacs/math/vec.h"
48 #include "gromacs/mdtypes/awh_params.h"
49 #include "gromacs/mdtypes/inputrec.h"
50 #include "gromacs/mdtypes/md_enums.h"
51 #include "gromacs/mdtypes/pull_params.h"
52 #include "gromacs/pbcutil/pbc.h"
53 #include "gromacs/pulling/pull.h"
54 #include "gromacs/random/seed.h"
55 #include "gromacs/topology/mtop_util.h"
56 #include "gromacs/utility/cstringutil.h"
57 #include "gromacs/utility/fatalerror.h"
58 #include "gromacs/utility/smalloc.h"
59 #include "gromacs/utility/stringutil.h"
60
61 #include "biasparams.h"
62 #include "biassharing.h"
63
64 namespace gmx
65 {
66
67 const char* eawhtarget_names[eawhtargetNR + 1] = { "constant", "cutoff", "boltzmann",
68                                                    "local-boltzmann", nullptr };
69
70 const char* eawhgrowth_names[eawhgrowthNR + 1] = { "exp-linear", "linear", nullptr };
71
72 const char* eawhpotential_names[eawhpotentialNR + 1] = { "convolved", "umbrella", nullptr };
73
74 const char* eawhcoordprovider_names[eawhcoordproviderNR + 1] = { "pull", "fep-lambda", nullptr };
75
76 namespace
77 {
78 /*! \brief
79  * Check the parameters of an AWH bias pull dimension.
80  *
81  * \param[in] prefix         Prefix for dimension parameters.
82  * \param[in,out] dimParams  AWH dimensional parameters.
83  * \param[in] pull_params    Pull parameters.
84  * \param[in,out] wi         Struct for bookeeping warnings.
85  */
86 void checkPullDimParams(const std::string&   prefix,
87                         AwhDimParams*        dimParams,
88                         const pull_params_t* pull_params,
89                         warninp_t            wi)
90 {
91     if (dimParams->coordIndex < 0)
92     {
93         gmx_fatal(FARGS,
94                   "Failed to read a valid coordinate index for %s-coord-index. "
95                   "Note that the pull coordinate indexing starts at 1.",
96                   prefix.c_str());
97     }
98     if (dimParams->coordIndex >= pull_params->ncoord)
99     {
100         gmx_fatal(FARGS,
101                   "The given AWH coordinate index (%d) is larger than the number of pull "
102                   "coordinates (%d)",
103                   dimParams->coordIndex + 1, pull_params->ncoord);
104     }
105     if (pull_params->coord[dimParams->coordIndex].rate != 0)
106     {
107         auto message = formatString(
108                 "Setting pull-coord%d-rate (%g) is incompatible with AWH biasing this coordinate",
109                 dimParams->coordIndex + 1, pull_params->coord[dimParams->coordIndex].rate);
110         warning_error(wi, message);
111     }
112
113     if (gmx_within_tol(dimParams->end - dimParams->origin, 0, GMX_REAL_EPS))
114     {
115         auto message = formatString(
116                 "The given interval length given by %s-start (%g) and %s-end (%g) is zero. "
117                 "This will result in only one point along this axis in the coordinate value grid.",
118                 prefix.c_str(), dimParams->origin, prefix.c_str(), dimParams->end);
119         warning(wi, message);
120     }
121
122     if (dimParams->forceConstant <= 0)
123     {
124         warning_error(wi, "The force AWH bias force constant should be > 0");
125     }
126
127     /* Grid params for each axis */
128     int eGeom = pull_params->coord[dimParams->coordIndex].eGeom;
129
130     /* Check that the requested interval is in allowed range */
131     if (eGeom == epullgDIST)
132     {
133         if (dimParams->origin < 0 || dimParams->end < 0)
134         {
135             gmx_fatal(FARGS,
136                       "%s-start (%g) or %s-end (%g) set to a negative value. With pull "
137                       "geometry distance coordinate values are non-negative. "
138                       "Perhaps you want to use geometry %s instead?",
139                       prefix.c_str(), dimParams->origin, prefix.c_str(), dimParams->end,
140                       EPULLGEOM(epullgDIR));
141         }
142     }
143     else if (eGeom == epullgANGLE || eGeom == epullgANGLEAXIS)
144     {
145         if (dimParams->origin < 0 || dimParams->end > 180)
146         {
147             gmx_fatal(FARGS,
148                       "%s-start (%g) and %s-end (%g) are outside of the allowed range "
149                       "0 to 180 deg for pull geometries %s and %s ",
150                       prefix.c_str(), dimParams->origin, prefix.c_str(), dimParams->end,
151                       EPULLGEOM(epullgANGLE), EPULLGEOM(epullgANGLEAXIS));
152         }
153     }
154     else if (eGeom == epullgDIHEDRAL)
155     {
156         if (dimParams->origin < -180 || dimParams->end > 180)
157         {
158             gmx_fatal(FARGS,
159                       "%s-start (%g) and %s-end (%g) are outside of the allowed range "
160                       "-180 to 180 deg for pull geometry %s. ",
161                       prefix.c_str(), dimParams->origin, prefix.c_str(), dimParams->end,
162                       EPULLGEOM(epullgDIHEDRAL));
163         }
164     }
165 }
166
167 /*! \brief
168  * Check parameters of an AWH bias in a free energy lambda state dimension.
169  *
170  * \param[in] prefix         Prefix for dimension parameters.
171  * \param[in,out] dimParams  AWH dimensional parameters.
172  * \param[in] lambdaParams   The free energy lambda related parameters.
173  * \param[in] efep           This is the type of FEP calculation (efep enumerator).
174  * \param[in,out] wi         Struct for bookeeping warnings.
175  */
176 void checkFepLambdaDimParams(const std::string&  prefix,
177                              const AwhDimParams* dimParams,
178                              const t_lambda*     lambdaParams,
179                              const int           efep,
180                              warninp_t           wi)
181 {
182     std::string opt;
183
184     if (!lambdaParams)
185     {
186         gmx_fatal(FARGS,
187                   "There must be free energy input if using AWH to steer the free energy lambda "
188                   "state.");
189     }
190
191     if (lambdaParams->lambda_neighbors != -1)
192     {
193         gmx_fatal(FARGS,
194                   "When running AWH coupled to the free energy lambda state all lambda states "
195                   "should be used as neighbors in order to get correct probabilities, i.e. "
196                   "calc-lambda-neighbors (%d) must be %d.",
197                   lambdaParams->lambda_neighbors, -1);
198     }
199
200     if (efep == efepSLOWGROWTH || lambdaParams->delta_lambda != 0)
201     {
202         gmx_fatal(FARGS,
203                   "AWH coupled to the free energy lambda state is not compatible with slow-growth "
204                   "and delta-lambda must be 0.");
205     }
206
207     if (efep == efepEXPANDED)
208     {
209         gmx_fatal(FARGS,
210                   "AWH is not treated like other expanded ensemble methods. Do not use expanded.");
211     }
212
213     if (dimParams->origin < 0)
214     {
215         opt = prefix + "-start";
216         gmx_fatal(FARGS,
217                   "When running AWH coupled to the free energy lambda state the lower lambda state "
218                   "for AWH, %s (%.0f), must be >= 0.",
219                   opt.c_str(), dimParams->origin);
220     }
221     if (dimParams->end >= lambdaParams->n_lambda)
222     {
223         opt = prefix + "-end";
224         gmx_fatal(FARGS,
225                   "When running AWH coupled to the free energy lambda state the upper lambda state "
226                   "for AWH, %s (%.0f), must be < n_lambda (%d).",
227                   opt.c_str(), dimParams->origin, lambdaParams->n_lambda);
228     }
229     if (gmx_within_tol(dimParams->end - dimParams->origin, 0, GMX_REAL_EPS))
230     {
231         auto message = formatString(
232                 "The given interval length given by %s-start (%g) and %s-end (%g) is zero. "
233                 "This will result in only one lambda point along this free energy lambda state "
234                 "axis in the coordinate value grid.",
235                 prefix.c_str(), dimParams->origin, prefix.c_str(), dimParams->end);
236         warning(wi, message);
237     }
238
239     if (dimParams->forceConstant != 0)
240     {
241         warning_error(
242                 wi,
243                 "The force AWH bias force constant is not used with free energy lambda state as "
244                 "coordinate provider.");
245     }
246 }
247
248 /*! \brief
249  * Check that AWH FEP is not combined with incompatible decoupling.
250  *
251  * \param[in] mtop      System topology.
252  * \param[in,out] wi    Struct for bookeeping warnings.
253  */
254 void checkFepLambdaDimDecouplingConsistency(const gmx_mtop_t& mtop, warninp_t wi)
255 {
256     if (haveFepPerturbedMasses(mtop))
257     {
258         warning(wi,
259                 "Masses may not be perturbed when using the free energy lambda state as AWH "
260                 "coordinate provider. If you are using fep-lambdas to specify lambda states "
261                 "make sure that you also specify mass-lambdas without perturbation.");
262     }
263     if (havePerturbedConstraints(mtop))
264     {
265         warning(wi,
266                 "Constraints may not be perturbed when using the free energy lambda state as AWH "
267                 "coordinate provider. If you are using fep-lambdas to specify lambda states "
268                 "make sure that you also specify mass-lambdas without perturbation.");
269     }
270 }
271
272 /*! \brief
273  * Read parameters of an AWH bias dimension.
274  *
275  * \param[in,out] inp        Input file entries.
276  * \param[in] prefix         Prefix for dimension parameters.
277  * \param[in,out] dimParams  AWH dimensional parameters.
278  * \param[in,out] wi         Struct for bookeeping warnings.
279  * \param[in] bComment       True if comments should be printed.
280  */
281 void readDimParams(std::vector<t_inpfile>* inp,
282                    const std::string&      prefix,
283                    AwhDimParams*           dimParams,
284                    warninp_t               wi,
285                    bool                    bComment)
286 {
287     std::string opt;
288     if (bComment)
289     {
290         printStringNoNewline(
291                 inp,
292                 "The provider of the reaction coordinate, "
293                 "currently only 'pull' and 'fep-lambda' (free energy lambda state) is supported");
294     }
295     opt                       = prefix + "-coord-provider";
296     dimParams->eCoordProvider = get_eeenum(inp, opt, eawhcoordprovider_names, wi);
297
298     if (bComment)
299     {
300         printStringNoNewline(inp, "The coordinate index for this dimension");
301     }
302     opt = prefix + "-coord-index";
303     int coordIndexInput;
304     coordIndexInput = get_eint(inp, opt, 1, wi);
305
306     /* The pull coordinate indices start at 1 in the input file, at 0 internally */
307     dimParams->coordIndex = coordIndexInput - 1;
308
309     if (bComment)
310     {
311         printStringNoNewline(inp, "Start and end values for each coordinate dimension");
312     }
313     opt               = prefix + "-start";
314     dimParams->origin = get_ereal(inp, opt, 0., wi);
315     opt               = prefix + "-end";
316     dimParams->end    = get_ereal(inp, opt, 0., wi);
317
318     if (bComment)
319     {
320         printStringNoNewline(
321                 inp, "The force constant for this coordinate (kJ/mol/nm^2 or kJ/mol/rad^2)");
322     }
323     opt                      = prefix + "-force-constant";
324     dimParams->forceConstant = get_ereal(inp, opt, 0, wi);
325
326     if (bComment)
327     {
328         printStringNoNewline(inp, "Estimated diffusion constant (nm^2/ps, rad^2/ps or ps^-1)");
329     }
330     opt                  = prefix + "-diffusion";
331     dimParams->diffusion = get_ereal(inp, opt, 0, wi);
332
333     if (bComment)
334     {
335         printStringNoNewline(inp,
336                              "Diameter that needs to be sampled around a point before it is "
337                              "considered covered. In FEP dimensions the cover diameter is "
338                              "specified in lambda states.");
339     }
340     opt                      = prefix + "-cover-diameter";
341     dimParams->coverDiameter = get_ereal(inp, opt, 0, wi);
342 }
343
344 /*! \brief
345  * Check the parameters of an AWH bias dimension.
346  *
347  * \param[in] prefix         Prefix for dimension parameters.
348  * \param[in,out] dimParams  AWH dimensional parameters.
349  * \param[in] ir             Input parameter struct.
350  * \param[in,out] wi         Struct for bookeeping warnings.
351  */
352 void checkDimParams(const std::string& prefix, AwhDimParams* dimParams, const t_inputrec* ir, warninp_t wi)
353 {
354     if (dimParams->eCoordProvider == eawhcoordproviderPULL)
355     {
356         if (!ir->bPull)
357         {
358             gmx_fatal(FARGS,
359                       "AWH biasing along a pull dimension is only compatible with COM pulling "
360                       "turned on");
361         }
362         checkPullDimParams(prefix, dimParams, ir->pull, wi);
363     }
364     else if (dimParams->eCoordProvider == eawhcoordproviderFREE_ENERGY_LAMBDA)
365     {
366         if (ir->efep == efepNO)
367         {
368             gmx_fatal(FARGS,
369                       "AWH biasing along a free energy lambda state dimension is only compatible "
370                       "with free energy turned on");
371         }
372         checkFepLambdaDimParams(prefix, dimParams, ir->fepvals, ir->efep, wi);
373     }
374     else
375     {
376         gmx_fatal(FARGS,
377                   "AWH biasing can only be  applied to pull and free energy lambda state "
378                   "coordinates");
379     }
380 }
381
382 /*! \brief
383  * Check consistency of input at the AWH bias level.
384  *
385  * \param[in]     awhBiasParams  AWH bias parameters.
386  * \param[in,out] wi             Struct for bookkeeping warnings.
387  */
388 void checkInputConsistencyAwhBias(const AwhBiasParams& awhBiasParams, warninp_t wi)
389 {
390     /* Covering diameter and sharing warning. */
391     for (int d = 0; d < awhBiasParams.ndim; d++)
392     {
393         double coverDiameter = awhBiasParams.dimParams[d].coverDiameter;
394         if (awhBiasParams.shareGroup <= 0 && coverDiameter > 0)
395         {
396             warning(wi,
397                     "The covering diameter is only relevant to set for bias sharing simulations.");
398         }
399     }
400 }
401
402 /*! \brief
403  * Read parameters of an AWH bias.
404  *
405  * \param[in,out] inp            Input file entries.
406  * \param[in,out] awhBiasParams  AWH dimensional parameters.
407  * \param[in]     prefix         Prefix for bias parameters.
408  * \param[in,out] wi             Struct for bookeeping warnings.
409  * \param[in]     bComment       True if comments should be printed.
410  */
411 void readBiasParams(std::vector<t_inpfile>* inp,
412                     AwhBiasParams*          awhBiasParams,
413                     const std::string&      prefix,
414                     warninp_t               wi,
415                     bool                    bComment)
416 {
417     if (bComment)
418     {
419         printStringNoNewline(inp, "Estimated initial PMF error (kJ/mol)");
420     }
421
422     std::string opt             = prefix + "-error-init";
423     awhBiasParams->errorInitial = get_ereal(inp, opt, 10, wi);
424
425     if (bComment)
426     {
427         printStringNoNewline(inp,
428                              "Growth rate of the reference histogram determining the bias update "
429                              "size: exp-linear or linear");
430     }
431     opt                    = prefix + "-growth";
432     awhBiasParams->eGrowth = get_eeenum(inp, opt, eawhgrowth_names, wi);
433
434     if (bComment)
435     {
436         printStringNoNewline(inp,
437                              "Start the simulation by equilibrating histogram towards the target "
438                              "distribution: no or yes");
439     }
440     opt                                 = prefix + "-equilibrate-histogram";
441     awhBiasParams->equilibrateHistogram = (get_eeenum(inp, opt, yesno_names, wi) != 0);
442
443     if (bComment)
444     {
445         printStringNoNewline(
446                 inp, "Target distribution type: constant, cutoff, boltzmann or local-boltzmann");
447     }
448     opt                    = prefix + "-target";
449     awhBiasParams->eTarget = get_eeenum(inp, opt, eawhtarget_names, wi);
450
451     if (bComment)
452     {
453         printStringNoNewline(inp,
454                              "Boltzmann beta scaling factor for target distribution types "
455                              "'boltzmann' and 'boltzmann-local'");
456     }
457     opt                              = prefix + "-target-beta-scaling";
458     awhBiasParams->targetBetaScaling = get_ereal(inp, opt, 0, wi);
459
460     if (bComment)
461     {
462         printStringNoNewline(inp, "Free energy cutoff value for target distribution type 'cutoff'");
463     }
464     opt                         = prefix + "-target-cutoff";
465     awhBiasParams->targetCutoff = get_ereal(inp, opt, 0, wi);
466
467     if (bComment)
468     {
469         printStringNoNewline(inp, "Initialize PMF and target with user data: no or yes");
470     }
471     opt                      = prefix + "-user-data";
472     awhBiasParams->bUserData = get_eeenum(inp, opt, yesno_names, wi);
473
474     if (bComment)
475     {
476         printStringNoNewline(inp, "Group index to share the bias with, 0 means not shared");
477     }
478     opt                       = prefix + "-share-group";
479     awhBiasParams->shareGroup = get_eint(inp, opt, 0, wi);
480
481     if (bComment)
482     {
483         printStringNoNewline(inp, "Dimensionality of the coordinate");
484     }
485     opt                 = prefix + "-ndim";
486     awhBiasParams->ndim = get_eint(inp, opt, 0, wi);
487
488     /* Check this before starting to read the AWH dimension parameters. */
489     if (awhBiasParams->ndim <= 0 || awhBiasParams->ndim > c_biasMaxNumDim)
490     {
491         gmx_fatal(FARGS, "%s (%d) needs to be > 0 and at most %d\n", opt.c_str(),
492                   awhBiasParams->ndim, c_biasMaxNumDim);
493     }
494     snew(awhBiasParams->dimParams, awhBiasParams->ndim);
495     for (int d = 0; d < awhBiasParams->ndim; d++)
496     {
497         bComment              = bComment && d == 0;
498         std::string prefixdim = prefix + formatString("-dim%d", d + 1);
499         readDimParams(inp, prefixdim, &awhBiasParams->dimParams[d], wi, bComment);
500     }
501 }
502
503 /*! \brief
504  * Check the parameters of an AWH bias.
505  *
506  * \param[in]     awhBiasParams  AWH dimensional parameters.
507  * \param[in]     prefix         Prefix for bias parameters.
508  * \param[in]     ir             Input parameter struct.
509  * \param[in,out] wi             Struct for bookeeping warnings.
510  */
511 void checkBiasParams(const AwhBiasParams* awhBiasParams, const std::string& prefix, const t_inputrec* ir, warninp_t wi)
512 {
513     std::string opt = prefix + "-error-init";
514     if (awhBiasParams->errorInitial <= 0)
515     {
516         gmx_fatal(FARGS, "%s needs to be > 0.", opt.c_str());
517     }
518
519     opt = prefix + "-equilibrate-histogram";
520     if (awhBiasParams->equilibrateHistogram && awhBiasParams->eGrowth != eawhgrowthEXP_LINEAR)
521     {
522         auto message =
523                 formatString("Option %s will only have an effect for histogram growth type '%s'.",
524                              opt.c_str(), EAWHGROWTH(eawhgrowthEXP_LINEAR));
525         warning(wi, message);
526     }
527
528     if ((awhBiasParams->eTarget == eawhtargetLOCALBOLTZMANN)
529         && (awhBiasParams->eGrowth == eawhgrowthEXP_LINEAR))
530     {
531         auto message = formatString(
532                 "Target type '%s' combined with histogram growth type '%s' is not "
533                 "expected to give stable bias updates. You probably want to use growth type "
534                 "'%s' instead.",
535                 EAWHTARGET(eawhtargetLOCALBOLTZMANN), EAWHGROWTH(eawhgrowthEXP_LINEAR),
536                 EAWHGROWTH(eawhgrowthLINEAR));
537         warning(wi, message);
538     }
539
540     opt = prefix + "-target-beta-scaling";
541     switch (awhBiasParams->eTarget)
542     {
543         case eawhtargetBOLTZMANN:
544         case eawhtargetLOCALBOLTZMANN:
545             if (awhBiasParams->targetBetaScaling < 0 || awhBiasParams->targetBetaScaling > 1)
546             {
547                 gmx_fatal(FARGS, "%s = %g is not useful for target type %s.", opt.c_str(),
548                           awhBiasParams->targetBetaScaling, EAWHTARGET(awhBiasParams->eTarget));
549             }
550             break;
551         default:
552             if (awhBiasParams->targetBetaScaling != 0)
553             {
554                 gmx_fatal(
555                         FARGS,
556                         "Value for %s (%g) set explicitly but will not be used for target type %s.",
557                         opt.c_str(), awhBiasParams->targetBetaScaling,
558                         EAWHTARGET(awhBiasParams->eTarget));
559             }
560             break;
561     }
562
563     opt = prefix + "-target-cutoff";
564     switch (awhBiasParams->eTarget)
565     {
566         case eawhtargetCUTOFF:
567             if (awhBiasParams->targetCutoff <= 0)
568             {
569                 gmx_fatal(FARGS, "%s = %g is not useful for target type %s.", opt.c_str(),
570                           awhBiasParams->targetCutoff, EAWHTARGET(awhBiasParams->eTarget));
571             }
572             break;
573         default:
574             if (awhBiasParams->targetCutoff != 0)
575             {
576                 gmx_fatal(
577                         FARGS,
578                         "Value for %s (%g) set explicitly but will not be used for target type %s.",
579                         opt.c_str(), awhBiasParams->targetCutoff, EAWHTARGET(awhBiasParams->eTarget));
580             }
581             break;
582     }
583
584     opt = prefix + "-share-group";
585     if (awhBiasParams->shareGroup < 0)
586     {
587         warning_error(wi, "AWH bias share-group should be >= 0");
588     }
589
590     opt = prefix + "-ndim";
591     if (awhBiasParams->ndim <= 0 || awhBiasParams->ndim > c_biasMaxNumDim)
592     {
593         gmx_fatal(FARGS, "%s (%d) needs to be > 0 and at most %d\n", opt.c_str(),
594                   awhBiasParams->ndim, c_biasMaxNumDim);
595     }
596     if (awhBiasParams->ndim > 2)
597     {
598         warning_note(wi,
599                      "For awh-dim > 2 the estimate based on the diffusion and the initial error is "
600                      "currently only a rough guideline."
601                      " You should verify its usefulness for your system before production runs!");
602     }
603     for (int d = 0; d < awhBiasParams->ndim; d++)
604     {
605         std::string prefixdim = prefix + formatString("-dim%d", d + 1);
606         checkDimParams(prefixdim, &awhBiasParams->dimParams[d], ir, wi);
607     }
608
609     /* Check consistencies here that cannot be checked at read time at a lower level. */
610     checkInputConsistencyAwhBias(*awhBiasParams, wi);
611 }
612
613 /*! \brief
614  * Check consistency of input at the AWH level.
615  *
616  * \param[in]     awhParams  AWH parameters.
617  * \param[in,out] wi         Struct for bookkeeping warnings.
618  */
619 void checkInputConsistencyAwh(const AwhParams& awhParams, warninp_t wi)
620 {
621     /* Each pull coord can map to at most 1 AWH coord.
622      * Check that we have a shared bias when requesting multisim sharing.
623      */
624     bool haveSharedBias = false;
625     for (int k1 = 0; k1 < awhParams.numBias; k1++)
626     {
627         const AwhBiasParams& awhBiasParams1 = awhParams.awhBiasParams[k1];
628
629         if (awhBiasParams1.shareGroup > 0)
630         {
631             haveSharedBias = true;
632         }
633
634         /* k1 is the reference AWH, k2 is the AWH we compare with (can be equal to k1) */
635         for (int k2 = k1; k2 < awhParams.numBias; k2++)
636         {
637             for (int d1 = 0; d1 < awhBiasParams1.ndim; d1++)
638             {
639                 if (awhBiasParams1.dimParams[d1].eCoordProvider == eawhcoordproviderFREE_ENERGY_LAMBDA)
640                 {
641                     continue;
642                 }
643                 const AwhBiasParams& awhBiasParams2 = awhParams.awhBiasParams[k2];
644
645                 /* d1 is the reference dimension of the reference AWH. d2 is the dim index of the AWH to compare with. */
646                 for (int d2 = 0; d2 < awhBiasParams2.ndim; d2++)
647                 {
648                     if (awhBiasParams2.dimParams[d2].eCoordProvider == eawhcoordproviderFREE_ENERGY_LAMBDA)
649                     {
650                         continue;
651                     }
652                     /* Give an error if (d1, k1) is different from (d2, k2) but the pull coordinate is the same */
653                     if ((d1 != d2 || k1 != k2)
654                         && (awhBiasParams1.dimParams[d1].coordIndex == awhBiasParams2.dimParams[d2].coordIndex))
655                     {
656                         char errormsg[STRLEN];
657                         sprintf(errormsg,
658                                 "One pull coordinate (%d) cannot be mapped to two separate AWH "
659                                 "dimensions (awh%d-dim%d and awh%d-dim%d). "
660                                 "If this is really what you want to do you will have to duplicate "
661                                 "this pull coordinate.",
662                                 awhBiasParams1.dimParams[d1].coordIndex + 1, k1 + 1, d1 + 1, k2 + 1,
663                                 d2 + 1);
664                         gmx_fatal(FARGS, "%s", errormsg);
665                     }
666                 }
667             }
668         }
669     }
670
671     if (awhParams.shareBiasMultisim && !haveSharedBias)
672     {
673         warning(wi,
674                 "Sharing of biases over multiple simulations is requested, but no bias is marked "
675                 "as shared (share-group > 0)");
676     }
677
678     /* mdrun does not support this (yet), but will check again */
679     if (haveBiasSharingWithinSimulation(awhParams))
680     {
681         warning(wi,
682                 "You have shared biases within a single simulation, but mdrun does not support "
683                 "this (yet)");
684     }
685 }
686 } // namespace
687
688 AwhParams* readAwhParams(std::vector<t_inpfile>* inp, warninp_t wi)
689 {
690     AwhParams* awhParams;
691     snew(awhParams, 1);
692     std::string opt;
693
694     /* Parameters common for all biases */
695
696     printStringNoNewline(inp, "The way to apply the biasing potential: convolved or umbrella");
697     opt                   = "awh-potential";
698     awhParams->ePotential = get_eeenum(inp, opt, eawhpotential_names, wi);
699
700     printStringNoNewline(inp,
701                          "The random seed used for sampling the umbrella center in the case of "
702                          "umbrella type potential");
703     opt             = "awh-seed";
704     awhParams->seed = get_eint(inp, opt, -1, wi);
705     if (awhParams->seed == -1)
706     {
707         awhParams->seed = static_cast<int>(gmx::makeRandomSeed());
708         fprintf(stderr, "Setting the AWH bias MC random seed to %" PRId64 "\n", awhParams->seed);
709     }
710
711     printStringNoNewline(inp, "Data output interval in number of steps");
712     opt               = "awh-nstout";
713     awhParams->nstOut = get_eint(inp, opt, 100000, wi);
714
715     printStringNoNewline(inp, "Coordinate sampling interval in number of steps");
716     opt                       = "awh-nstsample";
717     awhParams->nstSampleCoord = get_eint(inp, opt, 10, wi);
718
719     printStringNoNewline(inp, "Free energy and bias update interval in number of samples");
720     opt                                   = "awh-nsamples-update";
721     awhParams->numSamplesUpdateFreeEnergy = get_eint(inp, opt, 10, wi);
722
723     printStringNoNewline(
724             inp, "When true, biases with share-group>0 are shared between multiple simulations");
725     opt                          = "awh-share-multisim";
726     awhParams->shareBiasMultisim = (get_eeenum(inp, opt, yesno_names, wi) != 0);
727
728     printStringNoNewline(inp, "The number of independent AWH biases");
729     opt                = "awh-nbias";
730     awhParams->numBias = get_eint(inp, opt, 1, wi);
731     /* Check this before starting to read the AWH biases. */
732     if (awhParams->numBias <= 0)
733     {
734         gmx_fatal(FARGS, "%s needs to be an integer > 0", opt.c_str());
735     }
736
737     /* Read the parameters specific to each AWH bias */
738     snew(awhParams->awhBiasParams, awhParams->numBias);
739
740     for (int k = 0; k < awhParams->numBias; k++)
741     {
742         bool        bComment  = (k == 0);
743         std::string prefixawh = formatString("awh%d", k + 1);
744         readBiasParams(inp, &awhParams->awhBiasParams[k], prefixawh, wi, bComment);
745     }
746
747     return awhParams;
748 }
749
750 void checkAwhParams(const AwhParams* awhParams, const t_inputrec* ir, warninp_t wi)
751 {
752     std::string opt;
753
754     opt = "awh-nstout";
755     if (awhParams->nstOut <= 0)
756     {
757         auto message = formatString("Not writing AWH output with AWH (%s = %d) does not make sense",
758                                     opt.c_str(), awhParams->nstOut);
759         warning_error(wi, message);
760     }
761     /* This restriction can be removed by changing a flag of print_ebin() */
762     if (ir->nstenergy == 0 || awhParams->nstOut % ir->nstenergy != 0)
763     {
764         auto message = formatString("%s (%d) should be a multiple of nstenergy (%d)", opt.c_str(),
765                                     awhParams->nstOut, ir->nstenergy);
766         warning_error(wi, message);
767     }
768
769     opt = "awh-nsamples-update";
770     if (awhParams->numSamplesUpdateFreeEnergy <= 0)
771     {
772         warning_error(wi, opt + " needs to be an integer > 0");
773     }
774
775     for (int k = 0; k < awhParams->numBias; k++)
776     {
777         std::string prefixawh = formatString("awh%d", k + 1);
778         checkBiasParams(&awhParams->awhBiasParams[k], prefixawh, ir, wi);
779     }
780
781     /* Do a final consistency check before returning */
782     checkInputConsistencyAwh(*awhParams, wi);
783
784     if (ir->init_step != 0)
785     {
786         warning_error(wi, "With AWH init-step should be 0");
787     }
788 }
789
790 /*! \brief
791  * Gets the period of a pull coordinate.
792  *
793  * \param[in] pullCoordParams   The parameters for the pull coordinate.
794  * \param[in] pbc               The PBC setup
795  * \param[in] intervalLength    The length of the AWH interval for this pull coordinate
796  * \returns the period (or 0 if not periodic).
797  */
798 static double get_pull_coord_period(const t_pull_coord& pullCoordParams, const t_pbc& pbc, const real intervalLength)
799 {
800     double period = 0;
801
802     if (pullCoordParams.eGeom == epullgDIR)
803     {
804         const real margin = 0.001;
805         // Make dims periodic when the interval covers > 95%
806         const real periodicFraction = 0.95;
807
808         // Check if the pull direction is along a box vector
809         for (int dim = 0; dim < pbc.ndim_ePBC; dim++)
810         {
811             const real boxLength    = norm(pbc.box[dim]);
812             const real innerProduct = iprod(pullCoordParams.vec, pbc.box[dim]);
813             if (innerProduct >= (1 - margin) * boxLength && innerProduct <= (1 + margin) * boxLength)
814             {
815                 if (intervalLength > (1 + margin) * boxLength)
816                 {
817                     gmx_fatal(FARGS,
818                               "The AWH interval (%f nm) for a pull coordinate is larger than the "
819                               "box size (%f nm)",
820                               intervalLength, boxLength);
821                 }
822
823                 if (intervalLength > periodicFraction * boxLength)
824                 {
825                     period = boxLength;
826                 }
827             }
828         }
829     }
830     else if (pullCoordParams.eGeom == epullgDIHEDRAL)
831     {
832         /* The dihedral angle is periodic in -180 to 180 deg */
833         period = 360;
834     }
835
836     return period;
837 }
838
839 /*! \brief
840  * Checks if the given interval is defined in the correct periodic interval.
841  *
842  * \param[in] origin      Start value of interval.
843  * \param[in] end         End value of interval.
844  * \param[in] period      Period (or 0 if not periodic).
845  * \returns true if the end point values are in the correct periodic interval.
846  */
847 static bool intervalIsInPeriodicInterval(double origin, double end, double period)
848 {
849     return (period == 0) || (std::fabs(origin) <= 0.5 * period && std::fabs(end) <= 0.5 * period);
850 }
851
852 /*! \brief
853  * Checks if a value is within an interval.
854  *
855  * \param[in] origin      Start value of interval.
856  * \param[in] end         End value of interval.
857  * \param[in] period      Period (or 0 if not periodic).
858  * \param[in] value       Value to check.
859  * \returns true if the value is within the interval.
860  */
861 static bool valueIsInInterval(double origin, double end, double period, double value)
862 {
863     bool bIn_interval;
864
865     if (period > 0)
866     {
867         if (origin < end)
868         {
869             /* The interval closes within the periodic interval */
870             bIn_interval = (value >= origin) && (value <= end);
871         }
872         else
873         {
874             /* The interval wraps around the periodic boundary */
875             bIn_interval = ((value >= origin) && (value <= 0.5 * period))
876                            || ((value >= -0.5 * period) && (value <= end));
877         }
878     }
879     else
880     {
881         bIn_interval = (value >= origin) && (value <= end);
882     }
883
884     return bIn_interval;
885 }
886
887 /*! \brief
888  * Check if the starting configuration is consistent with the given interval.
889  *
890  * \param[in]     awhParams  AWH parameters.
891  * \param[in,out] wi         Struct for bookeeping warnings.
892  */
893 static void checkInputConsistencyInterval(const AwhParams* awhParams, warninp_t wi)
894 {
895     for (int k = 0; k < awhParams->numBias; k++)
896     {
897         AwhBiasParams* awhBiasParams = &awhParams->awhBiasParams[k];
898         for (int d = 0; d < awhBiasParams->ndim; d++)
899         {
900             AwhDimParams* dimParams  = &awhBiasParams->dimParams[d];
901             int           coordIndex = dimParams->coordIndex;
902             double origin = dimParams->origin, end = dimParams->end, period = dimParams->period;
903             double coordValueInit = dimParams->coordValueInit;
904
905             if ((period == 0) && (origin > end))
906             {
907                 gmx_fatal(FARGS,
908                           "For the non-periodic pull coordinates awh%d-dim%d-start (%f) cannot be "
909                           "larger than awh%d-dim%d-end (%f)",
910                           k + 1, d + 1, origin, k + 1, d + 1, end);
911             }
912
913             /* Currently we assume symmetric periodic intervals, meaning we use [-period/2, period/2] as the reference interval.
914                Make sure the AWH interval is within this reference interval.
915
916                Note: we could fairly simply allow using a  more general interval (e.g. [x, x + period]) but it complicates
917                things slightly and I don't see that there is a great need for it. It would also mean that the interval would
918                depend on AWH input. Also, for dihedral angles you would always want the reference interval to be -180, +180,
919                independent of AWH parameters.
920              */
921             if (!intervalIsInPeriodicInterval(origin, end, period))
922             {
923                 gmx_fatal(FARGS,
924                           "When using AWH with periodic pull coordinate geometries "
925                           "awh%d-dim%d-start (%.8g) and "
926                           "awh%d-dim%d-end (%.8g) should cover at most one period (%.8g) and take "
927                           "values in between "
928                           "minus half a period and plus half a period, i.e. in the interval [%.8g, "
929                           "%.8g].",
930                           k + 1, d + 1, origin, k + 1, d + 1, end, period, -0.5 * period, 0.5 * period);
931             }
932
933             /* Warn if the pull initial coordinate value is not in the grid */
934             if (!valueIsInInterval(origin, end, period, coordValueInit))
935             {
936                 auto message = formatString(
937                         "The initial coordinate value (%.8g) for pull coordinate index %d falls "
938                         "outside "
939                         "of the sampling nterval awh%d-dim%d-start (%.8g) to awh%d-dim%d-end "
940                         "(%.8g). "
941                         "This can lead to large initial forces pulling the coordinate towards the "
942                         "sampling interval.",
943                         coordValueInit, coordIndex + 1, k + 1, d + 1, origin, k + 1, d + 1, end);
944                 warning(wi, message);
945             }
946         }
947     }
948 }
949
950 /*! \brief
951  * Sets AWH parameters, for one AWH pull dimension.
952  *
953  * \param[in,out] dimParams          AWH dimension parameters.
954  * \param[in]     biasIndex             The index of the bias containing this AWH pull dimension.
955  * \param[in]     dimIndex              The index of this AWH pull dimension.
956  * \param[in]     pull_params           Pull parameters.
957  * \param[in,out] pull_work             Pull working struct to register AWH bias in.
958  * \param[in]     pbc                   A pbc information structure.
959  * \param[in]     compressibility       Compressibility matrix for pressure coupling,
960  * pass all 0 without pressure coupling.
961  * \param[in,out] wi                    Struct for bookeeping warnings.
962  *
963  */
964 static void setStateDependentAwhPullDimParams(AwhDimParams*        dimParams,
965                                               const int            biasIndex,
966                                               const int            dimIndex,
967                                               const pull_params_t* pull_params,
968                                               pull_t*              pull_work,
969                                               const t_pbc&         pbc,
970                                               const tensor&        compressibility,
971                                               warninp_t            wi)
972 {
973     const t_pull_coord& pullCoordParams = pull_params->coord[dimParams->coordIndex];
974
975     if (pullCoordParams.eGeom == epullgDIRPBC)
976     {
977         gmx_fatal(FARGS,
978                   "AWH does not support pull geometry '%s'. "
979                   "If the maximum distance between the groups is always "
980                   "less than half the box size, "
981                   "you can use geometry '%s' instead.",
982                   EPULLGEOM(epullgDIRPBC), EPULLGEOM(epullgDIR));
983     }
984
985     dimParams->period = get_pull_coord_period(pullCoordParams, pbc, dimParams->end - dimParams->origin);
986     // We would like to check for scaling, but we don't have the full inputrec available here
987     if (dimParams->period > 0
988         && !(pullCoordParams.eGeom == epullgANGLE || pullCoordParams.eGeom == epullgDIHEDRAL))
989     {
990         bool coordIsScaled = false;
991         for (int d2 = 0; d2 < DIM; d2++)
992         {
993             if (pullCoordParams.vec[d2] != 0 && norm2(compressibility[d2]) != 0)
994             {
995                 coordIsScaled = true;
996             }
997         }
998         if (coordIsScaled)
999         {
1000             std::string mesg = gmx::formatString(
1001                     "AWH dimension %d of bias %d is periodic with pull geometry '%s', "
1002                     "while you should are applying pressure scaling to the "
1003                     "corresponding box vector, this is not supported.",
1004                     dimIndex + 1, biasIndex + 1, EPULLGEOM(pullCoordParams.eGeom));
1005             warning(wi, mesg.c_str());
1006         }
1007     }
1008
1009     /* The initial coordinate value, converted to external user units. */
1010     dimParams->coordValueInit = get_pull_coord_value(pull_work, dimParams->coordIndex, &pbc);
1011
1012     dimParams->coordValueInit *= pull_conversion_factor_internal2userinput(&pullCoordParams);
1013 }
1014
1015 void setStateDependentAwhParams(AwhParams*           awhParams,
1016                                 const pull_params_t* pull_params,
1017                                 pull_t*              pull_work,
1018                                 const matrix         box,
1019                                 PbcType              pbcType,
1020                                 const tensor&        compressibility,
1021                                 const t_grpopts*     inputrecGroupOptions,
1022                                 const real           initLambda,
1023                                 const gmx_mtop_t&    mtop,
1024                                 warninp_t            wi)
1025 {
1026     /* The temperature is not really state depenendent but is not known
1027      * when read_awhParams is called (in get ir).
1028      * It is known first after do_index has been called in grompp.cpp.
1029      */
1030     if (inputrecGroupOptions->ref_t == nullptr || inputrecGroupOptions->ref_t[0] <= 0)
1031     {
1032         gmx_fatal(FARGS, "AWH biasing is only supported for temperatures > 0");
1033     }
1034     for (int i = 1; i < inputrecGroupOptions->ngtc; i++)
1035     {
1036         if (inputrecGroupOptions->ref_t[i] != inputrecGroupOptions->ref_t[0])
1037         {
1038             gmx_fatal(FARGS,
1039                       "AWH biasing is currently only supported for identical temperatures for all "
1040                       "temperature coupling groups");
1041         }
1042     }
1043
1044     t_pbc pbc;
1045     set_pbc(&pbc, pbcType, box);
1046
1047     for (int k = 0; k < awhParams->numBias; k++)
1048     {
1049         AwhBiasParams* awhBiasParams = &awhParams->awhBiasParams[k];
1050         for (int d = 0; d < awhBiasParams->ndim; d++)
1051         {
1052             AwhDimParams* dimParams = &awhBiasParams->dimParams[d];
1053             if (dimParams->eCoordProvider == eawhcoordproviderPULL)
1054             {
1055                 setStateDependentAwhPullDimParams(dimParams, k, d, pull_params, pull_work, pbc,
1056                                                   compressibility, wi);
1057             }
1058             else
1059             {
1060                 dimParams->coordValueInit = initLambda;
1061                 checkFepLambdaDimDecouplingConsistency(mtop, wi);
1062             }
1063         }
1064     }
1065     checkInputConsistencyInterval(awhParams, wi);
1066
1067     /* Register AWH as external potential with pull (for AWH dimensions that use pull as
1068      * reaction coordinate provider) to check consistency. */
1069     Awh::registerAwhWithPull(*awhParams, pull_work);
1070 }
1071
1072 } // namespace gmx