2 * This file is part of the GROMACS molecular simulation package.
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,2021, 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.
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.
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.
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.
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.
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.
41 #include "read_params.h"
45 #include "gromacs/applied_forces/awh/awh.h"
46 #include "gromacs/fileio/readinp.h"
47 #include "gromacs/fileio/warninp.h"
48 #include "gromacs/math/units.h"
49 #include "gromacs/math/utilities.h"
50 #include "gromacs/math/vec.h"
51 #include "gromacs/mdtypes/awh_params.h"
52 #include "gromacs/mdtypes/inputrec.h"
53 #include "gromacs/mdtypes/md_enums.h"
54 #include "gromacs/mdtypes/multipletimestepping.h"
55 #include "gromacs/mdtypes/pull_params.h"
56 #include "gromacs/pbcutil/pbc.h"
57 #include "gromacs/pulling/pull.h"
58 #include "gromacs/random/seed.h"
59 #include "gromacs/topology/mtop_util.h"
60 #include "gromacs/utility/cstringutil.h"
61 #include "gromacs/utility/enumerationhelpers.h"
62 #include "gromacs/utility/fatalerror.h"
63 #include "gromacs/utility/iserializer.h"
64 #include "gromacs/utility/smalloc.h"
65 #include "gromacs/utility/stringutil.h"
67 #include "biasparams.h"
68 #include "biassharing.h"
73 const char* enumValueToString(AwhTargetType enumValue)
75 constexpr gmx::EnumerationArray<AwhTargetType, const char*> awhTargetTypeNames = {
76 "constant", "cutoff", "boltzmann", "local-boltzmann"
78 return awhTargetTypeNames[enumValue];
81 const char* enumValueToString(AwhHistogramGrowthType enumValue)
83 constexpr gmx::EnumerationArray<AwhHistogramGrowthType, const char*> awhHistogramGrowthTypeNames = {
84 "exp-linear", "linear"
86 return awhHistogramGrowthTypeNames[enumValue];
89 const char* enumValueToString(AwhPotentialType enumValue)
91 constexpr gmx::EnumerationArray<AwhPotentialType, const char*> awhPotentialTypeNames = {
92 "convolved", "umbrella"
94 return awhPotentialTypeNames[enumValue];
97 const char* enumValueToString(AwhCoordinateProviderType enumValue)
99 constexpr gmx::EnumerationArray<AwhCoordinateProviderType, const char*> awhCoordinateProviderTypeNames = {
102 return awhCoordinateProviderTypeNames[enumValue];
109 * Check multiple time-stepping consistency between AWH and pull and/or FEP
111 * \param[in] inputrec Inputput parameter struct.
112 * \param[in,out] wi Struct for bookeeping warnings.
114 void checkMtsConsistency(const t_inputrec& inputrec, warninp_t wi)
116 if (!inputrec.useMts)
121 GMX_RELEASE_ASSERT(inputrec.mtsLevels.size() == 2, "Only 2 MTS levels supported here");
123 bool usesPull = false;
124 bool usesFep = false;
125 for (const auto& awhBiasParam : inputrec.awhParams->awhBiasParams())
127 for (const auto& dimParam : awhBiasParam.dimParams())
129 switch (dimParam.coordinateProvider())
131 case AwhCoordinateProviderType::Pull: usesPull = true; break;
132 case AwhCoordinateProviderType::FreeEnergyLambda: usesFep = true; break;
133 default: GMX_RELEASE_ASSERT(false, "Unsupported coord provider");
137 const int awhMtsLevel = forceGroupMtsLevel(inputrec.mtsLevels, MtsForceGroups::Awh);
138 if (usesPull && forceGroupMtsLevel(inputrec.mtsLevels, MtsForceGroups::Pull) != awhMtsLevel)
141 "When AWH is applied to pull coordinates, pull and AWH should be computed at "
142 "the same MTS level");
144 if (usesFep && awhMtsLevel != ssize(inputrec.mtsLevels) - 1)
147 "When AWH is applied to the free-energy lambda with MTS, AWH should be "
148 "computed at the slow MTS level");
151 if (inputrec.awhParams->nstSampleCoord() % inputrec.mtsLevels[awhMtsLevel].stepFactor != 0)
154 "With MTS applied to AWH, awh-nstsample should be a multiple of mts-factor");
159 * Check the parameters of an AWH bias pull dimension.
161 * \param[in] prefix Prefix for dimension parameters.
162 * \param[in,out] dimParams AWH dimensional parameters.
163 * \param[in] pull_params Pull parameters.
164 * \param[in,out] wi Struct for bookeeping warnings.
166 void checkPullDimParams(const std::string& prefix,
167 const AwhDimParams& dimParams,
168 const pull_params_t& pull_params,
171 if (dimParams.coordinateIndex() < 0)
174 "Failed to read a valid coordinate index for %s-coord-index. "
175 "Note that the pull coordinate indexing starts at 1.",
178 if (dimParams.coordinateIndex() >= pull_params.ncoord)
181 "The given AWH coordinate index (%d) is larger than the number of pull "
183 dimParams.coordinateIndex() + 1,
186 if (pull_params.coord[dimParams.coordinateIndex()].rate != 0)
188 auto message = formatString(
189 "Setting pull-coord%d-rate (%g) is incompatible with AWH biasing this coordinate",
190 dimParams.coordinateIndex() + 1,
191 pull_params.coord[dimParams.coordinateIndex()].rate);
192 warning_error(wi, message);
195 if (gmx_within_tol(dimParams.end() - dimParams.origin(), 0, GMX_REAL_EPS))
197 auto message = formatString(
198 "The given interval length given by %s-start (%g) and %s-end (%g) is zero. "
199 "This will result in only one point along this axis in the coordinate value grid.",
204 warning(wi, message);
207 if (dimParams.forceConstant() <= 0)
209 warning_error(wi, "The force AWH bias force constant should be > 0");
212 /* Grid params for each axis */
213 PullGroupGeometry eGeom = pull_params.coord[dimParams.coordinateIndex()].eGeom;
215 /* Check that the requested interval is in allowed range */
216 if (eGeom == PullGroupGeometry::Distance)
218 if (dimParams.origin() < 0 || dimParams.end() < 0)
221 "%s-start (%g) or %s-end (%g) set to a negative value. With pull "
222 "geometry distance coordinate values are non-negative. "
223 "Perhaps you want to use geometry %s instead?",
228 enumValueToString(PullGroupGeometry::Direction));
231 else if (eGeom == PullGroupGeometry::Angle || eGeom == PullGroupGeometry::AngleAxis)
233 if (dimParams.origin() < 0 || dimParams.end() > 180)
236 "%s-start (%g) and %s-end (%g) are outside of the allowed range "
237 "0 to 180 deg for pull geometries %s and %s ",
242 enumValueToString(PullGroupGeometry::Angle),
243 enumValueToString(PullGroupGeometry::AngleAxis));
246 else if (eGeom == PullGroupGeometry::Dihedral)
248 if (dimParams.origin() < -180 || dimParams.end() > 180)
251 "%s-start (%g) and %s-end (%g) are outside of the allowed range "
252 "-180 to 180 deg for pull geometry %s. ",
257 enumValueToString(PullGroupGeometry::Dihedral));
263 * Check parameters of an AWH bias in a free energy lambda state dimension.
265 * \param[in] prefix Prefix for dimension parameters.
266 * \param[in,out] dimParams AWH dimensional parameters.
267 * \param[in] lambdaParams The free energy lambda related parameters.
268 * \param[in] efep This is the type of FEP calculation (efep enumerator).
269 * \param[in,out] wi Struct for bookeeping warnings.
271 void checkFepLambdaDimParams(const std::string& prefix,
272 const AwhDimParams& dimParams,
273 const t_lambda* lambdaParams,
274 const FreeEnergyPerturbationType efep,
282 "There must be free energy input if using AWH to steer the free energy lambda "
286 if (lambdaParams->lambda_neighbors != -1)
289 "When running AWH coupled to the free energy lambda state all lambda states "
290 "should be used as neighbors in order to get correct probabilities, i.e. "
291 "calc-lambda-neighbors (%d) must be %d.",
292 lambdaParams->lambda_neighbors,
296 if (efep == FreeEnergyPerturbationType::SlowGrowth || lambdaParams->delta_lambda != 0)
299 "AWH coupled to the free energy lambda state is not compatible with slow-growth "
300 "and delta-lambda must be 0.");
303 if (efep == FreeEnergyPerturbationType::Expanded)
306 "AWH is not treated like other expanded ensemble methods. Do not use expanded.");
309 if (dimParams.origin() < 0)
311 opt = prefix + "-start";
313 "When running AWH coupled to the free energy lambda state the lower lambda state "
314 "for AWH, %s (%.0f), must be >= 0.",
318 if (dimParams.end() >= lambdaParams->n_lambda)
320 opt = prefix + "-end";
322 "When running AWH coupled to the free energy lambda state the upper lambda state "
323 "for AWH, %s (%.0f), must be < n_lambda (%d).",
326 lambdaParams->n_lambda);
328 if (gmx_within_tol(dimParams.end() - dimParams.origin(), 0, GMX_REAL_EPS))
330 auto message = formatString(
331 "The given interval length given by %s-start (%g) and %s-end (%g) is zero. "
332 "This will result in only one lambda point along this free energy lambda state "
333 "axis in the coordinate value grid.",
338 warning(wi, message);
341 if (dimParams.forceConstant() != 0)
345 "The force AWH bias force constant is not used with free energy lambda state as "
346 "coordinate provider.");
351 * Check that AWH FEP is not combined with incompatible decoupling.
353 * \param[in] mtop System topology.
354 * \param[in,out] wi Struct for bookeeping warnings.
356 void checkFepLambdaDimDecouplingConsistency(const gmx_mtop_t& mtop, warninp_t wi)
358 if (haveFepPerturbedMasses(mtop))
361 "Masses may not be perturbed when using the free energy lambda state as AWH "
362 "coordinate provider. If you are using fep-lambdas to specify lambda states "
363 "make sure that you also specify mass-lambdas without perturbation.");
365 if (havePerturbedConstraints(mtop))
368 "Constraints may not be perturbed when using the free energy lambda state as AWH "
369 "coordinate provider. If you are using fep-lambdas to specify lambda states "
370 "make sure that you also specify mass-lambdas without perturbation.");
375 * Check the parameters of an AWH bias dimension.
377 * \param[in] prefix Prefix for dimension parameters.
378 * \param[in,out] dimParams AWH dimensional parameters.
379 * \param[in] ir Input parameter struct.
380 * \param[in,out] wi Struct for bookeeping warnings.
382 void checkDimParams(const std::string& prefix, const AwhDimParams& dimParams, const t_inputrec& ir, warninp_t wi)
384 if (dimParams.coordinateProvider() == AwhCoordinateProviderType::Pull)
389 "AWH biasing along a pull dimension is only compatible with COM pulling "
392 checkPullDimParams(prefix, dimParams, *ir.pull, wi);
394 else if (dimParams.coordinateProvider() == AwhCoordinateProviderType::FreeEnergyLambda)
396 if (ir.efep == FreeEnergyPerturbationType::No)
399 "AWH biasing along a free energy lambda state dimension is only compatible "
400 "with free energy turned on");
402 checkFepLambdaDimParams(prefix, dimParams, ir.fepvals.get(), ir.efep, wi);
407 "AWH biasing can only be applied to pull and free energy lambda state "
413 * Check the parameters of an AWH bias.
415 * \param[in] awhBiasParams AWH dimensional parameters.
416 * \param[in] prefix Prefix for bias parameters.
417 * \param[in] ir Input parameter struct.
418 * \param[in,out] wi Struct for bookeeping warnings.
420 void checkBiasParams(const AwhBiasParams& awhBiasParams, const std::string& prefix, const t_inputrec& ir, warninp_t wi)
422 std::string opt = prefix + "-error-init";
423 if (awhBiasParams.initialErrorEstimate() <= 0)
425 gmx_fatal(FARGS, "%s needs to be > 0.", opt.c_str());
428 opt = prefix + "-equilibrate-histogram";
429 if (awhBiasParams.equilibrateHistogram()
430 && awhBiasParams.growthType() != AwhHistogramGrowthType::ExponentialLinear)
433 formatString("Option %s will only have an effect for histogram growth type '%s'.",
435 enumValueToString(AwhHistogramGrowthType::ExponentialLinear));
436 warning(wi, message);
439 if ((awhBiasParams.targetDistribution() == AwhTargetType::LocalBoltzmann)
440 && (awhBiasParams.growthType() == AwhHistogramGrowthType::ExponentialLinear))
442 auto message = formatString(
443 "Target type '%s' combined with histogram growth type '%s' is not "
444 "expected to give stable bias updates. You probably want to use growth type "
446 enumValueToString(AwhTargetType::LocalBoltzmann),
447 enumValueToString(AwhHistogramGrowthType::ExponentialLinear),
448 enumValueToString(AwhHistogramGrowthType::Linear));
449 warning(wi, message);
452 opt = prefix + "-target-beta-scaling";
453 switch (awhBiasParams.targetDistribution())
455 case AwhTargetType::Boltzmann:
456 case AwhTargetType::LocalBoltzmann:
457 if (awhBiasParams.targetBetaScaling() < 0 || awhBiasParams.targetBetaScaling() > 1)
460 "%s = %g is not useful for target type %s.",
462 awhBiasParams.targetBetaScaling(),
463 enumValueToString(awhBiasParams.targetDistribution()));
467 if (awhBiasParams.targetBetaScaling() != 0)
471 "Value for %s (%g) set explicitly but will not be used for target type %s.",
473 awhBiasParams.targetBetaScaling(),
474 enumValueToString(awhBiasParams.targetDistribution()));
479 opt = prefix + "-target-cutoff";
480 switch (awhBiasParams.targetDistribution())
482 case AwhTargetType::Cutoff:
483 if (awhBiasParams.targetCutoff() <= 0)
486 "%s = %g is not useful for target type %s.",
488 awhBiasParams.targetCutoff(),
489 enumValueToString(awhBiasParams.targetDistribution()));
493 if (awhBiasParams.targetCutoff() != 0)
497 "Value for %s (%g) set explicitly but will not be used for target type %s.",
499 awhBiasParams.targetCutoff(),
500 enumValueToString(awhBiasParams.targetDistribution()));
505 opt = prefix + "-share-group";
506 if (awhBiasParams.shareGroup() < 0)
508 warning_error(wi, "AWH bias share-group should be >= 0");
511 opt = prefix + "-ndim";
512 if (awhBiasParams.ndim() <= 0 || awhBiasParams.ndim() > c_biasMaxNumDim)
514 gmx_fatal(FARGS, "%s (%d) needs to be > 0 and at most %d\n", opt.c_str(), awhBiasParams.ndim(), c_biasMaxNumDim);
516 if (awhBiasParams.ndim() > 2)
519 "For awh-dim > 2 the estimate based on the diffusion and the initial error is "
520 "currently only a rough guideline."
521 " You should verify its usefulness for your system before production runs!");
523 for (int d = 0; d < awhBiasParams.ndim(); d++)
525 std::string prefixdim = prefix + formatString("-dim%d", d + 1);
526 checkDimParams(prefixdim, awhBiasParams.dimParams()[d], ir, wi);
531 * Check consistency of input at the AWH bias level.
533 * \param[in] awhBiasParams AWH bias parameters.
534 * \param[in,out] wi Struct for bookkeeping warnings.
536 void checkInputConsistencyAwhBias(const AwhBiasParams& awhBiasParams, warninp_t wi)
538 /* Covering diameter and sharing warning. */
539 auto awhBiasDimensionParams = awhBiasParams.dimParams();
540 for (const auto& dimensionParam : awhBiasDimensionParams)
542 double coverDiameter = dimensionParam.coverDiameter();
543 if (awhBiasParams.shareGroup() <= 0 && coverDiameter > 0)
546 "The covering diameter is only relevant to set for bias sharing simulations.");
552 * Check consistency of input at the AWH level.
554 * \param[in] awhParams AWH parameters.
555 * \param[in,out] wi Struct for bookkeeping warnings.
557 void checkInputConsistencyAwh(const AwhParams& awhParams, warninp_t wi)
559 /* Each pull coord can map to at most 1 AWH coord.
560 * Check that we have a shared bias when requesting multisim sharing.
562 bool haveSharedBias = false;
563 auto awhBiasParams = awhParams.awhBiasParams();
564 for (int k1 = 0; k1 < awhParams.numBias(); k1++)
566 const AwhBiasParams& awhBiasParams1 = awhBiasParams[k1];
568 if (awhBiasParams1.shareGroup() > 0)
570 haveSharedBias = true;
573 /* k1 is the reference AWH, k2 is the AWH we compare with (can be equal to k1) */
574 for (int k2 = k1; k2 < awhParams.numBias(); k2++)
576 const AwhBiasParams& awhBiasParams2 = awhBiasParams[k2];
577 const auto& dimParams1 = awhBiasParams1.dimParams();
578 const auto& dimParams2 = awhBiasParams2.dimParams();
579 for (int d1 = 0; d1 < gmx::ssize(dimParams1); d1++)
581 if (dimParams1[d1].coordinateProvider() == AwhCoordinateProviderType::FreeEnergyLambda)
585 /* d1 is the reference dimension of the reference AWH. d2 is the dim index of the AWH to compare with. */
586 for (int d2 = 0; d2 < gmx::ssize(dimParams2); d2++)
588 if (dimParams2[d2].coordinateProvider() == AwhCoordinateProviderType::FreeEnergyLambda)
592 /* Give an error if (d1, k1) is different from (d2, k2) but the pull coordinate is the same */
593 if ((d1 != d2 || k1 != k2)
594 && (dimParams1[d1].coordinateIndex() == dimParams2[d2].coordinateIndex()))
596 char errormsg[STRLEN];
598 "One pull coordinate (%d) cannot be mapped to two separate AWH "
599 "dimensions (awh%d-dim%d and awh%d-dim%d). "
600 "If this is really what you want to do you will have to duplicate "
601 "this pull coordinate.",
602 dimParams1[d1].coordinateIndex() + 1,
607 gmx_fatal(FARGS, "%s", errormsg);
614 if (awhParams.shareBiasMultisim() && !haveSharedBias)
617 "Sharing of biases over multiple simulations is requested, but no bias is marked "
618 "as shared (share-group > 0)");
621 /* mdrun does not support this (yet), but will check again */
622 if (haveBiasSharingWithinSimulation(awhParams))
625 "You have shared biases within a single simulation, but mdrun does not support "
632 AwhDimParams::AwhDimParams(std::vector<t_inpfile>* inp, const std::string& prefix, warninp_t wi, bool bComment)
637 printStringNoNewline(
639 "The provider of the reaction coordinate, "
640 "currently only 'pull' and 'fep-lambda' (free energy lambda state) is supported");
643 opt = prefix + "-coord-provider";
644 eCoordProvider_ = getEnum<AwhCoordinateProviderType>(inp, opt.c_str(), wi);
648 printStringNoNewline(inp, "The coordinate index for this dimension");
650 opt = prefix + "-coord-index";
652 coordIndexInput = get_eint(inp, opt, 1, wi);
653 if (coordIndexInput < 1)
656 "Failed to read a valid coordinate index for %s. "
657 "Note that the pull coordinate indexing starts at 1.",
661 /* The pull coordinate indices start at 1 in the input file, at 0 internally */
662 coordIndex_ = coordIndexInput - 1;
666 printStringNoNewline(inp, "Start and end values for each coordinate dimension");
669 opt = prefix + "-start";
670 origin_ = get_ereal(inp, opt, 0., wi);
672 opt = prefix + "-end";
673 end_ = get_ereal(inp, opt, 0., wi);
677 printStringNoNewline(
678 inp, "The force constant for this coordinate (kJ/mol/nm^2 or kJ/mol/rad^2)");
680 opt = prefix + "-force-constant";
681 forceConstant_ = get_ereal(inp, opt, 0, wi);
685 printStringNoNewline(inp, "Estimated diffusion constant (nm^2/ps or rad^2/ps or ps^-1)");
687 opt = prefix + "-diffusion";
688 double diffusionValue = get_ereal(inp, opt, 0, wi);
689 if (diffusionValue <= 0)
691 const double diffusion_default = 1e-5;
692 auto message = formatString(
693 "%s not explicitly set by user. You can choose to use a default "
694 "value (%g nm^2/ps or rad^2/ps) but this may very well be "
695 "non-optimal for your system!",
698 warning(wi, message);
699 diffusionValue = diffusion_default;
701 diffusion_ = diffusionValue;
705 printStringNoNewline(inp,
706 "Diameter that needs to be sampled around a point before it is "
707 "considered covered. In FEP dimensions the cover diameter is "
708 "specified in lambda states.");
710 opt = prefix + "-cover-diameter";
711 coverDiameter_ = get_ereal(inp, opt, 0, wi);
714 AwhDimParams::AwhDimParams(ISerializer* serializer)
716 GMX_RELEASE_ASSERT(serializer->reading(),
717 "Can not use writing serializer for creating datastructure");
718 serializer->doEnumAsInt(&eCoordProvider_);
719 serializer->doInt(&coordIndex_);
720 serializer->doDouble(&origin_);
721 serializer->doDouble(&end_);
722 serializer->doDouble(&period_);
723 serializer->doDouble(&forceConstant_);
724 serializer->doDouble(&diffusion_);
725 serializer->doDouble(&coordValueInit_);
726 serializer->doDouble(&coverDiameter_);
729 void AwhDimParams::serialize(ISerializer* serializer)
731 GMX_RELEASE_ASSERT(!serializer->reading(),
732 "Can not use reading serializer for writing datastructure");
733 serializer->doEnumAsInt(&eCoordProvider_);
734 serializer->doInt(&coordIndex_);
735 serializer->doDouble(&origin_);
736 serializer->doDouble(&end_);
737 serializer->doDouble(&period_);
738 serializer->doDouble(&forceConstant_);
739 serializer->doDouble(&diffusion_);
740 serializer->doDouble(&coordValueInit_);
741 serializer->doDouble(&coverDiameter_);
744 AwhBiasParams::AwhBiasParams(std::vector<t_inpfile>* inp, const std::string& prefix, warninp_t wi, bool bComment)
748 printStringNoNewline(inp, "Estimated initial PMF error (kJ/mol)");
751 std::string opt = prefix + "-error-init";
752 errorInitial_ = get_ereal(inp, opt, 10, wi);
756 printStringNoNewline(inp,
757 "Growth rate of the reference histogram determining the bias update "
758 "size: exp-linear or linear");
760 opt = prefix + "-growth";
761 eGrowth_ = getEnum<AwhHistogramGrowthType>(inp, opt.c_str(), wi);
765 printStringNoNewline(inp,
766 "Start the simulation by equilibrating histogram towards the target "
767 "distribution: no or yes");
769 opt = prefix + "-equilibrate-histogram";
770 equilibrateHistogram_ = (getEnum<Boolean>(inp, opt.c_str(), wi) != Boolean::No);
774 printStringNoNewline(
775 inp, "Target distribution type: constant, cutoff, boltzmann or local-boltzmann");
777 opt = prefix + "-target";
778 eTarget_ = getEnum<AwhTargetType>(inp, opt.c_str(), wi);
782 printStringNoNewline(inp,
783 "Boltzmann beta scaling factor for target distribution types "
784 "'boltzmann' and 'boltzmann-local'");
786 opt = prefix + "-target-beta-scaling";
787 targetBetaScaling_ = get_ereal(inp, opt, 0, wi);
791 printStringNoNewline(inp, "Free energy cutoff value for target distribution type 'cutoff'");
793 opt = prefix + "-target-cutoff";
794 targetCutoff_ = get_ereal(inp, opt, 0, wi);
798 printStringNoNewline(inp, "Initialize PMF and target with user data: no or yes");
800 opt = prefix + "-user-data";
801 bUserData_ = getEnum<Boolean>(inp, opt.c_str(), wi) != Boolean::No;
805 printStringNoNewline(inp, "Group index to share the bias with, 0 means not shared");
807 opt = prefix + "-share-group";
808 shareGroup_ = get_eint(inp, opt, 0, wi);
812 printStringNoNewline(inp, "Dimensionality of the coordinate");
814 opt = prefix + "-ndim";
815 int ndim = get_eint(inp, opt, 0, wi);
817 /* Check this before starting to read the AWH dimension parameters. */
818 if (ndim <= 0 || ndim > c_biasMaxNumDim)
820 gmx_fatal(FARGS, "%s (%d) needs to be > 0 and at most %d\n", opt.c_str(), ndim, c_biasMaxNumDim);
822 for (int d = 0; d < ndim; d++)
824 bComment = bComment && d == 0;
825 std::string prefixdim = prefix + formatString("-dim%d", d + 1);
826 dimParams_.emplace_back(inp, prefixdim, wi, bComment);
830 AwhBiasParams::AwhBiasParams(ISerializer* serializer)
832 GMX_RELEASE_ASSERT(serializer->reading(),
833 "Can not use writing serializer to create datastructure");
834 serializer->doEnumAsInt(&eTarget_);
835 serializer->doDouble(&targetBetaScaling_);
836 serializer->doDouble(&targetCutoff_);
837 serializer->doEnumAsInt(&eGrowth_);
839 serializer->doInt(&temp);
840 bUserData_ = static_cast<bool>(temp);
841 serializer->doDouble(&errorInitial_);
842 int numDimensions = dimParams_.size();
843 serializer->doInt(&numDimensions);
844 serializer->doInt(&shareGroup_);
845 serializer->doBool(&equilibrateHistogram_);
847 for (int k = 0; k < numDimensions; k++)
849 dimParams_.emplace_back(serializer);
851 /* Check consistencies here that cannot be checked at read time at a lower level. */
852 checkInputConsistencyAwhBias(*this, nullptr);
855 void AwhBiasParams::serialize(ISerializer* serializer)
857 GMX_RELEASE_ASSERT(!serializer->reading(),
858 "Can not use reading serializer to write datastructure");
859 serializer->doEnumAsInt(&eTarget_);
860 serializer->doDouble(&targetBetaScaling_);
861 serializer->doDouble(&targetCutoff_);
862 serializer->doEnumAsInt(&eGrowth_);
863 int temp = static_cast<int>(bUserData_);
864 serializer->doInt(&temp);
865 serializer->doDouble(&errorInitial_);
866 int numDimensions = ndim();
867 serializer->doInt(&numDimensions);
868 serializer->doInt(&shareGroup_);
869 serializer->doBool(&equilibrateHistogram_);
871 for (int k = 0; k < numDimensions; k++)
873 dimParams_[k].serialize(serializer);
877 AwhParams::AwhParams(std::vector<t_inpfile>* inp, warninp_t wi)
881 /* Parameters common for all biases */
883 printStringNoNewline(inp, "The way to apply the biasing potential: convolved or umbrella");
884 opt = "awh-potential";
885 potentialEnum_ = getEnum<AwhPotentialType>(inp, opt.c_str(), wi);
887 printStringNoNewline(inp,
888 "The random seed used for sampling the umbrella center in the case of "
889 "umbrella type potential");
891 seed_ = get_eint(inp, opt, -1, wi);
894 seed_ = static_cast<int>(gmx::makeRandomSeed());
895 fprintf(stderr, "Setting the AWH bias MC random seed to %" PRId64 "\n", seed_);
898 printStringNoNewline(inp, "Data output interval in number of steps");
900 nstOut_ = get_eint(inp, opt, 100000, wi);
902 printStringNoNewline(inp, "Coordinate sampling interval in number of steps");
903 opt = "awh-nstsample";
904 nstSampleCoord_ = get_eint(inp, opt, 10, wi);
906 printStringNoNewline(inp, "Free energy and bias update interval in number of samples");
907 opt = "awh-nsamples-update";
908 numSamplesUpdateFreeEnergy_ = get_eint(inp, opt, 10, wi);
910 printStringNoNewline(
911 inp, "When true, biases with share-group>0 are shared between multiple simulations");
912 opt = "awh-share-multisim";
913 shareBiasMultisim_ = (getEnum<Boolean>(inp, opt.c_str(), wi) != Boolean::No);
915 printStringNoNewline(inp, "The number of independent AWH biases");
917 int numBias = get_eint(inp, opt, 1, wi);
918 /* Check this before starting to read the AWH biases. */
921 gmx_fatal(FARGS, "%s needs to be an integer > 0", opt.c_str());
924 /* Read the parameters specific to each AWH bias */
925 for (int k = 0; k < numBias; k++)
927 bool bComment = (k == 0);
928 std::string prefixawh = formatString("awh%d", k + 1);
929 awhBiasParams_.emplace_back(inp, prefixawh, wi, bComment);
931 checkInputConsistencyAwh(*this, wi);
934 AwhParams::AwhParams(ISerializer* serializer)
936 GMX_RELEASE_ASSERT(serializer->reading(),
937 "Can not use writing serializer to read AWH parameters");
938 int numberOfBiases = awhBiasParams_.size();
939 serializer->doInt(&numberOfBiases);
940 serializer->doInt(&nstOut_);
941 serializer->doInt64(&seed_);
942 serializer->doInt(&nstSampleCoord_);
943 serializer->doInt(&numSamplesUpdateFreeEnergy_);
944 serializer->doEnumAsInt(&potentialEnum_);
945 serializer->doBool(&shareBiasMultisim_);
947 if (numberOfBiases > 0)
949 for (int k = 0; k < numberOfBiases; k++)
951 awhBiasParams_.emplace_back(serializer);
954 checkInputConsistencyAwh(*this, nullptr);
957 void AwhParams::serialize(ISerializer* serializer)
959 GMX_RELEASE_ASSERT(!serializer->reading(),
960 "Can not use reading serializer to write AWH parameters");
961 int numberOfBiases = numBias();
962 serializer->doInt(&numberOfBiases);
963 serializer->doInt(&nstOut_);
964 serializer->doInt64(&seed_);
965 serializer->doInt(&nstSampleCoord_);
966 serializer->doInt(&numSamplesUpdateFreeEnergy_);
967 serializer->doEnumAsInt(&potentialEnum_);
968 serializer->doBool(&shareBiasMultisim_);
970 if (numberOfBiases > 0)
972 for (int k = 0; k < numberOfBiases; k++)
974 awhBiasParams_[k].serialize(serializer);
980 * Gets the period of a pull coordinate.
982 * \param[in] pullCoordParams The parameters for the pull coordinate.
983 * \param[in] pbc The PBC setup
984 * \param[in] intervalLength The length of the AWH interval for this pull coordinate
985 * \returns the period (or 0 if not periodic).
987 static double get_pull_coord_period(const t_pull_coord& pullCoordParams, const t_pbc& pbc, const real intervalLength)
991 if (pullCoordParams.eGeom == PullGroupGeometry::Direction)
993 const real margin = 0.001;
994 // Make dims periodic when the interval covers > 95%
995 const real periodicFraction = 0.95;
997 // Check if the pull direction is along a box vector
998 for (int dim = 0; dim < pbc.ndim_ePBC; dim++)
1000 const real boxLength = norm(pbc.box[dim]);
1001 const real innerProduct = iprod(pullCoordParams.vec, pbc.box[dim]);
1002 if (innerProduct >= (1 - margin) * boxLength && innerProduct <= (1 + margin) * boxLength)
1004 if (intervalLength > (1 + margin) * boxLength)
1007 "The AWH interval (%f nm) for a pull coordinate is larger than the "
1013 if (intervalLength > periodicFraction * boxLength)
1020 else if (pullCoordParams.eGeom == PullGroupGeometry::Dihedral)
1022 /* The dihedral angle is periodic in -180 to 180 deg */
1030 * Checks if the given interval is defined in the correct periodic interval.
1032 * \param[in] origin Start value of interval.
1033 * \param[in] end End value of interval.
1034 * \param[in] period Period (or 0 if not periodic).
1035 * \returns true if the end point values are in the correct periodic interval.
1037 static bool intervalIsInPeriodicInterval(double origin, double end, double period)
1039 return (period == 0) || (std::fabs(origin) <= 0.5 * period && std::fabs(end) <= 0.5 * period);
1043 * Checks if a value is within an interval.
1045 * \param[in] origin Start value of interval.
1046 * \param[in] end End value of interval.
1047 * \param[in] period Period (or 0 if not periodic).
1048 * \param[in] value Value to check.
1049 * \returns true if the value is within the interval.
1051 static bool valueIsInInterval(double origin, double end, double period, double value)
1059 /* The interval closes within the periodic interval */
1060 bIn_interval = (value >= origin) && (value <= end);
1064 /* The interval wraps around the periodic boundary */
1065 bIn_interval = ((value >= origin) && (value <= 0.5 * period))
1066 || ((value >= -0.5 * period) && (value <= end));
1071 bIn_interval = (value >= origin) && (value <= end);
1074 return bIn_interval;
1078 * Check if the starting configuration is consistent with the given interval.
1080 * \param[in] awhParams AWH parameters.
1081 * \param[in,out] wi Struct for bookeeping warnings.
1083 static void checkInputConsistencyInterval(const AwhParams& awhParams, warninp_t wi)
1085 const auto& awhBiasParams = awhParams.awhBiasParams();
1086 for (int k = 0; k < gmx::ssize(awhBiasParams); k++)
1088 const auto& dimParams = awhBiasParams[k].dimParams();
1089 for (int d = 0; d < gmx::ssize(dimParams); d++)
1091 int coordIndex = dimParams[d].coordinateIndex();
1092 double origin = dimParams[d].origin(), end = dimParams[d].end(),
1093 period = dimParams[d].period();
1094 double coordValueInit = dimParams[d].initialCoordinate();
1096 if ((period == 0) && (origin > end))
1099 "For the non-periodic pull coordinates awh%d-dim%d-start (%f) cannot be "
1100 "larger than awh%d-dim%d-end (%f)",
1109 /* Currently we assume symmetric periodic intervals, meaning we use [-period/2, period/2] as the reference interval.
1110 Make sure the AWH interval is within this reference interval.
1112 Note: we could fairly simply allow using a more general interval (e.g. [x, x + period]) but it complicates
1113 things slightly and I don't see that there is a great need for it. It would also mean that the interval would
1114 depend on AWH input. Also, for dihedral angles you would always want the reference interval to be -180, +180,
1115 independent of AWH parameters.
1117 if (!intervalIsInPeriodicInterval(origin, end, period))
1120 "When using AWH with periodic pull coordinate geometries "
1121 "awh%d-dim%d-start (%.8g) and "
1122 "awh%d-dim%d-end (%.8g) should cover at most one period (%.8g) and take "
1123 "values in between "
1124 "minus half a period and plus half a period, i.e. in the interval [%.8g, "
1137 /* Warn if the pull initial coordinate value is not in the grid */
1138 if (!valueIsInInterval(origin, end, period, coordValueInit))
1140 auto message = formatString(
1141 "The initial coordinate value (%.8g) for pull coordinate index %d falls "
1143 "of the sampling nterval awh%d-dim%d-start (%.8g) to awh%d-dim%d-end "
1145 "This can lead to large initial forces pulling the coordinate towards the "
1146 "sampling interval.",
1155 warning(wi, message);
1162 * Sets AWH parameters, for one AWH pull dimension.
1164 * \param[in,out] dimParams AWH dimension parameters.
1165 * \param[in] biasIndex The index of the bias containing this AWH pull dimension.
1166 * \param[in] dimIndex The index of this AWH pull dimension.
1167 * \param[in] pull_params Pull parameters.
1168 * \param[in,out] pull_work Pull working struct to register AWH bias in.
1169 * \param[in] pbc A pbc information structure.
1170 * \param[in] compressibility Compressibility matrix for pressure coupling,
1171 * pass all 0 without pressure coupling.
1172 * \param[in,out] wi Struct for bookeeping warnings.
1175 static void setStateDependentAwhPullDimParams(AwhDimParams* dimParams,
1176 const int biasIndex,
1178 const pull_params_t& pull_params,
1181 const tensor& compressibility,
1184 const t_pull_coord& pullCoordParams = pull_params.coord[dimParams->coordinateIndex()];
1186 if (pullCoordParams.eGeom == PullGroupGeometry::DirectionPBC)
1189 "AWH does not support pull geometry '%s'. "
1190 "If the maximum distance between the groups is always "
1191 "less than half the box size, "
1192 "you can use geometry '%s' instead.",
1193 enumValueToString(PullGroupGeometry::DirectionPBC),
1194 enumValueToString(PullGroupGeometry::Direction));
1197 dimParams->setPeriod(
1198 get_pull_coord_period(pullCoordParams, pbc, dimParams->end() - dimParams->origin()));
1199 // We would like to check for scaling, but we don't have the full inputrec available here
1200 if (dimParams->period() > 0
1201 && !(pullCoordParams.eGeom == PullGroupGeometry::Angle
1202 || pullCoordParams.eGeom == PullGroupGeometry::Dihedral))
1204 bool coordIsScaled = false;
1205 for (int d2 = 0; d2 < DIM; d2++)
1207 if (pullCoordParams.vec[d2] != 0 && norm2(compressibility[d2]) != 0)
1209 coordIsScaled = true;
1214 std::string mesg = gmx::formatString(
1215 "AWH dimension %d of bias %d is periodic with pull geometry '%s', "
1216 "while you should be applying pressure scaling to the "
1217 "corresponding box vector, this is not supported.",
1220 enumValueToString(pullCoordParams.eGeom));
1221 warning(wi, mesg.c_str());
1225 /* The initial coordinate value, converted to external user units. */
1226 double initialCoordinate = get_pull_coord_value(pull_work, dimParams->coordinateIndex(), pbc);
1227 initialCoordinate *= pull_conversion_factor_internal2userinput(pullCoordParams);
1228 dimParams->setInitialCoordinate(initialCoordinate);
1231 void setStateDependentAwhParams(AwhParams* awhParams,
1232 const pull_params_t& pull_params,
1236 const tensor& compressibility,
1237 const t_grpopts* inputrecGroupOptions,
1238 const real initLambda,
1239 const gmx_mtop_t& mtop,
1242 /* The temperature is not really state depenendent but is not known
1243 * when read_awhParams is called (in get ir).
1244 * It is known first after do_index has been called in grompp.cpp.
1246 if (inputrecGroupOptions->ref_t == nullptr || inputrecGroupOptions->ref_t[0] <= 0)
1248 gmx_fatal(FARGS, "AWH biasing is only supported for temperatures > 0");
1250 for (int i = 1; i < inputrecGroupOptions->ngtc; i++)
1252 if (inputrecGroupOptions->ref_t[i] != inputrecGroupOptions->ref_t[0])
1255 "AWH biasing is currently only supported for identical temperatures for all "
1256 "temperature coupling groups");
1261 set_pbc(&pbc, pbcType, box);
1263 auto awhBiasParams = awhParams->awhBiasParams();
1264 for (int k = 0; k < awhParams->numBias(); k++)
1266 auto awhBiasDimensionParams = awhBiasParams[k].dimParams();
1267 for (int d = 0; d < awhBiasParams[k].ndim(); d++)
1269 AwhDimParams* dimParams = &awhBiasDimensionParams[d];
1270 if (dimParams->coordinateProvider() == AwhCoordinateProviderType::Pull)
1272 setStateDependentAwhPullDimParams(
1273 dimParams, k, d, pull_params, pull_work, pbc, compressibility, wi);
1277 dimParams->setInitialCoordinate(initLambda);
1278 checkFepLambdaDimDecouplingConsistency(mtop, wi);
1282 checkInputConsistencyInterval(*awhParams, wi);
1284 /* Register AWH as external potential with pull (for AWH dimensions that use pull as
1285 * reaction coordinate provider) to check consistency. */
1286 Awh::registerAwhWithPull(*awhParams, pull_work);
1289 void checkAwhParams(const AwhParams& awhParams, const t_inputrec& ir, warninp_t wi)
1292 checkMtsConsistency(ir, wi);
1295 if (awhParams.nstout() <= 0)
1297 auto message = formatString("Not writing AWH output with AWH (%s = %d) does not make sense",
1299 awhParams.nstout());
1300 warning_error(wi, message);
1302 /* This restriction can be removed by changing a flag of print_ebin() */
1303 if (ir.nstenergy == 0 || awhParams.nstout() % ir.nstenergy != 0)
1305 auto message = formatString(
1306 "%s (%d) should be a multiple of nstenergy (%d)", opt.c_str(), awhParams.nstout(), ir.nstenergy);
1307 warning_error(wi, message);
1310 opt = "awh-nsamples-update";
1311 if (awhParams.numSamplesUpdateFreeEnergy() <= 0)
1313 warning_error(wi, opt + " needs to be an integer > 0");
1316 bool haveFepLambdaDim = false;
1317 const auto awhBiasParams = awhParams.awhBiasParams();
1318 for (int k = 0; k < awhParams.numBias() && !haveFepLambdaDim; k++)
1320 std::string prefixawh = formatString("awh%d", k + 1);
1321 checkBiasParams(awhBiasParams[k], prefixawh, ir, wi);
1322 /* Check if there is a FEP lambda dimension. */
1323 const auto dimParams = awhBiasParams[k].dimParams();
1324 haveFepLambdaDim = std::any_of(dimParams.begin(), dimParams.end(), [](const auto& dimParam) {
1325 return dimParam.coordinateProvider() == AwhCoordinateProviderType::FreeEnergyLambda;
1329 if (haveFepLambdaDim)
1331 if (awhParams.nstSampleCoord() % ir.nstcalcenergy != 0)
1333 opt = "awh-nstsample";
1334 auto message = formatString(
1335 "%s (%d) should be a multiple of nstcalcenergy (%d) when using AWH for "
1336 "sampling an FEP lambda dimension",
1338 awhParams.nstSampleCoord(),
1340 warning_error(wi, message);
1342 if (awhParams.potential() != AwhPotentialType::Umbrella)
1344 opt = "awh-potential";
1345 auto message = formatString(
1346 "%s (%s) must be set to %s when using AWH for sampling an FEP lambda dimension",
1348 enumValueToString(awhParams.potential()),
1349 enumValueToString(AwhPotentialType::Umbrella));
1350 warning_error(wi, message);
1354 if (ir.init_step != 0)
1356 warning_error(wi, "With AWH init-step should be 0");
1360 bool awhHasFepLambdaDimension(const AwhParams& awhParams)
1362 for (const auto& biasParams : awhParams.awhBiasParams())
1364 for (const auto& dimParams : biasParams.dimParams())
1366 if (dimParams.coordinateProvider() == AwhCoordinateProviderType::FreeEnergyLambda)