From 351ed2c1bdec653c700d15f2ecb40ebd47b7fa68 Mon Sep 17 00:00:00 2001
From: Anna Mareike Kostelecky <anmako96@web.de>
Date: Wed, 26 Feb 2025 14:21:09 +0100
Subject: [PATCH] [md][boundary][freeflowporenetwork] add energy and
 compositional coupling conditions

---
 .../freeflowporenetwork/couplingconditions.hh | 480 +++++++++++++++++-
 .../freeflowporenetwork/couplingmanager.hh    |  64 ++-
 2 files changed, 512 insertions(+), 32 deletions(-)

diff --git a/dumux/multidomain/boundary/freeflowporenetwork/couplingconditions.hh b/dumux/multidomain/boundary/freeflowporenetwork/couplingconditions.hh
index 000e79d6f6..aade0712f0 100644
--- a/dumux/multidomain/boundary/freeflowporenetwork/couplingconditions.hh
+++ b/dumux/multidomain/boundary/freeflowporenetwork/couplingconditions.hh
@@ -16,6 +16,7 @@
 #include <numeric>
 
 #include <dune/common/exceptions.hh>
+#include <dune/common/fvector.hh>
 
 #include <dumux/common/properties.hh>
 #include <dumux/common/math.hh>
@@ -39,8 +40,8 @@ template<class MDTraits, class CouplingManager>
 using FreeFlowPoreNetworkCouplingConditions
     = FreeFlowPoreNetworkCouplingConditionsImplementation<
         MDTraits, CouplingManager,
-        GetPropType<typename MDTraits::template SubDomain<0>::TypeTag, Properties::ModelTraits>::enableEnergyBalance(),
-        (GetPropType<typename MDTraits::template SubDomain<0>::TypeTag, Properties::ModelTraits>::numFluidComponents() > 1)
+        GetPropType<typename MDTraits::template SubDomain<1>::TypeTag, Properties::ModelTraits>::enableEnergyBalance(),
+        (GetPropType<typename MDTraits::template SubDomain<1>::TypeTag, Properties::ModelTraits>::numFluidComponents() > 1)
     >;
 
 /*!
@@ -105,7 +106,6 @@ public:
     static constexpr auto couplingCompIdx(Dune::index_constant<i> id, int coupledCompIdx)
     { return IndexHelper::couplingCompIdx(id, coupledCompIdx); }
 
-
     /*!
      * \brief Returns the momentum flux across the coupling boundary.
      *
@@ -201,13 +201,37 @@ public:
             return (upwindWeight * outsideQuantity + (1.0 - upwindWeight) * insideQuantity) * volumeFlow;
     }
 
-protected:
+    /*!
+     * \brief Evaluate the conductive energy flux across the interface.
+     */
+    template<std::size_t i, std::size_t j, bool isNI = enableEnergyBalance, typename std::enable_if_t<isNI, int> = 0>
+    static Scalar conductiveEnergyFlux(Dune::index_constant<i> domainI,
+                                       Dune::index_constant<j> domainJ,
+                                       const SubControlVolumeFace<freeFlowMassIndex>& scvf,
+                                       const SubControlVolume<i>& scvI,
+                                       const SubControlVolume<j>& scvJ,
+                                       const VolumeVariables<i>& volVarsI,
+                                       const VolumeVariables<j>& volVarsJ)
+    {
+        // use properties (distance and thermal conductivity) for transimissibillity coefficient
+        //  only from FF side as vertex (pore body) of PNM grid lies on boundary
+        const auto& freeFlowVolVars = std::get<const VolumeVariables<freeFlowMassIndex>&>(std::forward_as_tuple(volVarsI, volVarsJ));
+        const auto& ffScv = std::get<const SubControlVolume<freeFlowMassIndex>&>(std::forward_as_tuple(scvI, scvJ));
+        // distance from FF cell center to interface
+        const Scalar distance = getDistance_(ffScv, scvf);
+        const Scalar tij = freeFlowVolVars.fluidThermalConductivity() / distance;
+
+        const Scalar deltaT = volVarsJ.temperature() - volVarsI.temperature();
 
+        return -deltaT * tij;
+    }
+
+protected:
     /*!
      * \brief Returns the distance between an scvf and the corresponding scv center.
      */
     template<class Scv, class Scvf>
-    Scalar getDistance_(const Scv& scv, const Scvf& scvf) const
+    static Scalar getDistance_(const Scv& scv, const Scvf& scvf)
     {
         return (scv.dofPosition() - scvf.ipGlobal()).two_norm();
     }
@@ -256,19 +280,26 @@ public:
                                         const CouplingContext& context)
     {
         Scalar massFlux(0.0);
+        const auto& pnmVolVars = insideVolVars[scv.indexInElement()];
 
         for (const auto& c : context)
         {
-            // positive values indicate flux into pore-network region
+            // positive values of normal free-flow velocity indicate flux leaving the free flow into the pore-network region
             const Scalar normalFFVelocity = c.velocity * c.scvf.unitOuterNormal();
-            const bool pnmIsUpstream = std::signbit(normalFFVelocity);
 
-            const Scalar pnmDensity = insideVolVars[scv].density(couplingPhaseIdx(ParentType::poreNetworkIndex));
-            const Scalar ffDensity = c.volVars.density(couplingPhaseIdx(ParentType::freeFlowMassIndex));
+            // normal pnm velocity (correspondign to its normal vector) is in the opposite direction of normal free flow velocity
+            // positive values of normal pnm velocity indicate flux leaving the pnm into the free-flow region
+            const Scalar normalPNMVelocity = -normalFFVelocity;
+            const bool pnmIsUpstream = std::signbit(normalFFVelocity);
             const Scalar area = c.scvf.area() * c.volVars.extrusionFactor();
 
-            // flux is used as source term: positive values mean influx
-            massFlux += ParentType::advectiveFlux(pnmDensity, ffDensity, normalFFVelocity*area, pnmIsUpstream);
+            auto flux = massFlux_(domainI, domainJ, c.scvf, scv, c.scv, pnmVolVars, c.volVars, normalPNMVelocity, pnmIsUpstream);
+            // flux is used as source term (volumetric flux): positive values mean influx
+            // thus, it is multiplied with area and we flip the sign
+            flux *= area;
+            flux *= -1.0;
+
+            massFlux += flux;
         }
 
         return massFlux;
@@ -288,16 +319,435 @@ public:
         // positive values indicate flux into pore-network region
         const Scalar normalFFVelocity = context.velocity * scvf.unitOuterNormal();
         const bool ffIsUpstream = !std::signbit(normalFFVelocity);
-
-        const Scalar ffDensity = insideVolVars[scvf.insideScvIdx()].density(couplingPhaseIdx(ParentType::freeFlowMassIndex));
-        const Scalar pnmDensity = context.volVars.density(couplingPhaseIdx(ParentType::poreNetworkIndex));
+        const auto& insideScv = fvGeometry.scv(scvf.insideScvIdx());
+        const auto& ffVolVars = insideVolVars[scvf.insideScvIdx()];
 
         // flux is used in Neumann condition: positive values mean flux out of free-flow domain
-        return ParentType::advectiveFlux(ffDensity, pnmDensity, normalFFVelocity, ffIsUpstream);
+        return massFlux_(domainI, domainJ, scvf, insideScv, context.scv, ffVolVars, context.volVars, normalFFVelocity, ffIsUpstream);
+    }
+
+    /*!
+     * \brief Returns the energy flux across the coupling boundary as seen from the pore network.
+     */
+    template<class CouplingContext, bool isNI = enableEnergyBalance, typename std::enable_if_t<isNI, int> = 0>
+    static Scalar energyCouplingCondition(Dune::index_constant<ParentType::poreNetworkIndex> domainI,
+                                          Dune::index_constant<ParentType::freeFlowMassIndex> domainJ,
+                                          const FVElementGeometry<ParentType::poreNetworkIndex>& fvGeometry,
+                                          const SubControlVolume<ParentType::poreNetworkIndex>& scv,
+                                          const ElementVolumeVariables<ParentType::poreNetworkIndex>& insideVolVars,
+                                          const CouplingContext& context)
+    {
+        Scalar energyFlux(0.0);
+
+        //use VolumeVariables (same type as for context.volVars) instead of ElementVolumeVariables
+        const auto& pnmVolVars = insideVolVars[scv.indexInElement()];
+
+        for(const auto& c : context)
+        {
+            // positive values indicate flux into pore-network region
+            const Scalar normalFFVelocity = c.velocity * c.scvf.unitOuterNormal();
+            const bool pnmIsUpstream = std::signbit(normalFFVelocity);
+            const Scalar normalPNMVelocity = -normalFFVelocity;
+            const Scalar area = c.scvf.area() * c.volVars.extrusionFactor();
+
+            auto flux = energyFlux_(domainI, domainJ, c.scvf, scv, c.scv, pnmVolVars, c.volVars, normalPNMVelocity, pnmIsUpstream);
+
+            // flux is used as source term (volumetric flux): positive values mean influx
+            // thus, it is multiplied with area and we flip the sign
+            flux *= area;
+            flux *= -1.0;
+
+            energyFlux += flux;
+        }
+
+        return energyFlux;
+    }
+
+    /*!
+     * \brief Returns the energy flux across the coupling boundary as seen from the free-flow domain.
+     */
+    template<class CouplingContext, bool isNI = enableEnergyBalance, typename std::enable_if_t<isNI, int> = 0>
+    static Scalar energyCouplingCondition(Dune::index_constant<ParentType::freeFlowMassIndex> domainI,
+                                          Dune::index_constant<ParentType::poreNetworkIndex> domainJ,
+                                          const FVElementGeometry<ParentType::freeFlowMassIndex>& fvGeometry,
+                                          const SubControlVolumeFace<ParentType::freeFlowMassIndex>& scvf,
+                                          const ElementVolumeVariables<ParentType::freeFlowMassIndex>& insideVolVars,
+                                          const CouplingContext& context)
+    {
+        // positive values indicate flux into pore-network region
+        const Scalar normalFFVelocity = context.velocity * scvf.unitOuterNormal();
+        const bool ffIsUpstream = !std::signbit(normalFFVelocity);
+        const auto& insideScv = fvGeometry.scv(scvf.insideScvIdx());
+        //use VolumeVariables (same type as for context.volVars) instead of ElementVolumeVariables
+        const auto& ffVolVars = insideVolVars[scvf.insideScvIdx()];
+
+        return energyFlux_(domainI, domainJ, scvf, insideScv, context.scv, ffVolVars, context.volVars, normalFFVelocity, ffIsUpstream);
+    }
+
+private:
+    /*!
+     * \brief Evaluate the mole/mass flux across the interface.
+     */
+    template<std::size_t i, std::size_t j>
+    static Scalar massFlux_(Dune::index_constant<i> domainI,
+                                   Dune::index_constant<j> domainJ,
+                                   const SubControlVolumeFace<ParentType::freeFlowMassIndex>& scvf,
+                                   const SubControlVolume<i>& scvI,
+                                   const SubControlVolume<j>& scvJ,
+                                   const VolumeVariables<i>& insideVolVars,
+                                   const VolumeVariables<j>& outsideVolVars,
+                                   const Scalar velocity,
+                                   const bool insideIsUpstream)
+    {
+        Scalar flux(0.0);
+
+        const Scalar insideDensity = insideVolVars.density(couplingPhaseIdx(domainI));
+        const Scalar outsideDensity = outsideVolVars.density(couplingPhaseIdx(domainJ));
+
+        flux = ParentType::advectiveFlux(insideDensity, outsideDensity, velocity, insideIsUpstream);
+
+        return flux;
+    }
+
+    /*!
+     * \brief Evaluate the energy flux across the interface.
+     */
+    template<std::size_t i, std::size_t j, bool isNI = enableEnergyBalance, typename std::enable_if_t<isNI, int> = 0>
+    static Scalar energyFlux_(Dune::index_constant<i> domainI,
+                              Dune::index_constant<j> domainJ,
+                              const SubControlVolumeFace<ParentType::freeFlowMassIndex>& scvf,
+                              const SubControlVolume<i>& scvI,
+                              const SubControlVolume<j>& scvJ,
+                              const VolumeVariables<i>& insideVolVars,
+                              const VolumeVariables<j>& outsideVolVars,
+                              const Scalar velocity,
+                              const bool insideIsUpstream)
+    {
+        Scalar flux(0.0);
+
+        // convective fluxes
+        const Scalar insideTerm = insideVolVars.density(couplingPhaseIdx(domainI)) * insideVolVars.enthalpy(couplingPhaseIdx(domainI));
+        const Scalar outsideTerm = outsideVolVars.density(couplingPhaseIdx(domainJ)) * outsideVolVars.enthalpy(couplingPhaseIdx(domainJ));
+
+        flux += ParentType::advectiveFlux(insideTerm, outsideTerm, velocity, insideIsUpstream);
+
+        // conductive energy fluxes
+        flux += ParentType::conductiveEnergyFlux(domainI, domainJ, scvf, scvI, scvJ, insideVolVars, outsideVolVars);
+
+        return flux;
+    }
+};
+
+/*!
+ * \ingroup FreeFlowPoreNetworkCoupling
+ * \brief Coupling conditions specialization for compositional models.
+ */
+template<class MDTraits, class CouplingManager, bool enableEnergyBalance>
+class FreeFlowPoreNetworkCouplingConditionsImplementation<MDTraits, CouplingManager, enableEnergyBalance, true>
+: public FreeFlowPoreNetworkCouplingConditionsImplementationBase<MDTraits, CouplingManager>
+{
+    using ParentType = FreeFlowPoreNetworkCouplingConditionsImplementationBase<MDTraits, CouplingManager>;
+    using Scalar = typename MDTraits::Scalar;
+
+    // the sub domain type tags
+    template<std::size_t id>
+    using SubDomainTypeTag = typename MDTraits::template SubDomain<id>::TypeTag;
+
+    template<std::size_t id> using GridGeometry = GetPropType<SubDomainTypeTag<id>, Properties::GridGeometry>;
+    template<std::size_t id> using Element = typename GridGeometry<id>::GridView::template Codim<0>::Entity;
+    template<std::size_t id> using FVElementGeometry = typename GridGeometry<id>::LocalView;
+    template<std::size_t id> using SubControlVolumeFace = typename GridGeometry<id>::LocalView::SubControlVolumeFace;
+    template<std::size_t id> using SubControlVolume = typename GridGeometry<id>::LocalView::SubControlVolume;
+    template<std::size_t id> using Indices = typename GetPropType<SubDomainTypeTag<id>, Properties::ModelTraits>::Indices;
+    template<std::size_t id> using ElementVolumeVariables = typename GetPropType<SubDomainTypeTag<id>, Properties::GridVolumeVariables>::LocalView;
+    template<std::size_t id> using VolumeVariables  = typename GetPropType<SubDomainTypeTag<id>, Properties::GridVolumeVariables>::VolumeVariables;
+    template<std::size_t id> using FluidSystem = typename VolumeVariables<id>::FluidSystem;
+    template<std::size_t id> using Problem = GetPropType<SubDomainTypeTag<id>, Properties::Problem>;
+    template<std::size_t id> using NumEqVector = typename Problem<id>::Traits::NumEqVector;
+
+    static constexpr bool useMoles = GetPropType<SubDomainTypeTag<ParentType::freeFlowMassIndex>, Properties::ModelTraits>::useMoles();
+    static constexpr auto numComponents = GetPropType<SubDomainTypeTag<ParentType::freeFlowMassIndex>, Properties::ModelTraits>::numFluidComponents();
+    static constexpr auto referenceSystemFormulation = GetPropType<SubDomainTypeTag<ParentType::freeFlowMassIndex>, Properties::MolecularDiffusionType>::referenceSystemFormulation();
+    static constexpr auto replaceCompEqIdx = GetPropType<SubDomainTypeTag<ParentType::freeFlowMassIndex>, Properties::ModelTraits>::replaceCompEqIdx();
+
+    static_assert(GetPropType<SubDomainTypeTag<ParentType::poreNetworkIndex>, Properties::ModelTraits>::numFluidComponents() == numComponents,
+                  "Models must have same number of components");
+
+public:
+    using ParentType::ParentType;
+    using ParentType::couplingPhaseIdx;
+    using ParentType::couplingCompIdx;
+
+    using NumCompVector = Dune::FieldVector<Scalar, numComponents>;
+
+    /*!
+     * \brief Returns the mass flux across the coupling boundary as seen from the pore-network domain.
+     */
+    template<class CouplingContext>
+    static NumCompVector massCouplingCondition(Dune::index_constant<ParentType::poreNetworkIndex> domainI,
+                                               Dune::index_constant<ParentType::freeFlowMassIndex> domainJ,
+                                               const FVElementGeometry<ParentType::poreNetworkIndex>& fvGeometry,
+                                               const SubControlVolume<ParentType::poreNetworkIndex>& scv,
+                                               const ElementVolumeVariables<ParentType::poreNetworkIndex>& insideVolVars,
+                                               const CouplingContext& context)
+    {
+        NumCompVector massFlux(0.0);
+        const auto& pnmVolVars = insideVolVars[scv.indexInElement()];
+
+        for (const auto& c : context)
+        {
+            // positive values indicate flux into pore-network region
+            const Scalar normalFFVelocity = c.velocity * c.scvf.unitOuterNormal();
+            const bool pnmIsUpstream = std::signbit(normalFFVelocity);
+            const Scalar normalPNMVelocity = -normalFFVelocity;
+            const Scalar area = c.scvf.area() * c.volVars.extrusionFactor();
+
+            auto flux = massFlux_(domainI, domainJ, c.scvf, scv, c.scv, pnmVolVars, c.volVars, normalPNMVelocity, pnmIsUpstream);
+
+            // flux is used as source term (volumetric flux): positive values mean influx
+            // thus, it is multiplied with area and we flip the sign
+            flux *= area;
+            flux *= -1.0;
+
+            massFlux += flux;
+        }
+
+        return massFlux;
+    }
+
+    /*!
+     * \brief Returns the mass flux across the coupling boundary as seen from the free-flow domain.
+     */
+    template<class CouplingContext>
+    static NumCompVector massCouplingCondition(Dune::index_constant<ParentType::freeFlowMassIndex> domainI,
+                                               Dune::index_constant<ParentType::poreNetworkIndex> domainJ,
+                                               const FVElementGeometry<ParentType::freeFlowMassIndex>& fvGeometry,
+                                               const SubControlVolumeFace<ParentType::freeFlowMassIndex>& scvf,
+                                               const ElementVolumeVariables<ParentType::freeFlowMassIndex>& insideVolVars,
+                                               const CouplingContext& context)
+    {
+        // positive values indicate flux into pore-network region
+        const Scalar normalFFVelocity = context.velocity * scvf.unitOuterNormal();
+        const bool ffIsUpstream = !std::signbit(normalFFVelocity);
+        const auto& insideScv = fvGeometry.scv(scvf.insideScvIdx());
+        const auto& ffVolVars = insideVolVars[scvf.insideScvIdx()];
+
+        return massFlux_(domainI, domainJ, scvf, insideScv, context.scv, ffVolVars, context.volVars, normalFFVelocity, ffIsUpstream);
+    }
+
+    /*!
+     * \brief Returns the energy flux across the coupling boundary as seen from the pore network.
+     */
+    template<class CouplingContext, bool isNI = enableEnergyBalance, typename std::enable_if_t<isNI, int> = 0>
+    static Scalar energyCouplingCondition(Dune::index_constant<ParentType::poreNetworkIndex> domainI,
+                                          Dune::index_constant<ParentType::freeFlowMassIndex> domainJ,
+                                          const FVElementGeometry<ParentType::poreNetworkIndex>& fvGeometry,
+                                          const SubControlVolume<ParentType::poreNetworkIndex>& scv,
+                                          const ElementVolumeVariables<ParentType::poreNetworkIndex>& insideVolVars,
+                                          const CouplingContext& context)
+    {
+        Scalar energyFlux(0.0);
+
+        //use VolumeVariables (same type as for context.volVars) instead of ElementVolumeVariables
+        const auto& pnmVolVars = insideVolVars[scv.indexInElement()];
+
+        for(const auto& c : context)
+        {
+            // positive values indicate flux into pore-network region
+            const Scalar normalFFVelocity = c.velocity * c.scvf.unitOuterNormal();
+            const bool pnmIsUpstream = std::signbit(normalFFVelocity);
+            const Scalar normalPNMVelocity = -normalFFVelocity;
+            const Scalar area = c.scvf.area() * c.volVars.extrusionFactor();
+
+            auto flux = energyFlux_(domainI, domainJ, c.scvf, scv, c.scv, pnmVolVars, c.volVars, normalPNMVelocity, pnmIsUpstream);
+
+            // flux is used as source term (volumetric flux): positive values mean influx
+            // thus, it is multiplied with area and we flip the sign
+            flux *= area;
+            flux *= -1.0;
+            energyFlux += flux;
+        }
+
+        return energyFlux;
+    }
+
+    /*!
+     * \brief Returns the energy flux across the coupling boundary as seen from the free-flow domain.
+     */
+    template<class CouplingContext, bool isNI = enableEnergyBalance, typename std::enable_if_t<isNI, int> = 0>
+    static Scalar energyCouplingCondition(Dune::index_constant<ParentType::freeFlowMassIndex> domainI,
+                                          Dune::index_constant<ParentType::poreNetworkIndex> domainJ,
+                                          const FVElementGeometry<ParentType::freeFlowMassIndex>& fvGeometry,
+                                          const SubControlVolumeFace<ParentType::freeFlowMassIndex>& scvf,
+                                          const ElementVolumeVariables<ParentType::freeFlowMassIndex>& insideVolVars,
+                                          const CouplingContext& context)
+    {
+        // positive values indicate flux into pore-network region
+        const Scalar normalFFVelocity = context.velocity * scvf.unitOuterNormal();
+        const bool ffIsUpstream = !std::signbit(normalFFVelocity);
+        const auto& insideScv = fvGeometry.scv(scvf.insideScvIdx());
+        //use VolumeVariables (same type as for context.volVars) instead of ElementVolumeVariables
+        const auto& ffVolVars = insideVolVars[scvf.insideScvIdx()];
+
+        return energyFlux_(domainI, domainJ, scvf, insideScv, context.scv, ffVolVars, context.volVars, normalFFVelocity, ffIsUpstream);
     }
 
 private:
+    /*!
+     * \brief Evaluate the compositional mole/mass flux across the interface.
+     */
+    template<std::size_t i, std::size_t j>
+    static NumCompVector massFlux_(Dune::index_constant<i> domainI,
+                                   Dune::index_constant<j> domainJ,
+                                   const SubControlVolumeFace<ParentType::freeFlowMassIndex>& scvf,
+                                   const SubControlVolume<i>& scvI,
+                                   const SubControlVolume<j>& scvJ,
+                                   const VolumeVariables<i>& insideVolVars,
+                                   const VolumeVariables<j>& outsideVolVars,
+                                   const Scalar velocity,
+                                   const bool insideIsUpstream)
+    {
+        NumCompVector flux(0.0);
+
+        auto moleOrMassFraction = [&](const auto& volVars, int phaseIdx, int compIdx)
+        { return useMoles ? volVars.moleFraction(phaseIdx, compIdx) : volVars.massFraction(phaseIdx, compIdx); };
+
+        auto moleOrMassDensity = [&](const auto& volVars, int phaseIdx)
+        { return useMoles ? volVars.molarDensity(phaseIdx) : volVars.density(phaseIdx); };
+
+        // advective fluxes
+        auto insideTerm = [&](int compIdx)
+        { return moleOrMassFraction(insideVolVars, couplingPhaseIdx(domainI), compIdx) * moleOrMassDensity(insideVolVars, couplingPhaseIdx(domainI)); };
 
+        auto outsideTerm = [&](int compIdx)
+        { return moleOrMassFraction(outsideVolVars, couplingPhaseIdx(domainJ), compIdx) * moleOrMassDensity(outsideVolVars, couplingPhaseIdx(domainJ)); };
+
+
+        for (int compIdx = 0; compIdx < numComponents; ++compIdx)
+        {
+            const int domainICompIdx = couplingCompIdx(domainI, compIdx);
+            const int domainJCompIdx = couplingCompIdx(domainJ, compIdx);
+
+            assert(FluidSystem<i>::componentName(domainICompIdx) == FluidSystem<j>::componentName(domainJCompIdx));
+
+            flux[domainICompIdx] += ParentType::advectiveFlux(insideTerm(domainICompIdx), outsideTerm(domainJCompIdx), velocity, insideIsUpstream);
+        }
+
+        // diffusive fluxes
+        NumCompVector diffusiveFlux = diffusiveMolecularFlux_(domainI, domainJ, scvf, scvI, scvJ, insideVolVars, outsideVolVars);
+
+       //convert to correct units if necessary
+        if (referenceSystemFormulation == ReferenceSystemFormulation::massAveraged && useMoles)
+        {
+            for (int compIdx = 0; compIdx < numComponents; ++compIdx)
+            {
+                const int domainICompIdx = couplingCompIdx(domainI, compIdx);
+                diffusiveFlux[domainICompIdx] /= FluidSystem<i>::molarMass(domainICompIdx);
+            }
+        }
+        if (referenceSystemFormulation == ReferenceSystemFormulation::molarAveraged && !useMoles)
+        {
+            for (int compIdx = 0; compIdx < numComponents; ++compIdx)
+            {
+                const int domainICompIdx = couplingCompIdx(domainI, compIdx);
+                diffusiveFlux[domainICompIdx] *= FluidSystem<i>::molarMass(domainICompIdx);
+            }
+        }
+
+        // total flux
+        flux += diffusiveFlux;
+
+        // convert to total mass/mole balance, if set be user
+        if (replaceCompEqIdx < numComponents)
+            flux[replaceCompEqIdx] = std::accumulate(flux.begin(), flux.end(), 0.0);
+
+        return flux;
+    }
+
+    /*!
+     * \brief Evaluate the energy flux (convective and conductive) across the interface.
+     */
+    template<std::size_t i, std::size_t j, bool isNI = enableEnergyBalance, typename std::enable_if_t<isNI, int> = 0>
+    static Scalar energyFlux_(Dune::index_constant<i> domainI,
+                              Dune::index_constant<j> domainJ,
+                              const SubControlVolumeFace<ParentType::freeFlowMassIndex>& scvf,
+                              const SubControlVolume<i>& scvI,
+                              const SubControlVolume<j>& scvJ,
+                              const VolumeVariables<i>& insideVolVars,
+                              const VolumeVariables<j>& outsideVolVars,
+                              const Scalar velocity,
+                              const bool insideIsUpstream)
+    {
+        Scalar flux(0.0);
+
+        // convective fluxes
+        const Scalar insideTerm = insideVolVars.density(couplingPhaseIdx(domainI)) * insideVolVars.enthalpy(couplingPhaseIdx(domainI));
+        const Scalar outsideTerm = outsideVolVars.density(couplingPhaseIdx(domainJ)) * outsideVolVars.enthalpy(couplingPhaseIdx(domainJ));
+
+        flux += ParentType::advectiveFlux(insideTerm, outsideTerm, velocity, insideIsUpstream);
+
+        // conductive fluxes
+        flux += ParentType::conductiveEnergyFlux(domainI, domainJ, scvf, scvI, scvJ, insideVolVars, outsideVolVars);
+
+        // TODO: add contribution from diffusive fluxes
+
+        return flux;
+    }
+
+    /*!
+     * \brief Evaluate the diffusive mole/mass flux across the interface.
+     */
+    template<std::size_t i, std::size_t j>
+    static NumCompVector diffusiveMolecularFlux_(Dune::index_constant<i> domainI,
+                                                 Dune::index_constant<j> domainJ,
+                                                 const SubControlVolumeFace<ParentType::freeFlowMassIndex>& scvf,
+                                                 const SubControlVolume<i>& scvI,
+                                                 const SubControlVolume<j>& scvJ,
+                                                 const VolumeVariables<i>& volVarsI,
+                                                 const VolumeVariables<j>& volVarsJ)
+    {
+        NumCompVector diffusiveFlux(0.0);
+        const Scalar avgDensity = 0.5*(massOrMolarDensity(volVarsI, referenceSystemFormulation, couplingPhaseIdx(domainI))
+                                     + massOrMolarDensity(volVarsJ, referenceSystemFormulation, couplingPhaseIdx(domainJ)));
+
+        const auto& freeFlowVolVars = std::get<const VolumeVariables<ParentType::freeFlowMassIndex>&>(std::forward_as_tuple(volVarsI, volVarsJ));
+        const auto& ffScv = std::get<const SubControlVolume<ParentType::freeFlowMassIndex>&>(std::forward_as_tuple(scvI, scvJ));
+        const Scalar distance = ParentType::getDistance_(ffScv, scvf);
+
+        for (int compIdx = 1; compIdx < numComponents; ++compIdx)
+        {
+            const int freeFlowMainCompIdx = couplingPhaseIdx(ParentType::freeFlowMassIndex);
+            const int domainICompIdx = couplingCompIdx(domainI, compIdx);
+            const int domainJCompIdx = couplingCompIdx(domainJ, compIdx);
+
+            assert(FluidSystem<i>::componentName(domainICompIdx) == FluidSystem<j>::componentName(domainJCompIdx));
+
+            const Scalar massOrMoleFractionI = massOrMoleFraction(volVarsI, referenceSystemFormulation, couplingPhaseIdx(domainI), domainICompIdx);
+            const Scalar massOrMoleFractionJ = massOrMoleFraction(volVarsJ, referenceSystemFormulation, couplingPhaseIdx(domainJ), domainJCompIdx);
+            const Scalar deltaMassOrMoleFrac = massOrMoleFractionJ - massOrMoleFractionI;
+
+            const Scalar tij = freeFlowVolVars.effectiveDiffusionCoefficient(couplingPhaseIdx(ParentType::freeFlowMassIndex),
+                                                                             freeFlowMainCompIdx,
+                                                                             couplingCompIdx(ParentType::freeFlowMassIndex, compIdx))
+                                                                             / distance;
+            diffusiveFlux[domainICompIdx] += -avgDensity * tij * deltaMassOrMoleFrac;
+        }
+
+        const Scalar cumulativeFlux = std::accumulate(diffusiveFlux.begin(), diffusiveFlux.end(), 0.0);
+        diffusiveFlux[couplingCompIdx(domainI, 0)] = -cumulativeFlux;
+
+        return diffusiveFlux;
+    }
+
+    static Scalar getComponentEnthalpy_(const VolumeVariables<ParentType::freeFlowMassIndex>& volVars, int phaseIdx, int compIdx)
+    {
+        return FluidSystem<ParentType::freeFlowMassIndex>::componentEnthalpy(volVars.fluidState(), 0, compIdx);
+    }
+
+    static Scalar getComponentEnthalpy_(const VolumeVariables<ParentType::poreNetworkIndex>& volVars, int phaseIdx, int compIdx)
+    {
+        return FluidSystem<ParentType::poreNetworkIndex>::componentEnthalpy(volVars.fluidState(), phaseIdx, compIdx);
+    }
 };
 
 } // end namespace Dumux
diff --git a/dumux/multidomain/boundary/freeflowporenetwork/couplingmanager.hh b/dumux/multidomain/boundary/freeflowporenetwork/couplingmanager.hh
index 0c42137141..b032ea77cc 100644
--- a/dumux/multidomain/boundary/freeflowporenetwork/couplingmanager.hh
+++ b/dumux/multidomain/boundary/freeflowporenetwork/couplingmanager.hh
@@ -235,12 +235,12 @@ public:
     /*!
      * \brief Returns the mass flux across the coupling boundary.
      */
-    auto massCouplingCondition(Dune::index_constant<poreNetworkIndex> domainI, Dune::index_constant<freeFlowMassIndex> domainJ,
+    auto massCouplingCondition(Dune::index_constant<poreNetworkIndex> domainI,
+                               Dune::index_constant<freeFlowMassIndex> domainJ,
                                const FVElementGeometry<poreNetworkIndex>& fvGeometry,
                                const typename FVElementGeometry<poreNetworkIndex>::SubControlVolume& scv,
                                const ElementVolumeVariables<poreNetworkIndex>& elemVolVars) const
     {
-
         const auto& couplingContext = this->subApply(domainI, domainJ, [&](const auto& cm, auto&& ii, auto&& jj) -> const auto& {
             return cm.couplingContext(ii, fvGeometry, scv);
         });
@@ -261,12 +261,12 @@ public:
     /*!
      * \brief Returns the mass flux across the coupling boundary.
      */
-    auto massCouplingCondition(Dune::index_constant<freeFlowMassIndex> domainI, Dune::index_constant<poreNetworkIndex> domainJ,
+    auto massCouplingCondition(Dune::index_constant<freeFlowMassIndex> domainI,
+                               Dune::index_constant<poreNetworkIndex> domainJ,
                                const FVElementGeometry<freeFlowMassIndex>& fvGeometry,
                                const typename FVElementGeometry<freeFlowMassIndex>::SubControlVolumeFace& scvf,
                                const ElementVolumeVariables<freeFlowMassIndex>& elemVolVars) const
     {
-
         const auto& couplingContext = this->subApply(domainI, domainJ, [&](const auto& cm, auto&& ii, auto&& jj) -> const auto& {
             return cm.couplingContext(ii, fvGeometry, scvf);
         });
@@ -275,6 +275,49 @@ public:
         return CouplingConditions::massCouplingCondition(domainI, domainJ, fvGeometry, scvf, elemVolVars, couplingContext);
     }
 
+    /*!
+     * \brief Returns the energy flux across the coupling boundary.
+     */
+    auto energyCouplingCondition(Dune::index_constant<poreNetworkIndex> domainI,
+                                 Dune::index_constant<freeFlowMassIndex> domainJ,
+                                 const FVElementGeometry<poreNetworkIndex>& fvGeometry,
+                                 const typename FVElementGeometry<poreNetworkIndex>::SubControlVolume& scv,
+                                 const ElementVolumeVariables<poreNetworkIndex>& elemVolVars) const
+    {
+        const auto& couplingContext = this->subApply(domainI, domainJ, [&](const auto& cm, auto&& ii, auto&& jj) -> const auto& {
+            return cm.couplingContext(ii, fvGeometry, scv);
+        });
+
+        const auto& freeFlowMassGridGeometry = this->subApply(domainI, domainJ, [&](const auto& cm, auto&& ii, auto&& jj) -> const auto& {
+            return cm.problem(jj).gridGeometry();
+        });
+
+        for (auto& c : couplingContext)
+        {
+            const auto& freeFlowElement = freeFlowMassGridGeometry.element(c.scv.elementIndex());
+            c.velocity = this->subCouplingManager(freeFlowMomentumIndex, freeFlowMassIndex).faceVelocity(freeFlowElement, c.scvf);
+        }
+
+        return CouplingConditions::energyCouplingCondition(domainI, domainJ, fvGeometry, scv, elemVolVars, couplingContext);
+    }
+
+    /*!
+     * \brief Returns the energy flux across the coupling boundary.
+     */
+    auto energyCouplingCondition(Dune::index_constant<freeFlowMassIndex> domainI,
+                                 Dune::index_constant<poreNetworkIndex> domainJ,
+                                 const FVElementGeometry<freeFlowMassIndex>& fvGeometry,
+                                 const typename FVElementGeometry<freeFlowMassIndex>::SubControlVolumeFace& scvf,
+                                 const ElementVolumeVariables<freeFlowMassIndex>& elemVolVars) const
+    {
+        const auto& couplingContext = this->subApply(domainI, domainJ, [&](const auto& cm, auto&& ii, auto&& jj) -> const auto& {
+            return cm.couplingContext(ii, fvGeometry, scvf);
+        });
+
+        couplingContext.velocity = this->subCouplingManager(freeFlowMomentumIndex, freeFlowMassIndex).faceVelocity(fvGeometry.element(), scvf);
+        return CouplingConditions::energyCouplingCondition(domainI, domainJ, fvGeometry, scvf, elemVolVars, couplingContext);
+    }
+
     //////////////////////// Conditions for FreeFlowMomentum - PoreNetwork coupling //////////
     ///////////////////////////////////////////////////////////////////////////////////////////
 
@@ -444,19 +487,6 @@ public:
         });
     }
 
-    // /*!
-    //  * \brief Returns whether a given scvf is coupled to the other domain
-    //  */
-    // bool isCoupledLateralScvf(Dune::index_constant<freeFlowMomentumIndex> domainI,
-    //                           Dune::index_constant<poreNetworkIndex> domainJ,
-    //                           const SubControlVolumeFace<freeFlowMomentumIndex>& scvf) const
-    // {
-    //     return this->subApply(domainI, domainJ, [&](const auto& cm, auto&& ii, auto&& jj){
-    //         return cm.isCoupledLateralScvf(ii, scvf);
-    //     });
-    // }
-
-
     using ParentType::couplingStencil;
     /*!
      * \brief returns an iterable container of all indices of degrees of freedom of domain j
-- 
GitLab