Add lambda state to dhdl.xvg with AWH fep
[alexxy/gromacs.git] / src / gromacs / applied_forces / awh / read_params.cpp
index 8a4345959ff76b1bdf582b4147c7cb2cb13f97de..f2b1805209eb113ecb0642b9f1eb82b543d6668e 100644 (file)
  * To help us fund GROMACS development, we humbly ask that you cite
  * the research papers on the package. Check out http://www.gromacs.org.
  */
+
 #include "gmxpre.h"
 
 #include "read_params.h"
 
+#include <algorithm>
+
 #include "gromacs/applied_forces/awh/awh.h"
 #include "gromacs/fileio/readinp.h"
 #include "gromacs/fileio/warninp.h"
@@ -55,7 +58,9 @@
 #include "gromacs/random/seed.h"
 #include "gromacs/topology/mtop_util.h"
 #include "gromacs/utility/cstringutil.h"
+#include "gromacs/utility/enumerationhelpers.h"
 #include "gromacs/utility/fatalerror.h"
+#include "gromacs/utility/iserializer.h"
 #include "gromacs/utility/smalloc.h"
 #include "gromacs/utility/stringutil.h"
 
 namespace gmx
 {
 
-const char* eawhtarget_names[eawhtargetNR + 1] = { "constant", "cutoff", "boltzmann",
-                                                   "local-boltzmann", nullptr };
+const char* enumValueToString(AwhTargetType enumValue)
+{
+    constexpr gmx::EnumerationArray<AwhTargetType, const char*> awhTargetTypeNames = {
+        "constant", "cutoff", "boltzmann", "local-boltzmann"
+    };
+    return awhTargetTypeNames[enumValue];
+}
 
-const char* eawhgrowth_names[eawhgrowthNR + 1] = { "exp-linear", "linear", nullptr };
+const char* enumValueToString(AwhHistogramGrowthType enumValue)
+{
+    constexpr gmx::EnumerationArray<AwhHistogramGrowthType, const char*> awhHistogramGrowthTypeNames = {
+        "exp-linear", "linear"
+    };
+    return awhHistogramGrowthTypeNames[enumValue];
+}
 
-const char* eawhpotential_names[eawhpotentialNR + 1] = { "convolved", "umbrella", nullptr };
+const char* enumValueToString(AwhPotentialType enumValue)
+{
+    constexpr gmx::EnumerationArray<AwhPotentialType, const char*> awhPotentialTypeNames = {
+        "convolved", "umbrella"
+    };
+    return awhPotentialTypeNames[enumValue];
+}
 
-const char* eawhcoordprovider_names[eawhcoordproviderNR + 1] = { "pull", "fep-lambda", nullptr };
+const char* enumValueToString(AwhCoordinateProviderType enumValue)
+{
+    constexpr gmx::EnumerationArray<AwhCoordinateProviderType, const char*> awhCoordinateProviderTypeNames = {
+        "pull", "fep-lambda"
+    };
+    return awhCoordinateProviderTypeNames[enumValue];
+}
 
 namespace
 {
@@ -94,15 +122,14 @@ void checkMtsConsistency(const t_inputrec& inputrec, warninp_t wi)
 
     bool usesPull = false;
     bool usesFep  = false;
-    for (int b = 0; b < inputrec.awhParams->numBias; b++)
+    for (const auto& awhBiasParam : inputrec.awhParams->awhBiasParams())
     {
-        const auto& biasParams = inputrec.awhParams->awhBiasParams[b];
-        for (int d = 0; d < biasParams.ndim; d++)
+        for (const auto& dimParam : awhBiasParam.dimParams())
         {
-            switch (biasParams.dimParams[d].eCoordProvider)
+            switch (dimParam.coordinateProvider())
             {
-                case eawhcoordproviderPULL: usesPull = true; break;
-                case eawhcoordproviderFREE_ENERGY_LAMBDA: usesFep = true; break;
+                case AwhCoordinateProviderType::Pull: usesPull = true; break;
+                case AwhCoordinateProviderType::FreeEnergyLambda: usesFep = true; break;
                 default: GMX_RELEASE_ASSERT(false, "Unsupported coord provider");
             }
         }
@@ -121,7 +148,7 @@ void checkMtsConsistency(const t_inputrec& inputrec, warninp_t wi)
                       "computed at the slow MTS level");
     }
 
-    if (inputrec.awhParams->nstSampleCoord % inputrec.mtsLevels[awhMtsLevel].stepFactor != 0)
+    if (inputrec.awhParams->nstSampleCoord() % inputrec.mtsLevels[awhMtsLevel].stepFactor != 0)
     {
         warning_error(wi,
                       "With MTS applied to AWH, awh-nstsample should be a multiple of mts-factor");
@@ -137,82 +164,97 @@ void checkMtsConsistency(const t_inputrec& inputrec, warninp_t wi)
  * \param[in,out] wi         Struct for bookeeping warnings.
  */
 void checkPullDimParams(const std::string&   prefix,
-                        AwhDimParams*        dimParams,
+                        const AwhDimParams&  dimParams,
                         const pull_params_t& pull_params,
                         warninp_t            wi)
 {
-    if (dimParams->coordIndex < 0)
+    if (dimParams.coordinateIndex() < 0)
     {
         gmx_fatal(FARGS,
                   "Failed to read a valid coordinate index for %s-coord-index. "
                   "Note that the pull coordinate indexing starts at 1.",
                   prefix.c_str());
     }
-    if (dimParams->coordIndex >= pull_params.ncoord)
+    if (dimParams.coordinateIndex() >= pull_params.ncoord)
     {
         gmx_fatal(FARGS,
                   "The given AWH coordinate index (%d) is larger than the number of pull "
                   "coordinates (%d)",
-                  dimParams->coordIndex + 1, pull_params.ncoord);
+                  dimParams.coordinateIndex() + 1,
+                  pull_params.ncoord);
     }
-    if (pull_params.coord[dimParams->coordIndex].rate != 0)
+    if (pull_params.coord[dimParams.coordinateIndex()].rate != 0)
     {
         auto message = formatString(
                 "Setting pull-coord%d-rate (%g) is incompatible with AWH biasing this coordinate",
-                dimParams->coordIndex + 1, pull_params.coord[dimParams->coordIndex].rate);
+                dimParams.coordinateIndex() + 1,
+                pull_params.coord[dimParams.coordinateIndex()].rate);
         warning_error(wi, message);
     }
 
-    if (gmx_within_tol(dimParams->end - dimParams->origin, 0, GMX_REAL_EPS))
+    if (gmx_within_tol(dimParams.end() - dimParams.origin(), 0, GMX_REAL_EPS))
     {
         auto message = formatString(
                 "The given interval length given by %s-start (%g) and %s-end (%g) is zero. "
                 "This will result in only one point along this axis in the coordinate value grid.",
-                prefix.c_str(), dimParams->origin, prefix.c_str(), dimParams->end);
+                prefix.c_str(),
+                dimParams.origin(),
+                prefix.c_str(),
+                dimParams.end());
         warning(wi, message);
     }
 
-    if (dimParams->forceConstant <= 0)
+    if (dimParams.forceConstant() <= 0)
     {
         warning_error(wi, "The force AWH bias force constant should be > 0");
     }
 
     /* Grid params for each axis */
-    int eGeom = pull_params.coord[dimParams->coordIndex].eGeom;
+    PullGroupGeometry eGeom = pull_params.coord[dimParams.coordinateIndex()].eGeom;
 
     /* Check that the requested interval is in allowed range */
-    if (eGeom == epullgDIST)
+    if (eGeom == PullGroupGeometry::Distance)
     {
-        if (dimParams->origin < 0 || dimParams->end < 0)
+        if (dimParams.origin() < 0 || dimParams.end() < 0)
         {
             gmx_fatal(FARGS,
                       "%s-start (%g) or %s-end (%g) set to a negative value. With pull "
                       "geometry distance coordinate values are non-negative. "
                       "Perhaps you want to use geometry %s instead?",
-                      prefix.c_str(), dimParams->origin, prefix.c_str(), dimParams->end,
-                      EPULLGEOM(epullgDIR));
+                      prefix.c_str(),
+                      dimParams.origin(),
+                      prefix.c_str(),
+                      dimParams.end(),
+                      enumValueToString(PullGroupGeometry::Direction));
         }
     }
-    else if (eGeom == epullgANGLE || eGeom == epullgANGLEAXIS)
+    else if (eGeom == PullGroupGeometry::Angle || eGeom == PullGroupGeometry::AngleAxis)
     {
-        if (dimParams->origin < 0 || dimParams->end > 180)
+        if (dimParams.origin() < 0 || dimParams.end() > 180)
         {
             gmx_fatal(FARGS,
                       "%s-start (%g) and %s-end (%g) are outside of the allowed range "
                       "0 to 180 deg for pull geometries %s and %s ",
-                      prefix.c_str(), dimParams->origin, prefix.c_str(), dimParams->end,
-                      EPULLGEOM(epullgANGLE), EPULLGEOM(epullgANGLEAXIS));
+                      prefix.c_str(),
+                      dimParams.origin(),
+                      prefix.c_str(),
+                      dimParams.end(),
+                      enumValueToString(PullGroupGeometry::Angle),
+                      enumValueToString(PullGroupGeometry::AngleAxis));
         }
     }
-    else if (eGeom == epullgDIHEDRAL)
+    else if (eGeom == PullGroupGeometry::Dihedral)
     {
-        if (dimParams->origin < -180 || dimParams->end > 180)
+        if (dimParams.origin() < -180 || dimParams.end() > 180)
         {
             gmx_fatal(FARGS,
                       "%s-start (%g) and %s-end (%g) are outside of the allowed range "
                       "-180 to 180 deg for pull geometry %s. ",
-                      prefix.c_str(), dimParams->origin, prefix.c_str(), dimParams->end,
-                      EPULLGEOM(epullgDIHEDRAL));
+                      prefix.c_str(),
+                      dimParams.origin(),
+                      prefix.c_str(),
+                      dimParams.end(),
+                      enumValueToString(PullGroupGeometry::Dihedral));
         }
     }
 }
@@ -226,11 +268,11 @@ void checkPullDimParams(const std::string&   prefix,
  * \param[in] efep           This is the type of FEP calculation (efep enumerator).
  * \param[in,out] wi         Struct for bookeeping warnings.
  */
-void checkFepLambdaDimParams(const std::string&  prefix,
-                             const AwhDimParams* dimParams,
-                             const t_lambda*     lambdaParams,
-                             const int           efep,
-                             warninp_t           wi)
+void checkFepLambdaDimParams(const std::string&               prefix,
+                             const AwhDimParams&              dimParams,
+                             const t_lambda*                  lambdaParams,
+                             const FreeEnergyPerturbationType efep,
+                             warninp_t                        wi)
 {
     std::string opt;
 
@@ -247,49 +289,56 @@ void checkFepLambdaDimParams(const std::string&  prefix,
                   "When running AWH coupled to the free energy lambda state all lambda states "
                   "should be used as neighbors in order to get correct probabilities, i.e. "
                   "calc-lambda-neighbors (%d) must be %d.",
-                  lambdaParams->lambda_neighbors, -1);
+                  lambdaParams->lambda_neighbors,
+                  -1);
     }
 
-    if (efep == efepSLOWGROWTH || lambdaParams->delta_lambda != 0)
+    if (efep == FreeEnergyPerturbationType::SlowGrowth || lambdaParams->delta_lambda != 0)
     {
         gmx_fatal(FARGS,
                   "AWH coupled to the free energy lambda state is not compatible with slow-growth "
                   "and delta-lambda must be 0.");
     }
 
-    if (efep == efepEXPANDED)
+    if (efep == FreeEnergyPerturbationType::Expanded)
     {
         gmx_fatal(FARGS,
                   "AWH is not treated like other expanded ensemble methods. Do not use expanded.");
     }
 
-    if (dimParams->origin < 0)
+    if (dimParams.origin() < 0)
     {
         opt = prefix + "-start";
         gmx_fatal(FARGS,
                   "When running AWH coupled to the free energy lambda state the lower lambda state "
                   "for AWH, %s (%.0f), must be >= 0.",
-                  opt.c_str(), dimParams->origin);
+                  opt.c_str(),
+                  dimParams.origin());
     }
-    if (dimParams->end >= lambdaParams->n_lambda)
+    if (dimParams.end() >= lambdaParams->n_lambda)
     {
         opt = prefix + "-end";
         gmx_fatal(FARGS,
                   "When running AWH coupled to the free energy lambda state the upper lambda state "
                   "for AWH, %s (%.0f), must be < n_lambda (%d).",
-                  opt.c_str(), dimParams->origin, lambdaParams->n_lambda);
+                  opt.c_str(),
+                  dimParams.origin(),
+                  lambdaParams->n_lambda);
     }
-    if (gmx_within_tol(dimParams->end - dimParams->origin, 0, GMX_REAL_EPS))
+    if (gmx_within_tol(dimParams.end() - dimParams.origin(), 0, GMX_REAL_EPS))
     {
         auto message = formatString(
                 "The given interval length given by %s-start (%g) and %s-end (%g) is zero. "
                 "This will result in only one lambda point along this free energy lambda state "
                 "axis in the coordinate value grid.",
-                prefix.c_str(), dimParams->origin, prefix.c_str(), dimParams->end);
+                prefix.c_str(),
+                dimParams.origin(),
+                prefix.c_str(),
+                dimParams.end());
         warning(wi, message);
     }
 
-    if (dimParams->forceConstant != 0)
+    if (dimParams.forceConstant() != 0)
     {
         warning_error(
                 wi,
@@ -322,78 +371,6 @@ void checkFepLambdaDimDecouplingConsistency(const gmx_mtop_t& mtop, warninp_t wi
     }
 }
 
-/*! \brief
- * Read parameters of an AWH bias dimension.
- *
- * \param[in,out] inp        Input file entries.
- * \param[in] prefix         Prefix for dimension parameters.
- * \param[in,out] dimParams  AWH dimensional parameters.
- * \param[in,out] wi         Struct for bookeeping warnings.
- * \param[in] bComment       True if comments should be printed.
- */
-void readDimParams(std::vector<t_inpfile>* inp,
-                   const std::string&      prefix,
-                   AwhDimParams*           dimParams,
-                   warninp_t               wi,
-                   bool                    bComment)
-{
-    std::string opt;
-    if (bComment)
-    {
-        printStringNoNewline(
-                inp,
-                "The provider of the reaction coordinate, "
-                "currently only 'pull' and 'fep-lambda' (free energy lambda state) is supported");
-    }
-    opt                       = prefix + "-coord-provider";
-    dimParams->eCoordProvider = get_eeenum(inp, opt, eawhcoordprovider_names, wi);
-
-    if (bComment)
-    {
-        printStringNoNewline(inp, "The coordinate index for this dimension");
-    }
-    opt = prefix + "-coord-index";
-    int coordIndexInput;
-    coordIndexInput = get_eint(inp, opt, 1, wi);
-
-    /* The pull coordinate indices start at 1 in the input file, at 0 internally */
-    dimParams->coordIndex = coordIndexInput - 1;
-
-    if (bComment)
-    {
-        printStringNoNewline(inp, "Start and end values for each coordinate dimension");
-    }
-    opt               = prefix + "-start";
-    dimParams->origin = get_ereal(inp, opt, 0., wi);
-    opt               = prefix + "-end";
-    dimParams->end    = get_ereal(inp, opt, 0., wi);
-
-    if (bComment)
-    {
-        printStringNoNewline(
-                inp, "The force constant for this coordinate (kJ/mol/nm^2 or kJ/mol/rad^2)");
-    }
-    opt                      = prefix + "-force-constant";
-    dimParams->forceConstant = get_ereal(inp, opt, 0, wi);
-
-    if (bComment)
-    {
-        printStringNoNewline(inp, "Estimated diffusion constant (nm^2/ps, rad^2/ps or ps^-1)");
-    }
-    opt                  = prefix + "-diffusion";
-    dimParams->diffusion = get_ereal(inp, opt, 0, wi);
-
-    if (bComment)
-    {
-        printStringNoNewline(inp,
-                             "Diameter that needs to be sampled around a point before it is "
-                             "considered covered. In FEP dimensions the cover diameter is "
-                             "specified in lambda states.");
-    }
-    opt                      = prefix + "-cover-diameter";
-    dimParams->coverDiameter = get_ereal(inp, opt, 0, wi);
-}
-
 /*! \brief
  * Check the parameters of an AWH bias dimension.
  *
@@ -402,27 +379,27 @@ void readDimParams(std::vector<t_inpfile>* inp,
  * \param[in] ir             Input parameter struct.
  * \param[in,out] wi         Struct for bookeeping warnings.
  */
-void checkDimParams(const std::string& prefix, AwhDimParams* dimParams, const t_inputrec* ir, warninp_t wi)
+void checkDimParams(const std::string& prefix, const AwhDimParams& dimParams, const t_inputrec& ir, warninp_t wi)
 {
-    if (dimParams->eCoordProvider == eawhcoordproviderPULL)
+    if (dimParams.coordinateProvider() == AwhCoordinateProviderType::Pull)
     {
-        if (!ir->bPull)
+        if (!ir.bPull)
         {
             gmx_fatal(FARGS,
                       "AWH biasing along a pull dimension is only compatible with COM pulling "
                       "turned on");
         }
-        checkPullDimParams(prefix, dimParams, *ir->pull, wi);
+        checkPullDimParams(prefix, dimParams, *ir.pull, wi);
     }
-    else if (dimParams->eCoordProvider == eawhcoordproviderFREE_ENERGY_LAMBDA)
+    else if (dimParams.coordinateProvider() == AwhCoordinateProviderType::FreeEnergyLambda)
     {
-        if (ir->efep == efepNO)
+        if (ir.efep == FreeEnergyPerturbationType::No)
         {
             gmx_fatal(FARGS,
                       "AWH biasing along a free energy lambda state dimension is only compatible "
                       "with free energy turned on");
         }
-        checkFepLambdaDimParams(prefix, dimParams, ir->fepvals, ir->efep, wi);
+        checkFepLambdaDimParams(prefix, dimParams, ir.fepvals.get(), ir.efep, wi);
     }
     else
     {
@@ -432,127 +409,6 @@ void checkDimParams(const std::string& prefix, AwhDimParams* dimParams, const t_
     }
 }
 
-/*! \brief
- * Check consistency of input at the AWH bias level.
- *
- * \param[in]     awhBiasParams  AWH bias parameters.
- * \param[in,out] wi             Struct for bookkeeping warnings.
- */
-void checkInputConsistencyAwhBias(const AwhBiasParams& awhBiasParams, warninp_t wi)
-{
-    /* Covering diameter and sharing warning. */
-    for (int d = 0; d < awhBiasParams.ndim; d++)
-    {
-        double coverDiameter = awhBiasParams.dimParams[d].coverDiameter;
-        if (awhBiasParams.shareGroup <= 0 && coverDiameter > 0)
-        {
-            warning(wi,
-                    "The covering diameter is only relevant to set for bias sharing simulations.");
-        }
-    }
-}
-
-/*! \brief
- * Read parameters of an AWH bias.
- *
- * \param[in,out] inp            Input file entries.
- * \param[in,out] awhBiasParams  AWH dimensional parameters.
- * \param[in]     prefix         Prefix for bias parameters.
- * \param[in,out] wi             Struct for bookeeping warnings.
- * \param[in]     bComment       True if comments should be printed.
- */
-void readBiasParams(std::vector<t_inpfile>* inp,
-                    AwhBiasParams*          awhBiasParams,
-                    const std::string&      prefix,
-                    warninp_t               wi,
-                    bool                    bComment)
-{
-    if (bComment)
-    {
-        printStringNoNewline(inp, "Estimated initial PMF error (kJ/mol)");
-    }
-
-    std::string opt             = prefix + "-error-init";
-    awhBiasParams->errorInitial = get_ereal(inp, opt, 10, wi);
-
-    if (bComment)
-    {
-        printStringNoNewline(inp,
-                             "Growth rate of the reference histogram determining the bias update "
-                             "size: exp-linear or linear");
-    }
-    opt                    = prefix + "-growth";
-    awhBiasParams->eGrowth = get_eeenum(inp, opt, eawhgrowth_names, wi);
-
-    if (bComment)
-    {
-        printStringNoNewline(inp,
-                             "Start the simulation by equilibrating histogram towards the target "
-                             "distribution: no or yes");
-    }
-    opt                                 = prefix + "-equilibrate-histogram";
-    awhBiasParams->equilibrateHistogram = (get_eeenum(inp, opt, yesno_names, wi) != 0);
-
-    if (bComment)
-    {
-        printStringNoNewline(
-                inp, "Target distribution type: constant, cutoff, boltzmann or local-boltzmann");
-    }
-    opt                    = prefix + "-target";
-    awhBiasParams->eTarget = get_eeenum(inp, opt, eawhtarget_names, wi);
-
-    if (bComment)
-    {
-        printStringNoNewline(inp,
-                             "Boltzmann beta scaling factor for target distribution types "
-                             "'boltzmann' and 'boltzmann-local'");
-    }
-    opt                              = prefix + "-target-beta-scaling";
-    awhBiasParams->targetBetaScaling = get_ereal(inp, opt, 0, wi);
-
-    if (bComment)
-    {
-        printStringNoNewline(inp, "Free energy cutoff value for target distribution type 'cutoff'");
-    }
-    opt                         = prefix + "-target-cutoff";
-    awhBiasParams->targetCutoff = get_ereal(inp, opt, 0, wi);
-
-    if (bComment)
-    {
-        printStringNoNewline(inp, "Initialize PMF and target with user data: no or yes");
-    }
-    opt                      = prefix + "-user-data";
-    awhBiasParams->bUserData = get_eeenum(inp, opt, yesno_names, wi);
-
-    if (bComment)
-    {
-        printStringNoNewline(inp, "Group index to share the bias with, 0 means not shared");
-    }
-    opt                       = prefix + "-share-group";
-    awhBiasParams->shareGroup = get_eint(inp, opt, 0, wi);
-
-    if (bComment)
-    {
-        printStringNoNewline(inp, "Dimensionality of the coordinate");
-    }
-    opt                 = prefix + "-ndim";
-    awhBiasParams->ndim = get_eint(inp, opt, 0, wi);
-
-    /* Check this before starting to read the AWH dimension parameters. */
-    if (awhBiasParams->ndim <= 0 || awhBiasParams->ndim > c_biasMaxNumDim)
-    {
-        gmx_fatal(FARGS, "%s (%d) needs to be > 0 and at most %d\n", opt.c_str(),
-                  awhBiasParams->ndim, c_biasMaxNumDim);
-    }
-    snew(awhBiasParams->dimParams, awhBiasParams->ndim);
-    for (int d = 0; d < awhBiasParams->ndim; d++)
-    {
-        bComment              = bComment && d == 0;
-        std::string prefixdim = prefix + formatString("-dim%d", d + 1);
-        readDimParams(inp, prefixdim, &awhBiasParams->dimParams[d], wi, bComment);
-    }
-}
-
 /*! \brief
  * Check the parameters of an AWH bias.
  *
@@ -561,106 +417,135 @@ void readBiasParams(std::vector<t_inpfile>* inp,
  * \param[in]     ir             Input parameter struct.
  * \param[in,out] wi             Struct for bookeeping warnings.
  */
-void checkBiasParams(const AwhBiasParams* awhBiasParams, const std::string& prefix, const t_inputrec* ir, warninp_t wi)
+void checkBiasParams(const AwhBiasParams& awhBiasParams, const std::string& prefix, const t_inputrec& ir, warninp_t wi)
 {
     std::string opt = prefix + "-error-init";
-    if (awhBiasParams->errorInitial <= 0)
+    if (awhBiasParams.initialErrorEstimate() <= 0)
     {
         gmx_fatal(FARGS, "%s needs to be > 0.", opt.c_str());
     }
 
     opt = prefix + "-equilibrate-histogram";
-    if (awhBiasParams->equilibrateHistogram && awhBiasParams->eGrowth != eawhgrowthEXP_LINEAR)
+    if (awhBiasParams.equilibrateHistogram()
+        && awhBiasParams.growthType() != AwhHistogramGrowthType::ExponentialLinear)
     {
         auto message =
                 formatString("Option %s will only have an effect for histogram growth type '%s'.",
-                             opt.c_str(), EAWHGROWTH(eawhgrowthEXP_LINEAR));
+                             opt.c_str(),
+                             enumValueToString(AwhHistogramGrowthType::ExponentialLinear));
         warning(wi, message);
     }
 
-    if ((awhBiasParams->eTarget == eawhtargetLOCALBOLTZMANN)
-        && (awhBiasParams->eGrowth == eawhgrowthEXP_LINEAR))
+    if ((awhBiasParams.targetDistribution() == AwhTargetType::LocalBoltzmann)
+        && (awhBiasParams.growthType() == AwhHistogramGrowthType::ExponentialLinear))
     {
         auto message = formatString(
                 "Target type '%s' combined with histogram growth type '%s' is not "
                 "expected to give stable bias updates. You probably want to use growth type "
                 "'%s' instead.",
-                EAWHTARGET(eawhtargetLOCALBOLTZMANN), EAWHGROWTH(eawhgrowthEXP_LINEAR),
-                EAWHGROWTH(eawhgrowthLINEAR));
+                enumValueToString(AwhTargetType::LocalBoltzmann),
+                enumValueToString(AwhHistogramGrowthType::ExponentialLinear),
+                enumValueToString(AwhHistogramGrowthType::Linear));
         warning(wi, message);
     }
 
     opt = prefix + "-target-beta-scaling";
-    switch (awhBiasParams->eTarget)
+    switch (awhBiasParams.targetDistribution())
     {
-        case eawhtargetBOLTZMANN:
-        case eawhtargetLOCALBOLTZMANN:
-            if (awhBiasParams->targetBetaScaling < 0 || awhBiasParams->targetBetaScaling > 1)
+        case AwhTargetType::Boltzmann:
+        case AwhTargetType::LocalBoltzmann:
+            if (awhBiasParams.targetBetaScaling() < 0 || awhBiasParams.targetBetaScaling() > 1)
             {
-                gmx_fatal(FARGS, "%s = %g is not useful for target type %s.", opt.c_str(),
-                          awhBiasParams->targetBetaScaling, EAWHTARGET(awhBiasParams->eTarget));
+                gmx_fatal(FARGS,
+                          "%s = %g is not useful for target type %s.",
+                          opt.c_str(),
+                          awhBiasParams.targetBetaScaling(),
+                          enumValueToString(awhBiasParams.targetDistribution()));
             }
             break;
         default:
-            if (awhBiasParams->targetBetaScaling != 0)
+            if (awhBiasParams.targetBetaScaling() != 0)
             {
                 gmx_fatal(
                         FARGS,
                         "Value for %s (%g) set explicitly but will not be used for target type %s.",
-                        opt.c_str(), awhBiasParams->targetBetaScaling,
-                        EAWHTARGET(awhBiasParams->eTarget));
+                        opt.c_str(),
+                        awhBiasParams.targetBetaScaling(),
+                        enumValueToString(awhBiasParams.targetDistribution()));
             }
             break;
     }
 
     opt = prefix + "-target-cutoff";
-    switch (awhBiasParams->eTarget)
+    switch (awhBiasParams.targetDistribution())
     {
-        case eawhtargetCUTOFF:
-            if (awhBiasParams->targetCutoff <= 0)
+        case AwhTargetType::Cutoff:
+            if (awhBiasParams.targetCutoff() <= 0)
             {
-                gmx_fatal(FARGS, "%s = %g is not useful for target type %s.", opt.c_str(),
-                          awhBiasParams->targetCutoff, EAWHTARGET(awhBiasParams->eTarget));
+                gmx_fatal(FARGS,
+                          "%s = %g is not useful for target type %s.",
+                          opt.c_str(),
+                          awhBiasParams.targetCutoff(),
+                          enumValueToString(awhBiasParams.targetDistribution()));
             }
             break;
         default:
-            if (awhBiasParams->targetCutoff != 0)
+            if (awhBiasParams.targetCutoff() != 0)
             {
                 gmx_fatal(
                         FARGS,
                         "Value for %s (%g) set explicitly but will not be used for target type %s.",
-                        opt.c_str(), awhBiasParams->targetCutoff, EAWHTARGET(awhBiasParams->eTarget));
+                        opt.c_str(),
+                        awhBiasParams.targetCutoff(),
+                        enumValueToString(awhBiasParams.targetDistribution()));
             }
             break;
     }
 
     opt = prefix + "-share-group";
-    if (awhBiasParams->shareGroup < 0)
+    if (awhBiasParams.shareGroup() < 0)
     {
         warning_error(wi, "AWH bias share-group should be >= 0");
     }
 
     opt = prefix + "-ndim";
-    if (awhBiasParams->ndim <= 0 || awhBiasParams->ndim > c_biasMaxNumDim)
+    if (awhBiasParams.ndim() <= 0 || awhBiasParams.ndim() > c_biasMaxNumDim)
     {
-        gmx_fatal(FARGS, "%s (%d) needs to be > 0 and at most %d\n", opt.c_str(),
-                  awhBiasParams->ndim, c_biasMaxNumDim);
+        gmx_fatal(FARGS, "%s (%d) needs to be > 0 and at most %d\n", opt.c_str(), awhBiasParams.ndim(), c_biasMaxNumDim);
     }
-    if (awhBiasParams->ndim > 2)
+    if (awhBiasParams.ndim() > 2)
     {
         warning_note(wi,
                      "For awh-dim > 2 the estimate based on the diffusion and the initial error is "
                      "currently only a rough guideline."
                      " You should verify its usefulness for your system before production runs!");
     }
-    for (int d = 0; d < awhBiasParams->ndim; d++)
+    for (int d = 0; d < awhBiasParams.ndim(); d++)
     {
         std::string prefixdim = prefix + formatString("-dim%d", d + 1);
-        checkDimParams(prefixdim, &awhBiasParams->dimParams[d], ir, wi);
+        checkDimParams(prefixdim, awhBiasParams.dimParams()[d], ir, wi);
     }
+}
 
-    /* Check consistencies here that cannot be checked at read time at a lower level. */
-    checkInputConsistencyAwhBias(*awhBiasParams, wi);
+/*! \brief
+ * Check consistency of input at the AWH bias level.
+ *
+ * \param[in]     awhBiasParams  AWH bias parameters.
+ * \param[in,out] wi             Struct for bookkeeping warnings.
+ */
+void checkInputConsistencyAwhBias(const AwhBiasParams& awhBiasParams, warninp_t wi)
+{
+    /* Covering diameter and sharing warning. */
+    auto awhBiasDimensionParams = awhBiasParams.dimParams();
+    for (const auto& dimensionParam : awhBiasDimensionParams)
+    {
+        double coverDiameter = dimensionParam.coverDiameter();
+        if (awhBiasParams.shareGroup() <= 0 && coverDiameter > 0)
+        {
+            warning(wi,
+                    "The covering diameter is only relevant to set for bias sharing simulations.");
+        }
+    }
 }
 
 /*! \brief
@@ -675,36 +560,38 @@ void checkInputConsistencyAwh(const AwhParams& awhParams, warninp_t wi)
      * Check that we have a shared bias when requesting multisim sharing.
      */
     bool haveSharedBias = false;
-    for (int k1 = 0; k1 < awhParams.numBias; k1++)
+    auto awhBiasParams  = awhParams.awhBiasParams();
+    for (int k1 = 0; k1 < awhParams.numBias(); k1++)
     {
-        const AwhBiasParams& awhBiasParams1 = awhParams.awhBiasParams[k1];
+        const AwhBiasParams& awhBiasParams1 = awhBiasParams[k1];
 
-        if (awhBiasParams1.shareGroup > 0)
+        if (awhBiasParams1.shareGroup() > 0)
         {
             haveSharedBias = true;
         }
 
         /* k1 is the reference AWH, k2 is the AWH we compare with (can be equal to k1) */
-        for (int k2 = k1; k2 < awhParams.numBias; k2++)
+        for (int k2 = k1; k2 < awhParams.numBias(); k2++)
         {
-            for (int d1 = 0; d1 < awhBiasParams1.ndim; d1++)
+            const AwhBiasParams& awhBiasParams2 = awhBiasParams[k2];
+            const auto&          dimParams1     = awhBiasParams1.dimParams();
+            const auto&          dimParams2     = awhBiasParams2.dimParams();
+            for (int d1 = 0; d1 < gmx::ssize(dimParams1); d1++)
             {
-                if (awhBiasParams1.dimParams[d1].eCoordProvider == eawhcoordproviderFREE_ENERGY_LAMBDA)
+                if (dimParams1[d1].coordinateProvider() == AwhCoordinateProviderType::FreeEnergyLambda)
                 {
                     continue;
                 }
-                const AwhBiasParams& awhBiasParams2 = awhParams.awhBiasParams[k2];
-
                 /* d1 is the reference dimension of the reference AWH. d2 is the dim index of the AWH to compare with. */
-                for (int d2 = 0; d2 < awhBiasParams2.ndim; d2++)
+                for (int d2 = 0; d2 < gmx::ssize(dimParams2); d2++)
                 {
-                    if (awhBiasParams2.dimParams[d2].eCoordProvider == eawhcoordproviderFREE_ENERGY_LAMBDA)
+                    if (dimParams2[d2].coordinateProvider() == AwhCoordinateProviderType::FreeEnergyLambda)
                     {
                         continue;
                     }
                     /* Give an error if (d1, k1) is different from (d2, k2) but the pull coordinate is the same */
                     if ((d1 != d2 || k1 != k2)
-                        && (awhBiasParams1.dimParams[d1].coordIndex == awhBiasParams2.dimParams[d2].coordIndex))
+                        && (dimParams1[d1].coordinateIndex() == dimParams2[d2].coordinateIndex()))
                     {
                         char errormsg[STRLEN];
                         sprintf(errormsg,
@@ -712,7 +599,10 @@ void checkInputConsistencyAwh(const AwhParams& awhParams, warninp_t wi)
                                 "dimensions (awh%d-dim%d and awh%d-dim%d). "
                                 "If this is really what you want to do you will have to duplicate "
                                 "this pull coordinate.",
-                                awhBiasParams1.dimParams[d1].coordIndex + 1, k1 + 1, d1 + 1, k2 + 1,
+                                dimParams1[d1].coordinateIndex() + 1,
+                                k1 + 1,
+                                d1 + 1,
+                                k2 + 1,
                                 d2 + 1);
                         gmx_fatal(FARGS, "%s", errormsg);
                     }
@@ -721,7 +611,7 @@ void checkInputConsistencyAwh(const AwhParams& awhParams, warninp_t wi)
         }
     }
 
-    if (awhParams.shareBiasMultisim && !haveSharedBias)
+    if (awhParams.shareBiasMultisim() && !haveSharedBias)
     {
         warning(wi,
                 "Sharing of biases over multiple simulations is requested, but no bias is marked "
@@ -736,142 +626,354 @@ void checkInputConsistencyAwh(const AwhParams& awhParams, warninp_t wi)
                 "this (yet)");
     }
 }
+
 } // namespace
 
-AwhParams* readAwhParams(std::vector<t_inpfile>* inp, warninp_t wi)
+AwhDimParams::AwhDimParams(std::vector<t_inpfile>* inp, const std::string& prefix, warninp_t wi, bool bComment)
+{
+    std::string opt;
+    if (bComment)
+    {
+        printStringNoNewline(
+                inp,
+                "The provider of the reaction coordinate, "
+                "currently only 'pull' and 'fep-lambda' (free energy lambda state) is supported");
+    }
+
+    opt             = prefix + "-coord-provider";
+    eCoordProvider_ = getEnum<AwhCoordinateProviderType>(inp, opt.c_str(), wi);
+
+    if (bComment)
+    {
+        printStringNoNewline(inp, "The coordinate index for this dimension");
+    }
+    opt = prefix + "-coord-index";
+    int coordIndexInput;
+    coordIndexInput = get_eint(inp, opt, 1, wi);
+    if (coordIndexInput < 1)
+    {
+        gmx_fatal(FARGS,
+                  "Failed to read a valid coordinate index for %s. "
+                  "Note that the pull coordinate indexing starts at 1.",
+                  opt.c_str());
+    }
+
+    /* The pull coordinate indices start at 1 in the input file, at 0 internally */
+    coordIndex_ = coordIndexInput - 1;
+
+    if (bComment)
+    {
+        printStringNoNewline(inp, "Start and end values for each coordinate dimension");
+    }
+
+    opt     = prefix + "-start";
+    origin_ = get_ereal(inp, opt, 0., wi);
+
+    opt  = prefix + "-end";
+    end_ = get_ereal(inp, opt, 0., wi);
+
+    if (bComment)
+    {
+        printStringNoNewline(
+                inp, "The force constant for this coordinate (kJ/mol/nm^2 or kJ/mol/rad^2)");
+    }
+    opt            = prefix + "-force-constant";
+    forceConstant_ = get_ereal(inp, opt, 0, wi);
+
+    if (bComment)
+    {
+        printStringNoNewline(inp, "Estimated diffusion constant (nm^2/ps or rad^2/ps or ps^-1)");
+    }
+    opt                   = prefix + "-diffusion";
+    double diffusionValue = get_ereal(inp, opt, 0, wi);
+    if (diffusionValue <= 0)
+    {
+        const double diffusion_default = 1e-5;
+        auto         message           = formatString(
+                "%s not explicitly set by user. You can choose to use a default "
+                "value (%g nm^2/ps or rad^2/ps) but this may very well be "
+                "non-optimal for your system!",
+                opt.c_str(),
+                diffusion_default);
+        warning(wi, message);
+        diffusionValue = diffusion_default;
+    }
+    diffusion_ = diffusionValue;
+
+    if (bComment)
+    {
+        printStringNoNewline(inp,
+                             "Diameter that needs to be sampled around a point before it is "
+                             "considered covered. In FEP dimensions the cover diameter is "
+                             "specified in lambda states.");
+    }
+    opt            = prefix + "-cover-diameter";
+    coverDiameter_ = get_ereal(inp, opt, 0, wi);
+}
+
+AwhDimParams::AwhDimParams(ISerializer* serializer)
+{
+    GMX_RELEASE_ASSERT(serializer->reading(),
+                       "Can not use writing serializer for creating datastructure");
+    serializer->doEnumAsInt(&eCoordProvider_);
+    serializer->doInt(&coordIndex_);
+    serializer->doDouble(&origin_);
+    serializer->doDouble(&end_);
+    serializer->doDouble(&period_);
+    serializer->doDouble(&forceConstant_);
+    serializer->doDouble(&diffusion_);
+    serializer->doDouble(&coordValueInit_);
+    serializer->doDouble(&coverDiameter_);
+}
+
+void AwhDimParams::serialize(ISerializer* serializer)
+{
+    GMX_RELEASE_ASSERT(!serializer->reading(),
+                       "Can not use reading serializer for writing datastructure");
+    serializer->doEnumAsInt(&eCoordProvider_);
+    serializer->doInt(&coordIndex_);
+    serializer->doDouble(&origin_);
+    serializer->doDouble(&end_);
+    serializer->doDouble(&period_);
+    serializer->doDouble(&forceConstant_);
+    serializer->doDouble(&diffusion_);
+    serializer->doDouble(&coordValueInit_);
+    serializer->doDouble(&coverDiameter_);
+}
+
+AwhBiasParams::AwhBiasParams(std::vector<t_inpfile>* inp, const std::string& prefix, warninp_t wi, bool bComment)
+{
+    if (bComment)
+    {
+        printStringNoNewline(inp, "Estimated initial PMF error (kJ/mol)");
+    }
+
+    std::string opt = prefix + "-error-init";
+    errorInitial_   = get_ereal(inp, opt, 10, wi);
+
+    if (bComment)
+    {
+        printStringNoNewline(inp,
+                             "Growth rate of the reference histogram determining the bias update "
+                             "size: exp-linear or linear");
+    }
+    opt      = prefix + "-growth";
+    eGrowth_ = getEnum<AwhHistogramGrowthType>(inp, opt.c_str(), wi);
+
+    if (bComment)
+    {
+        printStringNoNewline(inp,
+                             "Start the simulation by equilibrating histogram towards the target "
+                             "distribution: no or yes");
+    }
+    opt                   = prefix + "-equilibrate-histogram";
+    equilibrateHistogram_ = (getEnum<Boolean>(inp, opt.c_str(), wi) != Boolean::No);
+
+    if (bComment)
+    {
+        printStringNoNewline(
+                inp, "Target distribution type: constant, cutoff, boltzmann or local-boltzmann");
+    }
+    opt      = prefix + "-target";
+    eTarget_ = getEnum<AwhTargetType>(inp, opt.c_str(), wi);
+
+    if (bComment)
+    {
+        printStringNoNewline(inp,
+                             "Boltzmann beta scaling factor for target distribution types "
+                             "'boltzmann' and 'boltzmann-local'");
+    }
+    opt                = prefix + "-target-beta-scaling";
+    targetBetaScaling_ = get_ereal(inp, opt, 0, wi);
+
+    if (bComment)
+    {
+        printStringNoNewline(inp, "Free energy cutoff value for target distribution type 'cutoff'");
+    }
+    opt           = prefix + "-target-cutoff";
+    targetCutoff_ = get_ereal(inp, opt, 0, wi);
+
+    if (bComment)
+    {
+        printStringNoNewline(inp, "Initialize PMF and target with user data: no or yes");
+    }
+    opt        = prefix + "-user-data";
+    bUserData_ = getEnum<Boolean>(inp, opt.c_str(), wi) != Boolean::No;
+
+    if (bComment)
+    {
+        printStringNoNewline(inp, "Group index to share the bias with, 0 means not shared");
+    }
+    opt         = prefix + "-share-group";
+    shareGroup_ = get_eint(inp, opt, 0, wi);
+
+    if (bComment)
+    {
+        printStringNoNewline(inp, "Dimensionality of the coordinate");
+    }
+    opt      = prefix + "-ndim";
+    int ndim = get_eint(inp, opt, 0, wi);
+
+    /* Check this before starting to read the AWH dimension parameters. */
+    if (ndim <= 0 || ndim > c_biasMaxNumDim)
+    {
+        gmx_fatal(FARGS, "%s (%d) needs to be > 0 and at most %d\n", opt.c_str(), ndim, c_biasMaxNumDim);
+    }
+    for (int d = 0; d < ndim; d++)
+    {
+        bComment              = bComment && d == 0;
+        std::string prefixdim = prefix + formatString("-dim%d", d + 1);
+        dimParams_.emplace_back(inp, prefixdim, wi, bComment);
+    }
+}
+
+AwhBiasParams::AwhBiasParams(ISerializer* serializer)
+{
+    GMX_RELEASE_ASSERT(serializer->reading(),
+                       "Can not use writing serializer to create datastructure");
+    serializer->doEnumAsInt(&eTarget_);
+    serializer->doDouble(&targetBetaScaling_);
+    serializer->doDouble(&targetCutoff_);
+    serializer->doEnumAsInt(&eGrowth_);
+    int temp = 0;
+    serializer->doInt(&temp);
+    bUserData_ = static_cast<bool>(temp);
+    serializer->doDouble(&errorInitial_);
+    int numDimensions = dimParams_.size();
+    serializer->doInt(&numDimensions);
+    serializer->doInt(&shareGroup_);
+    serializer->doBool(&equilibrateHistogram_);
+
+    for (int k = 0; k < numDimensions; k++)
+    {
+        dimParams_.emplace_back(serializer);
+    }
+    /* Check consistencies here that cannot be checked at read time at a lower level. */
+    checkInputConsistencyAwhBias(*this, nullptr);
+}
+
+void AwhBiasParams::serialize(ISerializer* serializer)
+{
+    GMX_RELEASE_ASSERT(!serializer->reading(),
+                       "Can not use reading serializer to write datastructure");
+    serializer->doEnumAsInt(&eTarget_);
+    serializer->doDouble(&targetBetaScaling_);
+    serializer->doDouble(&targetCutoff_);
+    serializer->doEnumAsInt(&eGrowth_);
+    int temp = static_cast<int>(bUserData_);
+    serializer->doInt(&temp);
+    serializer->doDouble(&errorInitial_);
+    int numDimensions = ndim();
+    serializer->doInt(&numDimensions);
+    serializer->doInt(&shareGroup_);
+    serializer->doBool(&equilibrateHistogram_);
+
+    for (int k = 0; k < numDimensions; k++)
+    {
+        dimParams_[k].serialize(serializer);
+    }
+}
+
+AwhParams::AwhParams(std::vector<t_inpfile>* inp, warninp_t wi)
 {
-    AwhParams* awhParams;
-    snew(awhParams, 1);
     std::string opt;
 
     /* Parameters common for all biases */
 
     printStringNoNewline(inp, "The way to apply the biasing potential: convolved or umbrella");
-    opt                   = "awh-potential";
-    awhParams->ePotential = get_eeenum(inp, opt, eawhpotential_names, wi);
+    opt            = "awh-potential";
+    potentialEnum_ = getEnum<AwhPotentialType>(inp, opt.c_str(), wi);
 
     printStringNoNewline(inp,
                          "The random seed used for sampling the umbrella center in the case of "
                          "umbrella type potential");
-    opt             = "awh-seed";
-    awhParams->seed = get_eint(inp, opt, -1, wi);
-    if (awhParams->seed == -1)
+    opt   = "awh-seed";
+    seed_ = get_eint(inp, opt, -1, wi);
+    if (seed_ == -1)
     {
-        awhParams->seed = static_cast<int>(gmx::makeRandomSeed());
-        fprintf(stderr, "Setting the AWH bias MC random seed to %" PRId64 "\n", awhParams->seed);
+        seed_ = static_cast<int>(gmx::makeRandomSeed());
+        fprintf(stderr, "Setting the AWH bias MC random seed to %" PRId64 "\n", seed_);
     }
 
     printStringNoNewline(inp, "Data output interval in number of steps");
-    opt               = "awh-nstout";
-    awhParams->nstOut = get_eint(inp, opt, 100000, wi);
+    opt     = "awh-nstout";
+    nstOut_ = get_eint(inp, opt, 100000, wi);
 
     printStringNoNewline(inp, "Coordinate sampling interval in number of steps");
-    opt                       = "awh-nstsample";
-    awhParams->nstSampleCoord = get_eint(inp, opt, 10, wi);
+    opt             = "awh-nstsample";
+    nstSampleCoord_ = get_eint(inp, opt, 10, wi);
 
     printStringNoNewline(inp, "Free energy and bias update interval in number of samples");
-    opt                                   = "awh-nsamples-update";
-    awhParams->numSamplesUpdateFreeEnergy = get_eint(inp, opt, 10, wi);
+    opt                         = "awh-nsamples-update";
+    numSamplesUpdateFreeEnergy_ = get_eint(inp, opt, 10, wi);
 
     printStringNoNewline(
             inp, "When true, biases with share-group>0 are shared between multiple simulations");
-    opt                          = "awh-share-multisim";
-    awhParams->shareBiasMultisim = (get_eeenum(inp, opt, yesno_names, wi) != 0);
+    opt                = "awh-share-multisim";
+    shareBiasMultisim_ = (getEnum<Boolean>(inp, opt.c_str(), wi) != Boolean::No);
 
     printStringNoNewline(inp, "The number of independent AWH biases");
-    opt                = "awh-nbias";
-    awhParams->numBias = get_eint(inp, opt, 1, wi);
+    opt         = "awh-nbias";
+    int numBias = get_eint(inp, opt, 1, wi);
     /* Check this before starting to read the AWH biases. */
-    if (awhParams->numBias <= 0)
+    if (numBias <= 0)
     {
         gmx_fatal(FARGS, "%s needs to be an integer > 0", opt.c_str());
     }
 
     /* Read the parameters specific to each AWH bias */
-    snew(awhParams->awhBiasParams, awhParams->numBias);
-
-    for (int k = 0; k < awhParams->numBias; k++)
+    for (int k = 0; k < numBias; k++)
     {
         bool        bComment  = (k == 0);
         std::string prefixawh = formatString("awh%d", k + 1);
-        readBiasParams(inp, &awhParams->awhBiasParams[k], prefixawh, wi, bComment);
+        awhBiasParams_.emplace_back(inp, prefixawh, wi, bComment);
     }
-
-    return awhParams;
+    checkInputConsistencyAwh(*this, wi);
 }
 
-void checkAwhParams(const AwhParams* awhParams, const t_inputrec* ir, warninp_t wi)
+AwhParams::AwhParams(ISerializer* serializer)
 {
-    std::string opt;
-
-    checkMtsConsistency(*ir, wi);
-
-    opt = "awh-nstout";
-    if (awhParams->nstOut <= 0)
-    {
-        auto message = formatString("Not writing AWH output with AWH (%s = %d) does not make sense",
-                                    opt.c_str(), awhParams->nstOut);
-        warning_error(wi, message);
-    }
-    /* This restriction can be removed by changing a flag of print_ebin() */
-    if (ir->nstenergy == 0 || awhParams->nstOut % ir->nstenergy != 0)
-    {
-        auto message = formatString("%s (%d) should be a multiple of nstenergy (%d)", opt.c_str(),
-                                    awhParams->nstOut, ir->nstenergy);
-        warning_error(wi, message);
-    }
-
-    opt = "awh-nsamples-update";
-    if (awhParams->numSamplesUpdateFreeEnergy <= 0)
-    {
-        warning_error(wi, opt + " needs to be an integer > 0");
-    }
-
-    bool haveFepLambdaDim = false;
-    for (int k = 0; k < awhParams->numBias; k++)
-    {
-        std::string prefixawh = formatString("awh%d", k + 1);
-        checkBiasParams(&awhParams->awhBiasParams[k], prefixawh, ir, wi);
-        /* Check if there is a FEP lambda dimension. */
-        for (int l = 0; l < awhParams->awhBiasParams[k].ndim; l++)
+    GMX_RELEASE_ASSERT(serializer->reading(),
+                       "Can not use writing serializer to read AWH parameters");
+    int numberOfBiases = awhBiasParams_.size();
+    serializer->doInt(&numberOfBiases);
+    serializer->doInt(&nstOut_);
+    serializer->doInt64(&seed_);
+    serializer->doInt(&nstSampleCoord_);
+    serializer->doInt(&numSamplesUpdateFreeEnergy_);
+    serializer->doEnumAsInt(&potentialEnum_);
+    serializer->doBool(&shareBiasMultisim_);
+
+    if (numberOfBiases > 0)
+    {
+        for (int k = 0; k < numberOfBiases; k++)
         {
-            if (awhParams->awhBiasParams[k].dimParams[l].eCoordProvider == eawhcoordproviderFREE_ENERGY_LAMBDA)
-            {
-                haveFepLambdaDim = true;
-                break;
-            }
+            awhBiasParams_.emplace_back(serializer);
         }
     }
+    checkInputConsistencyAwh(*this, nullptr);
+}
 
-    if (haveFepLambdaDim)
-    {
-        if (awhParams->nstSampleCoord % ir->nstcalcenergy != 0)
-        {
-            opt          = "awh-nstsample";
-            auto message = formatString(
-                    "%s (%d) should be a multiple of nstcalcenergy (%d) when using AWH for "
-                    "sampling an FEP lambda dimension",
-                    opt.c_str(), awhParams->nstSampleCoord, ir->nstcalcenergy);
-            warning_error(wi, message);
-        }
-        if (awhParams->ePotential != eawhpotentialUMBRELLA)
+void AwhParams::serialize(ISerializer* serializer)
+{
+    GMX_RELEASE_ASSERT(!serializer->reading(),
+                       "Can not use reading serializer to write AWH parameters");
+    int numberOfBiases = numBias();
+    serializer->doInt(&numberOfBiases);
+    serializer->doInt(&nstOut_);
+    serializer->doInt64(&seed_);
+    serializer->doInt(&nstSampleCoord_);
+    serializer->doInt(&numSamplesUpdateFreeEnergy_);
+    serializer->doEnumAsInt(&potentialEnum_);
+    serializer->doBool(&shareBiasMultisim_);
+
+    if (numberOfBiases > 0)
+    {
+        for (int k = 0; k < numberOfBiases; k++)
         {
-            opt          = "awh-potential";
-            auto message = formatString(
-                    "%s (%s) must be set to %s when using AWH for sampling an FEP lambda dimension",
-                    opt.c_str(), eawhpotential_names[awhParams->ePotential],
-                    eawhpotential_names[eawhpotentialUMBRELLA]);
-            warning_error(wi, message);
+            awhBiasParams_[k].serialize(serializer);
         }
     }
-
-    /* Do a final consistency check before returning */
-    checkInputConsistencyAwh(*awhParams, wi);
-
-    if (ir->init_step != 0)
-    {
-        warning_error(wi, "With AWH init-step should be 0");
-    }
 }
 
 /*! \brief
@@ -886,7 +988,7 @@ static double get_pull_coord_period(const t_pull_coord& pullCoordParams, const t
 {
     double period = 0;
 
-    if (pullCoordParams.eGeom == epullgDIR)
+    if (pullCoordParams.eGeom == PullGroupGeometry::Direction)
     {
         const real margin = 0.001;
         // Make dims periodic when the interval covers > 95%
@@ -904,7 +1006,8 @@ static double get_pull_coord_period(const t_pull_coord& pullCoordParams, const t
                     gmx_fatal(FARGS,
                               "The AWH interval (%f nm) for a pull coordinate is larger than the "
                               "box size (%f nm)",
-                              intervalLength, boxLength);
+                              intervalLength,
+                              boxLength);
                 }
 
                 if (intervalLength > periodicFraction * boxLength)
@@ -914,7 +1017,7 @@ static double get_pull_coord_period(const t_pull_coord& pullCoordParams, const t
             }
         }
     }
-    else if (pullCoordParams.eGeom == epullgDIHEDRAL)
+    else if (pullCoordParams.eGeom == PullGroupGeometry::Dihedral)
     {
         /* The dihedral angle is periodic in -180 to 180 deg */
         period = 360;
@@ -977,24 +1080,30 @@ static bool valueIsInInterval(double origin, double end, double period, double v
  * \param[in]     awhParams  AWH parameters.
  * \param[in,out] wi         Struct for bookeeping warnings.
  */
-static void checkInputConsistencyInterval(const AwhParams* awhParams, warninp_t wi)
+static void checkInputConsistencyInterval(const AwhParams& awhParams, warninp_t wi)
 {
-    for (int k = 0; k < awhParams->numBias; k++)
+    const auto& awhBiasParams = awhParams.awhBiasParams();
+    for (int k = 0; k < gmx::ssize(awhBiasParams); k++)
     {
-        AwhBiasParams* awhBiasParams = &awhParams->awhBiasParams[k];
-        for (int d = 0; d < awhBiasParams->ndim; d++)
+        const auto& dimParams = awhBiasParams[k].dimParams();
+        for (int d = 0; d < gmx::ssize(dimParams); d++)
         {
-            AwhDimParams* dimParams  = &awhBiasParams->dimParams[d];
-            int           coordIndex = dimParams->coordIndex;
-            double origin = dimParams->origin, end = dimParams->end, period = dimParams->period;
-            double coordValueInit = dimParams->coordValueInit;
+            int    coordIndex = dimParams[d].coordinateIndex();
+            double origin = dimParams[d].origin(), end = dimParams[d].end(),
+                   period         = dimParams[d].period();
+            double coordValueInit = dimParams[d].initialCoordinate();
 
             if ((period == 0) && (origin > end))
             {
                 gmx_fatal(FARGS,
                           "For the non-periodic pull coordinates awh%d-dim%d-start (%f) cannot be "
                           "larger than awh%d-dim%d-end (%f)",
-                          k + 1, d + 1, origin, k + 1, d + 1, end);
+                          k + 1,
+                          d + 1,
+                          origin,
+                          k + 1,
+                          d + 1,
+                          end);
             }
 
             /* Currently we assume symmetric periodic intervals, meaning we use [-period/2, period/2] as the reference interval.
@@ -1014,7 +1123,15 @@ static void checkInputConsistencyInterval(const AwhParams* awhParams, warninp_t
                           "values in between "
                           "minus half a period and plus half a period, i.e. in the interval [%.8g, "
                           "%.8g].",
-                          k + 1, d + 1, origin, k + 1, d + 1, end, period, -0.5 * period, 0.5 * period);
+                          k + 1,
+                          d + 1,
+                          origin,
+                          k + 1,
+                          d + 1,
+                          end,
+                          period,
+                          -0.5 * period,
+                          0.5 * period);
             }
 
             /* Warn if the pull initial coordinate value is not in the grid */
@@ -1027,7 +1144,14 @@ static void checkInputConsistencyInterval(const AwhParams* awhParams, warninp_t
                         "(%.8g). "
                         "This can lead to large initial forces pulling the coordinate towards the "
                         "sampling interval.",
-                        coordValueInit, coordIndex + 1, k + 1, d + 1, origin, k + 1, d + 1, end);
+                        coordValueInit,
+                        coordIndex + 1,
+                        k + 1,
+                        d + 1,
+                        origin,
+                        k + 1,
+                        d + 1,
+                        end);
                 warning(wi, message);
             }
         }
@@ -1051,28 +1175,31 @@ static void checkInputConsistencyInterval(const AwhParams* awhParams, warninp_t
 static void setStateDependentAwhPullDimParams(AwhDimParams*        dimParams,
                                               const int            biasIndex,
                                               const int            dimIndex,
-                                              const pull_params_t* pull_params,
+                                              const pull_params_t& pull_params,
                                               pull_t*              pull_work,
                                               const t_pbc&         pbc,
                                               const tensor&        compressibility,
                                               warninp_t            wi)
 {
-    const t_pull_coord& pullCoordParams = pull_params->coord[dimParams->coordIndex];
+    const t_pull_coord& pullCoordParams = pull_params.coord[dimParams->coordinateIndex()];
 
-    if (pullCoordParams.eGeom == epullgDIRPBC)
+    if (pullCoordParams.eGeom == PullGroupGeometry::DirectionPBC)
     {
         gmx_fatal(FARGS,
                   "AWH does not support pull geometry '%s'. "
                   "If the maximum distance between the groups is always "
                   "less than half the box size, "
                   "you can use geometry '%s' instead.",
-                  EPULLGEOM(epullgDIRPBC), EPULLGEOM(epullgDIR));
+                  enumValueToString(PullGroupGeometry::DirectionPBC),
+                  enumValueToString(PullGroupGeometry::Direction));
     }
 
-    dimParams->period = get_pull_coord_period(pullCoordParams, pbc, dimParams->end - dimParams->origin);
+    dimParams->setPeriod(
+            get_pull_coord_period(pullCoordParams, pbc, dimParams->end() - dimParams->origin()));
     // We would like to check for scaling, but we don't have the full inputrec available here
-    if (dimParams->period > 0
-        && !(pullCoordParams.eGeom == epullgANGLE || pullCoordParams.eGeom == epullgDIHEDRAL))
+    if (dimParams->period() > 0
+        && !(pullCoordParams.eGeom == PullGroupGeometry::Angle
+             || pullCoordParams.eGeom == PullGroupGeometry::Dihedral))
     {
         bool coordIsScaled = false;
         for (int d2 = 0; d2 < DIM; d2++)
@@ -1086,17 +1213,19 @@ static void setStateDependentAwhPullDimParams(AwhDimParams*        dimParams,
         {
             std::string mesg = gmx::formatString(
                     "AWH dimension %d of bias %d is periodic with pull geometry '%s', "
-                    "while you should are applying pressure scaling to the "
+                    "while you should be applying pressure scaling to the "
                     "corresponding box vector, this is not supported.",
-                    dimIndex + 1, biasIndex + 1, EPULLGEOM(pullCoordParams.eGeom));
+                    dimIndex + 1,
+                    biasIndex + 1,
+                    enumValueToString(pullCoordParams.eGeom));
             warning(wi, mesg.c_str());
         }
     }
 
     /* The initial coordinate value, converted to external user units. */
-    dimParams->coordValueInit = get_pull_coord_value(pull_work, dimParams->coordIndex, &pbc);
-
-    dimParams->coordValueInit *= pull_conversion_factor_internal2userinput(&pullCoordParams);
+    double initialCoordinate = get_pull_coord_value(pull_work, dimParams->coordinateIndex(), pbc);
+    initialCoordinate *= pull_conversion_factor_internal2userinput(pullCoordParams);
+    dimParams->setInitialCoordinate(initialCoordinate);
 }
 
 void setStateDependentAwhParams(AwhParams*           awhParams,
@@ -1131,29 +1260,117 @@ void setStateDependentAwhParams(AwhParams*           awhParams,
     t_pbc pbc;
     set_pbc(&pbc, pbcType, box);
 
-    for (int k = 0; k < awhParams->numBias; k++)
+    auto awhBiasParams = awhParams->awhBiasParams();
+    for (int k = 0; k < awhParams->numBias(); k++)
     {
-        AwhBiasParams* awhBiasParams = &awhParams->awhBiasParams[k];
-        for (int d = 0; d < awhBiasParams->ndim; d++)
+        auto awhBiasDimensionParams = awhBiasParams[k].dimParams();
+        for (int d = 0; d < awhBiasParams[k].ndim(); d++)
         {
-            AwhDimParams* dimParams = &awhBiasParams->dimParams[d];
-            if (dimParams->eCoordProvider == eawhcoordproviderPULL)
+            AwhDimParams* dimParams = &awhBiasDimensionParams[d];
+            if (dimParams->coordinateProvider() == AwhCoordinateProviderType::Pull)
             {
-                setStateDependentAwhPullDimParams(dimParams, k, d, &pull_params, pull_work, pbc,
-                                                  compressibility, wi);
+                setStateDependentAwhPullDimParams(
+                        dimParams, k, d, pull_params, pull_work, pbc, compressibility, wi);
             }
             else
             {
-                dimParams->coordValueInit = initLambda;
+                dimParams->setInitialCoordinate(initLambda);
                 checkFepLambdaDimDecouplingConsistency(mtop, wi);
             }
         }
     }
-    checkInputConsistencyInterval(awhParams, wi);
+    checkInputConsistencyInterval(*awhParams, wi);
 
     /* Register AWH as external potential with pull (for AWH dimensions that use pull as
      * reaction coordinate provider) to check consistency. */
     Awh::registerAwhWithPull(*awhParams, pull_work);
 }
 
+void checkAwhParams(const AwhParams& awhParams, const t_inputrec& ir, warninp_t wi)
+{
+    std::string opt;
+    checkMtsConsistency(ir, wi);
+
+    opt = "awh-nstout";
+    if (awhParams.nstout() <= 0)
+    {
+        auto message = formatString("Not writing AWH output with AWH (%s = %d) does not make sense",
+                                    opt.c_str(),
+                                    awhParams.nstout());
+        warning_error(wi, message);
+    }
+    /* This restriction can be removed by changing a flag of print_ebin() */
+    if (ir.nstenergy == 0 || awhParams.nstout() % ir.nstenergy != 0)
+    {
+        auto message = formatString(
+                "%s (%d) should be a multiple of nstenergy (%d)", opt.c_str(), awhParams.nstout(), ir.nstenergy);
+        warning_error(wi, message);
+    }
+
+    opt = "awh-nsamples-update";
+    if (awhParams.numSamplesUpdateFreeEnergy() <= 0)
+    {
+        warning_error(wi, opt + " needs to be an integer > 0");
+    }
+
+    bool       haveFepLambdaDim = false;
+    const auto awhBiasParams    = awhParams.awhBiasParams();
+    for (int k = 0; k < awhParams.numBias() && !haveFepLambdaDim; k++)
+    {
+        std::string prefixawh = formatString("awh%d", k + 1);
+        checkBiasParams(awhBiasParams[k], prefixawh, ir, wi);
+        /* Check if there is a FEP lambda dimension. */
+        const auto dimParams = awhBiasParams[k].dimParams();
+        haveFepLambdaDim = std::any_of(dimParams.begin(), dimParams.end(), [](const auto& dimParam) {
+            return dimParam.coordinateProvider() == AwhCoordinateProviderType::FreeEnergyLambda;
+        });
+    }
+
+    if (haveFepLambdaDim)
+    {
+        if (awhParams.nstSampleCoord() % ir.nstcalcenergy != 0)
+        {
+            opt          = "awh-nstsample";
+            auto message = formatString(
+                    "%s (%d) should be a multiple of nstcalcenergy (%d) when using AWH for "
+                    "sampling an FEP lambda dimension",
+                    opt.c_str(),
+                    awhParams.nstSampleCoord(),
+                    ir.nstcalcenergy);
+            warning_error(wi, message);
+        }
+        if (awhParams.potential() != AwhPotentialType::Umbrella)
+        {
+            opt          = "awh-potential";
+            auto message = formatString(
+                    "%s (%s) must be set to %s when using AWH for sampling an FEP lambda dimension",
+                    opt.c_str(),
+                    enumValueToString(awhParams.potential()),
+                    enumValueToString(AwhPotentialType::Umbrella));
+            warning_error(wi, message);
+        }
+    }
+
+    if (ir.init_step != 0)
+    {
+        warning_error(wi, "With AWH init-step should be 0");
+    }
+}
+
+bool awhHasFepLambdaDimension(const AwhParams& awhParams)
+{
+    for (const auto& biasParams : awhParams.awhBiasParams())
+    {
+        for (const auto& dimParams : biasParams.dimParams())
+        {
+            if (dimParams.coordinateProvider() == AwhCoordinateProviderType::FreeEnergyLambda)
+            {
+                return true;
+            }
+        }
+    }
+
+    return false;
+}
+
 } // namespace gmx