Add lambda state to dhdl.xvg with AWH fep
[alexxy/gromacs.git] / src / gromacs / mdlib / energyoutput.cpp
index 2f03c787c971b42451b4e267bc9e0cf75652f5e0..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.
  * To help us fund GROMACS development, we humbly ask that you cite
  * the research papers on the package. Check out http://www.gromacs.org.
  */
+/*! \internal \file
+ * \brief Defines code that writes energy-like quantities.
+ *
+ * \author Mark Abraham <mark.j.abraham@gmail.com>
+ * \author Paul Bauer <paul.bauer.q@gmail.com>
+ * \author Artem Zhmurov <zhmurov@gmail.com>
+ *
+ * \ingroup module_mdlib
+ */
 #include "gmxpre.h"
 
 #include "energyoutput.h"
 #include <cstdlib>
 #include <cstring>
 
+#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"
@@ -57,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"
 
-static const char *conrmsd_nm[] = { "Constr. rmsd", "Constr.2 rmsd" };
-
-static const char *boxs_nm[] = { "Box-X", "Box-Y", "Box-Z" };
-
-static const char *tricl_boxs_nm[] = {
-    "Box-XX", "Box-YY", "Box-ZZ",
-    "Box-YX", "Box-ZX", "Box-ZY"
-};
+#include "energydrifttracker.h"
 
-static const char *vol_nm[] = { "Volume" };
+//! Labels for energy file quantities
+//! \{
+// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
+static const char* conrmsd_nm[] = { "Constr. rmsd", "Constr.2 rmsd" };
 
-static const char *dens_nm[] = {"Density" };
+static constexpr std::array<const char*, 3> boxs_nm = { "Box-X", "Box-Y", "Box-Z" };
 
-static const char *pv_nm[] = {"pV" };
+static constexpr std::array<const char*, 6> tricl_boxs_nm = { "Box-XX", "Box-YY", "Box-ZZ",
+                                                              "Box-YX", "Box-ZX", "Box-ZY" };
 
-static const char *enthalpy_nm[] = {"Enthalpy" };
+// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
+static const char* vol_nm[] = { "Volume" };
 
-static const char *boxvel_nm[] = {
-    "Box-Vel-XX", "Box-Vel-YY", "Box-Vel-ZZ",
-    "Box-Vel-YX", "Box-Vel-ZX", "Box-Vel-ZY"
-};
+// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
+static const char* dens_nm[] = { "Density" };
 
-#define NBOXS asize(boxs_nm)
-#define NTRICLBOXS asize(tricl_boxs_nm)
+// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
+static const char* pv_nm[] = { "pV" };
 
-const char *egrp_nm[egNR+1] = {
-    "Coul-SR", "LJ-SR", "Buck-SR",
-    "Coul-14", "LJ-14", nullptr
-};
+// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
+static const char* enthalpy_nm[] = { "Enthalpy" };
 
-/* forward declaration */
-typedef struct t_mde_delta_h_coll t_mde_delta_h_coll;
+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" };
 
-namespace gmx
+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];
+}
 
-namespace detail
-{
+//! \}
 
-/* This is the collection of energy averages collected during mdrun, and to
-   be written out to the .edr file. */
-struct t_mdebin
-{
-    double              delta_t;
-    t_ebin             *ebin;
-    int                 ie, iconrmsd, ib, ivol, idens, ipv, ienthalpy;
-    int                 isvir, ifvir, ipres, ivir, isurft, ipc, itemp, itc, itcb, iu, imu;
-    int                 ivcos, ivisc;
-    int                 nE, nEg, nEc, nTC, nTCP, nU, nNHC;
-    int                *igrp;
-    int                 mde_n, mdeb_n;
-    real               *tmp_r;
-    rvec               *tmp_v;
-    gmx_bool            bConstr;
-    gmx_bool            bConstrVir;
-    gmx_bool            bTricl;
-    gmx_bool            bDynBox;
-    gmx_bool            bNHC_trotter;
-    gmx_bool            bPrintNHChains;
-    gmx_bool            bMTTK;
-    gmx_bool            bMu; /* true if dipole is calculated */
-    gmx_bool            bDiagPres;
-    gmx_bool            bPres;
-    int                 f_nre;
-    int                 epc;
-    real                ref_p;
-    int                 etc;
-    int                 nCrmsd;
-    gmx_bool            bEner[F_NRE];
-    gmx_bool            bEInd[egNR];
-    char              **print_grpnms;
-
-    FILE               *fp_dhdl; /* the dhdl.xvg output file */
-    double             *dE;      /* energy components for dhdl.xvg output */
-    t_mde_delta_h_coll *dhc;     /* the delta U components (raw data + histogram) */
-    real               *temperatures;
-};
-
-}   // namespace detail
-
-using detail::t_mdebin;
-
-namespace
+static bool haveFepLambdaMoves(const t_inputrec& inputrec)
 {
+    return (inputrec.bExpanded && inputrec.expandedvals->elmcmove > LambdaMoveCalculation::No)
+           || (inputrec.efep != FreeEnergyPerturbationType::No && inputrec.bDoAwh
+               && awhHasFepLambdaDimension(*inputrec.awhParams));
+}
 
-//! Legacy init function
-t_mdebin *init_mdebin(ener_file_t       fp_ene,
-                      const gmx_mtop_t *mtop,
-                      const t_inputrec *ir,
-                      const pull_t     *pull_work,
-                      FILE             *fp_dhdl,
-                      bool              isRerun)
+namespace gmx
 {
-    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;
-    t_mdebin                  *md;
-    int                        i, j, ni, nj, n, k, kk, ncon, nset;
-    gmx_bool                   bBHAM, b14;
-
-    snew(md, 1);
 
-    if (EI_DYNAMICS(ir->eI))
-    {
-        md->delta_t = ir->delta_t;
+/*! \brief Energy output class
+ *
+ * This is the collection of energy averages collected during mdrun, and to
+ * be written out to the .edr file.
+ *
+ * \todo Use more std containers.
+ * \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&         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* 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
     {
-        md->delta_t = 0;
+        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);
-    md->bConstr    = (ncon > 0 || nset > 0) && !isRerun;
-    md->bConstrVir = FALSE;
-    if (md->bConstr)
+    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)
         {
-            md->nCrmsd = 1;
+            nCrmsd_ = 1;
         }
-        md->bConstrVir = (getenv("GMX_CONSTRAINTVIR") != nullptr);
     }
     else
     {
-        md->nCrmsd = 0;
+        nCrmsd_ = 0;
     }
 
     /* Energy monitoring */
-    for (i = 0; i < egNR; i++)
+    for (auto& term : bEInd_)
     {
-        md->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++)
     {
-        md->bEner[i] = FALSE;
-        if (isRerun &&
-            (i == F_EKIN || i == F_ETOT || i == F_ECONSERVED ||
-             i == F_TEMP || i == F_PDISPCORR || i == F_PRES))
-        {
-            continue;
-        }
-        if (i == F_LJ)
-        {
-            md->bEner[i] = !bBHAM;
-        }
-        else if (i == F_BHAM)
-        {
-            md->bEner[i] = bBHAM;
-        }
-        else if (i == F_EQM)
-        {
-            md->bEner[i] = ir->bQMMM;
-        }
-        else if (i == F_RF_EXCL)
-        {
-            md->bEner[i] = (EEL_RF(ir->coulombtype) && ir->cutoff_scheme == ecutsGROUP);
-        }
-        else if (i == F_COUL_RECIP)
-        {
-            md->bEner[i] = EEL_FULL(ir->coulombtype);
-        }
-        else if (i == F_LJ_RECIP)
-        {
-            md->bEner[i] = EVDW_PME(ir->vdwtype);
-        }
-        else if (i == F_LJ14)
-        {
-            md->bEner[i] = b14;
-        }
-        else if (i == F_COUL14)
-        {
-            md->bEner[i] = b14;
-        }
-        else if (i == F_LJC14_Q || i == F_LJC_PAIRS_NB)
-        {
-            md->bEner[i] = FALSE;
-        }
-        else if ((i == F_DVDL_COUL && ir->fepvals->separate_dvdl[efptCOUL]) ||
-                 (i == F_DVDL_VDW  && ir->fepvals->separate_dvdl[efptVDW]) ||
-                 (i == F_DVDL_BONDED && ir->fepvals->separate_dvdl[efptBONDED]) ||
-                 (i == F_DVDL_RESTRAINT && ir->fepvals->separate_dvdl[efptRESTRAINT]) ||
-                 (i == F_DKDL && ir->fepvals->separate_dvdl[efptMASS]) ||
-                 (i == F_DVDL && ir->fepvals->separate_dvdl[efptFEP]))
-        {
-            md->bEner[i] = (ir->efep != efepNO);
-        }
-        else if ((interaction_function[i].flags & IF_VSITE) ||
-                 (i == F_CONSTR) || (i == F_CONSTRNC) || (i == F_SETTLE))
-        {
-            md->bEner[i] = FALSE;
-        }
-        else if ((i == F_COUL_SR) || (i == F_EPOT) || (i == F_PRES)  || (i == F_EQM))
-        {
-            md->bEner[i] = TRUE;
-        }
-        else if ((i == F_ETOT) || (i == F_EKIN) || (i == F_TEMP))
-        {
-            md->bEner[i] = EI_DYNAMICS(ir->eI);
-        }
-        else if (i == F_DISPCORR || i == F_PDISPCORR)
-        {
-            md->bEner[i] = (ir->eDispCorr != edispcNO);
-        }
-        else if (i == F_DISRESVIOL)
-        {
-            md->bEner[i] = (gmx_mtop_ftype_count(mtop, F_DISRES) > 0);
-        }
-        else if (i == F_ORIRESDEV)
-        {
-            md->bEner[i] = (gmx_mtop_ftype_count(mtop, F_ORIRES) > 0);
-        }
-        else if (i == F_CONNBONDS)
-        {
-            md->bEner[i] = FALSE;
-        }
-        else if (i == F_COM_PULL)
-        {
-            md->bEner[i] = ((ir->bPull && pull_have_potential(pull_work)) ||
-                            ir->bRot);
-        }
-        else if (i == F_ECONSERVED)
-        {
-            md->bEner[i] = (integratorHasConservedEnergyQuantity(ir));
-        }
-        else
-        {
-            md->bEner[i] = (gmx_mtop_ftype_count(mtop, i) > 0);
-        }
+        bEner_[i] = (gmx_mtop_ftype_count(mtop, i) > 0)
+                    && ((interaction_function[i].flags & IF_VSITE) == 0);
     }
 
-    md->f_nre = 0;
+    if (!isRerun)
+    {
+        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(&inputrec);
+        bEner_[F_PDISPCORR]  = (inputrec.eDispCorr != DispersionCorrectionType::No);
+        bEner_[F_PRES]       = true;
+    }
+
+    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);
+
+    // Check MDModules for any energy output
+    MDModulesEnergyOutputToDensityFittingRequestChecker mdModulesAddOutputToDensityFittingFieldRequest;
+    mdModulesNotifiers.simulationSetupNotifier_.notify(&mdModulesAddOutputToDensityFittingFieldRequest);
+
+    bEner_[F_DENSITYFITTING] = mdModulesAddOutputToDensityFittingFieldRequest.energyOutputToDensityFitting_;
+
+    MDModulesEnergyOutputToQMMMRequestChecker mdModulesAddOutputToQMMMFieldRequest;
+    mdModulesNotifiers.simulationSetupNotifier_.notify(&mdModulesAddOutputToQMMMFieldRequest);
+
+    bEner_[F_EQM] = mdModulesAddOutputToQMMMFieldRequest.energyOutputToQMMM_;
+
+    // Counting the energy terms that will be printed and saving their names
+    f_nre_ = 0;
     for (i = 0; i < F_NRE; i++)
     {
-        if (md->bEner[i])
+        if (bEner_[i])
         {
-            ener_nm[md->f_nre] = interaction_function[i].longname;
-            md->f_nre++;
+            ener_nm[f_nre_] = interaction_function[i].longname;
+            f_nre_++;
         }
     }
 
-    md->epc            = isRerun ? epcNO : ir->epc;
-    md->bDiagPres      = !TRICLINIC(ir->ref_p) && !isRerun;
-    md->ref_p          = (ir->ref_p[XX][XX]+ir->ref_p[YY][YY]+ir->ref_p[ZZ][ZZ])/DIM;
-    md->bTricl         = TRICLINIC(ir->compress) || TRICLINIC(ir->deform);
-    md->bDynBox        = inputrecDynamicBox(ir);
-    md->etc            = isRerun ? etcNO : ir->etc;
-    md->bNHC_trotter   = inputrecNvtTrotter(ir) && !isRerun;
-    md->bPrintNHChains = ir->bPrintNHChains && !isRerun;
-    md->bMTTK          = (inputrecNptTrotter(ir) || inputrecNphTrotter(ir)) && !isRerun;
-    md->bMu            = inputrecNeedMutot(ir);
-    md->bPres          = !isRerun;
+    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;
 
-    md->ebin  = mk_ebin();
+    ebin_ = mk_ebin();
     /* Pass NULL for unit to let get_ebin_space determine the units
      * for interaction_function[i].longname
      */
-    md->ie    = get_ebin_space(md->ebin, md->f_nre, ener_nm, nullptr);
-    if (md->nCrmsd)
+    ie_ = get_ebin_space(ebin_, f_nre_, ener_nm, nullptr);
+    if (nCrmsd_)
     {
-        /* This should be called directly after the call for md->ie,
-         * such that md->iconrmsd follows directly in the list.
+        /* This should be called directly after the call for ie_,
+         * such that iconrmsd_ follows directly in the list.
          */
-        md->iconrmsd = get_ebin_space(md->ebin, md->nCrmsd, conrmsd_nm, "");
+        iconrmsd_ = get_ebin_space(ebin_, nCrmsd_, conrmsd_nm, "");
     }
-    if (md->bDynBox)
+    if (bDynBox_)
     {
-        md->ib    = get_ebin_space(md->ebin,
-                                   md->bTricl ? NTRICLBOXS : NBOXS,
-                                   md->bTricl ? tricl_boxs_nm : boxs_nm,
-                                   unit_length);
-        md->ivol  = get_ebin_space(md->ebin, 1, vol_nm,  unit_volume);
-        md->idens = get_ebin_space(md->ebin, 1, dens_nm, unit_density_SI);
-        if (md->bDiagPres)
+        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);
+        idens_ = get_ebin_space(ebin_, 1, dens_nm, unit_density_SI);
+        if (bDiagPres_)
         {
-            md->ipv       = get_ebin_space(md->ebin, 1, pv_nm,   unit_energy);
-            md->ienthalpy = get_ebin_space(md->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 (md->bConstrVir)
-    {
-        md->isvir = get_ebin_space(md->ebin, asize(sv_nm), sv_nm, unit_energy);
-        md->ifvir = get_ebin_space(md->ebin, asize(fv_nm), fv_nm, unit_energy);
-    }
-    if (md->bPres)
+    if (bPres_)
     {
-        md->ivir   = get_ebin_space(md->ebin, asize(vir_nm), vir_nm, unit_energy);
-        md->ipres  = get_ebin_space(md->ebin, asize(pres_nm), pres_nm, unit_pres_bar);
-        md->isurft = get_ebin_space(md->ebin, asize(surft_nm), surft_nm,
-                                    unit_surft_bar);
+        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);
     }
-    if (md->epc == epcPARRINELLORAHMAN || md->epc == epcMTTK)
+    if (epc_ == PressureCoupling::ParrinelloRahman || epc_ == PressureCoupling::Mttk)
     {
-        md->ipc = get_ebin_space(md->ebin, md->bTricl ? 6 : 3,
-                                 boxvel_nm, unit_vel);
+        ipc_ = get_ebin_space(ebin_, bTricl_ ? boxvel_nm.size() : DIM, boxvel_nm.data(), unit_vel);
     }
-    if (md->bMu)
+    if (bMu_)
     {
-        md->imu    = get_ebin_space(md->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)
     {
-        md->ivcos = get_ebin_space(md->ebin, asize(vcos_nm), vcos_nm, unit_vel);
-        md->ivisc = get_ebin_space(md->ebin, asize(visc_nm), visc_nm,
-                                   unit_invvisc_SI);
+        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);
     }
 
     /* Energy monitoring */
-    for (i = 0; i < egNR; i++)
+    for (auto& term : bEInd_)
     {
-        md->bEInd[i] = FALSE;
+        term = false;
     }
-    md->bEInd[egCOULSR] = TRUE;
-    md->bEInd[egLJSR  ] = TRUE;
+    bEInd_[NonBondedEnergyTerms::CoulombSR] = true;
+    bEInd_[NonBondedEnergyTerms::LJSR]      = true;
 
     if (bBHAM)
     {
-        md->bEInd[egLJSR]   = FALSE;
-        md->bEInd[egBHAMSR] = TRUE;
+        bEInd_[NonBondedEnergyTerms::LJSR]         = false;
+        bEInd_[NonBondedEnergyTerms::BuckinghamSR] = true;
     }
     if (b14)
     {
-        md->bEInd[egLJ14]   = TRUE;
-        md->bEInd[egCOUL14] = TRUE;
+        bEInd_[NonBondedEnergyTerms::LJ14]      = true;
+        bEInd_[NonBondedEnergyTerms::Coulomb14] = true;
     }
-    md->nEc = 0;
-    for (i = 0; (i < egNR); i++)
+    nEc_ = 0;
+    for (auto term : bEInd_)
     {
-        if (md->bEInd[i])
+        if (term)
         {
-            md->nEc++;
+            nEc_++;
         }
     }
-    n       = groups->groups[SimulationAtomGroupType::EnergyOutput].size();
-    md->nEg = n;
-    md->nE  = (n*(n+1))/2;
+    n    = groups->groups[SimulationAtomGroupType::EnergyOutput].size();
+    nEg_ = n;
+    nE_  = (n * (n + 1)) / 2;
 
-    snew(md->igrp, md->nE);
-    if (md->nE > 1)
+    igrp_.resize(nE_);
+    if (nE_ > 1)
     {
         n = 0;
-        snew(gnm, md->nEc);
-        for (k = 0; (k < md->nEc); k++)
+        snew(gnm, nEc_);
+        for (int k = 0; (k < nEc_); k++)
         {
             snew(gnm[k], STRLEN);
         }
@@ -468,152 +387,150 @@ t_mdebin *init_mdebin(ener_file_t       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 (md->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++;
                     }
                 }
-                md->igrp[n] = get_ebin_space(md->ebin, md->nEc,
-                                             gnm, unit_energy);
+                igrp_[n] = get_ebin_space(ebin_, nEc_, gnm, unit_energy);
                 n++;
             }
         }
-        for (k = 0; (k < md->nEc); k++)
+        for (int k = 0; (k < nEc_); k++)
         {
             sfree(gnm[k]);
         }
         sfree(gnm);
 
-        if (n != md->nE)
+        if (n != nE_)
         {
             gmx_incons("Number of energy terms wrong");
         }
     }
 
-    md->nTC  = isRerun ? 0 : groups->groups[SimulationAtomGroupType::TemperatureCoupling].size();
-    md->nNHC = ir->opts.nhchainlength; /* shorthand for number of NH chains */
-    if (md->bMTTK)
+    nTC_  = isRerun ? 0 : groups->groups[SimulationAtomGroupType::TemperatureCoupling].size();
+    nNHC_ = inputrec.opts.nhchainlength; /* shorthand for number of NH chains */
+    if (bMTTK_)
     {
-        md->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
     {
-        md->nTCP = 0;
+        nTCP_ = 0;
     }
-    if (md->etc == etcNOSEHOOVER)
+    if (etc_ == TemperatureCoupling::NoseHoover)
     {
-        if (md->bNHC_trotter)
+        if (bNHC_trotter_)
         {
-            md->mde_n = 2*md->nNHC*md->nTC;
+            mde_n_ = 2 * nNHC_ * nTC_;
         }
         else
         {
-            md->mde_n = 2*md->nTC;
+            mde_n_ = 2 * nTC_;
         }
-        if (md->epc == epcMTTK)
+        if (epc_ == PressureCoupling::Mttk)
         {
-            md->mdeb_n = 2*md->nNHC*md->nTCP;
+            mdeb_n_ = 2 * nNHC_ * nTCP_;
         }
     }
     else
     {
-        md->mde_n  = md->nTC;
-        md->mdeb_n = 0;
+        mde_n_  = nTC_;
+        mdeb_n_ = 0;
     }
 
-    snew(md->tmp_r, md->mde_n);
-
+    tmp_r_.resize(mde_n_);
     // TODO redo the group name memory management to make it more clear
-    char **grpnms;
-    snew(grpnms, std::max(md->mde_n, md->mdeb_n)); // Just in case md->mdeb_n > md->mde_n
+    char** grpnms;
+    snew(grpnms, std::max(mde_n_, mdeb_n_)); // Just in case mdeb_n_ > mde_n_
 
-    for (i = 0; (i < md->nTC); i++)
+    for (i = 0; (i < nTC_); i++)
     {
         ni = groups->groups[SimulationAtomGroupType::TemperatureCoupling][i];
         sprintf(buf, "T-%s", *(groups->groupNames[ni]));
         grpnms[i] = gmx_strdup(buf);
     }
-    md->itemp = get_ebin_space(md->ebin, md->nTC, grpnms,
-                               unit_temp_K);
-    for (i = 0; i < md->nTC; i++)
+    itemp_ = get_ebin_space(ebin_, nTC_, grpnms, unit_temp_K);
+    for (i = 0; i < nTC_; i++)
     {
         sfree(grpnms[i]);
     }
 
     int allocated = 0;
-    if (md->etc == etcNOSEHOOVER)
+    if (etc_ == TemperatureCoupling::NoseHoover)
     {
-        if (md->bPrintNHChains)
+        if (bPrintNHChains_)
         {
-            if (md->bNHC_trotter)
+            if (bNHC_trotter_)
             {
-                for (i = 0; (i < md->nTC); i++)
+                for (i = 0; (i < nTC_); i++)
                 {
                     ni   = groups->groups[SimulationAtomGroupType::TemperatureCoupling][i];
                     bufi = *(groups->groupNames[ni]);
-                    for (j = 0; (j < md->nNHC); j++)
+                    for (j = 0; (j < nNHC_); j++)
                     {
                         sprintf(buf, "Xi-%d-%s", j, bufi);
-                        grpnms[2*(i*md->nNHC+j)] = gmx_strdup(buf);
+                        grpnms[2 * (i * nNHC_ + j)] = gmx_strdup(buf);
                         sprintf(buf, "vXi-%d-%s", j, bufi);
-                        grpnms[2*(i*md->nNHC+j)+1] = gmx_strdup(buf);
+                        grpnms[2 * (i * nNHC_ + j) + 1] = gmx_strdup(buf);
                     }
                 }
-                md->itc = get_ebin_space(md->ebin, md->mde_n,
-                                         grpnms, unit_invtime);
-                allocated = md->mde_n;
-                if (md->bMTTK)
+                itc_      = get_ebin_space(ebin_, mde_n_, grpnms, unit_invtime);
+                allocated = mde_n_;
+                if (bMTTK_)
                 {
-                    for (i = 0; (i < md->nTCP); i++)
+                    for (i = 0; (i < nTCP_); i++)
                     {
-                        bufi = baro_nm[0];  /* All barostat DOF's together for now. */
-                        for (j = 0; (j < md->nNHC); j++)
+                        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*md->nNHC+j)] = gmx_strdup(buf);
+                            grpnms[2 * (i * nNHC_ + j)] = gmx_strdup(buf);
                             sprintf(buf, "vXi-%d-%s", j, bufi);
-                            grpnms[2*(i*md->nNHC+j)+1] = gmx_strdup(buf);
+                            grpnms[2 * (i * nNHC_ + j) + 1] = gmx_strdup(buf);
                         }
                     }
-                    md->itcb = get_ebin_space(md->ebin, md->mdeb_n,
-                                              grpnms, unit_invtime);
-                    allocated = md->mdeb_n;
+                    itcb_     = get_ebin_space(ebin_, mdeb_n_, grpnms, unit_invtime);
+                    allocated = mdeb_n_;
                 }
             }
             else
             {
-                for (i = 0; (i < md->nTC); i++)
+                for (i = 0; (i < nTC_); i++)
                 {
                     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);
                 }
-                md->itc = get_ebin_space(md->ebin, md->mde_n,
-                                         grpnms, unit_invtime);
-                allocated = md->mde_n;
+                itc_      = get_ebin_space(ebin_, mde_n_, grpnms, unit_invtime);
+                allocated = mde_n_;
             }
         }
     }
-    else if (md->etc == etcBERENDSEN || md->etc == etcYES ||
-             md->etc == etcVRESCALE)
+    else if (etc_ == TemperatureCoupling::Berendsen || etc_ == TemperatureCoupling::Yes
+             || etc_ == TemperatureCoupling::VRescale)
     {
-        for (i = 0; (i < md->nTC); i++)
+        for (i = 0; (i < nTC_); i++)
         {
             ni = groups->groups[SimulationAtomGroupType::TemperatureCoupling][i];
             sprintf(buf, "Lamb-%s", *(groups->groupNames[ni]));
             grpnms[i] = gmx_strdup(buf);
         }
-        md->itc   = get_ebin_space(md->ebin, md->mde_n, grpnms, "");
-        allocated = md->mde_n;
+        itc_      = get_ebin_space(ebin_, mde_n_, grpnms, "");
+        allocated = mde_n_;
     }
 
     for (i = 0; i < allocated; i++)
@@ -622,105 +539,61 @@ t_mdebin *init_mdebin(ener_file_t       fp_ene,
     }
     sfree(grpnms);
 
-    md->nU = groups->groups[SimulationAtomGroupType::Acceleration].size();
-    snew(md->tmp_v, md->nU);
-    if (md->nU > 1)
+    /* Note that fp_ene should be valid on the master rank and null otherwise */
+    if (fp_ene != nullptr && startingBehavior != StartingBehavior::RestartWithAppending)
     {
-        snew(grpnms, 3*md->nU);
-        for (i = 0; (i < md->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);
-        }
-        md->iu = get_ebin_space(md->ebin, 3*md->nU, grpnms, unit_vel);
-        for (i = 0; i < 3*md->nU; i++)
-        {
-            sfree(grpnms[i]);
-        }
-        sfree(grpnms);
+        do_enxnms(fp_ene, &ebin_->nener, &ebin_->enm);
     }
 
-    if (fp_ene)
-    {
-        do_enxnms(fp_ene, &md->ebin->nener, &md->ebin->enm);
-    }
-
-    md->print_grpnms = nullptr;
-
     /* check whether we're going to write dh histograms */
-    md->dhc = nullptr;
-    if (ir->fepvals->separate_dhdl_file == esepdhdlfileNO)
+    dhc_ = nullptr;
+    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(md->dhc, 1);
-
-            mde_delta_h_coll_init(md->dhc, ir);
+            dhc_ = std::make_unique<t_mde_delta_h_coll>(inputrec);
         }
-        md->fp_dhdl = nullptr;
-        snew(md->dE, ir->fepvals->n_lambda);
+        fp_dhdl_ = nullptr;
+        dE_.resize(inputrec.fepvals->n_lambda);
     }
     else
     {
-        md->fp_dhdl = fp_dhdl;
-        snew(md->dE, ir->fepvals->n_lambda);
+        fp_dhdl_ = fp_dhdl;
+        dE_.resize(inputrec.fepvals->n_lambda);
     }
-    if (ir->bSimTemp)
+    if (inputrec.bSimTemp)
     {
-        int i;
-        snew(md->temperatures, ir->fepvals->n_lambda);
-        for (i = 0; i < ir->fepvals->n_lambda; i++)
-        {
-            md->temperatures[i] = ir->simtempvals->temperatures[i];
-        }
+        temperatures_ = inputrec.simtempvals->temperatures;
+    }
+
+    if (EI_MD(inputrec.eI) && !simulationsShareState)
+    {
+        conservedEnergyTracker_ = std::make_unique<EnergyDriftTracker>(mtop.natoms);
     }
-    return md;
 }
 
-//! Legacy cleanup function
-void done_mdebin(t_mdebin *mdebin)
+EnergyOutput::~EnergyOutput()
 {
-    sfree(mdebin->igrp);
-    sfree(mdebin->tmp_r);
-    sfree(mdebin->tmp_v);
-    done_ebin(mdebin->ebin);
-    done_mde_delta_h_coll(mdebin->dhc);
-    sfree(mdebin->dE);
-    sfree(mdebin->temperatures);
-    if (mdebin->nE > 1 && mdebin->print_grpnms != nullptr)
-    {
-        for (int n = 0; n < mdebin->nE; n++)
-        {
-            sfree(mdebin->print_grpnms[n]);
-        }
-        sfree(mdebin->print_grpnms);
-    }
-    sfree(mdebin);
+    done_ebin(ebin_);
 }
 
-} // namespace
 } // namespace gmx
 
-/* print a lambda vector to a string
-   fep = the inputrec's FEP input data
-   i = the index of the lambda vector
-   get_native_lambda = whether to print the native lambda
-   get_names = whether to print the names rather than the values
-   str = the pre-allocated string buffer to print to. */
-static void print_lambda_vector(t_lambda *fep, int i,
-                                gmx_bool get_native_lambda, gmx_bool get_names,
-                                char *str)
+/*! \brief Print a lambda vector to a string
+ *
+ * \param[in] fep                The inputrec's FEP input data
+ * \param[in] i                  The index of the lambda vector
+ * \param[in] get_native_lambda  Whether to print the native lambda
+ * \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)
 {
-    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])
         {
@@ -732,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])
         {
@@ -749,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, ", ");
             }
@@ -766,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;
-    gmx_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])
         {
@@ -797,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);
@@ -815,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;
     }
@@ -846,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);
@@ -909,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;
             }
@@ -923,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;
         }
@@ -940,9 +807,9 @@ FILE *open_dhdl(const char *filename, const t_inputrec *ir,
 
         for (i = fep->lambda_start_n; i < fep->lambda_stop_n; i++)
         {
-            print_lambda_vector(fep, i, FALSE, FALSE, lambda_vec_str);
+            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);
@@ -955,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;
         }
@@ -974,50 +840,45 @@ FILE *open_dhdl(const char *filename, const t_inputrec *ir,
 
 namespace gmx
 {
-namespace
-{
 
-//! Legacy update function
-void upd_mdebin(t_mdebin               *md,
-                gmx_bool                bDoDHDL,
-                gmx_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)
+void EnergyOutput::addDataAtEnergyStep(bool                    bDoDHDL,
+                                       bool                    bSum,
+                                       double                  time,
+                                       real                    tmass,
+                                       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[NTRICLBOXS], 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
      * the last timestep to match the trajectory frames.
      */
-    add_ebin_indexed(md->ebin, md->ie, gmx::ArrayRef<gmx_bool>(md->bEner), enerd->term, bSum);
-    if (md->nCrmsd)
+    add_ebin_indexed(ebin_, ie_, gmx::ArrayRef<bool>(bEner_), enerd->term, bSum);
+    if (nCrmsd_)
     {
         crmsd[0] = constr->rmsd();
-        add_ebin(md->ebin, md->iconrmsd, md->nCrmsd, crmsd, FALSE);
+        add_ebin(ebin_, iconrmsd_, nCrmsd_, crmsd, false);
     }
-    if (md->bDynBox)
+    if (bDynBox_)
     {
         int nboxs;
-        if (md->bTricl)
+        if (bTricl_)
         {
             bs[0] = box[XX][XX];
             bs[1] = box[YY][YY];
@@ -1025,84 +886,80 @@ void upd_mdebin(t_mdebin               *md,
             bs[3] = box[YY][XX];
             bs[4] = box[ZZ][XX];
             bs[5] = box[ZZ][YY];
-            nboxs = NTRICLBOXS;
+            nboxs = tricl_boxs_nm.size();
         }
         else
         {
             bs[0] = box[XX][XX];
             bs[1] = box[YY][YY];
             bs[2] = box[ZZ][ZZ];
-            nboxs = NBOXS;
+            nboxs = boxs_nm.size();
         }
-        vol  = box[XX][XX]*box[YY][YY]*box[ZZ][ZZ];
-        dens = (tmass*AMU)/(vol*NANO*NANO*NANO);
-        add_ebin(md->ebin, md->ib, nboxs, bs, bSum);
-        add_ebin(md->ebin, md->ivol, 1, &vol, bSum);
-        add_ebin(md->ebin, md->idens, 1, &dens, bSum);
+        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);
 
-        if (md->bDiagPres)
+        if (bDiagPres_)
         {
             /* This is pV (in kJ/mol).  The pressure is the reference pressure,
                not the instantaneous pressure */
-            pv = vol*md->ref_p/PRESFAC;
+            pv = vol * ref_p_ / gmx::c_presfac;
 
-            add_ebin(md->ebin, md->ipv, 1, &pv, bSum);
+            add_ebin(ebin_, ipv_, 1, &pv, bSum);
             enthalpy = pv + enerd->term[F_ETOT];
-            add_ebin(md->ebin, md->ienthalpy, 1, &enthalpy, bSum);
+            add_ebin(ebin_, ienthalpy_, 1, &enthalpy, bSum);
         }
     }
-    if (md->bConstrVir)
-    {
-        add_ebin(md->ebin, md->isvir, 9, svir[0], bSum);
-        add_ebin(md->ebin, md->ifvir, 9, fvir[0], bSum);
-    }
-    if (md->bPres)
+    if (bPres_)
     {
-        add_ebin(md->ebin, md->ivir, 9, vir[0], bSum);
-        add_ebin(md->ebin, md->ipres, 9, pres[0], bSum);
-        tmp = (pres[ZZ][ZZ]-(pres[XX][XX]+pres[YY][YY])*0.5)*box[ZZ][ZZ];
-        add_ebin(md->ebin, md->isurft, 1, &tmp, bSum);
+        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];
+        add_ebin(ebin_, isurft_, 1, &tmp, bSum);
     }
-    if (md->epc == epcPARRINELLORAHMAN || md->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];
-        add_ebin(md->ebin, md->ipc, md->bTricl ? 6 : 3, tmp6, bSum);
+        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 (md->bMu)
+    if (bMu_)
     {
-        add_ebin(md->ebin, md->imu, 3, mu_tot, bSum);
+        add_ebin(ebin_, imu_, 3, mu_tot, bSum);
     }
     if (ekind && ekind->cosacc.cos_accel != 0)
     {
-        vol  = box[XX][XX]*box[YY][YY]*box[ZZ][ZZ];
-        dens = (tmass*AMU)/(vol*NANO*NANO*NANO);
-        add_ebin(md->ebin, md->ivcos, 1, &(ekind->cosacc.vcos), bSum);
+        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)));
-        add_ebin(md->ebin, md->ivisc, 1, &tmp, bSum);
+        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 (md->nE > 1)
+    if (nE_ > 1)
     {
         n = 0;
-        for (int i = 0; (i < md->nEg); i++)
+        for (int i = 0; (i < nEg_); i++)
         {
-            for (j = i; (j < md->nEg); j++)
+            for (j = i; (j < nEg_); j++)
             {
-                gid = GID(i, j, md->nEg);
-                for (k = kk = 0; (k < egNR); k++)
+                gid = GID(i, j, nEg_);
+                for (k = kk = 0; (k < static_cast<int>(NonBondedEnergyTerms::Count)); k++)
                 {
-                    if (md->bEInd[k])
+                    if (bEInd_[k])
                     {
-                        eee[kk++] = enerd->grpp.ener[k][gid];
+                        eee[kk++] = enerd->grpp.energyGroupPairTerms[k][gid];
                     }
                 }
-                add_ebin(md->ebin, md->igrp[n], md->nEc, eee, bSum);
+                add_ebin(ebin_, igrp_[n], nEc_, eee, bSum);
                 n++;
             }
         }
@@ -1110,509 +967,442 @@ void upd_mdebin(t_mdebin               *md,
 
     if (ekind)
     {
-        for (int i = 0; (i < md->nTC); i++)
+        for (int i = 0; (i < nTC_); i++)
         {
-            md->tmp_r[i] = ekind->tcstat[i].T;
+            tmp_r_[i] = ekind->tcstat[i].T;
         }
-        add_ebin(md->ebin, md->itemp, md->nTC, md->tmp_r, bSum);
+        add_ebin(ebin_, itemp_, nTC_, tmp_r_.data(), bSum);
 
-        if (md->etc == etcNOSEHOOVER)
+        if (etc_ == TemperatureCoupling::NoseHoover)
         {
             /* whether to print Nose-Hoover chains: */
-            if (md->bPrintNHChains)
+            if (bPrintNHChains_)
             {
-                if (md->bNHC_trotter)
+                if (bNHC_trotter_)
                 {
-                    for (int i = 0; (i < md->nTC); i++)
+                    for (int i = 0; (i < nTC_); i++)
                     {
-                        for (j = 0; j < md->nNHC; j++)
+                        for (j = 0; j < nNHC_; j++)
                         {
-                            k                = i*md->nNHC+j;
-                            md->tmp_r[2*k]   = state->nosehoover_xi[k];
-                            md->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(md->ebin, md->itc, md->mde_n, md->tmp_r, bSum);
+                    add_ebin(ebin_, itc_, mde_n_, tmp_r_.data(), bSum);
 
-                    if (md->bMTTK)
+                    if (bMTTK_)
                     {
-                        for (int i = 0; (i < md->nTCP); i++)
+                        for (int i = 0; (i < nTCP_); i++)
                         {
-                            for (j = 0; j < md->nNHC; j++)
+                            for (j = 0; j < nNHC_; j++)
                             {
-                                k                = i*md->nNHC+j;
-                                md->tmp_r[2*k]   = state->nhpres_xi[k];
-                                md->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(md->ebin, md->itcb, md->mdeb_n, md->tmp_r, bSum);
+                        add_ebin(ebin_, itcb_, mdeb_n_, tmp_r_.data(), bSum);
                     }
                 }
                 else
                 {
-                    for (int i = 0; (i < md->nTC); i++)
+                    for (int i = 0; (i < nTC_); i++)
                     {
-                        md->tmp_r[2*i]   = state->nosehoover_xi[i];
-                        md->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(md->ebin, md->itc, md->mde_n, md->tmp_r, bSum);
+                    add_ebin(ebin_, itc_, mde_n_, tmp_r_.data(), bSum);
                 }
             }
         }
-        else if (md->etc == etcBERENDSEN || md->etc == etcYES ||
-                 md->etc == etcVRESCALE)
+        else if (etc_ == TemperatureCoupling::Berendsen || etc_ == TemperatureCoupling::Yes
+                 || etc_ == TemperatureCoupling::VRescale)
         {
-            for (int i = 0; (i < md->nTC); i++)
+            for (int i = 0; (i < nTC_); i++)
             {
-                md->tmp_r[i] = ekind->tcstat[i].lambda;
+                tmp_r_[i] = ekind->tcstat[i].lambda;
             }
-            add_ebin(md->ebin, md->itc, md->nTC, md->tmp_r, bSum);
-        }
-    }
-
-    if (ekind && md->nU > 1)
-    {
-        for (int i = 0; (i < md->nU); i++)
-        {
-            copy_rvec(ekind->grpstat[i].u, md->tmp_v[i]);
+            add_ebin(ebin_, itc_, nTC_, tmp_r_.data(), bSum);
         }
-        add_ebin(md->ebin, md->iu, 3*md->nU, md->tmp_v[0], bSum);
     }
 
-    ebin_increase_count(md->ebin, bSum);
+    ebin_increase_count(1, ebin_, bSum);
 
-    /* BAR + thermodynamic integration values */
-    if ((md->fp_dhdl || md->dhc) && 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 */
-            md->dE[i] = enerd->enerpart_lambda[i+1]-enerd->enerpart_lambda[0];
-            if (md->temperatures != nullptr)
+            dE_[i] = foreignTerms.deltaH(i);
+            if (!temperatures_.empty())
             {
+                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? */
-                md->dE[i] += (md->temperatures[i]/
-                              md->temperatures[state->fep_state]-1.0)*
-                    enerd->term[F_EKIN];
+                dE_[i] += (temperatures_[i] / temperatures_[fep_state] - 1.0) * enerd->term[F_EKIN];
             }
         }
 
-        if (md->fp_dhdl)
+        if (fp_dhdl_)
         {
-            fprintf(md->fp_dhdl, "%.4f", time);
+            fprintf(fp_dhdl_, "%.4f", time);
             /* the current free energy state */
 
             /* print the current state if we are doing expanded ensemble */
-            if (expand->elmcmove > elmcmoveNO)
+            if (haveFepLambdaMoves_)
             {
-                fprintf(md->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(md->fp_dhdl, " %#.8g", store_energy);
+                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(md->fp_dhdl, " %#.8g", enerd->term[F_DVDL+i]);
+                        fprintf(fp_dhdl_, " %#.8g", enerd->term[F_DVDL + static_cast<int>(i)]);
                     }
                 }
             }
             for (int i = fep->lambda_start_n; i < fep->lambda_stop_n; i++)
             {
-                fprintf(md->fp_dhdl, " %#.8g", md->dE[i]);
+                fprintf(fp_dhdl_, " %#.8g", dE_[i]);
             }
-            if (md->bDynBox &&
-                md->bDiagPres &&
-                (md->epc != epcNO) &&
-                !enerd->enerpart_lambda.empty() &&
-                (fep->init_lambda < 0))
+            if (bDynBox_ && bDiagPres_ && (epc_ != PressureCoupling::No)
+                && foreignTerms.numLambdas() > 0 && (fep->init_lambda < 0))
             {
-                fprintf(md->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(md->fp_dhdl, "\n");
+            fprintf(fp_dhdl_, "\n");
             /* and the binary free energy output */
         }
-        if (md->dhc && 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(md->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,
-                                    md->dE + fep->lambda_start_n,
+                                    dE_.data() + fep->lambda_start_n,
                                     time);
         }
     }
-}
-
-}   // namespace
 
-void EnergyOutput::recordNonEnergyStep()
-{
-    ebin_increase_count(mdebin->ebin, false);
-}
-
-namespace
-{
-
-//! Legacy output function
-void npr(FILE *log, int n, char c)
-{
-    for (; (n > 0); n--)
+    if (conservedEnergyTracker_)
     {
-        fprintf(log, "%c", c);
+        conservedEnergyTracker_->addPoint(
+                time, bEner_[F_ECONSERVED] ? enerd->term[F_ECONSERVED] : enerd->term[F_ETOT]);
     }
 }
 
-//! Legacy output function
-void pprint(FILE *log, const char *s, t_mdebin *md)
+void EnergyOutput::recordNonEnergyStep()
 {
-    char CHAR = '#';
-    int  slen;
-    char buf1[22], buf2[22];
-
-    slen = strlen(s);
-    fprintf(log, "\t<======  ");
-    npr(log, slen, CHAR);
-    fprintf(log, "  ==>\n");
-    fprintf(log, "\t<====  %s  ====>\n", s);
-    fprintf(log, "\t<==  ");
-    npr(log, slen, CHAR);
-    fprintf(log, "  ======>\n\n");
-
-    fprintf(log, "\tStatistics over %s steps using %s frames\n",
-            gmx_step_str(md->ebin->nsteps_sim, buf1),
-            gmx_step_str(md->ebin->nsum_sim, buf2));
-    fprintf(log, "\n");
+    ebin_increase_count(1, ebin_, false);
 }
 
-}   // namespace
-
-void print_ebin_header(FILE *log, int64_t steps, double time)
+void EnergyOutput::printHeader(FILE* log, 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);
 }
 
-namespace
-{
-
-// TODO It is too many responsibilities for this function to handle
-// both .edr and .log output for both per-time and time-average data.
-//! Legacy ebin output function
-void print_ebin(ener_file_t fp_ene, gmx_bool bEne, gmx_bool bDR, gmx_bool bOR,
-                FILE *log,
-                int64_t step, double time,
-                int mode,
-                t_mdebin *md, t_fcdata *fcd,
-                SimulationGroups *groups, t_grpopts *opts,
-                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)
 {
-    /*static char **grpnms=NULL;*/
-    char         buf[246];
-    int          i, j, n, ni, nj, b;
-    int          ndisre = 0;
-
+    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;
     /* 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];
-
-    t_enxframe   fr;
+    int   nr[enxNR];
+    int   id[enxNR];
+    real* block[enxNR];
+    /* Optional additional old-style (real-only) blocks. */
+    for (int i = 0; i < enxNR; i++)
+    {
+        nr[i] = 0;
+    }
 
-    if (mode == eprAVER && md->ebin->nsum_sim <= 0)
+    if (bOR && fcd->orires)
     {
-        if (log)
-        {
-            fprintf(log, "Not enough data recorded to report energy averages\n");
-        }
-        return;
+        t_oriresdata& orires = *fcd->orires;
+        diagonalize_orires_tensors(&orires);
+        nr[enxOR]     = orires.numRestraints;
+        block[enxOR]  = orires.orientationsTimeAndEnsembleAv.data();
+        id[enxOR]     = enxOR;
+        nr[enxORI]    = (orires.orientations.data() != orires.orientationsTimeAndEnsembleAv.data())
+                                ? orires.numRestraints
+                                : 0;
+        block[enxORI] = orires.orientations.data();
+        id[enxORI]    = enxORI;
+        nr[enxORT]    = ssize(orires.eigenOutput);
+        block[enxORT] = orires.eigenOutput.data();
+        id[enxORT]    = enxORT;
     }
 
-    switch (mode)
-    {
-        case eprNORMAL:
-            init_enxframe(&fr);
-            fr.t            = time;
-            fr.step         = step;
-            fr.nsteps       = md->ebin->nsteps;
-            fr.dt           = md->delta_t;
-            fr.nsum         = md->ebin->nsum;
-            fr.nre          = (bEne) ? md->ebin->nener : 0;
-            fr.ener         = md->ebin->e;
-            ndisre          = bDR ? fcd->disres.npair : 0;
-            /* Optional additional old-style (real-only) blocks. */
-            for (i = 0; i < enxNR; i++)
-            {
-                nr[i] = 0;
-            }
-            if (bOR && fcd->orires.nr > 0)
+    /* whether we are going to write anything out: */
+    if (fr.nre || ndisre || nr[enxOR] || nr[enxORI])
+    {
+        /* the old-style blocks go first */
+        fr.nblock = 0;
+        for (int i = 0; i < enxNR; i++)
+        {
+            if (nr[i] > 0)
             {
-                diagonalize_orires_tensors(&(fcd->orires));
-                nr[enxOR]     = fcd->orires.nr;
-                block[enxOR]  = fcd->orires.otav;
-                id[enxOR]     = enxOR;
-                nr[enxORI]    = (fcd->orires.oinsl != fcd->orires.otav) ?
-                    fcd->orires.nr : 0;
-                block[enxORI] = fcd->orires.oinsl;
-                id[enxORI]    = enxORI;
-                nr[enxORT]    = fcd->orires.nex*12;
-                block[enxORT] = fcd->orires.eig;
-                id[enxORT]    = enxORT;
+                fr.nblock = i + 1;
             }
-
-            /* whether we are going to wrte anything out: */
-            if (fr.nre || ndisre || nr[enxOR] || nr[enxORI])
-            {
-
-                /* the old-style blocks go first */
-                fr.nblock = 0;
-                for (i = 0; i < enxNR; i++)
-                {
-                    if (nr[i] > 0)
-                    {
-                        fr.nblock = i + 1;
-                    }
-                }
-                add_blocks_enxframe(&fr, fr.nblock);
-                for (b = 0; b < fr.nblock; b++)
-                {
-                    add_subblocks_enxblock(&(fr.block[b]), 1);
-                    fr.block[b].id        = id[b];
-                    fr.block[b].sub[0].nr = nr[b];
+        }
+        add_blocks_enxframe(&fr, fr.nblock);
+        for (int b = 0; b < fr.nblock; b++)
+        {
+            add_subblocks_enxblock(&(fr.block[b]), 1);
+            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].fval = block[b];
+            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
-                }
+        }
 
-                /* check for disre block & fill it. */
-                if (ndisre > 0)
-                {
-                    int db = fr.nblock;
-                    fr.nblock += 1;
-                    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;
+        /* check for disre block & fill it. */
+        if (ndisre > 0)
+        {
+            int db = fr.nblock;
+            fr.nblock += 1;
+            add_blocks_enxframe(&fr, fr.nblock);
+
+            add_subblocks_enxblock(&(fr.block[db]), 2);
+            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 */
+        }
+        /* here we can put new-style blocks */
 
-                /* Free energy perturbation blocks */
-                if (md->dhc)
-                {
-                    mde_delta_h_coll_handle_block(md->dhc, &fr, fr.nblock);
-                }
+        /* Free energy perturbation blocks */
+        if (dhc_)
+        {
+            mde_delta_h_coll_handle_block(dhc_.get(), &fr, fr.nblock);
+        }
 
-                /* we can now free & reset the data in the blocks */
-                if (md->dhc)
-                {
-                    mde_delta_h_coll_reset(md->dhc);
-                }
+        /* we can now free & reset the data in the blocks */
+        if (dhc_)
+        {
+            mde_delta_h_coll_reset(dhc_.get());
+        }
 
-                /* AWH bias blocks. */
-                if (awh != nullptr)  // TODO: add boolean in t_mdebin. See in mdebin.h.
-                {
-                    awh->writeToEnergyFrame(step, &fr);
-                }
+        /* AWH bias blocks. */
+        if (awh != nullptr) // TODO: add boolean flag.
+        {
+            awh->writeToEnergyFrame(step, &fr);
+        }
 
-                /* do the actual I/O */
-                do_enx(fp_ene, &fr);
-                if (fr.nre)
-                {
-                    /* We have stored the sums, so reset the sum history */
-                    reset_ebin_sums(md->ebin);
-                }
-            }
-            free_enxframe(&fr);
-            break;
-        case eprAVER:
-            if (log)
-            {
-                pprint(log, "A V E R A G E S", md);
-            }
-            break;
-        case eprRMS:
-            if (log)
-            {
-                pprint(log, "R M S - F L U C T U A T I O N S", md);
-            }
-            break;
-        default:
-            gmx_fatal(FARGS, "Invalid print mode (%d)", mode);
+        /* do the actual I/O */
+        do_enx(fp_ene, &fr);
+        if (fr.nre)
+        {
+            /* We have stored the sums, so reset the sum history */
+            reset_ebin_sums(ebin_);
+        }
+    }
+    free_enxframe(&fr);
+    if (log)
+    {
+        if (bOR && 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);
+        fprintf(log, "\n");
     }
+}
 
+void EnergyOutput::printAnnealingTemperatures(FILE* log, const SimulationGroups* groups, const t_grpopts* opts)
+{
     if (log)
     {
         if (opts)
         {
-            for (i = 0; i < opts->ngtc; i++)
+            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]);
                 }
             }
+            fprintf(log, "\n");
         }
-        if (mode == eprNORMAL && bOR && fcd->orires.nr > 0)
+    }
+}
+
+void EnergyOutput::printAverages(FILE* log, const SimulationGroups* groups)
+{
+    if (ebin_->nsum_sim <= 0)
+    {
+        if (log)
         {
-            print_orires_log(log, &(fcd->orires));
+            fprintf(log, "Not enough data recorded to report energy averages\n");
         }
+        return;
+    }
+    if (log)
+    {
+
+        char buf1[22], buf2[22];
+
+        fprintf(log, "\t<======  ###############  ==>\n");
+        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",
+                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, md->ebin, md->ie, md->f_nre+md->nCrmsd, 5, mode, TRUE);
+        pr_ebin(log, ebin_, ie_, f_nre_ + nCrmsd_, 5, eprAVER, true);
         fprintf(log, "\n");
 
-        if (mode == eprAVER)
+        if (bDynBox_)
         {
-            if (md->bDynBox)
-            {
-                pr_ebin(log, md->ebin, md->ib, md->bTricl ? NTRICLBOXS : NBOXS, 5,
-                        mode, TRUE);
-                fprintf(log, "\n");
-            }
-            if (md->bConstrVir)
-            {
-                fprintf(log, "   Constraint Virial (%s)\n", unit_energy);
-                pr_ebin(log, md->ebin, md->isvir, 9, 3, mode, FALSE);
-                fprintf(log, "\n");
-                fprintf(log, "   Force Virial (%s)\n", unit_energy);
-                pr_ebin(log, md->ebin, md->ifvir, 9, 3, mode, FALSE);
-                fprintf(log, "\n");
-            }
-            if (md->bPres)
-            {
-                fprintf(log, "   Total Virial (%s)\n", unit_energy);
-                pr_ebin(log, md->ebin, md->ivir, 9, 3, mode, FALSE);
-                fprintf(log, "\n");
-                fprintf(log, "   Pressure (%s)\n", unit_pres_bar);
-                pr_ebin(log, md->ebin, md->ipres, 9, 3, mode, FALSE);
-                fprintf(log, "\n");
-            }
-            if (md->bMu)
-            {
-                fprintf(log, "   Total Dipole (%s)\n", unit_dipole_D);
-                pr_ebin(log, md->ebin, md->imu, 3, 3, mode, FALSE);
-                fprintf(log, "\n");
-            }
+            pr_ebin(log, ebin_, ib_, bTricl_ ? tricl_boxs_nm.size() : boxs_nm.size(), 5, eprAVER, true);
+            fprintf(log, "\n");
+        }
+        if (bPres_)
+        {
+            fprintf(log, "   Total Virial (%s)\n", unit_energy);
+            pr_ebin(log, ebin_, ivir_, 9, 3, eprAVER, false);
+            fprintf(log, "\n");
+            fprintf(log, "   Pressure (%s)\n", unit_pres_bar);
+            pr_ebin(log, ebin_, ipres_, 9, 3, eprAVER, false);
+            fprintf(log, "\n");
+        }
+        if (bMu_)
+        {
+            fprintf(log, "   Total Dipole (%s)\n", unit_dipole_D);
+            pr_ebin(log, ebin_, imu_, 3, 3, eprAVER, false);
+            fprintf(log, "\n");
+        }
 
-            if (md->nE > 1)
+        if (nE_ > 1)
+        {
+            int padding = 8 - strlen(unit_energy);
+            fprintf(log, "%*sEpot (%s)   ", padding, "", unit_energy);
+            for (auto key : keysOf(bEInd_))
             {
-                if (md->print_grpnms == nullptr)
+                if (bEInd_[key])
                 {
-                    snew(md->print_grpnms, md->nE);
-                    n = 0;
-                    for (i = 0; (i < md->nEg); i++)
-                    {
-                        ni = groups->groups[SimulationAtomGroupType::EnergyOutput][i];
-                        for (j = i; (j < md->nEg); j++)
-                        {
-                            nj = groups->groups[SimulationAtomGroupType::EnergyOutput][j];
-                            sprintf(buf, "%s-%s", *(groups->groupNames[ni]),
-                                    *(groups->groupNames[nj]));
-                            md->print_grpnms[n++] = gmx_strdup(buf);
-                        }
-                    }
-                }
-                sprintf(buf, "Epot (%s)", unit_energy);
-                fprintf(log, "%15s   ", buf);
-                for (i = 0; (i < egNR); i++)
-                {
-                    if (md->bEInd[i])
-                    {
-                        fprintf(log, "%12s   ", egrp_nm[i]);
-                    }
+                    fprintf(log, "%12s   ", enumValueToString(key));
                 }
-                fprintf(log, "\n");
-                for (i = 0; (i < md->nE); i++)
-                {
-                    fprintf(log, "%15s", md->print_grpnms[i]);
-                    pr_ebin(log, md->ebin, md->igrp[i], md->nEc, md->nEc, mode,
-                            FALSE);
-                }
-                fprintf(log, "\n");
-            }
-            if (md->nTC > 1)
-            {
-                pr_ebin(log, md->ebin, md->itemp, md->nTC, 4, mode, TRUE);
-                fprintf(log, "\n");
             }
-            if (md->nU > 1)
+            fprintf(log, "\n");
+
+            int n = 0;
+            for (int i = 0; (i < nEg_); i++)
             {
-                fprintf(log, "%15s   %12s   %12s   %12s\n",
-                        "Group", "Ux", "Uy", "Uz");
-                for (i = 0; (i < md->nU); i++)
+                int ni = groups->groups[SimulationAtomGroupType::EnergyOutput][i];
+                for (int j = i; (j < nEg_); j++)
                 {
-                    ni = groups->groups[SimulationAtomGroupType::Acceleration][i];
-                    fprintf(log, "%15s", *groups->groupNames[ni]);
-                    pr_ebin(log, md->ebin, md->iu+3*i, 3, 3, mode, 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++;
                 }
-                fprintf(log, "\n");
             }
+            fprintf(log, "\n");
+        }
+        if (nTC_ > 1)
+        {
+            pr_ebin(log, ebin_, itemp_, nTC_, 4, eprAVER, true);
+            fprintf(log, "\n");
         }
     }
-
 }
 
-//! Legacy update function
-void update_energyhistory(energyhistory_t * enerhist, const t_mdebin * mdebin)
+void EnergyOutput::fillEnergyHistory(energyhistory_t* enerhist) const
 {
-    const t_ebin * const ebin = mdebin->ebin;
+    const t_ebin* const ebin = ebin_;
 
     enerhist->nsteps     = ebin->nsteps;
     enerhist->nsum       = ebin->nsum;
@@ -1642,120 +1432,67 @@ void update_energyhistory(energyhistory_t * enerhist, const t_mdebin * mdebin)
             enerhist->ener_sum_sim[i] = ebin->e_sim[i].esum;
         }
     }
-    if (mdebin->dhc)
+    if (dhc_)
     {
-        mde_delta_h_coll_update_energyhistory(mdebin->dhc, enerhist);
+        mde_delta_h_coll_update_energyhistory(dhc_.get(), enerhist);
     }
 }
 
-//! Legacy restore function
-void restore_energyhistory_from_state(t_mdebin              * mdebin,
-                                      const energyhistory_t * enerhist)
+void EnergyOutput::restoreFromEnergyHistory(const energyhistory_t& enerhist)
 {
-    unsigned int nener = static_cast<unsigned int>(mdebin->ebin->nener);
+    unsigned int nener = static_cast<unsigned int>(ebin_->nener);
 
-    GMX_RELEASE_ASSERT(enerhist, "Need valid history to restore");
-
-    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());
     }
 
-    mdebin->ebin->nsteps     = enerhist->nsteps;
-    mdebin->ebin->nsum       = enerhist->nsum;
-    mdebin->ebin->nsteps_sim = enerhist->nsteps_sim;
-    mdebin->ebin->nsum_sim   = enerhist->nsum_sim;
+    ebin_->nsteps     = enerhist.nsteps;
+    ebin_->nsum       = enerhist.nsum;
+    ebin_->nsteps_sim = enerhist.nsteps_sim;
+    ebin_->nsum_sim   = enerhist.nsum_sim;
 
-    for (int i = 0; i < mdebin->ebin->nener; i++)
+    for (int i = 0; i < ebin_->nener; i++)
     {
-        mdebin->ebin->e[i].eav  =
-            (enerhist->nsum > 0 ? enerhist->ener_ave[i] : 0);
-        mdebin->ebin->e[i].esum =
-            (enerhist->nsum > 0 ? enerhist->ener_sum[i] : 0);
-        mdebin->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 (mdebin->dhc)
+    if (dhc_)
     {
-        mde_delta_h_coll_restore_energyhistory(mdebin->dhc, enerhist->deltaHForeignLambdas.get());
+        mde_delta_h_coll_restore_energyhistory(dhc_.get(), enerhist.deltaHForeignLambdas.get());
     }
 }
 
-}   // namespace
-
-EnergyOutput::EnergyOutput()
-    : mdebin(nullptr)
-{
-}
-
-void EnergyOutput::prepare(ener_file        *fp_ene,
-                           const gmx_mtop_t *mtop,
-                           const t_inputrec *ir,
-                           const pull_t     *pull_work,
-                           FILE             *fp_dhdl,
-                           bool              isRerun)
-{
-    mdebin = init_mdebin(fp_ene, mtop, ir, pull_work, fp_dhdl, isRerun);
-}
-
-EnergyOutput::~EnergyOutput()
-{
-    done_mdebin(mdebin);
-}
-
-t_ebin *EnergyOutput::getEbin()
-{
-    return mdebin->ebin;
-}
-
-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)
-{
-    upd_mdebin(mdebin, bDoDHDL, bSum, time, tmass, enerd, state, fep,
-               expand, box, svir, fvir, vir, pres, ekind, mu_tot, constr);
-}
-
-void EnergyOutput::printStepToEnergyFile(ener_file *fp_ene, bool bEne, bool bDR, bool bOR,
-                                         FILE *log,
-                                         int64_t step, double time,
-                                         int mode,
-                                         t_fcdata *fcd,
-                                         SimulationGroups *groups, t_grpopts *opts,
-                                         gmx::Awh *awh)
-{
-    print_ebin(fp_ene, bEne, bDR, bOR, log, step, time, mode,
-               mdebin, fcd, groups, opts, awh);
-}
-
 int EnergyOutput::numEnergyTerms() const
 {
-    return mdebin->ebin->nener;
+    return ebin_->nener;
 }
 
-void EnergyOutput::fillEnergyHistory(energyhistory_t *enerhist) const
+void EnergyOutput::printEnergyConservation(FILE* fplog, int simulationPart, bool usingMdIntegrator) const
 {
-    update_energyhistory(enerhist, mdebin);
-}
+    if (fplog == nullptr)
+    {
+        return;
+    }
 
-void EnergyOutput::restoreFromEnergyHistory(const energyhistory_t &enerhist)
-{
-    restore_energyhistory_from_state(mdebin, &enerhist);
+    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