Add lambda state to dhdl.xvg with AWH fep
[alexxy/gromacs.git] / src / gromacs / mdlib / energyoutput.cpp
index 5a8b55a1d040f26a10761d2fcad9df38bac7562f..2a6c91e9b6e5f1eedbb9065689b836a337f26325 100644 (file)
@@ -3,7 +3,8 @@
  *
  * Copyright (c) 1991-2000, University of Groningen, The Netherlands.
  * Copyright (c) 2001-2004, The GROMACS development team.
- * Copyright (c) 2013,2014,2015,2016,2017,2018,2019, by the GROMACS development team, led by
+ * Copyright (c) 2013,2014,2015,2016,2017 by the GROMACS development team.
+ * Copyright (c) 2018,2019,2020,2021, by the GROMACS development team, led by
  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
  * and including many others, as listed in the AUTHORS file in the
  * top-level source directory and at http://www.gromacs.org.
@@ -54,7 +55,8 @@
 #include <array>
 #include <string>
 
-#include "gromacs/awh/awh.h"
+#include "gromacs/applied_forces/awh/awh.h"
+#include "gromacs/applied_forces/awh/read_params.h"
 #include "gromacs/fileio/enxio.h"
 #include "gromacs/fileio/gmxfio.h"
 #include "gromacs/fileio/xvgr.h"
@@ -67,6 +69,7 @@
 #include "gromacs/mdlib/constr.h"
 #include "gromacs/mdlib/ebin.h"
 #include "gromacs/mdlib/mdebin_bar.h"
+#include "gromacs/mdrunutility/handlerestart.h"
 #include "gromacs/mdtypes/energyhistory.h"
 #include "gromacs/mdtypes/fcdata.h"
 #include "gromacs/mdtypes/group.h"
 #include "gromacs/topology/mtop_util.h"
 #include "gromacs/trajectory/energyframe.h"
 #include "gromacs/utility/arraysize.h"
+#include "gromacs/utility/enumerationhelpers.h"
 #include "gromacs/utility/fatalerror.h"
+#include "gromacs/utility/mdmodulesnotifiers.h"
 #include "gromacs/utility/smalloc.h"
 #include "gromacs/utility/stringutil.h"
 
+#include "energydrifttracker.h"
+
 //! Labels for energy file quantities
 //! \{
-static const char                 *conrmsd_nm[] = { "Constr. rmsd", "Constr.2 rmsd" };
+// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
+static const char* conrmsd_nm[] = { "Constr. rmsd", "Constr.2 rmsd" };
+
+static constexpr std::array<const char*, 3> boxs_nm = { "Box-X", "Box-Y", "Box-Z" };
 
-static std::array<const char *, 3> boxs_nm = { "Box-X", "Box-Y", "Box-Z" };
+static constexpr std::array<const char*, 6> tricl_boxs_nm = { "Box-XX", "Box-YY", "Box-ZZ",
+                                                              "Box-YX", "Box-ZX", "Box-ZY" };
 
-static std::array<const char *, 6> tricl_boxs_nm = {
-    "Box-XX", "Box-YY", "Box-ZZ",
-    "Box-YX", "Box-ZX", "Box-ZY"
-};
+// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
+static const char* vol_nm[] = { "Volume" };
 
-static const char                 *vol_nm[] = { "Volume" };
+// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
+static const char* dens_nm[] = { "Density" };
 
-static const char                 *dens_nm[] = {"Density" };
+// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
+static const char* pv_nm[] = { "pV" };
 
-static const char                 *pv_nm[] = {"pV" };
+// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
+static const char* enthalpy_nm[] = { "Enthalpy" };
 
-static const char                 *enthalpy_nm[] = {"Enthalpy" };
+static constexpr std::array<const char*, 6> boxvel_nm = { "Box-Vel-XX", "Box-Vel-YY", "Box-Vel-ZZ",
+                                                          "Box-Vel-YX", "Box-Vel-ZX", "Box-Vel-ZY" };
 
-static std::array<const char *, 6> boxvel_nm = {
-    "Box-Vel-XX", "Box-Vel-YY", "Box-Vel-ZZ",
-    "Box-Vel-YX", "Box-Vel-ZX", "Box-Vel-ZY"
-};
+const char* enumValueToString(NonBondedEnergyTerms enumValue)
+{
+    static constexpr gmx::EnumerationArray<NonBondedEnergyTerms, const char*> nonBondedEnergyTermTypeNames = {
+        "Coul-SR", "LJ-SR", "Buck-SR", "Coul-14", "LJ-14"
+    };
+    return nonBondedEnergyTermTypeNames[enumValue];
+}
 
-const char *egrp_nm[egNR+1] = {
-    "Coul-SR", "LJ-SR", "Buck-SR",
-    "Coul-14", "LJ-14", nullptr
-};
 //! \}
 
+static bool haveFepLambdaMoves(const t_inputrec& inputrec)
+{
+    return (inputrec.bExpanded && inputrec.expandedvals->elmcmove > LambdaMoveCalculation::No)
+           || (inputrec.efep != FreeEnergyPerturbationType::No && inputrec.bDoAwh
+               && awhHasFepLambdaDimension(*inputrec.awhParams));
+}
+
 namespace gmx
 {
 
@@ -121,87 +140,61 @@ namespace gmx
  * be written out to the .edr file.
  *
  * \todo Use more std containers.
- * \todo Remove GMX_CONSTRAINTVIR
  * \todo Write free-energy output also to energy file (after adding more tests)
  */
-EnergyOutput::EnergyOutput(ener_file        *fp_ene,
-                           const gmx_mtop_t *mtop,
-                           const t_inputrec *ir,
-                           const pull_t     *pull_work,
-                           FILE             *fp_dhdl,
-                           bool              isRerun)
+EnergyOutput::EnergyOutput(ener_file*                fp_ene,
+                           const gmx_mtop_t&         mtop,
+                           const t_inputrec&         inputrec,
+                           const pull_t*             pull_work,
+                           FILE*                     fp_dhdl,
+                           bool                      isRerun,
+                           const StartingBehavior    startingBehavior,
+                           const bool                simulationsShareState,
+                           const MDModulesNotifiers& mdModulesNotifiers) :
+    haveFepLambdaMoves_(haveFepLambdaMoves(inputrec))
 {
-    const char                *ener_nm[F_NRE];
-    static const char         *vir_nm[] = {
-        "Vir-XX", "Vir-XY", "Vir-XZ",
-        "Vir-YX", "Vir-YY", "Vir-YZ",
-        "Vir-ZX", "Vir-ZY", "Vir-ZZ"
-    };
-    static const char         *sv_nm[] = {
-        "ShakeVir-XX", "ShakeVir-XY", "ShakeVir-XZ",
-        "ShakeVir-YX", "ShakeVir-YY", "ShakeVir-YZ",
-        "ShakeVir-ZX", "ShakeVir-ZY", "ShakeVir-ZZ"
-    };
-    static const char         *fv_nm[] = {
-        "ForceVir-XX", "ForceVir-XY", "ForceVir-XZ",
-        "ForceVir-YX", "ForceVir-YY", "ForceVir-YZ",
-        "ForceVir-ZX", "ForceVir-ZY", "ForceVir-ZZ"
-    };
-    static const char         *pres_nm[] = {
-        "Pres-XX", "Pres-XY", "Pres-XZ",
-        "Pres-YX", "Pres-YY", "Pres-YZ",
-        "Pres-ZX", "Pres-ZY", "Pres-ZZ"
-    };
-    static const char         *surft_nm[] = {
-        "#Surf*SurfTen"
-    };
-    static const char         *mu_nm[] = {
-        "Mu-X", "Mu-Y", "Mu-Z"
-    };
-    static const char         *vcos_nm[] = {
-        "2CosZ*Vel-X"
-    };
-    static const char         *visc_nm[] = {
-        "1/Viscosity"
-    };
-    static const char         *baro_nm[] = {
-        "Barostat"
-    };
-
-    const SimulationGroups    *groups;
-    char                     **gnm;
-    char                       buf[256];
-    const char                *bufi;
-    int                        i, j, ni, nj, n, k, kk, ncon, nset;
-    bool                       bBHAM, b14;
-
-    if (EI_DYNAMICS(ir->eI))
-    {
-        delta_t_ = ir->delta_t;
+    const char*        ener_nm[F_NRE];
+    static const char* vir_nm[]   = { "Vir-XX", "Vir-XY", "Vir-XZ", "Vir-YX", "Vir-YY",
+                                    "Vir-YZ", "Vir-ZX", "Vir-ZY", "Vir-ZZ" };
+    static const char* pres_nm[]  = { "Pres-XX", "Pres-XY", "Pres-XZ", "Pres-YX", "Pres-YY",
+                                     "Pres-YZ", "Pres-ZX", "Pres-ZY", "Pres-ZZ" };
+    static const char* surft_nm[] = { "#Surf*SurfTen" };
+    static const char* mu_nm[]    = { "Mu-X", "Mu-Y", "Mu-Z" };
+    static const char* vcos_nm[]  = { "2CosZ*Vel-X" };
+    static const char* visc_nm[]  = { "1/Viscosity" };
+    static const char* baro_nm[]  = { "Barostat" };
+
+    const SimulationGroups* groups;
+    char**                  gnm;
+    char                    buf[256];
+    const char*             bufi;
+    int                     i, j, ni, nj, n, ncon, nset;
+    bool                    bBHAM, b14;
+
+    if (EI_DYNAMICS(inputrec.eI))
+    {
+        delta_t_ = inputrec.delta_t;
     }
     else
     {
         delta_t_ = 0;
     }
 
-    groups = &mtop->groups;
+    groups = &mtop.groups;
 
-    bBHAM = (mtop->ffparams.numTypes() > 0) && (mtop->ffparams.functype[0] == F_BHAM);
-    b14   = (gmx_mtop_ftype_count(mtop, F_LJ14) > 0 ||
-             gmx_mtop_ftype_count(mtop, F_LJC14_Q) > 0);
+    bBHAM = (mtop.ffparams.numTypes() > 0) && (mtop.ffparams.functype[0] == F_BHAM);
+    b14   = (gmx_mtop_ftype_count(mtop, F_LJ14) > 0 || gmx_mtop_ftype_count(mtop, F_LJC14_Q) > 0);
 
-    ncon           = gmx_mtop_ftype_count(mtop, F_CONSTR);
-    nset           = gmx_mtop_ftype_count(mtop, F_SETTLE);
-    bool bConstr   = (ncon > 0 || nset > 0) && !isRerun;
-    bConstrVir_ = false;
-    nCrmsd_     = 0;
+    ncon         = gmx_mtop_ftype_count(mtop, F_CONSTR);
+    nset         = gmx_mtop_ftype_count(mtop, F_SETTLE);
+    bool bConstr = (ncon > 0 || nset > 0) && !isRerun;
+    nCrmsd_      = 0;
     if (bConstr)
     {
-        if (ncon > 0 && ir->eConstrAlg == econtLINCS)
+        if (ncon > 0 && inputrec.eConstrAlg == ConstraintAlgorithm::Lincs)
         {
             nCrmsd_ = 1;
         }
-        bConstrVir_ = (getenv("GMX_CONSTRAINTVIR") != nullptr);
     }
     else
     {
@@ -209,60 +202,77 @@ EnergyOutput::EnergyOutput(ener_file        *fp_ene,
     }
 
     /* Energy monitoring */
-    for (i = 0; i < egNR; i++)
+    for (auto& term : bEInd_)
     {
-        bEInd_[i] = false;
+        term = false;
     }
 
     // Setting true only to those energy terms, that have active interactions and
     // are not vsite terms (not VSITE2, VSITE3, VSITE3FD, VSITE3FAD, VSITE3OUT, VSITE4FD, VSITE4FDN, or VSITEN)
     for (i = 0; i < F_NRE; i++)
     {
-        bEner_[i] = (gmx_mtop_ftype_count(mtop, i) > 0) &&
-            ((interaction_function[i].flags & IF_VSITE) == 0);
+        bEner_[i] = (gmx_mtop_ftype_count(mtop, i) > 0)
+                    && ((interaction_function[i].flags & IF_VSITE) == 0);
     }
 
     if (!isRerun)
     {
-        bEner_[F_EKIN]           = EI_DYNAMICS(ir->eI);
-        bEner_[F_ETOT]           = EI_DYNAMICS(ir->eI);
-        bEner_[F_TEMP]           = EI_DYNAMICS(ir->eI);
+        bEner_[F_EKIN] = EI_DYNAMICS(inputrec.eI);
+        bEner_[F_ETOT] = EI_DYNAMICS(inputrec.eI);
+        bEner_[F_TEMP] = EI_DYNAMICS(inputrec.eI);
 
-        bEner_[F_ECONSERVED]     = integratorHasConservedEnergyQuantity(ir);
-        bEner_[F_PDISPCORR]      = (ir->eDispCorr != edispcNO);
-        bEner_[F_PRES]           = true;
+        bEner_[F_ECONSERVED] = integratorHasConservedEnergyQuantity(&inputrec);
+        bEner_[F_PDISPCORR]  = (inputrec.eDispCorr != DispersionCorrectionType::No);
+        bEner_[F_PRES]       = true;
     }
 
-    bEner_[F_LJ]             = !bBHAM;
-    bEner_[F_BHAM]           = bBHAM;
-    bEner_[F_EQM]            = ir->bQMMM;
-    bEner_[F_RF_EXCL]        = (EEL_RF(ir->coulombtype) && ir->cutoff_scheme == ecutsGROUP);
-    bEner_[F_COUL_RECIP]     = EEL_FULL(ir->coulombtype);
-    bEner_[F_LJ_RECIP]       = EVDW_PME(ir->vdwtype);
-    bEner_[F_LJ14]           = b14;
-    bEner_[F_COUL14]         = b14;
-    bEner_[F_LJC14_Q]        = false;
-    bEner_[F_LJC_PAIRS_NB]   = false;
+    bEner_[F_LJ]   = !bBHAM;
+    bEner_[F_BHAM] = bBHAM;
+    bEner_[F_RF_EXCL] = (EEL_RF(inputrec.coulombtype) && inputrec.cutoff_scheme == CutoffScheme::Group);
+    bEner_[F_COUL_RECIP]   = EEL_FULL(inputrec.coulombtype);
+    bEner_[F_LJ_RECIP]     = EVDW_PME(inputrec.vdwtype);
+    bEner_[F_LJ14]         = b14;
+    bEner_[F_COUL14]       = b14;
+    bEner_[F_LJC14_Q]      = false;
+    bEner_[F_LJC_PAIRS_NB] = false;
+
+
+    bEner_[F_DVDL_COUL] = (inputrec.efep != FreeEnergyPerturbationType::No)
+                          && inputrec.fepvals->separate_dvdl[FreeEnergyPerturbationCouplingType::Coul];
+    bEner_[F_DVDL_VDW] = (inputrec.efep != FreeEnergyPerturbationType::No)
+                         && inputrec.fepvals->separate_dvdl[FreeEnergyPerturbationCouplingType::Vdw];
+    bEner_[F_DVDL_BONDED] = (inputrec.efep != FreeEnergyPerturbationType::No)
+                            && inputrec.fepvals->separate_dvdl[FreeEnergyPerturbationCouplingType::Bonded];
+    bEner_[F_DVDL_RESTRAINT] =
+            (inputrec.efep != FreeEnergyPerturbationType::No)
+            && inputrec.fepvals->separate_dvdl[FreeEnergyPerturbationCouplingType::Restraint];
+    bEner_[F_DKDL] = (inputrec.efep != FreeEnergyPerturbationType::No)
+                     && inputrec.fepvals->separate_dvdl[FreeEnergyPerturbationCouplingType::Mass];
+    bEner_[F_DVDL] = (inputrec.efep != FreeEnergyPerturbationType::No)
+                     && inputrec.fepvals->separate_dvdl[FreeEnergyPerturbationCouplingType::Fep];
+
+    bEner_[F_CONSTR]   = false;
+    bEner_[F_CONSTRNC] = false;
+    bEner_[F_SETTLE]   = false;
+
+    bEner_[F_COUL_SR] = true;
+    bEner_[F_EPOT]    = true;
 
+    bEner_[F_DISPCORR]   = (inputrec.eDispCorr != DispersionCorrectionType::No);
+    bEner_[F_DISRESVIOL] = (gmx_mtop_ftype_count(mtop, F_DISRES) > 0);
+    bEner_[F_ORIRESDEV]  = (gmx_mtop_ftype_count(mtop, F_ORIRES) > 0);
+    bEner_[F_COM_PULL]   = ((inputrec.bPull && pull_have_potential(*pull_work)) || inputrec.bRot);
 
-    bEner_[F_DVDL_COUL]      = (ir->efep != efepNO) && ir->fepvals->separate_dvdl[efptCOUL];
-    bEner_[F_DVDL_VDW]       = (ir->efep != efepNO) && ir->fepvals->separate_dvdl[efptVDW];
-    bEner_[F_DVDL_BONDED]    = (ir->efep != efepNO) && ir->fepvals->separate_dvdl[efptBONDED];
-    bEner_[F_DVDL_RESTRAINT] = (ir->efep != efepNO) && ir->fepvals->separate_dvdl[efptRESTRAINT];
-    bEner_[F_DKDL]           = (ir->efep != efepNO) && ir->fepvals->separate_dvdl[efptMASS];
-    bEner_[F_DVDL]           = (ir->efep != efepNO) && ir->fepvals->separate_dvdl[efptFEP];
+    // Check MDModules for any energy output
+    MDModulesEnergyOutputToDensityFittingRequestChecker mdModulesAddOutputToDensityFittingFieldRequest;
+    mdModulesNotifiers.simulationSetupNotifier_.notify(&mdModulesAddOutputToDensityFittingFieldRequest);
 
-    bEner_[F_CONSTR]         = false;
-    bEner_[F_CONSTRNC]       = false;
-    bEner_[F_SETTLE]         = false;
+    bEner_[F_DENSITYFITTING] = mdModulesAddOutputToDensityFittingFieldRequest.energyOutputToDensityFitting_;
 
-    bEner_[F_COUL_SR]        = true;
-    bEner_[F_EPOT]           = true;
+    MDModulesEnergyOutputToQMMMRequestChecker mdModulesAddOutputToQMMMFieldRequest;
+    mdModulesNotifiers.simulationSetupNotifier_.notify(&mdModulesAddOutputToQMMMFieldRequest);
 
-    bEner_[F_DISPCORR]       = (ir->eDispCorr != edispcNO);
-    bEner_[F_DISRESVIOL]     = (gmx_mtop_ftype_count(mtop, F_DISRES) > 0);
-    bEner_[F_ORIRESDEV]      = (gmx_mtop_ftype_count(mtop, F_ORIRES) > 0);
-    bEner_[F_COM_PULL]       = ((ir->bPull && pull_have_potential(pull_work)) || ir->bRot);
+    bEner_[F_EQM] = mdModulesAddOutputToQMMMFieldRequest.energyOutputToQMMM_;
 
     // Counting the energy terms that will be printed and saving their names
     f_nre_ = 0;
@@ -275,23 +285,23 @@ EnergyOutput::EnergyOutput(ener_file        *fp_ene,
         }
     }
 
-    epc_            = isRerun ? epcNO : ir->epc;
-    bDiagPres_      = !TRICLINIC(ir->ref_p) && !isRerun;
-    ref_p_          = (ir->ref_p[XX][XX]+ir->ref_p[YY][YY]+ir->ref_p[ZZ][ZZ])/DIM;
-    bTricl_         = TRICLINIC(ir->compress) || TRICLINIC(ir->deform);
-    bDynBox_        = inputrecDynamicBox(ir);
-    etc_            = isRerun ? etcNO : ir->etc;
-    bNHC_trotter_   = inputrecNvtTrotter(ir) && !isRerun;
-    bPrintNHChains_ = ir->bPrintNHChains && !isRerun;
-    bMTTK_          = (inputrecNptTrotter(ir) || inputrecNphTrotter(ir)) && !isRerun;
-    bMu_            = inputrecNeedMutot(ir);
+    epc_       = isRerun ? PressureCoupling::No : inputrec.epc;
+    bDiagPres_ = !TRICLINIC(inputrec.ref_p) && !isRerun;
+    ref_p_     = (inputrec.ref_p[XX][XX] + inputrec.ref_p[YY][YY] + inputrec.ref_p[ZZ][ZZ]) / DIM;
+    bTricl_    = TRICLINIC(inputrec.compress) || TRICLINIC(inputrec.deform);
+    bDynBox_   = inputrecDynamicBox(&inputrec);
+    etc_       = isRerun ? TemperatureCoupling::No : inputrec.etc;
+    bNHC_trotter_   = inputrecNvtTrotter(&inputrec) && !isRerun;
+    bPrintNHChains_ = inputrec.bPrintNHChains && !isRerun;
+    bMTTK_          = (inputrecNptTrotter(&inputrec) || inputrecNphTrotter(&inputrec)) && !isRerun;
+    bMu_            = inputrecNeedMutot(&inputrec);
     bPres_          = !isRerun;
 
-    ebin_  = mk_ebin();
+    ebin_ = mk_ebin();
     /* Pass NULL for unit to let get_ebin_space determine the units
      * for interaction_function[i].longname
      */
-    ie_    = get_ebin_space(ebin_, f_nre_, ener_nm, nullptr);
+    ie_ = get_ebin_space(ebin_, f_nre_, ener_nm, nullptr);
     if (nCrmsd_)
     {
         /* This should be called directly after the call for ie_,
@@ -302,81 +312,73 @@ EnergyOutput::EnergyOutput(ener_file        *fp_ene,
     if (bDynBox_)
     {
         ib_    = get_ebin_space(ebin_,
-                                bTricl_ ? tricl_boxs_nm.size() : boxs_nm.size(),
-                                bTricl_ ? tricl_boxs_nm.data() : boxs_nm.data(),
-                                unit_length);
-        ivol_  = get_ebin_space(ebin_, 1, vol_nm,  unit_volume);
+                             bTricl_ ? tricl_boxs_nm.size() : boxs_nm.size(),
+                             bTricl_ ? tricl_boxs_nm.data() : boxs_nm.data(),
+                             unit_length);
+        ivol_  = get_ebin_space(ebin_, 1, vol_nm, unit_volume);
         idens_ = get_ebin_space(ebin_, 1, dens_nm, unit_density_SI);
         if (bDiagPres_)
         {
-            ipv_       = get_ebin_space(ebin_, 1, pv_nm,   unit_energy);
-            ienthalpy_ = get_ebin_space(ebin_, 1, enthalpy_nm,   unit_energy);
+            ipv_       = get_ebin_space(ebin_, 1, pv_nm, unit_energy);
+            ienthalpy_ = get_ebin_space(ebin_, 1, enthalpy_nm, unit_energy);
         }
     }
-    if (bConstrVir_)
-    {
-        isvir_ = get_ebin_space(ebin_, asize(sv_nm), sv_nm, unit_energy);
-        ifvir_ = get_ebin_space(ebin_, asize(fv_nm), fv_nm, unit_energy);
-    }
     if (bPres_)
     {
         ivir_   = get_ebin_space(ebin_, asize(vir_nm), vir_nm, unit_energy);
         ipres_  = get_ebin_space(ebin_, asize(pres_nm), pres_nm, unit_pres_bar);
-        isurft_ = get_ebin_space(ebin_, asize(surft_nm), surft_nm,
-                                 unit_surft_bar);
+        isurft_ = get_ebin_space(ebin_, asize(surft_nm), surft_nm, unit_surft_bar);
     }
-    if (epc_ == epcPARRINELLORAHMAN || epc_ == epcMTTK)
+    if (epc_ == PressureCoupling::ParrinelloRahman || epc_ == PressureCoupling::Mttk)
     {
-        ipc_ = get_ebin_space(ebin_, bTricl_ ? boxvel_nm.size() : DIM,
-                              boxvel_nm.data(), unit_vel);
+        ipc_ = get_ebin_space(ebin_, bTricl_ ? boxvel_nm.size() : DIM, boxvel_nm.data(), unit_vel);
     }
     if (bMu_)
     {
-        imu_    = get_ebin_space(ebin_, asize(mu_nm), mu_nm, unit_dipole_D);
+        imu_ = get_ebin_space(ebin_, asize(mu_nm), mu_nm, unit_dipole_D);
     }
-    if (ir->cos_accel != 0)
+    if (inputrec.cos_accel != 0)
     {
         ivcos_ = get_ebin_space(ebin_, asize(vcos_nm), vcos_nm, unit_vel);
-        ivisc_ = get_ebin_space(ebin_, asize(visc_nm), visc_nm,
-                                unit_invvisc_SI);
+        ivisc_ = get_ebin_space(ebin_, asize(visc_nm), visc_nm, unit_invvisc_SI);
     }
 
     /* Energy monitoring */
-    for (i = 0; i < egNR; i++)
+    for (auto& term : bEInd_)
     {
-        bEInd_[i] = false;
+        term = false;
     }
-    bEInd_[egCOULSR] = true;
-    bEInd_[egLJSR  ] = true;
+    bEInd_[NonBondedEnergyTerms::CoulombSR] = true;
+    bEInd_[NonBondedEnergyTerms::LJSR]      = true;
 
     if (bBHAM)
     {
-        bEInd_[egLJSR]   = false;
-        bEInd_[egBHAMSR] = true;
+        bEInd_[NonBondedEnergyTerms::LJSR]         = false;
+        bEInd_[NonBondedEnergyTerms::BuckinghamSR] = true;
     }
     if (b14)
     {
-        bEInd_[egLJ14]   = true;
-        bEInd_[egCOUL14] = true;
+        bEInd_[NonBondedEnergyTerms::LJ14]      = true;
+        bEInd_[NonBondedEnergyTerms::Coulomb14] = true;
     }
     nEc_ = 0;
-    for (i = 0; (i < egNR); i++)
+    for (auto term : bEInd_)
     {
-        if (bEInd_[i])
+        if (term)
         {
             nEc_++;
         }
     }
-    n         = groups->groups[SimulationAtomGroupType::EnergyOutput].size();
-    nEg_      = n;
-    nE_       = (n*(n+1))/2;
+    n    = groups->groups[SimulationAtomGroupType::EnergyOutput].size();
+    nEg_ = n;
+    nE_  = (n * (n + 1)) / 2;
 
-    snew(igrp_, nE_);
+    igrp_.resize(nE_);
     if (nE_ > 1)
     {
         n = 0;
         snew(gnm, nEc_);
-        for (k = 0; (k < nEc_); k++)
+        for (int k = 0; (k < nEc_); k++)
         {
             snew(gnm[k], STRLEN);
         }
@@ -385,22 +387,25 @@ EnergyOutput::EnergyOutput(ener_file        *fp_ene,
             ni = groups->groups[SimulationAtomGroupType::EnergyOutput][i];
             for (j = i; (j < gmx::ssize(groups->groups[SimulationAtomGroupType::EnergyOutput])); j++)
             {
-                nj = groups->groups[SimulationAtomGroupType::EnergyOutput][j];
-                for (k = kk = 0; (k < egNR); k++)
+                nj    = groups->groups[SimulationAtomGroupType::EnergyOutput][j];
+                int k = 0;
+                for (auto key : keysOf(bEInd_))
                 {
-                    if (bEInd_[k])
+                    if (bEInd_[key])
                     {
-                        sprintf(gnm[kk], "%s:%s-%s", egrp_nm[k],
-                                *(groups->groupNames[ni]), *(groups->groupNames[nj]));
-                        kk++;
+                        sprintf(gnm[k],
+                                "%s:%s-%s",
+                                enumValueToString(key),
+                                *(groups->groupNames[ni]),
+                                *(groups->groupNames[nj]));
+                        k++;
                     }
                 }
-                igrp_[n] = get_ebin_space(ebin_, nEc_,
-                                          gnm, unit_energy);
+                igrp_[n] = get_ebin_space(ebin_, nEc_, gnm, unit_energy);
                 n++;
             }
         }
-        for (k = 0; (k < nEc_); k++)
+        for (int k = 0; (k < nEc_); k++)
         {
             sfree(gnm[k]);
         }
@@ -413,29 +418,29 @@ EnergyOutput::EnergyOutput(ener_file        *fp_ene,
     }
 
     nTC_  = isRerun ? 0 : groups->groups[SimulationAtomGroupType::TemperatureCoupling].size();
-    nNHC_ = ir->opts.nhchainlength; /* shorthand for number of NH chains */
+    nNHC_ = inputrec.opts.nhchainlength; /* shorthand for number of NH chains */
     if (bMTTK_)
     {
-        nTCP_ = 1;  /* assume only one possible coupling system for barostat
-                            for now */
+        nTCP_ = 1; /* assume only one possible coupling system for barostat
+                           for now */
     }
     else
     {
         nTCP_ = 0;
     }
-    if (etc_ == etcNOSEHOOVER)
+    if (etc_ == TemperatureCoupling::NoseHoover)
     {
         if (bNHC_trotter_)
         {
-            mde_n_ = 2*nNHC_*nTC_;
+            mde_n_ = 2 * nNHC_ * nTC_;
         }
         else
         {
-            mde_n_ = 2*nTC_;
+            mde_n_ = 2 * nTC_;
         }
-        if (epc_ == epcMTTK)
+        if (epc_ == PressureCoupling::Mttk)
         {
-            mdeb_n_ = 2*nNHC_*nTCP_;
+            mdeb_n_ = 2 * nNHC_ * nTCP_;
         }
     }
     else
@@ -444,9 +449,9 @@ EnergyOutput::EnergyOutput(ener_file        *fp_ene,
         mdeb_n_ = 0;
     }
 
-    snew(tmp_r_, mde_n_);
+    tmp_r_.resize(mde_n_);
     // TODO redo the group name memory management to make it more clear
-    char **grpnms;
+    char** grpnms;
     snew(grpnms, std::max(mde_n_, mdeb_n_)); // Just in case mdeb_n_ > mde_n_
 
     for (i = 0; (i < nTC_); i++)
@@ -455,15 +460,14 @@ EnergyOutput::EnergyOutput(ener_file        *fp_ene,
         sprintf(buf, "T-%s", *(groups->groupNames[ni]));
         grpnms[i] = gmx_strdup(buf);
     }
-    itemp_ = get_ebin_space(ebin_, nTC_, grpnms,
-                            unit_temp_K);
+    itemp_ = get_ebin_space(ebin_, nTC_, grpnms, unit_temp_K);
     for (i = 0; i < nTC_; i++)
     {
         sfree(grpnms[i]);
     }
 
     int allocated = 0;
-    if (etc_ == etcNOSEHOOVER)
+    if (etc_ == TemperatureCoupling::NoseHoover)
     {
         if (bPrintNHChains_)
         {
@@ -476,29 +480,27 @@ EnergyOutput::EnergyOutput(ener_file        *fp_ene,
                     for (j = 0; (j < nNHC_); j++)
                     {
                         sprintf(buf, "Xi-%d-%s", j, bufi);
-                        grpnms[2*(i*nNHC_+j)] = gmx_strdup(buf);
+                        grpnms[2 * (i * nNHC_ + j)] = gmx_strdup(buf);
                         sprintf(buf, "vXi-%d-%s", j, bufi);
-                        grpnms[2*(i*nNHC_+j)+1] = gmx_strdup(buf);
+                        grpnms[2 * (i * nNHC_ + j) + 1] = gmx_strdup(buf);
                     }
                 }
-                itc_ = get_ebin_space(ebin_, mde_n_,
-                                      grpnms, unit_invtime);
+                itc_      = get_ebin_space(ebin_, mde_n_, grpnms, unit_invtime);
                 allocated = mde_n_;
                 if (bMTTK_)
                 {
                     for (i = 0; (i < nTCP_); i++)
                     {
-                        bufi = baro_nm[0];  /* All barostat DOF's together for now. */
+                        bufi = baro_nm[0]; /* All barostat DOF's together for now. */
                         for (j = 0; (j < nNHC_); j++)
                         {
                             sprintf(buf, "Xi-%d-%s", j, bufi);
-                            grpnms[2*(i*nNHC_+j)] = gmx_strdup(buf);
+                            grpnms[2 * (i * nNHC_ + j)] = gmx_strdup(buf);
                             sprintf(buf, "vXi-%d-%s", j, bufi);
-                            grpnms[2*(i*nNHC_+j)+1] = gmx_strdup(buf);
+                            grpnms[2 * (i * nNHC_ + j) + 1] = gmx_strdup(buf);
                         }
                     }
-                    itcb_ = get_ebin_space(ebin_, mdeb_n_,
-                                           grpnms, unit_invtime);
+                    itcb_     = get_ebin_space(ebin_, mdeb_n_, grpnms, unit_invtime);
                     allocated = mdeb_n_;
                 }
             }
@@ -509,18 +511,17 @@ EnergyOutput::EnergyOutput(ener_file        *fp_ene,
                     ni   = groups->groups[SimulationAtomGroupType::TemperatureCoupling][i];
                     bufi = *(groups->groupNames[ni]);
                     sprintf(buf, "Xi-%s", bufi);
-                    grpnms[2*i] = gmx_strdup(buf);
+                    grpnms[2 * i] = gmx_strdup(buf);
                     sprintf(buf, "vXi-%s", bufi);
-                    grpnms[2*i+1] = gmx_strdup(buf);
+                    grpnms[2 * i + 1] = gmx_strdup(buf);
                 }
-                itc_ = get_ebin_space(ebin_, mde_n_,
-                                      grpnms, unit_invtime);
+                itc_      = get_ebin_space(ebin_, mde_n_, grpnms, unit_invtime);
                 allocated = mde_n_;
             }
         }
     }
-    else if (etc_ == etcBERENDSEN || etc_ == etcYES ||
-             etc_ == etcVRESCALE)
+    else if (etc_ == TemperatureCoupling::Berendsen || etc_ == TemperatureCoupling::Yes
+             || etc_ == TemperatureCoupling::VRescale)
     {
         for (i = 0; (i < nTC_); i++)
         {
@@ -528,8 +529,8 @@ EnergyOutput::EnergyOutput(ener_file        *fp_ene,
             sprintf(buf, "Lamb-%s", *(groups->groupNames[ni]));
             grpnms[i] = gmx_strdup(buf);
         }
-        itc_        = get_ebin_space(ebin_, mde_n_, grpnms, "");
-        allocated   = mde_n_;
+        itc_      = get_ebin_space(ebin_, mde_n_, grpnms, "");
+        allocated = mde_n_;
     }
 
     for (i = 0; i < allocated; i++)
@@ -538,82 +539,43 @@ EnergyOutput::EnergyOutput(ener_file        *fp_ene,
     }
     sfree(grpnms);
 
-    nU_ = groups->groups[SimulationAtomGroupType::Acceleration].size();
-    snew(tmp_v_, nU_);
-    if (nU_ > 1)
-    {
-        snew(grpnms, 3*nU_);
-        for (i = 0; (i < nU_); i++)
-        {
-            ni = groups->groups[SimulationAtomGroupType::Acceleration][i];
-            sprintf(buf, "Ux-%s", *(groups->groupNames[ni]));
-            grpnms[3*i+XX] = gmx_strdup(buf);
-            sprintf(buf, "Uy-%s", *(groups->groupNames[ni]));
-            grpnms[3*i+YY] = gmx_strdup(buf);
-            sprintf(buf, "Uz-%s", *(groups->groupNames[ni]));
-            grpnms[3*i+ZZ] = gmx_strdup(buf);
-        }
-        iu_ = get_ebin_space(ebin_, 3*nU_, grpnms, unit_vel);
-        for (i = 0; i < 3*nU_; i++)
-        {
-            sfree(grpnms[i]);
-        }
-        sfree(grpnms);
-    }
-
-    if (fp_ene)
+    /* Note that fp_ene should be valid on the master rank and null otherwise */
+    if (fp_ene != nullptr && startingBehavior != StartingBehavior::RestartWithAppending)
     {
         do_enxnms(fp_ene, &ebin_->nener, &ebin_->enm);
     }
 
     /* check whether we're going to write dh histograms */
     dhc_ = nullptr;
-    if (ir->fepvals->separate_dhdl_file == esepdhdlfileNO)
+    if (inputrec.fepvals->separate_dhdl_file == SeparateDhdlFile::No)
     {
         /* Currently dh histograms are only written with dynamics */
-        if (EI_DYNAMICS(ir->eI))
+        if (EI_DYNAMICS(inputrec.eI))
         {
-            snew(dhc_, 1);
-
-            mde_delta_h_coll_init(dhc_, ir);
+            dhc_ = std::make_unique<t_mde_delta_h_coll>(inputrec);
         }
         fp_dhdl_ = nullptr;
-        snew(dE_, ir->fepvals->n_lambda);
+        dE_.resize(inputrec.fepvals->n_lambda);
     }
     else
     {
         fp_dhdl_ = fp_dhdl;
-        snew(dE_, ir->fepvals->n_lambda);
+        dE_.resize(inputrec.fepvals->n_lambda);
     }
-    if (ir->bSimTemp)
+    if (inputrec.bSimTemp)
     {
-        int i;
-        snew(temperatures_, ir->fepvals->n_lambda);
-        numTemperatures_ = ir->fepvals->n_lambda;
-        for (i = 0; i < ir->fepvals->n_lambda; i++)
-        {
-            temperatures_[i] = ir->simtempvals->temperatures[i];
-        }
+        temperatures_ = inputrec.simtempvals->temperatures;
     }
-    else
+
+    if (EI_MD(inputrec.eI) && !simulationsShareState)
     {
-        numTemperatures_ = 0;
+        conservedEnergyTracker_ = std::make_unique<EnergyDriftTracker>(mtop.natoms);
     }
-
 }
 
 EnergyOutput::~EnergyOutput()
 {
-    sfree(igrp_);
-    sfree(tmp_r_);
-    sfree(tmp_v_);
     done_ebin(ebin_);
-    done_mde_delta_h_coll(dhc_);
-    sfree(dE_);
-    if (numTemperatures_ > 0)
-    {
-        sfree(temperatures_);
-    }
 }
 
 } // namespace gmx
@@ -626,14 +588,12 @@ EnergyOutput::~EnergyOutput()
  * \param[in] get_names          Whether to print the names rather than the values
  * \param[in,out] str            The pre-allocated string buffer to print to.
  */
-static void print_lambda_vector(t_lambda *fep, int i,
-                                bool get_native_lambda, bool get_names,
-                                char *str)
+static void print_lambda_vector(t_lambda* fep, int i, bool get_native_lambda, bool get_names, char* str)
 {
-    int    j, k = 0;
-    int    Nsep = 0;
+    int k    = 0;
+    int Nsep = 0;
 
-    for (j = 0; j < efptNR; j++)
+    for (auto j : keysOf(fep->separate_dvdl))
     {
         if (fep->separate_dvdl[j])
         {
@@ -645,7 +605,7 @@ static void print_lambda_vector(t_lambda *fep, int i,
     {
         str += sprintf(str, "("); /* set the opening parenthesis*/
     }
-    for (j = 0; j < efptNR; j++)
+    for (auto j : keysOf(fep->separate_dvdl))
     {
         if (fep->separate_dvdl[j])
         {
@@ -662,10 +622,10 @@ static void print_lambda_vector(t_lambda *fep, int i,
             }
             else
             {
-                str += sprintf(str, "%s", efpt_singular_names[j]);
+                str += sprintf(str, "%s", enumValueToStringSingular(j));
             }
             /* print comma for the next item */
-            if (k < Nsep-1)
+            if (k < Nsep - 1)
             {
                 str += sprintf(str, ", ");
             }
@@ -679,25 +639,23 @@ static void print_lambda_vector(t_lambda *fep, int i,
     }
 }
 
-FILE *open_dhdl(const char *filename, const t_inputrec *ir,
-                const gmx_output_env_t *oenv)
+FILE* open_dhdl(const char* filename, const t_inputrec* ir, const gmx_output_env_t* oenv)
 {
-    FILE       *fp;
+    FILE*       fp;
     const char *dhdl = "dH/d\\lambda", *deltag = "\\DeltaH", *lambda = "\\lambda",
-    *lambdastate     = "\\lambda state";
-    int         i, nsets, nsets_de, nsetsbegin;
-    int         n_lambda_terms = 0;
-    t_lambda   *fep            = ir->fepvals; /* for simplicity */
-    t_expanded *expand         = ir->expandedvals;
-    char        lambda_vec_str[STRLEN], lambda_name_str[STRLEN];
-
-    int         nsets_dhdl = 0;
-    int         s          = 0;
-    int         nsetsextend;
-    bool        write_pV = false;
+               *lambdastate = "\\lambda state";
+    int       i, nsets, nsets_de, nsetsbegin;
+    int       n_lambda_terms = 0;
+    t_lambda* fep            = ir->fepvals.get(); /* for simplicity */
+    char      lambda_vec_str[STRLEN], lambda_name_str[STRLEN];
+
+    int  nsets_dhdl = 0;
+    int  s          = 0;
+    int  nsetsextend;
+    bool write_pV = false;
 
     /* count the number of different lambda terms */
-    for (i = 0; i < efptNR; i++)
+    for (auto i : keysOf(fep->separate_dvdl))
     {
         if (fep->separate_dvdl[i])
         {
@@ -710,15 +668,14 @@ FILE *open_dhdl(const char *filename, const t_inputrec *ir,
     {
         title   = gmx::formatString("%s", dhdl);
         label_x = gmx::formatString("Time (ps)");
-        label_y = gmx::formatString("%s (%s %s)",
-                                    dhdl, unit_energy, "[\\lambda]\\S-1\\N");
+        label_y = gmx::formatString("%s (%s %s)", dhdl, unit_energy, "[\\lambda]\\S-1\\N");
     }
     else
     {
         title   = gmx::formatString("%s and %s", dhdl, deltag);
         label_x = gmx::formatString("Time (ps)");
-        label_y = gmx::formatString("%s and %s (%s %s)",
-                                    dhdl, deltag, unit_energy, "[\\8l\\4]\\S-1\\N");
+        label_y = gmx::formatString(
+                "%s and %s (%s %s)", dhdl, deltag, unit_energy, "[\\8l\\4]\\S-1\\N");
     }
     fp = gmx_fio_fopen(filename, "w+");
     xvgr_header(fp, title.c_str(), label_x, label_y, exvggtXNY, oenv);
@@ -728,29 +685,28 @@ FILE *open_dhdl(const char *filename, const t_inputrec *ir,
     {
         buf = gmx::formatString("T = %g (K) ", ir->opts.ref_t[0]);
     }
-    if ((ir->efep != efepSLOWGROWTH) && (ir->efep != efepEXPANDED))
+    if ((ir->efep != FreeEnergyPerturbationType::SlowGrowth)
+        && (ir->efep != FreeEnergyPerturbationType::Expanded)
+        && !(ir->bDoAwh && awhHasFepLambdaDimension(*ir->awhParams)))
     {
-        if ( (fep->init_lambda >= 0)  && (n_lambda_terms == 1 ))
+        if ((fep->init_lambda >= 0) && (n_lambda_terms == 1))
         {
             /* compatibility output */
             buf += gmx::formatString("%s = %.4f", lambda, fep->init_lambda);
         }
         else
         {
-            print_lambda_vector(fep, fep->init_fep_state, true, false,
-                                lambda_vec_str);
-            print_lambda_vector(fep, fep->init_fep_state, true, true,
-                                lambda_name_str);
-            buf += gmx::formatString("%s %d: %s = %s",
-                                     lambdastate, fep->init_fep_state,
-                                     lambda_name_str, lambda_vec_str);
+            print_lambda_vector(fep, fep->init_fep_state, true, false, lambda_vec_str);
+            print_lambda_vector(fep, fep->init_fep_state, true, true, lambda_name_str);
+            buf += gmx::formatString(
+                    "%s %d: %s = %s", lambdastate, fep->init_fep_state, lambda_name_str, lambda_vec_str);
         }
     }
     xvgr_subtitle(fp, buf.c_str(), oenv);
 
 
     nsets_dhdl = 0;
-    if (fep->dhdl_derivatives == edhdlderivativesYES)
+    if (fep->dhdl_derivatives == DhDlDerivativeCalculation::Yes)
     {
         nsets_dhdl = n_lambda_terms;
     }
@@ -759,58 +715,57 @@ FILE *open_dhdl(const char *filename, const t_inputrec *ir,
 
     nsets = nsets_dhdl + nsets_de; /* dhdl + fep differences */
 
-    if (fep->n_lambda > 0 && (expand->elmcmove > elmcmoveNO))
+    if (haveFepLambdaMoves(*ir))
     {
-        nsets += 1;   /*add fep state for expanded ensemble */
+        nsets += 1; /*add fep state for expanded ensemble */
     }
 
-    if (fep->edHdLPrintEnergy != edHdLPrintEnergyNO)
+    if (fep->edHdLPrintEnergy != FreeEnergyPrintEnergy::No)
     {
-        nsets += 1;  /* add energy to the dhdl as well */
+        nsets += 1; /* add energy to the dhdl as well */
     }
 
     nsetsextend = nsets;
-    if ((ir->epc != epcNO) && (fep->n_lambda > 0) && (fep->init_lambda < 0))
+    if ((ir->epc != PressureCoupling::No) && (fep->n_lambda > 0) && (fep->init_lambda < 0))
     {
         nsetsextend += 1; /* for PV term, other terms possible if required for
                              the reduced potential (only needed with foreign
                              lambda, and only output when init_lambda is not
                              set in order to maintain compatibility of the
                              dhdl.xvg file) */
-        write_pV     = true;
+        write_pV = true;
     }
     std::vector<std::string> setname(nsetsextend);
 
-    if (expand->elmcmove > elmcmoveNO)
+    if (haveFepLambdaMoves(*ir))
     {
         /* state for the fep_vals, if we have alchemical sampling */
         setname[s++] = "Thermodynamic state";
     }
 
-    if (fep->edHdLPrintEnergy != edHdLPrintEnergyNO)
+    if (fep->edHdLPrintEnergy != FreeEnergyPrintEnergy::No)
     {
         std::string energy;
         switch (fep->edHdLPrintEnergy)
         {
-            case edHdLPrintEnergyPOTENTIAL:
+            case FreeEnergyPrintEnergy::Potential:
                 energy = gmx::formatString("%s (%s)", "Potential Energy", unit_energy);
                 break;
-            case edHdLPrintEnergyTOTAL:
-            case edHdLPrintEnergyYES:
-            default:
-                energy = gmx::formatString("%s (%s)", "Total Energy", unit_energy);
+            case FreeEnergyPrintEnergy::Total:
+            case FreeEnergyPrintEnergy::Yes:
+            default: energy = gmx::formatString("%s (%s)", "Total Energy", unit_energy);
         }
         setname[s++] = energy;
     }
 
-    if (fep->dhdl_derivatives == edhdlderivativesYES)
+    if (fep->dhdl_derivatives == DhDlDerivativeCalculation::Yes)
     {
-        for (i = 0; i < efptNR; i++)
+        for (auto i : keysOf(fep->separate_dvdl))
         {
             if (fep->separate_dvdl[i])
             {
                 std::string derivative;
-                if ( (fep->init_lambda >= 0)  && (n_lambda_terms == 1 ))
+                if ((fep->init_lambda >= 0) && (n_lambda_terms == 1))
                 {
                     /* compatibility output */
                     derivative = gmx::formatString("%s %s %.4f", dhdl, lambda, fep->init_lambda);
@@ -822,8 +777,7 @@ FILE *open_dhdl(const char *filename, const t_inputrec *ir,
                     {
                         lam = fep->all_lambda[i][fep->init_fep_state];
                     }
-                    derivative = gmx::formatString("%s %s = %.4f", dhdl, efpt_singular_names[i],
-                                                   lam);
+                    derivative = gmx::formatString("%s %s = %.4f", dhdl, enumValueToStringSingular(i), lam);
                 }
                 setname[s++] = derivative;
             }
@@ -836,16 +790,16 @@ FILE *open_dhdl(const char *filename, const t_inputrec *ir,
          * from this xvg legend.
          */
 
-        if (expand->elmcmove > elmcmoveNO)
+        if (haveFepLambdaMoves(*ir))
         {
-            nsetsbegin = 1;  /* for including the expanded ensemble */
+            nsetsbegin = 1; /* for including the expanded ensemble */
         }
         else
         {
             nsetsbegin = 0;
         }
 
-        if (fep->edHdLPrintEnergy != edHdLPrintEnergyNO)
+        if (fep->edHdLPrintEnergy != FreeEnergyPrintEnergy::No)
         {
             nsetsbegin += 1;
         }
@@ -855,7 +809,7 @@ FILE *open_dhdl(const char *filename, const t_inputrec *ir,
         {
             print_lambda_vector(fep, i, false, false, lambda_vec_str);
             std::string buf;
-            if ( (fep->init_lambda >= 0)  && (n_lambda_terms == 1 ))
+            if ((fep->init_lambda >= 0) && (n_lambda_terms == 1))
             {
                 /* for compatible dhdl.xvg files */
                 buf = gmx::formatString("%s %s %s", deltag, lambda, lambda_vec_str);
@@ -868,9 +822,8 @@ FILE *open_dhdl(const char *filename, const t_inputrec *ir,
             if (ir->bSimTemp)
             {
                 /* print the temperature for this state if doing simulated annealing */
-                buf += gmx::formatString("T = %g (%s)",
-                                         ir->simtempvals->temperatures[s-(nsetsbegin)],
-                                         unit_temp_K);
+                buf += gmx::formatString(
+                        "T = %g (%s)", ir->simtempvals->temperatures[s - (nsetsbegin)], unit_temp_K);
             }
             setname[s++] = buf;
         }
@@ -892,26 +845,25 @@ void EnergyOutput::addDataAtEnergyStep(bool                    bDoDHDL,
                                        bool                    bSum,
                                        double                  time,
                                        real                    tmass,
-                                       gmx_enerdata_t         *enerd,
-                                       t_state                *state,
-                                       t_lambda               *fep,
-                                       t_expanded             *expand,
-                                       matrix                  box,
-                                       tensor                  svir,
-                                       tensor                  fvir,
-                                       tensor                  vir,
-                                       tensor                  pres,
-                                       gmx_ekindata_t         *ekind,
-                                       rvec                    mu_tot,
-                                       const gmx::Constraints *constr)
+                                       const gmx_enerdata_t*   enerd,
+                                       const t_lambda*         fep,
+                                       const matrix            box,
+                                       PTCouplingArrays        ptCouplingArrays,
+                                       int                     fep_state,
+                                       const tensor            vir,
+                                       const tensor            pres,
+                                       const gmx_ekindata_t*   ekind,
+                                       const rvec              mu_tot,
+                                       const gmx::Constraints* constr)
 {
-    int    j, k, kk, n, gid;
-    real   crmsd[2], tmp6[6];
-    real   bs[tricl_boxs_nm.size()], vol, dens, pv, enthalpy;
-    real   eee[egNR];
-    double store_dhdl[efptNR];
-    real   store_energy = 0;
-    real   tmp;
+    int  j, k, kk, n, gid;
+    real crmsd[2], tmp6[6];
+    real bs[tricl_boxs_nm.size()], vol, dens, enthalpy;
+    real eee[static_cast<int>(NonBondedEnergyTerms::Count)];
+    gmx::EnumerationArray<FreeEnergyPerturbationCouplingType, double> store_dhdl;
+    real                                                              store_energy = 0;
+    real                                                              tmp;
+    real pv = 0.0; // static analyzer warns about uninitialized variable warnings here.
 
     /* Do NOT use the box in the state variable, but the separate box provided
      * as an argument. This is because we sometimes need to write the box from
@@ -943,8 +895,8 @@ void EnergyOutput::addDataAtEnergyStep(bool                    bDoDHDL,
             bs[2] = box[ZZ][ZZ];
             nboxs = boxs_nm.size();
         }
-        vol  = box[XX][XX]*box[YY][YY]*box[ZZ][ZZ];
-        dens = (tmass*AMU)/(vol*NANO*NANO*NANO);
+        vol  = box[XX][XX] * box[YY][YY] * box[ZZ][ZZ];
+        dens = (tmass * gmx::c_amu) / (vol * gmx::c_nano * gmx::c_nano * gmx::c_nano);
         add_ebin(ebin_, ib_, nboxs, bs, bSum);
         add_ebin(ebin_, ivol_, 1, &vol, bSum);
         add_ebin(ebin_, idens_, 1, &dens, bSum);
@@ -953,33 +905,28 @@ void EnergyOutput::addDataAtEnergyStep(bool                    bDoDHDL,
         {
             /* This is pV (in kJ/mol).  The pressure is the reference pressure,
                not the instantaneous pressure */
-            pv = vol*ref_p_/PRESFAC;
+            pv = vol * ref_p_ / gmx::c_presfac;
 
             add_ebin(ebin_, ipv_, 1, &pv, bSum);
             enthalpy = pv + enerd->term[F_ETOT];
             add_ebin(ebin_, ienthalpy_, 1, &enthalpy, bSum);
         }
     }
-    if (bConstrVir_)
-    {
-        add_ebin(ebin_, isvir_, 9, svir[0], bSum);
-        add_ebin(ebin_, ifvir_, 9, fvir[0], bSum);
-    }
     if (bPres_)
     {
         add_ebin(ebin_, ivir_, 9, vir[0], bSum);
         add_ebin(ebin_, ipres_, 9, pres[0], bSum);
-        tmp = (pres[ZZ][ZZ]-(pres[XX][XX]+pres[YY][YY])*0.5)*box[ZZ][ZZ];
+        tmp = (pres[ZZ][ZZ] - (pres[XX][XX] + pres[YY][YY]) * 0.5) * box[ZZ][ZZ];
         add_ebin(ebin_, isurft_, 1, &tmp, bSum);
     }
-    if (epc_ == epcPARRINELLORAHMAN || epc_ == epcMTTK)
+    if (epc_ == PressureCoupling::ParrinelloRahman || epc_ == PressureCoupling::Mttk)
     {
-        tmp6[0] = state->boxv[XX][XX];
-        tmp6[1] = state->boxv[YY][YY];
-        tmp6[2] = state->boxv[ZZ][ZZ];
-        tmp6[3] = state->boxv[YY][XX];
-        tmp6[4] = state->boxv[ZZ][XX];
-        tmp6[5] = state->boxv[ZZ][YY];
+        tmp6[0] = ptCouplingArrays.boxv[XX][XX];
+        tmp6[1] = ptCouplingArrays.boxv[YY][YY];
+        tmp6[2] = ptCouplingArrays.boxv[ZZ][ZZ];
+        tmp6[3] = ptCouplingArrays.boxv[YY][XX];
+        tmp6[4] = ptCouplingArrays.boxv[ZZ][XX];
+        tmp6[5] = ptCouplingArrays.boxv[ZZ][YY];
         add_ebin(ebin_, ipc_, bTricl_ ? 6 : 3, tmp6, bSum);
     }
     if (bMu_)
@@ -988,12 +935,13 @@ void EnergyOutput::addDataAtEnergyStep(bool                    bDoDHDL,
     }
     if (ekind && ekind->cosacc.cos_accel != 0)
     {
-        vol  = box[XX][XX]*box[YY][YY]*box[ZZ][ZZ];
-        dens = (tmass*AMU)/(vol*NANO*NANO*NANO);
+        vol  = box[XX][XX] * box[YY][YY] * box[ZZ][ZZ];
+        dens = (tmass * gmx::c_amu) / (vol * gmx::c_nano * gmx::c_nano * gmx::c_nano);
         add_ebin(ebin_, ivcos_, 1, &(ekind->cosacc.vcos), bSum);
         /* 1/viscosity, unit 1/(kg m^-1 s^-1) */
-        tmp = 1/(ekind->cosacc.cos_accel/(ekind->cosacc.vcos*PICO)
-                 *dens*gmx::square(box[ZZ][ZZ]*NANO/(2*M_PI)));
+        tmp = 1
+              / (ekind->cosacc.cos_accel / (ekind->cosacc.vcos * gmx::c_pico) * dens
+                 * gmx::square(box[ZZ][ZZ] * gmx::c_nano / (2 * M_PI)));
         add_ebin(ebin_, ivisc_, 1, &tmp, bSum);
     }
     if (nE_ > 1)
@@ -1004,11 +952,11 @@ void EnergyOutput::addDataAtEnergyStep(bool                    bDoDHDL,
             for (j = i; (j < nEg_); j++)
             {
                 gid = GID(i, j, nEg_);
-                for (k = kk = 0; (k < egNR); k++)
+                for (k = kk = 0; (k < static_cast<int>(NonBondedEnergyTerms::Count)); k++)
                 {
                     if (bEInd_[k])
                     {
-                        eee[kk++] = enerd->grpp.ener[k][gid];
+                        eee[kk++] = enerd->grpp.energyGroupPairTerms[k][gid];
                     }
                 }
                 add_ebin(ebin_, igrp_[n], nEc_, eee, bSum);
@@ -1023,9 +971,9 @@ void EnergyOutput::addDataAtEnergyStep(bool                    bDoDHDL,
         {
             tmp_r_[i] = ekind->tcstat[i].T;
         }
-        add_ebin(ebin_, itemp_, nTC_, tmp_r_, bSum);
+        add_ebin(ebin_, itemp_, nTC_, tmp_r_.data(), bSum);
 
-        if (etc_ == etcNOSEHOOVER)
+        if (etc_ == TemperatureCoupling::NoseHoover)
         {
             /* whether to print Nose-Hoover chains: */
             if (bPrintNHChains_)
@@ -1036,12 +984,12 @@ void EnergyOutput::addDataAtEnergyStep(bool                    bDoDHDL,
                     {
                         for (j = 0; j < nNHC_; j++)
                         {
-                            k                  = i*nNHC_+j;
-                            tmp_r_[2*k]        = state->nosehoover_xi[k];
-                            tmp_r_[2*k+1]      = state->nosehoover_vxi[k];
+                            k                 = i * nNHC_ + j;
+                            tmp_r_[2 * k]     = ptCouplingArrays.nosehoover_xi[k];
+                            tmp_r_[2 * k + 1] = ptCouplingArrays.nosehoover_vxi[k];
                         }
                     }
-                    add_ebin(ebin_, itc_, mde_n_, tmp_r_, bSum);
+                    add_ebin(ebin_, itc_, mde_n_, tmp_r_.data(), bSum);
 
                     if (bMTTK_)
                     {
@@ -1049,43 +997,34 @@ void EnergyOutput::addDataAtEnergyStep(bool                    bDoDHDL,
                         {
                             for (j = 0; j < nNHC_; j++)
                             {
-                                k                  = i*nNHC_+j;
-                                tmp_r_[2*k]        = state->nhpres_xi[k];
-                                tmp_r_[2*k+1]      = state->nhpres_vxi[k];
+                                k                 = i * nNHC_ + j;
+                                tmp_r_[2 * k]     = ptCouplingArrays.nhpres_xi[k];
+                                tmp_r_[2 * k + 1] = ptCouplingArrays.nhpres_vxi[k];
                             }
                         }
-                        add_ebin(ebin_, itcb_, mdeb_n_, tmp_r_, bSum);
+                        add_ebin(ebin_, itcb_, mdeb_n_, tmp_r_.data(), bSum);
                     }
                 }
                 else
                 {
                     for (int i = 0; (i < nTC_); i++)
                     {
-                        tmp_r_[2*i]   = state->nosehoover_xi[i];
-                        tmp_r_[2*i+1] = state->nosehoover_vxi[i];
+                        tmp_r_[2 * i]     = ptCouplingArrays.nosehoover_xi[i];
+                        tmp_r_[2 * i + 1] = ptCouplingArrays.nosehoover_vxi[i];
                     }
-                    add_ebin(ebin_, itc_, mde_n_, tmp_r_, bSum);
+                    add_ebin(ebin_, itc_, mde_n_, tmp_r_.data(), bSum);
                 }
             }
         }
-        else if (etc_ == etcBERENDSEN || etc_ == etcYES ||
-                 etc_ == etcVRESCALE)
+        else if (etc_ == TemperatureCoupling::Berendsen || etc_ == TemperatureCoupling::Yes
+                 || etc_ == TemperatureCoupling::VRescale)
         {
             for (int i = 0; (i < nTC_); i++)
             {
                 tmp_r_[i] = ekind->tcstat[i].lambda;
             }
-            add_ebin(ebin_, itc_, nTC_, tmp_r_, bSum);
-        }
-    }
-
-    if (ekind && nU_ > 1)
-    {
-        for (int i = 0; (i < nU_); i++)
-        {
-            copy_rvec(ekind->grpstat[i].u, tmp_v_[i]);
+            add_ebin(ebin_, itc_, nTC_, tmp_r_.data(), bSum);
         }
-        add_ebin(ebin_, iu_, 3*nU_, tmp_v_[0], bSum);
     }
 
     ebin_increase_count(1, ebin_, bSum);
@@ -1093,19 +1032,21 @@ void EnergyOutput::addDataAtEnergyStep(bool                    bDoDHDL,
     // BAR + thermodynamic integration values
     if ((fp_dhdl_ || dhc_) && bDoDHDL)
     {
-        for (gmx::index i = 0; i < static_cast<gmx::index>(enerd->enerpart_lambda.size()) - 1; i++)
+        const auto& foreignTerms = enerd->foreignLambdaTerms;
+        for (int i = 0; i < foreignTerms.numLambdas(); i++)
         {
             /* zero for simulated tempering */
-            dE_[i] = enerd->enerpart_lambda[i+1]-enerd->enerpart_lambda[0];
-            if (numTemperatures_ > 0)
+            dE_[i] = foreignTerms.deltaH(i);
+            if (!temperatures_.empty())
             {
-                GMX_RELEASE_ASSERT(numTemperatures_ > state->fep_state, "Number of lambdas in state is bigger then in input record");
-                GMX_RELEASE_ASSERT(numTemperatures_ >= static_cast<gmx::index>(enerd->enerpart_lambda.size()) - 1, "Number of lambdas in energy data is bigger then in input record");
+                GMX_RELEASE_ASSERT(gmx::ssize(temperatures_) > fep_state,
+                                   "Number of lambdas in state is bigger then in input record");
+                GMX_RELEASE_ASSERT(
+                        gmx::ssize(temperatures_) >= foreignTerms.numLambdas(),
+                        "Number of lambdas in energy data is bigger then in input record");
                 /* MRS: is this right, given the way we have defined the exchange probabilities? */
                 /* is this even useful to have at all? */
-                dE_[i] += (temperatures_[i]/
-                           temperatures_[state->fep_state]-1.0)*
-                    enerd->term[F_EKIN];
+                dE_[i] += (temperatures_[i] / temperatures_[fep_state] - 1.0) * enerd->term[F_EKIN];
             }
         }
 
@@ -1115,35 +1056,34 @@ void EnergyOutput::addDataAtEnergyStep(bool                    bDoDHDL,
             /* the current free energy state */
 
             /* print the current state if we are doing expanded ensemble */
-            if (expand->elmcmove > elmcmoveNO)
+            if (haveFepLambdaMoves_)
             {
-                fprintf(fp_dhdl_, " %4d", state->fep_state);
+                fprintf(fp_dhdl_, " %4d", fep_state);
             }
             /* total energy (for if the temperature changes */
 
-            if (fep->edHdLPrintEnergy != edHdLPrintEnergyNO)
+            if (fep->edHdLPrintEnergy != FreeEnergyPrintEnergy::No)
             {
                 switch (fep->edHdLPrintEnergy)
                 {
-                    case edHdLPrintEnergyPOTENTIAL:
+                    case FreeEnergyPrintEnergy::Potential:
                         store_energy = enerd->term[F_EPOT];
                         break;
-                    case edHdLPrintEnergyTOTAL:
-                    case edHdLPrintEnergyYES:
-                    default:
-                        store_energy = enerd->term[F_ETOT];
+                    case FreeEnergyPrintEnergy::Total:
+                    case FreeEnergyPrintEnergy::Yes:
+                    default: store_energy = enerd->term[F_ETOT];
                 }
                 fprintf(fp_dhdl_, " %#.8g", store_energy);
             }
 
-            if (fep->dhdl_derivatives == edhdlderivativesYES)
+            if (fep->dhdl_derivatives == DhDlDerivativeCalculation::Yes)
             {
-                for (int i = 0; i < efptNR; i++)
+                for (auto i : keysOf(fep->separate_dvdl))
                 {
                     if (fep->separate_dvdl[i])
                     {
                         /* assumes F_DVDL is first */
-                        fprintf(fp_dhdl_, " %#.8g", enerd->term[F_DVDL+i]);
+                        fprintf(fp_dhdl_, " %#.8g", enerd->term[F_DVDL + static_cast<int>(i)]);
                     }
                 }
             }
@@ -1151,16 +1091,13 @@ void EnergyOutput::addDataAtEnergyStep(bool                    bDoDHDL,
             {
                 fprintf(fp_dhdl_, " %#.8g", dE_[i]);
             }
-            if (bDynBox_ &&
-                bDiagPres_ &&
-                (epc_ != epcNO) &&
-                !enerd->enerpart_lambda.empty() &&
-                (fep->init_lambda < 0))
+            if (bDynBox_ && bDiagPres_ && (epc_ != PressureCoupling::No)
+                && foreignTerms.numLambdas() > 0 && (fep->init_lambda < 0))
             {
-                fprintf(fp_dhdl_, " %#.8g", pv);  /* PV term only needed when
-                                                          there are alternate state
-                                                          lambda and we're not in
-                                                          compatibility mode */
+                fprintf(fp_dhdl_, " %#.8g", pv); /* PV term only needed when
+                                                         there are alternate state
+                                                         lambda and we're not in
+                                                         compatibility mode */
             }
             fprintf(fp_dhdl_, "\n");
             /* and the binary free energy output */
@@ -1168,27 +1105,32 @@ void EnergyOutput::addDataAtEnergyStep(bool                    bDoDHDL,
         if (dhc_ && bDoDHDL)
         {
             int idhdl = 0;
-            for (int i = 0; i < efptNR; i++)
+            for (auto i : keysOf(fep->separate_dvdl))
             {
                 if (fep->separate_dvdl[i])
                 {
                     /* assumes F_DVDL is first */
-                    store_dhdl[idhdl] = enerd->term[F_DVDL+i];
-                    idhdl            += 1;
+                    store_dhdl[idhdl] = enerd->term[F_DVDL + static_cast<int>(i)];
+                    idhdl += 1;
                 }
             }
             store_energy = enerd->term[F_ETOT];
             /* store_dh is dE */
-            mde_delta_h_coll_add_dh(dhc_,
-                                    static_cast<double>(state->fep_state),
+            mde_delta_h_coll_add_dh(dhc_.get(),
+                                    static_cast<double>(fep_state),
                                     store_energy,
                                     pv,
                                     store_dhdl,
-                                    dE_ + fep->lambda_start_n,
+                                    dE_.data() + fep->lambda_start_n,
                                     time);
         }
     }
-    ;
+
+    if (conservedEnergyTracker_)
+    {
+        conservedEnergyTracker_->addPoint(
+                time, bEner_[F_ECONSERVED] ? enerd->term[F_ECONSERVED] : enerd->term[F_ETOT]);
+    }
 }
 
 void EnergyOutput::recordNonEnergyStep()
@@ -1196,54 +1138,64 @@ void EnergyOutput::recordNonEnergyStep()
     ebin_increase_count(1, ebin_, false);
 }
 
-void EnergyOutput::printHeader(FILE *log, int64_t steps, double time)
+void EnergyOutput::printHeader(FILElog, int64_t steps, double time)
 {
     char buf[22];
 
-    fprintf(log, "   %12s   %12s\n"
+    fprintf(log,
+            "   %12s   %12s\n"
             "   %12s   %12.5f\n\n",
-            "Step", "Time", gmx_step_str(steps, buf), time);
+            "Step",
+            "Time",
+            gmx_step_str(steps, buf),
+            time);
 }
 
-void EnergyOutput::printStepToEnergyFile(ener_file *fp_ene, bool bEne, bool bDR, bool bOR,
-                                         FILE *log,
-                                         int64_t step, double time,
-                                         t_fcdata *fcd,
-                                         gmx::Awh *awh)
+void EnergyOutput::printStepToEnergyFile(ener_file* fp_ene,
+                                         bool       bEne,
+                                         bool       bDR,
+                                         bool       bOR,
+                                         FILE*      log,
+                                         int64_t    step,
+                                         double     time,
+                                         t_fcdata*  fcd,
+                                         gmx::Awh*  awh)
 {
-    t_enxframe   fr;
+    t_enxframe fr;
     init_enxframe(&fr);
-    fr.t            = time;
-    fr.step         = step;
-    fr.nsteps       = ebin_->nsteps;
-    fr.dt           = delta_t_;
-    fr.nsum         = ebin_->nsum;
-    fr.nre          = (bEne) ? ebin_->nener : 0;
-    fr.ener         = ebin_->e;
-    int ndisre          = bDR ? fcd->disres.npair : 0;
+    fr.t       = time;
+    fr.step    = step;
+    fr.nsteps  = ebin_->nsteps;
+    fr.dt      = delta_t_;
+    fr.nsum    = ebin_->nsum;
+    fr.nre     = (bEne) ? ebin_->nener : 0;
+    fr.ener    = ebin_->e;
+    int ndisre = bDR ? fcd->disres->npair : 0;
     /* these are for the old-style blocks (1 subblock, only reals), because
        there can be only one per ID for these */
-    int          nr[enxNR];
-    int          id[enxNR];
-    real        *block[enxNR];
+    int   nr[enxNR];
+    int   id[enxNR];
+    realblock[enxNR];
     /* Optional additional old-style (real-only) blocks. */
     for (int i = 0; i < enxNR; i++)
     {
         nr[i] = 0;
     }
 
-    if (bOR && fcd->orires.nr > 0)
+    if (bOR && fcd->orires)
     {
-        diagonalize_orires_tensors(&(fcd->orires));
-        nr[enxOR]     = fcd->orires.nr;
-        block[enxOR]  = fcd->orires.otav;
+        t_oriresdata& orires = *fcd->orires;
+        diagonalize_orires_tensors(&orires);
+        nr[enxOR]     = orires.numRestraints;
+        block[enxOR]  = orires.orientationsTimeAndEnsembleAv.data();
         id[enxOR]     = enxOR;
-        nr[enxORI]    = (fcd->orires.oinsl != fcd->orires.otav) ?
-            fcd->orires.nr : 0;
-        block[enxORI] = fcd->orires.oinsl;
+        nr[enxORI]    = (orires.orientations.data() != orires.orientationsTimeAndEnsembleAv.data())
+                                ? orires.numRestraints
+                                : 0;
+        block[enxORI] = orires.orientations.data();
         id[enxORI]    = enxORI;
-        nr[enxORT]    = fcd->orires.nex*12;
-        block[enxORT] = fcd->orires.eig;
+        nr[enxORT]    = ssize(orires.eigenOutput);
+        block[enxORT] = orires.eigenOutput.data();
         id[enxORT]    = enxORT;
     }
 
@@ -1266,11 +1218,11 @@ void EnergyOutput::printStepToEnergyFile(ener_file *fp_ene, bool bEne, bool bDR,
             fr.block[b].id        = id[b];
             fr.block[b].sub[0].nr = nr[b];
 #if !GMX_DOUBLE
-            fr.block[b].sub[0].type = xdr_datatype_float;
+            fr.block[b].sub[0].type = XdrDataType::Float;
             fr.block[b].sub[0].fval = block[b];
 #else
-            fr.block[b].sub[0].type = xdr_datatype_double;
-            fr.block[b].sub[0].dval = block[b];
+            fr.block[b].sub[0].type  = XdrDataType::Double;
+            fr.block[b].sub[0].dval  = block[b];
 #endif
         }
 
@@ -1282,19 +1234,20 @@ void EnergyOutput::printStepToEnergyFile(ener_file *fp_ene, bool bEne, bool bDR,
             add_blocks_enxframe(&fr, fr.nblock);
 
             add_subblocks_enxblock(&(fr.block[db]), 2);
-            fr.block[db].id        = enxDISRE;
-            fr.block[db].sub[0].nr = ndisre;
-            fr.block[db].sub[1].nr = ndisre;
+            const t_disresdata& disres = *fcd->disres;
+            fr.block[db].id            = enxDISRE;
+            fr.block[db].sub[0].nr     = ndisre;
+            fr.block[db].sub[1].nr     = ndisre;
 #if !GMX_DOUBLE
-            fr.block[db].sub[0].type = xdr_datatype_float;
-            fr.block[db].sub[1].type = xdr_datatype_float;
-            fr.block[db].sub[0].fval = fcd->disres.rt;
-            fr.block[db].sub[1].fval = fcd->disres.rm3tav;
+            fr.block[db].sub[0].type = XdrDataType::Float;
+            fr.block[db].sub[1].type = XdrDataType::Float;
+            fr.block[db].sub[0].fval = disres.rt;
+            fr.block[db].sub[1].fval = disres.rm3tav;
 #else
-            fr.block[db].sub[0].type = xdr_datatype_double;
-            fr.block[db].sub[1].type = xdr_datatype_double;
-            fr.block[db].sub[0].dval = fcd->disres.rt;
-            fr.block[db].sub[1].dval = fcd->disres.rm3tav;
+            fr.block[db].sub[0].type = XdrDataType::Double;
+            fr.block[db].sub[1].type = XdrDataType::Double;
+            fr.block[db].sub[0].dval = disres.rt;
+            fr.block[db].sub[1].dval = disres.rm3tav;
 #endif
         }
         /* here we can put new-style blocks */
@@ -1302,17 +1255,17 @@ void EnergyOutput::printStepToEnergyFile(ener_file *fp_ene, bool bEne, bool bDR,
         /* Free energy perturbation blocks */
         if (dhc_)
         {
-            mde_delta_h_coll_handle_block(dhc_, &fr, fr.nblock);
+            mde_delta_h_coll_handle_block(dhc_.get(), &fr, fr.nblock);
         }
 
         /* we can now free & reset the data in the blocks */
         if (dhc_)
         {
-            mde_delta_h_coll_reset(dhc_);
+            mde_delta_h_coll_reset(dhc_.get());
         }
 
         /* AWH bias blocks. */
-        if (awh != nullptr)  // TODO: add boolean flag.
+        if (awh != nullptr) // TODO: add boolean flag.
         {
             awh->writeToEnergyFrame(step, &fr);
         }
@@ -1328,18 +1281,18 @@ void EnergyOutput::printStepToEnergyFile(ener_file *fp_ene, bool bEne, bool bDR,
     free_enxframe(&fr);
     if (log)
     {
-        if (bOR && fcd->orires.nr > 0)
+        if (bOR && fcd->orires)
         {
-            print_orires_log(log, &(fcd->orires));
+            print_orires_log(log, fcd->orires.get());
         }
 
         fprintf(log, "   Energies (%s)\n", unit_energy);
-        pr_ebin(log, ebin_, ie_, f_nre_+nCrmsd_, 5, eprNORMAL, true);
+        pr_ebin(log, ebin_, ie_, f_nre_ + nCrmsd_, 5, eprNORMAL, true);
         fprintf(log, "\n");
     }
 }
 
-void EnergyOutput::printAnnealingTemperatures(FILE *log, SimulationGroups *groups, t_grpopts *opts)
+void EnergyOutput::printAnnealingTemperatures(FILE* log, const SimulationGroups* groups, const t_grpopts* opts)
 {
     if (log)
     {
@@ -1347,9 +1300,10 @@ void EnergyOutput::printAnnealingTemperatures(FILE *log, SimulationGroups *group
         {
             for (int i = 0; i < opts->ngtc; i++)
             {
-                if (opts->annealing[i] != eannNO)
+                if (opts->annealing[i] != SimulatedAnnealing::No)
                 {
-                    fprintf(log, "Current ref_t for group %s: %8.1f\n",
+                    fprintf(log,
+                            "Current ref_t for group %s: %8.1f\n",
                             *(groups->groupNames[groups->groups[SimulationAtomGroupType::TemperatureCoupling][i]]),
                             opts->ref_t[i]);
                 }
@@ -1359,7 +1313,7 @@ void EnergyOutput::printAnnealingTemperatures(FILE *log, SimulationGroups *group
     }
 }
 
-void EnergyOutput::printAverages(FILE *log, const SimulationGroups *groups)
+void EnergyOutput::printAverages(FILE* log, const SimulationGroups* groups)
 {
     if (ebin_->nsum_sim <= 0)
     {
@@ -1378,28 +1332,19 @@ void EnergyOutput::printAverages(FILE *log, const SimulationGroups *groups)
         fprintf(log, "\t<====  A V E R A G E S  ====>\n");
         fprintf(log, "\t<==  ###############  ======>\n\n");
 
-        fprintf(log, "\tStatistics over %s steps using %s frames\n",
+        fprintf(log,
+                "\tStatistics over %s steps using %s frames\n",
                 gmx_step_str(ebin_->nsteps_sim, buf1),
                 gmx_step_str(ebin_->nsum_sim, buf2));
         fprintf(log, "\n");
 
         fprintf(log, "   Energies (%s)\n", unit_energy);
-        pr_ebin(log, ebin_, ie_, f_nre_+nCrmsd_, 5, eprAVER, true);
+        pr_ebin(log, ebin_, ie_, f_nre_ + nCrmsd_, 5, eprAVER, true);
         fprintf(log, "\n");
 
         if (bDynBox_)
         {
-            pr_ebin(log, ebin_, ib_, bTricl_ ? tricl_boxs_nm.size() : boxs_nm.size(), 5,
-                    eprAVER, true);
-            fprintf(log, "\n");
-        }
-        if (bConstrVir_)
-        {
-            fprintf(log, "   Constraint Virial (%s)\n", unit_energy);
-            pr_ebin(log, ebin_, isvir_, 9, 3, eprAVER, false);
-            fprintf(log, "\n");
-            fprintf(log, "   Force Virial (%s)\n", unit_energy);
-            pr_ebin(log, ebin_, ifvir_, 9, 3, eprAVER, false);
+            pr_ebin(log, ebin_, ib_, bTricl_ ? tricl_boxs_nm.size() : boxs_nm.size(), 5, eprAVER, true);
             fprintf(log, "\n");
         }
         if (bPres_)
@@ -1422,11 +1367,11 @@ void EnergyOutput::printAverages(FILE *log, const SimulationGroups *groups)
         {
             int padding = 8 - strlen(unit_energy);
             fprintf(log, "%*sEpot (%s)   ", padding, "", unit_energy);
-            for (int i = 0; (i < egNR); i++)
+            for (auto key : keysOf(bEInd_))
             {
-                if (bEInd_[i])
+                if (bEInd_[key])
                 {
-                    fprintf(log, "%12s   ", egrp_nm[i]);
+                    fprintf(log, "%12s   ", enumValueToString(key));
                 }
             }
             fprintf(log, "\n");
@@ -1437,14 +1382,11 @@ void EnergyOutput::printAverages(FILE *log, const SimulationGroups *groups)
                 int ni = groups->groups[SimulationAtomGroupType::EnergyOutput][i];
                 for (int j = i; (j < nEg_); j++)
                 {
-                    int nj      = groups->groups[SimulationAtomGroupType::EnergyOutput][j];
-                    int padding = 14 - (strlen(*(groups->groupNames[ni])) +
-                                        strlen(*(groups->groupNames[nj])));
-                    fprintf(log, "%*s%s-%s", padding, "",
-                            *(groups->groupNames[ni]),
-                            *(groups->groupNames[nj]));
-                    pr_ebin(log, ebin_, igrp_[n], nEc_, nEc_, eprAVER,
-                            false);
+                    int nj = groups->groups[SimulationAtomGroupType::EnergyOutput][j];
+                    int padding =
+                            14 - (strlen(*(groups->groupNames[ni])) + strlen(*(groups->groupNames[nj])));
+                    fprintf(log, "%*s%s-%s", padding, "", *(groups->groupNames[ni]), *(groups->groupNames[nj]));
+                    pr_ebin(log, ebin_, igrp_[n], nEc_, nEc_, eprAVER, false);
                     n++;
                 }
             }
@@ -1455,24 +1397,12 @@ void EnergyOutput::printAverages(FILE *log, const SimulationGroups *groups)
             pr_ebin(log, ebin_, itemp_, nTC_, 4, eprAVER, true);
             fprintf(log, "\n");
         }
-        if (nU_ > 1)
-        {
-            fprintf(log, "%15s   %12s   %12s   %12s\n",
-                    "Group", "Ux", "Uy", "Uz");
-            for (int i = 0; (i < nU_); i++)
-            {
-                int ni = groups->groups[SimulationAtomGroupType::Acceleration][i];
-                fprintf(log, "%15s", *groups->groupNames[ni]);
-                pr_ebin(log, ebin_, iu_+3*i, 3, 3, eprAVER, false);
-            }
-            fprintf(log, "\n");
-        }
     }
 }
 
-void EnergyOutput::fillEnergyHistory(energyhistory_t *enerhist) const
+void EnergyOutput::fillEnergyHistory(energyhistory_tenerhist) const
 {
-    const t_ebin * const ebin = ebin_;
+    const t_ebin* const ebin = ebin_;
 
     enerhist->nsteps     = ebin->nsteps;
     enerhist->nsum       = ebin->nsum;
@@ -1504,19 +1434,23 @@ void EnergyOutput::fillEnergyHistory(energyhistory_t *enerhist) const
     }
     if (dhc_)
     {
-        mde_delta_h_coll_update_energyhistory(dhc_, enerhist);
+        mde_delta_h_coll_update_energyhistory(dhc_.get(), enerhist);
     }
 }
 
-void EnergyOutput::restoreFromEnergyHistory(const energyhistory_t &enerhist)
+void EnergyOutput::restoreFromEnergyHistory(const energyhistory_tenerhist)
 {
     unsigned int nener = static_cast<unsigned int>(ebin_->nener);
 
-    if ((enerhist.nsum     > 0 && nener != enerhist.ener_sum.size()) ||
-        (enerhist.nsum_sim > 0 && nener != enerhist.ener_sum_sim.size()))
+    if ((enerhist.nsum > 0 && nener != enerhist.ener_sum.size())
+        || (enerhist.nsum_sim > 0 && nener != enerhist.ener_sum_sim.size()))
     {
-        gmx_fatal(FARGS, "Mismatch between number of energies in run input (%u) and checkpoint file (%zu or %zu).",
-                  nener, enerhist.ener_sum.size(), enerhist.ener_sum_sim.size());
+        gmx_fatal(FARGS,
+                  "Mismatch between number of energies in run input (%u) and checkpoint file (%zu "
+                  "or %zu).",
+                  nener,
+                  enerhist.ener_sum.size(),
+                  enerhist.ener_sum_sim.size());
     }
 
     ebin_->nsteps     = enerhist.nsteps;
@@ -1526,16 +1460,13 @@ void EnergyOutput::restoreFromEnergyHistory(const energyhistory_t &enerhist)
 
     for (int i = 0; i < ebin_->nener; i++)
     {
-        ebin_->e[i].eav  =
-            (enerhist.nsum > 0 ? enerhist.ener_ave[i] : 0);
-        ebin_->e[i].esum =
-            (enerhist.nsum > 0 ? enerhist.ener_sum[i] : 0);
-        ebin_->e_sim[i].esum =
-            (enerhist.nsum_sim > 0 ? enerhist.ener_sum_sim[i] : 0);
+        ebin_->e[i].eav      = (enerhist.nsum > 0 ? enerhist.ener_ave[i] : 0);
+        ebin_->e[i].esum     = (enerhist.nsum > 0 ? enerhist.ener_sum[i] : 0);
+        ebin_->e_sim[i].esum = (enerhist.nsum_sim > 0 ? enerhist.ener_sum_sim[i] : 0);
     }
     if (dhc_)
     {
-        mde_delta_h_coll_restore_energyhistory(dhc_, enerhist.deltaHForeignLambdas.get());
+        mde_delta_h_coll_restore_energyhistory(dhc_.get(), enerhist.deltaHForeignLambdas.get());
     }
 }
 
@@ -1544,4 +1475,24 @@ int EnergyOutput::numEnergyTerms() const
     return ebin_->nener;
 }
 
+void EnergyOutput::printEnergyConservation(FILE* fplog, int simulationPart, bool usingMdIntegrator) const
+{
+    if (fplog == nullptr)
+    {
+        return;
+    }
+
+    if (conservedEnergyTracker_)
+    {
+        std::string partName = formatString("simulation part #%d", simulationPart);
+        fprintf(fplog, "\n%s\n", conservedEnergyTracker_->energyDriftString(partName).c_str());
+    }
+    else if (usingMdIntegrator)
+    {
+        fprintf(fplog,
+                "\nCannot report drift of the conserved energy quantity because simulations share "
+                "state\n\n");
+    }
+}
+
 } // namespace gmx