From a01f63fdb182b04180f700d083f6e1389bd251c3 Mon Sep 17 00:00:00 2001 From: Timo Koch <timokoch@math.uio.no> Date: Fri, 21 Oct 2022 21:41:44 +0000 Subject: [PATCH] Merge branch 'fix/tabulated-component-multithreading' into 'master' Refactor tabulated component for multithreading Closes #1189 See merge request dumux-repositories/dumux!3321 (cherry picked from commit f7a90b13cf4351c41811fa955f33b9e107d9394c) 8ace8f1d [component] Pass const l-value references to parallelFor 2b34ccec [tabulatedcomp] Cleanup b03f466d [tabulatedcomp] Use const& instead of && to receive member function fd7767d3 [tabcomp] Unify checks 947309a4 [tabulatedcomponent] Capture by value in lambda functions ae5b06cb [tabulatedcomponent] avoid explicit if/else b6f92d58 [tabulatedcomponent] use epsilon for float comparison 3c910454 [tabulatedcomponent] Use singleton for data b03fa10f [tabulatedcomponent][bugfix] Clamp index to allowed range d7b8d6ac [h2o] Use Brent's method for fluid pressure --- dumux/material/components/h2o.hh | 40 +- .../material/components/tabulatedcomponent.hh | 1860 ++++++++--------- 2 files changed, 883 insertions(+), 1017 deletions(-) diff --git a/dumux/material/components/h2o.hh b/dumux/material/components/h2o.hh index 3d0b6cf21c..2325b05afc 100644 --- a/dumux/material/components/h2o.hh +++ b/dumux/material/components/h2o.hh @@ -27,6 +27,7 @@ #include <cmath> #include <cassert> +#include <dumux/nonlinear/findscalarroot.hh> #include <dumux/material/idealgas.hh> #include <dumux/common/exceptions.hh> @@ -677,29 +678,24 @@ public: */ static Scalar liquidPressure(Scalar temperature, Scalar density) { - // We use the newton method for this. For the initial value we - // assume the pressure to be 10% higher than the vapor - // pressure - Scalar pressure = 1.1*vaporPressure(temperature); - Scalar eps = pressure*1e-7; - - Scalar deltaP = pressure*2; - - using std::abs; - for (int i = 0; i < 5 && abs(pressure*1e-9) < abs(deltaP); ++i) { - Scalar f = liquidDensity(temperature, pressure) - density; - - Scalar df_dp; - df_dp = liquidDensity(temperature, pressure + eps); - df_dp -= liquidDensity(temperature, pressure - eps); - df_dp /= 2*eps; - - deltaP = - f/df_dp; - - pressure += deltaP; + // We use Brent's method for this, with a pressure range including the surroundings of the + // vapor pressure line + Scalar minPressure = vaporPressure(temperature)/1.11; + Scalar maxPressure = 100e6; + const auto residualFunction = [&] (const Scalar pressure) { + return liquidDensity(temperature, pressure) - density; + }; + try + { + return findScalarRootBrent(minPressure, maxPressure, residualFunction); + } + catch (const NumericalProblem& e) + { + DUNE_THROW(NumericalProblem, + "searched for pressure(T=" << temperature << ",rho=" << density + <<") in [" << minPressure << ", " << maxPressure << "]: " + << e.what()); } - - return pressure; } /*! diff --git a/dumux/material/components/tabulatedcomponent.hh b/dumux/material/components/tabulatedcomponent.hh index 742ef7a669..f69d4cc54e 100644 --- a/dumux/material/components/tabulatedcomponent.hh +++ b/dumux/material/components/tabulatedcomponent.hh @@ -119,46 +119,28 @@ template<class C> constexpr inline bool hasGasViscosity() template<class C> constexpr inline bool hasGasPressure() { return Dune::Std::is_detected<CompHasGasPressure, C>::value && ComponentTraits<C>::hasGasState; } -} // end namespace Dumux::Components::Detail - -namespace Dumux::Components { - -/*! - * \ingroup Components - * \brief Tabulates all thermodynamic properties of a given component - * - * At the moment, this class can only handle the sub-critical fluids - * since it tabulates along the vapor pressure curve. - * - * \tparam Scalar The type used for scalar values - * \tparam RawComponent The component which ought to be tabulated - * \tparam useVaporPressure If set to true, the min/max pressure - * values for gas&liquid phase will be set - * depending on the vapor pressure. - */ -template <class RawComponent, bool useVaporPressure=true> -class TabulatedComponent +template<class RawComponent, bool useVaporPressure = true> +class TabulatedComponentTable { - using ThisType = TabulatedComponent<RawComponent, useVaporPressure>; -public: - //! export scalar type using Scalar = typename RawComponent::Scalar; + friend class TabulatedComponent<RawComponent, useVaporPressure>; - //! state that we are tabulated - static constexpr bool isTabulated = true; + struct GasPolicy + { + Scalar minP(std::size_t iT) const { return table.minGasPressure(iT); } + Scalar maxP(std::size_t iT) const { return table.maxGasPressure(iT); } + const TabulatedComponentTable<RawComponent, useVaporPressure>& table; + }; - /*! - * \brief Initialize the tables. - * - * \param tempMin The minimum of the temperature range in \f$\mathrm{[K]}\f$ - * \param tempMax The maximum of the temperature range in \f$\mathrm{[K]}\f$ - * \param nTemp The number of entries/steps within the temperature range - * \param pressMin The minimum of the pressure range in \f$\mathrm{[Pa]}\f$ - * \param pressMax The maximum of the pressure range in \f$\mathrm{[Pa]}\f$ - * \param nPress The number of entries/steps within the pressure range - */ - static void init(Scalar tempMin, Scalar tempMax, std::size_t nTemp, - Scalar pressMin, Scalar pressMax, std::size_t nPress) + struct LiquidPolicy + { + Scalar minP(std::size_t iT) const { return table.minLiquidPressure(iT); } + Scalar maxP(std::size_t iT) const { return table.maxLiquidPressure(iT); } + const TabulatedComponentTable<RawComponent, useVaporPressure>& table; + }; +public: + void init(Scalar tempMin, Scalar tempMax, std::size_t nTemp, + Scalar pressMin, Scalar pressMax, std::size_t nPress) { tempMin_ = tempMin; tempMax_ = tempMax; @@ -168,28 +150,15 @@ public: nPress_ = nPress; nDensity_ = nPress; -#ifndef NDEBUG - warningPrinted_ = false; -#endif - - std::cout << "-------------------------------------------------------------------------\n" - << "Initializing tables for the " << RawComponent::name() - << " fluid properties (" << nTemp*nPress << " entries).\n" - << "Temperature -> min: " << std::scientific << std::setprecision(3) - << tempMin << ", max: " << tempMax << ", n: " << nTemp << '\n' - << "Pressure -> min: " << std::scientific << std::setprecision(3) - << pressMin << ", max: " << pressMax << ", n: " << nPress << '\n' - << "-------------------------------------------------------------------------" << std::endl; - // resize & initialize the arrays with NaN assert(std::numeric_limits<Scalar>::has_quiet_NaN); const auto NaN = std::numeric_limits<Scalar>::quiet_NaN(); // initialize vapor pressure array depending on useVaporPressure vaporPressure_.resize(nTemp_, NaN); - initVaporPressure_(); + tabularizeVaporPressure_(); - if constexpr (ComponentTraits<ThisType>::hasGasState) + if constexpr (ComponentTraits<RawComponent>::hasGasState) { minGasDensity_.resize(nTemp_, NaN); maxGasDensity_.resize(nTemp_, NaN); @@ -203,16 +172,16 @@ public: if constexpr (RawComponent::gasIsCompressible()) gasPressure_.resize(numEntriesTp, NaN); - minMaxGasDensityInitialized_ = false; - gasEnthalpyInitialized_ = false; - gasHeatCapacityInitialized_ = false; - gasDensityInitialized_ = false; - gasViscosityInitialized_ = false; - gasThermalConductivityInitialized_ = false; - gasPressureInitialized_ = false; + minMaxGasDensityInitialized_ = tabularizeMinMaxGasDensity_(); + gasPressureInitialized_ = tabularizeGasPressure_(); + gasEnthalpyInitialized_ = tabularizeGasEnthalpy_(); + gasHeatCapacityInitialized_ = tabularizeGasHeatCapacity_(); + gasDensityInitialized_ = tabularizeGasDensity_(); + gasViscosityInitialized_ = tabularizeGasViscosity_(); + gasThermalConductivityInitialized_ = tabularizeGasThermalConductivity_(); } - if constexpr (ComponentTraits<ThisType>::hasLiquidState) + if constexpr (ComponentTraits<RawComponent>::hasLiquidState) { minLiquidDensity_.resize(nTemp_, NaN); maxLiquidDensity_.resize(nTemp_, NaN); @@ -227,1017 +196,1051 @@ public: if constexpr (RawComponent::liquidIsCompressible()) liquidPressure_.resize(numEntriesTp, NaN); - // reset all flags - minMaxLiquidDensityInitialized_ = false; - liquidEnthalpyInitialized_ = false; - liquidHeatCapacityInitialized_ = false; - liquidDensityInitialized_ = false; - liquidViscosityInitialized_ = false; - liquidThermalConductivityInitialized_ = false; - liquidPressureInitialized_ = false; + minMaxLiquidDensityInitialized_ = tabularizeMinMaxLiquidDensity_(); + liquidPressureInitialized_ = tabularizeLiquidPressure_(); + liquidEnthalpyInitialized_ = tabularizeLiquidEnthalpy_(); + liquidHeatCapacityInitialized_ = tabularizeLiquidHeatCapacity_(); + liquidDensityInitialized_ = tabularizeLiquidDensity_(); + liquidViscosityInitialized_ = tabularizeLiquidViscosity_(); + liquidThermalConductivityInitialized_ = tabularizeLiquidThermalConductivity_(); } + } - // in the multithreaded case precompute all array because lazy-loading - // leads to potential data races - if constexpr (!Multithreading::isSerial()) - { - if constexpr (ComponentTraits<ThisType>::hasGasState) - { - minMaxGasDensityInitialized_ = tabularizeMinMaxGasDensity_(); - gasPressureInitialized_ = tabularizeGasPressure_(); - gasEnthalpyInitialized_ = tabularizeGasEnthalpy_(); - gasHeatCapacityInitialized_ = tabularizeGasHeatCapacity_(); - gasDensityInitialized_ = tabularizeGasDensity_(); - gasViscosityInitialized_ = tabularizeGasViscosity_(); - gasThermalConductivityInitialized_ = tabularizeGasThermalConductivity_(); - } - - if constexpr (ComponentTraits<ThisType>::hasLiquidState) - { - minMaxLiquidDensityInitialized_ = tabularizeMinMaxLiquidDensity_(); - liquidPressureInitialized_ = tabularizeLiquidPressure_(); - liquidEnthalpyInitialized_ = tabularizeLiquidEnthalpy_(); - liquidHeatCapacityInitialized_ = tabularizeLiquidHeatCapacity_(); - liquidDensityInitialized_ = tabularizeLiquidDensity_(); - liquidViscosityInitialized_ = tabularizeLiquidViscosity_(); - liquidThermalConductivityInitialized_ = tabularizeLiquidThermalConductivity_(); - } - } + //! returns the index of an entry in a temperature field + inline Scalar tempIdx(Scalar temperature) const + { + return (nTemp_ - 1)*(temperature - tempMin_)/(tempMax_ - tempMin_); + } - initialized_ = true; + //! returns the index of an entry in a pressure field + inline Scalar pressLiquidIdx(Scalar pressure, std::size_t tempIdx) const + { + const Scalar plMin = minLiquidPressure(tempIdx); + const Scalar plMax = maxLiquidPressure(tempIdx); + return (nPress_ - 1)*(pressure - plMin)/(plMax - plMin); } - /*! - * \brief A human readable name for the component. - */ - static std::string name() - { return RawComponent::name(); } + //! returns the index of an entry in a temperature field + inline Scalar pressGasIdx(Scalar pressure, std::size_t tempIdx) const + { + const Scalar pgMin = minGasPressure(tempIdx); + const Scalar pgMax = maxGasPressure(tempIdx); + return (nPress_ - 1)*(pressure - pgMin)/(pgMax - pgMin); + } - /*! - * \brief The molar mass in \f$\mathrm{[kg/mol]}\f$ of the component. - */ - static constexpr Scalar molarMass() - { return RawComponent::molarMass(); } + //! returns the index of an entry in a density field + inline Scalar densityLiquidIdx(Scalar density, std::size_t tempIdx) const + { + const Scalar densityMin = minLiquidDensity_[tempIdx]; + const Scalar densityMax = maxLiquidDensity_[tempIdx]; + return (nDensity_ - 1) * (density - densityMin)/(densityMax - densityMin); + } - /*! - * \brief Returns the critical temperature in \f$\mathrm{[K]}\f$ of the component. - */ - static Scalar criticalTemperature() - { return RawComponent::criticalTemperature(); } + //! returns the index of an entry in a density field + inline Scalar densityGasIdx(Scalar density, std::size_t tempIdx) const + { + const Scalar densityMin = minGasDensity_[tempIdx]; + const Scalar densityMax = maxGasDensity_[tempIdx]; + return (nDensity_ - 1) * (density - densityMin)/(densityMax - densityMin); + } - /*! - * \brief Returns the critical pressure in \f$\mathrm{[Pa]}\f$ of the component. - */ - static Scalar criticalPressure() - { return RawComponent::criticalPressure(); } + //! returns the minimum tabularized liquid pressure at a given temperature index + inline Scalar minLiquidPressure(int tempIdx) const + { + using std::max; + if (!useVaporPressure) + return pressMin_; + else + return max(pressMin_, vaporPressure_[tempIdx] / 1.1); + } - /*! - * \brief Returns the temperature in \f$\mathrm{[K]}\f$ at the component's triple point. - */ - static Scalar tripleTemperature() - { return RawComponent::tripleTemperature(); } + //! returns the maximum tabularized liquid pressure at a given temperature index + inline Scalar maxLiquidPressure(int tempIdx) const + { + using std::max; + if (!useVaporPressure) + return pressMax_; + else + return max(pressMax_, vaporPressure_[tempIdx] * 1.1); + } - /*! - * \brief Returns the pressure in \f$\mathrm{[Pa]}\f$ at the component's triple point. - */ - static Scalar triplePressure() - { return RawComponent::triplePressure(); } + //! returns the minimum tabularized gas pressure at a given temperature index + inline Scalar minGasPressure(int tempIdx) const + { + using std::min; + if (!useVaporPressure) + return pressMin_; + else + return min(pressMin_, vaporPressure_[tempIdx] / 1.1 ); + } - /*! - * \brief The vapor pressure in \f$\mathrm{[Pa]}\f$ of the component at a given - * temperature. - * - * \param T temperature of component - */ - static Scalar vaporPressure(Scalar T) + //! returns the maximum tabularized gas pressure at a given temperature index + inline Scalar maxGasPressure(int tempIdx) const { - using std::isnan; - Scalar result = interpolateT_(vaporPressure_, T); - if (isnan(result)) - return RawComponent::vaporPressure(T); - return result; + using std::min; + if (!useVaporPressure) + return pressMax_; + else + return min(pressMax_, vaporPressure_[tempIdx] * 1.1); } - /*! - * \brief The vapor pressure in \f$\mathrm{[Pa]}\f$ of the component at a given - * temperature. - * - * The method is only called by the sequential flash, so tabulating is omitted. - * \param pressure pressure of component in \f$\mathrm{[Pa]}\f$ - */ - static Scalar vaporTemperature(Scalar pressure) + //! returns the minimum tabularized liquid density at a given temperature index + inline Scalar minLiquidDensity(int tempIdx) const + { return minLiquidDensity_[tempIdx]; } + + //! returns the maximum tabularized liquid density at a given temperature index + inline Scalar maxLiquidDensity(int tempIdx) const + { return maxLiquidDensity_[tempIdx]; } + + //! returns the minimum tabularized gas density at a given temperature index + inline Scalar minGasDensity(int tempIdx) const + { return minGasDensity_[tempIdx]; } + + //! returns the maximum tabularized gas density at a given temperature index + inline Scalar maxGasDensity(int tempIdx) const + { return maxGasDensity_[tempIdx]; } + + inline std::size_t nTemp() const { return nTemp_; } + inline std::size_t nPress() const { return nPress_; } + inline std::size_t nDensity() const { return nDensity_; } + + inline Scalar tempMax() const { return tempMax_; } + inline Scalar tempMin() const { return tempMin_; } + +private: + + //! initializes vapor pressure if useVaporPressure = true + template< bool useVP = useVaporPressure, std::enable_if_t<useVP, int> = 0 > + void tabularizeVaporPressure_() { - return RawComponent::vaporTemperature(pressure); + // fill the temperature-pressure arrays + Dumux::parallelFor(nTemp_, [=](std::size_t iT) + { + Scalar temperature = iT * (tempMax_ - tempMin_)/(nTemp_ - 1) + tempMin_; + vaporPressure_[iT] = RawComponent::vaporPressure(temperature); + }); } + //! if !useVaporPressure, do nothing here + template< bool useVP = useVaporPressure, std::enable_if_t<!useVP, int> = 0 > + void tabularizeVaporPressure_() {} + /*! - * \brief Specific enthalpy of the gas \f$\mathrm{[J/kg]}\f$. + * \brief Initializes property values as function of temperature and pressure. * - * \param temperature temperature of component in \f$\mathrm{[K]}\f$ - * \param pressure pressure of component in \f$\mathrm{[Pa]}\f$ + * \tparam PropFunc Function to evaluate the property prop(T, p) + * \tparam MinPFunc Function to evaluate the minimum pressure for a + * temperature index (depends on useVaporPressure) + * \tparam MaxPFunc Function to evaluate the maximum pressure for a + * temperature index (depends on useVaporPressure) + * + * \param f property function + * \param minP function to evaluate minimum pressure for temp idx + * \param maxP function to evaluate maximum pressure for temp idx + * \param values container to store property values */ - static const Scalar gasEnthalpy(Scalar temperature, Scalar pressure) + template<class PropFunc, class Policy> + void tabularizeTPArray_(const PropFunc& f, Policy policy, std::vector<typename RawComponent::Scalar>& values) const { - Scalar result = interpolateTP_(gasEnthalpy_, temperature, pressure, - pressGasIdx_, minGasPressure_, maxGasPressure_); - using std::isnan; - if (isnan(result)) + Dumux::parallelFor(nTemp_, [=,&values](std::size_t iT) { - if constexpr (Multithreading::isSerial()) + Scalar temperature = iT * (tempMax_ - tempMin_)/(nTemp_ - 1) + tempMin_; + + Scalar pMax = policy.maxP(iT); + Scalar pMin = policy.minP(iT); + for (std::size_t iP = 0; iP < nPress_; ++ iP) { - // lazy tabularization - if (!gasEnthalpyInitialized_) - { - gasEnthalpyInitialized_ = tabularizeGasEnthalpy_(); - if (gasEnthalpyInitialized_) - return gasEnthalpy(temperature, pressure); - } + Scalar pressure = iP * (pMax - pMin)/(nPress_ - 1) + pMin; + values[iT + iP*nTemp_] = f(temperature, pressure); } - - printWarning_("gasEnthalpy", temperature, pressure); - return RawComponent::gasEnthalpy(temperature, pressure); - } - return result; + }); } /*! - * \brief Specific enthalpy of the liquid \f$\mathrm{[J/kg]}\f$. + * \brief Initializes the minimum/maximum densities on the temperature range. * - * \param temperature temperature of component in \f$\mathrm{[K]}\f$ - * \param pressure pressure of component in \f$\mathrm{[Pa]}\f$ + * \tparam RhoFunc Function to evaluate the density rho(T, p) + * \tparam MinPFunc Function to evaluate the minimum pressure for a + * temperature index (depends on useVaporPressure) + * \tparam MaxPFunc Function to evaluate the maximum pressure for a + * temperature index (depends on useVaporPressure) + * + * \param rho density function + * \param minP function to evaluate minimum pressure for temp idx + * \param maxP function to evaluate maximum pressure for temp idx + * \param rhoMin container to store minimum density values + * \param rhoMax container to store maximum density values */ - static const Scalar liquidEnthalpy(Scalar temperature, Scalar pressure) + template<class RhoFunc, class Policy> + void tabularizeMinMaxRhoArray_(const RhoFunc& rho, Policy policy, + std::vector<typename RawComponent::Scalar>& rhoMin, + std::vector<typename RawComponent::Scalar>& rhoMax) const { - Scalar result = interpolateTP_(liquidEnthalpy_, temperature, pressure, - pressLiquidIdx_, minLiquidPressure_, maxLiquidPressure_); - using std::isnan; - if (isnan(result)) + Dumux::parallelFor(nTemp_, [=,&rhoMin,&rhoMax](std::size_t iT) { - if constexpr (Multithreading::isSerial()) - { - // lazy tabularization - if (!liquidEnthalpyInitialized_) - { - liquidEnthalpyInitialized_ = tabularizeLiquidEnthalpy_(); - if (liquidEnthalpyInitialized_) - return liquidEnthalpy(temperature, pressure); - } - } + Scalar temperature = iT * (tempMax_ - tempMin_)/(nTemp_ - 1) + tempMin_; - printWarning_("liquidEnthalpy", temperature, pressure); - return RawComponent::liquidEnthalpy(temperature, pressure); - } - return result; + using std::min; + rhoMin[iT] = rho(temperature, policy.minP(iT)); + rhoMax[iT] = rho(temperature, policy.maxP(min(iT + 1, nTemp_ - 1))); + }); } /*! - * \brief Specific isobaric heat capacity of the gas \f$\mathrm{[J/(kg*K)]}\f$. + * \brief Initializes pressure arrays as function of temperature and density. * - * \param temperature temperature of component in \f$\mathrm{[K]}\f$ - * \param pressure pressure of component in \f$\mathrm{[Pa]}\f$ + * \tparam PFunc Function to evaluate the pressure p(T, rho) + * + * \param pressure container to store pressure values + * \param p pressure function p(T, rho) + * \param rhoMin container with minimum density values + * \param rhoMax container with maximum density values */ - static const Scalar gasHeatCapacity(Scalar temperature, Scalar pressure) + template<class PFunc> + void tabularizePressureArray_(std::vector<typename RawComponent::Scalar>& pressure, + const PFunc& p, + const std::vector<typename RawComponent::Scalar>& rhoMin, + const std::vector<typename RawComponent::Scalar>& rhoMax) const { - Scalar result = interpolateTP_(gasHeatCapacity_, temperature, pressure, - pressGasIdx_, minGasPressure_, maxGasPressure_); - using std::isnan; - if (isnan(result)) + Dumux::parallelFor(nTemp_, [=,&pressure,&rhoMin,&rhoMax](std::size_t iT) { - if constexpr (Multithreading::isSerial()) + Scalar temperature = iT * (tempMax_ - tempMin_)/(nTemp_ - 1) + tempMin_; + + for (std::size_t iRho = 0; iRho < nDensity_; ++ iRho) { - // lazy tabularization - if (!gasHeatCapacityInitialized_) - { - gasHeatCapacityInitialized_ = tabularizeGasHeatCapacity_(); - if (gasHeatCapacityInitialized_) - return gasHeatCapacity(temperature, pressure); - } + Scalar density = Scalar(iRho)/(nDensity_ - 1) + * (rhoMax[iT] - rhoMin[iT]) + + rhoMin[iT]; + pressure[iT + iRho*nTemp_] = p(temperature, density); } - - printWarning_("gasHeatCapacity", temperature, pressure); - return RawComponent::gasHeatCapacity(temperature, pressure); - } - return result; + }); } - /*! - * \brief Specific isobaric heat capacity of the liquid \f$\mathrm{[J/(kg*K)]}\f$. - * - * \param temperature temperature of component in \f$\mathrm{[K]}\f$ - * \param pressure pressure of component in \f$\mathrm{[Pa]}\f$ - */ - static const Scalar liquidHeatCapacity(Scalar temperature, Scalar pressure) + template<class RC = RawComponent> + bool tabularizeGasEnthalpy_() { - Scalar result = interpolateTP_(liquidHeatCapacity_, temperature, pressure, - pressLiquidIdx_, minLiquidPressure_, maxLiquidPressure_); - using std::isnan; - if (isnan(result)) + if constexpr (Detail::hasGasEnthalpy<RC>()) { - if constexpr (Multithreading::isSerial()) - { - // lazy tabularization - if (!liquidHeatCapacityInitialized_) - { - liquidHeatCapacityInitialized_ = tabularizeLiquidHeatCapacity_(); - if (liquidHeatCapacityInitialized_) - return liquidHeatCapacity(temperature, pressure); - } - } - - printWarning_("liquidHeatCapacity", temperature, pressure); - return RawComponent::liquidHeatCapacity(temperature, pressure); + const auto gasEnth = [] (auto T, auto p) { return RC::gasEnthalpy(T, p); }; + tabularizeTPArray_(gasEnth, GasPolicy{ *this }, gasEnthalpy_); + return true; } - return result; + + return false; } - /*! - * \brief Specific internal energy of the gas \f$\mathrm{[J/kg]}\f$. - * - * \param temperature temperature of component in \f$\mathrm{[K]}\f$ - * \param pressure pressure of component in \f$\mathrm{[Pa]}\f$ - */ - static const Scalar gasInternalEnergy(Scalar temperature, Scalar pressure) + template<class RC = RawComponent> + bool tabularizeLiquidEnthalpy_() { - return gasEnthalpy(temperature, pressure) - pressure/gasDensity(temperature, pressure); + if constexpr (Detail::hasLiquidEnthalpy<RC>()) + { + const auto liqEnth = [] (auto T, auto p) { return RC::liquidEnthalpy(T, p); }; + tabularizeTPArray_(liqEnth, LiquidPolicy{ *this }, liquidEnthalpy_); + return true; + } + + return false; } - /*! - * \brief Specific internal energy of the liquid \f$\mathrm{[J/kg]}\f$. - * - * \param temperature temperature of component in \f$\mathrm{[K]}\f$ - * \param pressure pressure of component in \f$\mathrm{[Pa]}\f$ - */ - static const Scalar liquidInternalEnergy(Scalar temperature, Scalar pressure) + template<class RC = RawComponent> + bool tabularizeGasHeatCapacity_() { - return liquidEnthalpy(temperature, pressure) - pressure/liquidDensity(temperature, pressure); + if constexpr (Detail::hasGasHeatCapacity<RC>()) + { + const auto gasHC = [] (auto T, auto p) { return RC::gasHeatCapacity(T, p); }; + tabularizeTPArray_(gasHC, GasPolicy{ *this }, gasHeatCapacity_); + return true; + } + + return false; } - /*! - * \brief The pressure of gas in \f$\mathrm{[Pa]}\f$ at a given density and temperature. - * - * \param temperature temperature of component in \f$\mathrm{[K]}\f$ - * \param density density of component in \f$\mathrm{[kg/m^3]}\f$ - */ - static Scalar gasPressure(Scalar temperature, Scalar density) + template<class RC = RawComponent> + bool tabularizeLiquidHeatCapacity_() { - if constexpr (Multithreading::isSerial()) + if constexpr (Detail::hasLiquidHeatCapacity<RC>()) { - if (!minMaxGasDensityInitialized_) - minMaxGasDensityInitialized_ = tabularizeMinMaxGasDensity_(); + const auto liqHC = [] (auto T, auto p) { return RC::liquidHeatCapacity(T, p); }; + tabularizeTPArray_(liqHC, LiquidPolicy{ *this }, liquidHeatCapacity_); + return true; } - Scalar result = interpolateTRho_(gasPressure_, temperature, density, densityGasIdx_); - using std::isnan; - if (isnan(result)) - { - if constexpr (Multithreading::isSerial()) - { - if (!gasPressureInitialized_) - { - gasPressureInitialized_ = tabularizeGasPressure_(); - if (gasPressureInitialized_) - return gasPressure(temperature, density); - } - } + return false; + } - printWarning_("gasPressure", temperature, density); - return RawComponent::gasPressure(temperature, density); + template<class RC = RawComponent> + bool tabularizeMinMaxGasDensity_() + { + if constexpr (Detail::hasGasDensity<RC>()) + { + const auto gasRho = [] (auto T, auto p) { return RC::gasDensity(T, p); }; + tabularizeMinMaxRhoArray_(gasRho, GasPolicy{ *this }, minGasDensity_, maxGasDensity_); + return true; } - return result; + + return false; } - /*! - * \brief The pressure of liquid in \f$\mathrm{[Pa]}\f$ at a given density and temperature. - * - * \param temperature temperature of component in \f$\mathrm{[K]}\f$ - * \param density density of component in \f$\mathrm{[kg/m^3]}\f$ - */ - static Scalar liquidPressure(Scalar temperature, Scalar density) + template<class RC = RawComponent> + bool tabularizeMinMaxLiquidDensity_() { - if constexpr (Multithreading::isSerial()) + if constexpr (Detail::hasGasEnthalpy<RC>()) { - if (!minMaxLiquidDensityInitialized_) - minMaxLiquidDensityInitialized_ = tabularizeMinMaxLiquidDensity_(); + const auto liqRho = [] (auto T, auto p) { return RC::liquidDensity(T, p); }; + tabularizeMinMaxRhoArray_(liqRho, LiquidPolicy{ *this }, minLiquidDensity_, maxLiquidDensity_); + return true; } - Scalar result = interpolateTRho_(liquidPressure_, temperature, density, densityLiquidIdx_); - using std::isnan; - if (isnan(result)) - { - if constexpr (Multithreading::isSerial()) - { - // lazy tabularization - if (!liquidPressureInitialized_) - { - liquidPressureInitialized_ = tabularizeLiquidPressure_(); - if (liquidPressureInitialized_) - return liquidPressure(temperature, density); - } - } + return false; + } - printWarning_("liquidPressure", temperature, density); - return RawComponent::liquidPressure(temperature, density); + template<class RC = RawComponent> + bool tabularizeGasPressure_() + { + // pressure is only defined if the gas is compressible (this is usually the case) + if constexpr (Detail::hasGasPressure<RC>() && RC::gasIsCompressible()) + { + const auto gasPFunc = [] (auto T, auto rho) { return RC::gasPressure(T, rho); }; + tabularizePressureArray_(gasPressure_, gasPFunc, minGasDensity_, maxGasDensity_); + return true; } - return result; + + return false; } - /*! - * \brief Returns true if the gas phase is assumed to be compressible - */ - static constexpr bool gasIsCompressible() - { return RawComponent::gasIsCompressible(); } + template<class RC = RawComponent> + bool tabularizeLiquidPressure_() + { + // pressure is only defined if the liquid is compressible (this is often not the case) + if constexpr (Detail::hasLiquidPressure<RC>() && RC::liquidIsCompressible()) + { + const auto liqPFunc = [] (auto T, auto rho) { return RC::liquidPressure(T, rho); }; + tabularizePressureArray_(liquidPressure_, liqPFunc, minLiquidDensity_, maxLiquidDensity_); + return true; + } - /*! - * \brief Returns true if the liquid phase is assumed to be compressible - */ - static constexpr bool liquidIsCompressible() - { return RawComponent::liquidIsCompressible(); } + return false; + } - /*! - * \brief Returns true if the gas phase is assumed to be ideal - */ - static constexpr bool gasIsIdeal() - { return RawComponent::gasIsIdeal(); } + template<class RC = RawComponent> + bool tabularizeGasDensity_() + { + if constexpr (Detail::hasGasDensity<RC>()) + { + const auto gasRho = [] (auto T, auto p) { return RC::gasDensity(T, p); }; + tabularizeTPArray_(gasRho, GasPolicy{ *this }, gasDensity_); + return true; + } + return false; + } - /*! - * \brief The density of gas at a given pressure and temperature - * \f$\mathrm{[kg/m^3]}\f$. - * - * \param temperature temperature of component in \f$\mathrm{[K]}\f$ - * \param pressure pressure of component in \f$\mathrm{[Pa]}\f$ - */ - static Scalar gasDensity(Scalar temperature, Scalar pressure) + template<class RC = RawComponent> + bool tabularizeLiquidDensity_() { - Scalar result = interpolateTP_(gasDensity_, temperature, pressure, - pressGasIdx_, minGasPressure_, maxGasPressure_); - using std::isnan; - if (isnan(result)) + if constexpr (Detail::hasLiquidDensity<RC>()) { - if constexpr (Multithreading::isSerial()) - { - // lazy tabularization - if (!gasDensityInitialized_) - { - gasDensityInitialized_ = tabularizeGasDensity_(); - if (gasDensityInitialized_) - return gasDensity(temperature, pressure); - } - } - - printWarning_("gasDensity", temperature, pressure); - return RawComponent::gasDensity(temperature, pressure); + // TODO: we could get rid of the lambdas and pass the functor directly. But, + // currently Brine is a component (and not a fluid system) expecting a + // third argument with a default, which cannot be wrapped in a function pointer. + // For this reason we have to wrap this into a lambda here. + const auto liqRho = [] (auto T, auto p) { return RC::liquidDensity(T, p); }; + tabularizeTPArray_(liqRho, LiquidPolicy{ *this }, liquidDensity_); + return true; } - return result; - } - /*! - * \brief The molar density of gas in \f$\mathrm{[mol/m^3]}\f$ - * at a given pressure and temperature. - * - * \param temperature temperature of component in \f$\mathrm{[K]}\f$ - * \param pressure pressure of component in \f$\mathrm{[Pa]}\f$ - * - */ - static Scalar gasMolarDensity(Scalar temperature, Scalar pressure) - { return gasDensity(temperature, pressure)/molarMass(); } + return false; + } - /*! - * \brief The density of liquid at a given pressure and - * temperature \f$\mathrm{[kg/m^3]}\f$. - * - * \param temperature temperature of component in \f$\mathrm{[K]}\f$ - * \param pressure pressure of component in \f$\mathrm{[Pa]}\f$ - */ - static Scalar liquidDensity(Scalar temperature, Scalar pressure) + template<class RC = RawComponent> + bool tabularizeGasViscosity_() { - Scalar result = interpolateTP_(liquidDensity_, temperature, pressure, - pressLiquidIdx_, minLiquidPressure_, maxLiquidPressure_); - using std::isnan; - if (isnan(result)) + if constexpr (Detail::hasGasViscosity<RC>()) { - if constexpr (Multithreading::isSerial()) - { - // lazy tabularization - if (!liquidDensityInitialized_) - { - liquidDensityInitialized_ = tabularizeLiquidDensity_(); - if (liquidDensityInitialized_) - return liquidDensity(temperature, pressure); - } - } - - printWarning_("liquidDensity", temperature, pressure); - return RawComponent::liquidDensity(temperature, pressure); + const auto gasVisc = [] (auto T, auto p) { return RC::gasViscosity(T, p); }; + tabularizeTPArray_(gasVisc, GasPolicy{ *this }, gasViscosity_); + return true; } - return result; + return false; } - /*! - * \brief The molar density of liquid in \f$\mathrm{[mol/m^3]}\f$ - * at a given pressure and temperature. - * - * \param temperature temperature of component in \f$\mathrm{[K]}\f$ - * \param pressure pressure of component in \f$\mathrm{[Pa]}\f$ - * - */ - static Scalar liquidMolarDensity(Scalar temperature, Scalar pressure) - { return liquidDensity(temperature, pressure)/molarMass(); } - - /*! - * \brief The dynamic viscosity \f$\mathrm{[Pa*s]}\f$ of gas. - * - * \param temperature temperature of component in \f$\mathrm{[K]}\f$ - * \param pressure pressure of component in \f$\mathrm{[Pa]}\f$ - */ - static Scalar gasViscosity(Scalar temperature, Scalar pressure) + template<class RC = RawComponent> + bool tabularizeLiquidViscosity_() { - Scalar result = interpolateTP_(gasViscosity_, temperature, pressure, - pressGasIdx_, minGasPressure_, maxGasPressure_); - using std::isnan; - if (isnan(result)) + if constexpr (Detail::hasLiquidViscosity<RC>()) { - if constexpr (Multithreading::isSerial()) - { - // lazy tabularization - if (!gasViscosityInitialized_) - { - gasViscosityInitialized_ = tabularizeGasViscosity_(); - if (gasViscosityInitialized_) - return gasViscosity(temperature, pressure); - } - } - - printWarning_("gasViscosity", temperature, pressure); - return RawComponent::gasViscosity(temperature, pressure); + const auto liqVisc = [] (auto T, auto p) { return RC::liquidViscosity(T, p); }; + tabularizeTPArray_(liqVisc, LiquidPolicy{ *this }, liquidViscosity_); + return true; } - return result; + + return false; } - /*! - * \brief The dynamic viscosity \f$\mathrm{[Pa*s]}\f$ of liquid. - * - * \param temperature temperature of component in \f$\mathrm{[K]}\f$ - * \param pressure pressure of component in \f$\mathrm{[Pa]}\f$ - */ - static Scalar liquidViscosity(Scalar temperature, Scalar pressure) + template<class RC = RawComponent> + bool tabularizeGasThermalConductivity_() { - Scalar result = interpolateTP_(liquidViscosity_, temperature, pressure, - pressLiquidIdx_, minLiquidPressure_, maxLiquidPressure_); - using std::isnan; - if (isnan(result)) + if constexpr (Detail::hasGasThermalConductivity<RC>()) { - if constexpr (Multithreading::isSerial()) - { - // lazy tabularization - if (!liquidViscosityInitialized_) - { - liquidViscosityInitialized_ = tabularizeLiquidViscosity_(); - if (liquidViscosityInitialized_) - return liquidViscosity(temperature, pressure); - } - } - - printWarning_("liquidViscosity",temperature, pressure); - return RawComponent::liquidViscosity(temperature, pressure); + const auto gasTC = [] (auto T, auto p) { return RC::gasThermalConductivity(T, p); }; + tabularizeTPArray_(gasTC, GasPolicy{ *this }, gasThermalConductivity_); + return true; } - return result; + + return false; } - /*! - * \brief The thermal conductivity of gaseous water \f$\mathrm{[W/(m*K)]}\f$. - * - * \param temperature temperature of component in \f$\mathrm{[K]}\f$ - * \param pressure pressure of component in \f$\mathrm{[Pa]}\f$ - */ - static Scalar gasThermalConductivity(Scalar temperature, Scalar pressure) + template<class RC = RawComponent> + bool tabularizeLiquidThermalConductivity_() { - Scalar result = interpolateTP_(gasThermalConductivity_, temperature, pressure, - pressGasIdx_, minGasPressure_, maxGasPressure_); - using std::isnan; - if (isnan(result)) + if constexpr (Detail::hasLiquidThermalConductivity<RC>()) { - if constexpr (Multithreading::isSerial()) - { - // lazy tabularization - if (!gasThermalConductivityInitialized_) - { - gasThermalConductivityInitialized_ = tabularizeGasThermalConductivity_(); - if (gasThermalConductivityInitialized_) - return gasThermalConductivity(temperature, pressure); - } - } - - printWarning_("gasThermalConductivity", temperature, pressure); - return RawComponent::gasThermalConductivity(temperature, pressure); + const auto liqTC = [] (auto T, auto p) { return RC::liquidThermalConductivity(T, p); }; + tabularizeTPArray_(liqTC, LiquidPolicy{ *this }, liquidThermalConductivity_); + return true; } - return result; + + return false; } - /*! - * \brief The thermal conductivity of liquid water \f$\mathrm{[W/(m*K)]}\f$. - * - * \param temperature temperature of component in \f$\mathrm{[K]}\f$ - * \param pressure pressure of component in \f$\mathrm{[Pa]}\f$ - */ - static Scalar liquidThermalConductivity(Scalar temperature, Scalar pressure) - { - Scalar result = interpolateTP_(liquidThermalConductivity_, temperature, pressure, - pressLiquidIdx_, minLiquidPressure_, maxLiquidPressure_); - using std::isnan; - if (isnan(result)) - { - if constexpr (Multithreading::isSerial()) - { - // lazy tabularization - if (!liquidThermalConductivityInitialized_) - { - liquidThermalConductivityInitialized_ = tabularizeLiquidThermalConductivity_(); - if (liquidThermalConductivityInitialized_) - return liquidThermalConductivity(temperature, pressure); - } - } +private: + // 1D fields with the temperature as degree of freedom + std::vector<Scalar> vaporPressure_; - printWarning_("liquidThermalConductivity", temperature, pressure); - return RawComponent::liquidThermalConductivity(temperature, pressure); - } - return result; - } + std::vector<Scalar> minLiquidDensity_; + std::vector<Scalar> maxLiquidDensity_; + bool minMaxLiquidDensityInitialized_; + std::vector<Scalar> minGasDensity_; + std::vector<Scalar> maxGasDensity_; + bool minMaxGasDensityInitialized_; -private: - //! prints a warning if the result is not in range or the table has not been initialized - static void printWarning_(const std::string& quantity, Scalar arg1, Scalar arg2) + // 2D fields with the temperature and pressure as degrees of freedom + std::vector<Scalar> gasEnthalpy_; + std::vector<Scalar> liquidEnthalpy_; + bool gasEnthalpyInitialized_; + bool liquidEnthalpyInitialized_; + + std::vector<Scalar> gasHeatCapacity_; + std::vector<Scalar> liquidHeatCapacity_; + bool gasHeatCapacityInitialized_; + bool liquidHeatCapacityInitialized_; + + std::vector<Scalar> gasDensity_; + std::vector<Scalar> liquidDensity_; + bool gasDensityInitialized_; + bool liquidDensityInitialized_; + + std::vector<Scalar> gasViscosity_; + std::vector<Scalar> liquidViscosity_; + bool gasViscosityInitialized_; + bool liquidViscosityInitialized_; + + std::vector<Scalar> gasThermalConductivity_; + std::vector<Scalar> liquidThermalConductivity_; + bool gasThermalConductivityInitialized_; + bool liquidThermalConductivityInitialized_; + + // 2D fields with the temperature and density as degrees of freedom + std::vector<Scalar> gasPressure_; + std::vector<Scalar> liquidPressure_; + bool gasPressureInitialized_; + bool liquidPressureInitialized_; + + // temperature, pressure and density ranges + Scalar tempMin_; + Scalar tempMax_; + std::size_t nTemp_; + + Scalar pressMin_; + Scalar pressMax_; + std::size_t nPress_; + + Scalar densityMin_; + Scalar densityMax_; + std::size_t nDensity_; +}; + +} // end namespace Dumux::Components::Detail + +namespace Dumux::Components { + +/*! + * \ingroup Components + * \brief Tabulates all thermodynamic properties of a given component + * + * At the moment, this class can only handle the sub-critical fluids + * since it tabulates along the vapor pressure curve. + * + * \tparam Scalar The type used for scalar values + * \tparam RawComponent The component which ought to be tabulated + * \tparam useVaporPressure If set to true, the min/max pressure + * values for gas&liquid phase will be set + * depending on the vapor pressure. + */ +template <class RawComponent, bool useVaporPressure=true> +class TabulatedComponent +{ + using ThisType = TabulatedComponent<RawComponent, useVaporPressure>; + using Table = Detail::TabulatedComponentTable<RawComponent, useVaporPressure>; + + struct InterpolatePolicy { -#ifndef NDEBUG - if (warningPrinted_) - return; + using Scalar = typename RawComponent::Scalar; - if (!initialized_) - std::cerr << "Warning: tabulated component '" << name() - << "' has not been initialized. " - << "Call FluidSystem::init() to use the tabulation in order to reduce runtime. \n"; - else - std::cerr << "Warning: "<<quantity<<"(T="<<arg1<<", p="<<arg2<<") of component '"<<name() - << "' is outside tabulation range: ("<< tempMin_<<"<=T<="<<tempMax_<<"), (" - << pressMin_<<"<=p<=" <<pressMax_<<"). " - << "Forwarded to FluidSystem for direct evaluation of "<<quantity<<". \n"; - warningPrinted_ = true; -#endif - } + const Table& table; - //! initializes vapor pressure if useVaporPressure = true - template< bool useVP = useVaporPressure, std::enable_if_t<useVP, int> = 0 > - static void initVaporPressure_() + Scalar tempIdx(Scalar T) const { return table.tempIdx(T); } + Scalar tempMin() const { return table.tempMin(); } + Scalar tempMax() const { return table.tempMax(); } + std::size_t nTemp() const { return table.nTemp(); } + std::size_t nPress() const { return table.nPress(); } + std::size_t nDensity() const { return table.nDensity(); } + }; + + struct InterpolateGasPolicy : public InterpolatePolicy { - // fill the temperature-pressure arrays - Dumux::parallelFor(nTemp_, [&](std::size_t iT) - { - Scalar temperature = iT * (tempMax_ - tempMin_)/(nTemp_ - 1) + tempMin_; - vaporPressure_[iT] = RawComponent::vaporPressure(temperature); - }); - } + using Scalar = typename RawComponent::Scalar; + Scalar pressIdx(Scalar p, std::size_t tempIdx) const { return this->table.pressGasIdx(p, tempIdx); } + Scalar rhoIdx(Scalar rho, std::size_t tempIdx) const { return this->table.densityGasIdx(rho, tempIdx); } + Scalar minP(int tempIdx) const { return this->table.minGasPressure(tempIdx); } + Scalar maxP(int tempIdx) const { return this->table.maxGasPressure(tempIdx); } + Scalar minRho(int tempIdx) const { return this->table.minGasDensity(tempIdx); } + Scalar maxRho(int tempIdx) const { return this->table.maxGasDensity(tempIdx); } + }; + + struct InterpolateLiquidPolicy : public InterpolatePolicy + { + using Scalar = typename RawComponent::Scalar; + Scalar pressIdx(Scalar p, std::size_t tempIdx) const { return this->table.pressLiquidIdx(p, tempIdx); } + Scalar rhoIdx(Scalar rho, std::size_t tempIdx) const { return this->table.densityLiquidIdx(rho, tempIdx); } + Scalar minP(int tempIdx) const { return this->table.minLiquidPressure(tempIdx); } + Scalar maxP(int tempIdx) const { return this->table.maxLiquidPressure(tempIdx); } + Scalar minRho(int tempIdx) const { return this->table.minLiquidDensity(tempIdx); } + Scalar maxRho(int tempIdx) const { return this->table.maxLiquidDensity(tempIdx); } + }; +public: + //! export scalar type + using Scalar = typename RawComponent::Scalar; - //! if !useVaporPressure, do nothing here - template< bool useVP = useVaporPressure, std::enable_if_t<!useVP, int> = 0 > - static void initVaporPressure_() {} + //! state that we are tabulated + static constexpr bool isTabulated = true; /*! - * \brief Initializes property values as function of temperature and pressure. - * - * \tparam PropFunc Function to evaluate the property prop(T, p) - * \tparam MinPFunc Function to evaluate the minimum pressure for a - * temperature index (depends on useVaporPressure) - * \tparam MaxPFunc Function to evaluate the maximum pressure for a - * temperature index (depends on useVaporPressure) + * \brief Initialize the tables. * - * \param f property function - * \param minP function to evaluate minimum pressure for temp idx - * \param maxP function to evaluate maximum pressure for temp idx - * \param values container to store property values + * \param tempMin The minimum of the temperature range in \f$\mathrm{[K]}\f$ + * \param tempMax The maximum of the temperature range in \f$\mathrm{[K]}\f$ + * \param nTemp The number of entries/steps within the temperature range + * \param pressMin The minimum of the pressure range in \f$\mathrm{[Pa]}\f$ + * \param pressMax The maximum of the pressure range in \f$\mathrm{[Pa]}\f$ + * \param nPress The number of entries/steps within the pressure range */ - template<class PropFunc, class MinPFunc, class MaxPFunc> - static void initTPArray_(PropFunc&& f, MinPFunc&& minP, MaxPFunc&& maxP, std::vector<typename RawComponent::Scalar>& values) + static void init(Scalar tempMin, Scalar tempMax, std::size_t nTemp, + Scalar pressMin, Scalar pressMax, std::size_t nPress) { - Dumux::parallelFor(nTemp_, [&](std::size_t iT) - { - Scalar temperature = iT * (tempMax_ - tempMin_)/(nTemp_ - 1) + tempMin_; +#ifndef NDEBUG + warningPrinted_ = false; +#endif + std::cout << "-------------------------------------------------------------------------\n" + << "Initializing tables for the " << RawComponent::name() + << " fluid properties (" << nTemp*nPress << " entries).\n" + << "Temperature -> min: " << std::scientific << std::setprecision(3) + << tempMin << ", max: " << tempMax << ", n: " << nTemp << '\n' + << "Pressure -> min: " << std::scientific << std::setprecision(3) + << pressMin << ", max: " << pressMax << ", n: " << nPress << '\n' + << "-------------------------------------------------------------------------" << std::endl; - Scalar pMax = maxP(iT); - Scalar pMin = minP(iT); - for (std::size_t iP = 0; iP < nPress_; ++ iP) - { - Scalar pressure = iP * (pMax - pMin)/(nPress_ - 1) + pMin; - values[iT + iP*nTemp_] = f(temperature, pressure); - } - }); + table().init(tempMin, tempMax, nTemp, pressMin, pressMax, nPress); + + initialized_ = true; } /*! - * \brief Initializes the minimum/maximum densities on the temperature range. - * - * \tparam RhoFunc Function to evaluate the density rho(T, p) - * \tparam MinPFunc Function to evaluate the minimum pressure for a - * temperature index (depends on useVaporPressure) - * \tparam MaxPFunc Function to evaluate the maximum pressure for a - * temperature index (depends on useVaporPressure) - * - * \param rho density function - * \param minP function to evaluate minimum pressure for temp idx - * \param maxP function to evaluate maximum pressure for temp idx - * \param rhoMin container to store minimum density values - * \param rhoMax container to store maximum density values + * \brief A human readable name for the component. */ - template<class RhoFunc, class MinPFunc, class MaxPFunc> - static void initMinMaxRhoArray_(RhoFunc&& rho, - MinPFunc&& minP, - MaxPFunc&& maxP, - std::vector<typename RawComponent::Scalar>& rhoMin, - std::vector<typename RawComponent::Scalar>& rhoMax) - { - Dumux::parallelFor(nTemp_, [&](std::size_t iT) - { - Scalar temperature = iT * (tempMax_ - tempMin_)/(nTemp_ - 1) + tempMin_; - - rhoMin[iT] = rho(temperature, minP(iT)); - if (iT < nTemp_ - 1) - rhoMax[iT] = rho(temperature, maxP(iT + 1)); - else - rhoMax[iT] = rho(temperature, maxP(iT)); - }); - } + static std::string name() + { return RawComponent::name(); } /*! - * \brief Initializes pressure arrays as function of temperature and density. - * - * \tparam PFunc Function to evaluate the pressure p(T, rho) - * - * \param pressure container to store pressure values - * \param p pressure function p(T, rho) - * \param rhoMin container with minimum density values - * \param rhoMax container with maximum density values + * \brief The molar mass in \f$\mathrm{[kg/mol]}\f$ of the component. */ - template<class PFunc> - static void initPressureArray_(std::vector<typename RawComponent::Scalar>& pressure, PFunc&& p, - const std::vector<typename RawComponent::Scalar>& rhoMin, - const std::vector<typename RawComponent::Scalar>& rhoMax) - { - Dumux::parallelFor(nTemp_, [&](std::size_t iT) - { - Scalar temperature = iT * (tempMax_ - tempMin_)/(nTemp_ - 1) + tempMin_; + static constexpr Scalar molarMass() + { return RawComponent::molarMass(); } - for (std::size_t iRho = 0; iRho < nDensity_; ++ iRho) - { - Scalar density = Scalar(iRho)/(nDensity_ - 1) - * (rhoMax[iT] - rhoMin[iT]) - + rhoMin[iT]; - pressure[iT + iRho*nTemp_] = p(temperature, density); - } - }); - } + /*! + * \brief Returns the critical temperature in \f$\mathrm{[K]}\f$ of the component. + */ + static Scalar criticalTemperature() + { return RawComponent::criticalTemperature(); } - //! returns an interpolated value depending on temperature - static Scalar interpolateT_(const std::vector<typename RawComponent::Scalar>& values, Scalar T) - { - Scalar alphaT = tempIdx_(T); - if (alphaT < 0 || alphaT >= nTemp_ - 1) - return std::numeric_limits<Scalar>::quiet_NaN(); + /*! + * \brief Returns the critical pressure in \f$\mathrm{[Pa]}\f$ of the component. + */ + static Scalar criticalPressure() + { return RawComponent::criticalPressure(); } - const auto iT = static_cast<int>(alphaT); - alphaT -= iT; + /*! + * \brief Returns the temperature in \f$\mathrm{[K]}\f$ at the component's triple point. + */ + static Scalar tripleTemperature() + { return RawComponent::tripleTemperature(); } - return values[iT ]*(1 - alphaT) + - values[iT + 1]*( alphaT); - } + /*! + * \brief Returns the pressure in \f$\mathrm{[Pa]}\f$ at the component's triple point. + */ + static Scalar triplePressure() + { return RawComponent::triplePressure(); } - //! returns an interpolated value depending on temperature and pressure - template<class GetPIdx, class MinPFunc, class MaxPFunc> - static Scalar interpolateTP_(const std::vector<typename RawComponent::Scalar>& values, Scalar T, Scalar p, - GetPIdx&& getPIdx, MinPFunc&& minP, MaxPFunc&& maxP) + /*! + * \brief The vapor pressure in \f$\mathrm{[Pa]}\f$ of the component at a given + * temperature. + * + * \param T temperature of component + */ + static Scalar vaporPressure(Scalar T) { - Scalar alphaT = tempIdx_(T); - if (alphaT < 0 || alphaT >= nTemp_ - 1) { - return std::numeric_limits<Scalar>::quiet_NaN(); - } - using std::clamp; - const auto iT = clamp<int>(static_cast<int>(alphaT), 0, nTemp_ - 2); - alphaT -= iT; - - Scalar alphaP1 = getPIdx(p, iT); - Scalar alphaP2 = getPIdx(p, iT + 1); - - const auto iP1 = clamp<int>(static_cast<int>(alphaP1), 0, nPress_ - 2); - const auto iP2 = clamp<int>(static_cast<int>(alphaP2), 0, nPress_ - 2); - alphaP1 -= iP1; - alphaP2 -= iP2; - -#if 0 && !defined NDEBUG - if(!(0 <= alphaT && alphaT <= 1.0)) - DUNE_THROW(NumericalProblem, "Temperature out of range: " - << "T=" << T << " range: [" << tempMin_ << ", " << tempMax_ << "]"); - if(!(0 <= alphaP1 && alphaP1 <= 1.0)) - DUNE_THROW(NumericalProblem, "First pressure out of range: " - << "p=" << p << " range: [" << minP(tempIdx_(T)) << ", " << maxP(tempIdx_(T)) << "]"); - if(!(0 <= alphaP2 && alphaP2 <= 1.0)) - DUNE_THROW(NumericalProblem, "Second pressure out of range: " - << "p=" << p << " range: [" << minP(tempIdx_(T) + 1) << ", " << maxP(tempIdx_(T) + 1) << "]"); -#endif - - return values[(iT ) + (iP1 )*nTemp_]*(1 - alphaT)*(1 - alphaP1) + - values[(iT ) + (iP1 + 1)*nTemp_]*(1 - alphaT)*( alphaP1) + - values[(iT + 1) + (iP2 )*nTemp_]*( alphaT)*(1 - alphaP2) + - values[(iT + 1) + (iP2 + 1)*nTemp_]*( alphaT)*( alphaP2); + using std::isnan; + Scalar result = interpolateT_(table().vaporPressure_, T, InterpolatePolicy{{ table() }}); + if (isnan(result)) + return RawComponent::vaporPressure(T); + return result; } - //! returns an interpolated value for gas depending on temperature and density - template<class GetRhoIdx> - static Scalar interpolateTRho_(const std::vector<typename RawComponent::Scalar>& values, Scalar T, Scalar rho, GetRhoIdx&& rhoIdx) + /*! + * \brief The vapor pressure in \f$\mathrm{[Pa]}\f$ of the component at a given + * temperature. + * + * The method is only called by the sequential flash, so tabulating is omitted. + * \param pressure pressure of component in \f$\mathrm{[Pa]}\f$ + */ + static Scalar vaporTemperature(Scalar pressure) { - using std::clamp; - Scalar alphaT = tempIdx_(T); - const auto iT = clamp<int>(static_cast<int>(alphaT), 0, nTemp_ - 2); - alphaT -= iT; - - Scalar alphaP1 = rhoIdx(rho, iT); - Scalar alphaP2 = rhoIdx(rho, iT + 1); - const auto iP1 = clamp<int>(static_cast<int>(alphaP1), 0, nDensity_ - 2); - const auto iP2 = clamp<int>(static_cast<int>(alphaP2), 0, nDensity_ - 2); - alphaP1 -= iP1; - alphaP2 -= iP2; - - return values[(iT ) + (iP1 )*nTemp_]*(1 - alphaT)*(1 - alphaP1) + - values[(iT ) + (iP1 + 1)*nTemp_]*(1 - alphaT)*( alphaP1) + - values[(iT + 1) + (iP2 )*nTemp_]*( alphaT)*(1 - alphaP2) + - values[(iT + 1) + (iP2 + 1)*nTemp_]*( alphaT)*( alphaP2); + return RawComponent::vaporTemperature(pressure); } - //! returns the index of an entry in a temperature field - static Scalar tempIdx_(Scalar temperature) + /*! + * \brief Specific enthalpy of the gas \f$\mathrm{[J/kg]}\f$. + * + * \param temperature temperature of component in \f$\mathrm{[K]}\f$ + * \param pressure pressure of component in \f$\mathrm{[Pa]}\f$ + */ + static const Scalar gasEnthalpy(Scalar temperature, Scalar pressure) { - return (nTemp_ - 1)*(temperature - tempMin_)/(tempMax_ - tempMin_); + Scalar result = interpolateTP_(table().gasEnthalpy_, temperature, pressure, InterpolateGasPolicy{{ table() }}); + using std::isnan; + if (isnan(result)) + { + printWarningTP_("gasEnthalpy", temperature, pressure, InterpolateGasPolicy{{ table() }}); + return RawComponent::gasEnthalpy(temperature, pressure); + } + return result; } - //! returns the index of an entry in a pressure field - static Scalar pressLiquidIdx_(Scalar pressure, std::size_t tempIdx) + /*! + * \brief Specific enthalpy of the liquid \f$\mathrm{[J/kg]}\f$. + * + * \param temperature temperature of component in \f$\mathrm{[K]}\f$ + * \param pressure pressure of component in \f$\mathrm{[Pa]}\f$ + */ + static const Scalar liquidEnthalpy(Scalar temperature, Scalar pressure) { - Scalar plMin = minLiquidPressure_(tempIdx); - Scalar plMax = maxLiquidPressure_(tempIdx); - - return (nPress_ - 1)*(pressure - plMin)/(plMax - plMin); + Scalar result = interpolateTP_(table().liquidEnthalpy_, temperature, pressure, InterpolateLiquidPolicy{{ table() }}); + using std::isnan; + if (isnan(result)) + { + printWarningTP_("liquidEnthalpy", temperature, pressure, InterpolateLiquidPolicy{{ table() }}); + return RawComponent::liquidEnthalpy(temperature, pressure); + } + return result; } - //! returns the index of an entry in a temperature field - static Scalar pressGasIdx_(Scalar pressure, std::size_t tempIdx) + /*! + * \brief Specific isobaric heat capacity of the gas \f$\mathrm{[J/(kg*K)]}\f$. + * + * \param temperature temperature of component in \f$\mathrm{[K]}\f$ + * \param pressure pressure of component in \f$\mathrm{[Pa]}\f$ + */ + static const Scalar gasHeatCapacity(Scalar temperature, Scalar pressure) { - Scalar pgMin = minGasPressure_(tempIdx); - Scalar pgMax = maxGasPressure_(tempIdx); - - return (nPress_ - 1)*(pressure - pgMin)/(pgMax - pgMin); + Scalar result = interpolateTP_(table().gasHeatCapacity_, temperature, pressure, InterpolateGasPolicy{{ table() }}); + using std::isnan; + if (isnan(result)) + { + printWarningTP_("gasHeatCapacity", temperature, pressure, InterpolateGasPolicy{{ table() }}); + return RawComponent::gasHeatCapacity(temperature, pressure); + } + return result; } - //! returns the index of an entry in a density field - static Scalar densityLiquidIdx_(Scalar density, std::size_t tempIdx) + /*! + * \brief Specific isobaric heat capacity of the liquid \f$\mathrm{[J/(kg*K)]}\f$. + * + * \param temperature temperature of component in \f$\mathrm{[K]}\f$ + * \param pressure pressure of component in \f$\mathrm{[Pa]}\f$ + */ + static const Scalar liquidHeatCapacity(Scalar temperature, Scalar pressure) { - Scalar densityMin = minLiquidDensity_[tempIdx]; - Scalar densityMax = maxLiquidDensity_[tempIdx]; - return (nDensity_ - 1) * (density - densityMin)/(densityMax - densityMin); + Scalar result = interpolateTP_(table().liquidHeatCapacity_, temperature, pressure, InterpolateLiquidPolicy{{ table() }}); + using std::isnan; + if (isnan(result)) + { + printWarningTP_("liquidHeatCapacity", temperature, pressure, InterpolateLiquidPolicy{{ table() }}); + return RawComponent::liquidHeatCapacity(temperature, pressure); + } + return result; } - //! returns the index of an entry in a density field - static Scalar densityGasIdx_(Scalar density, std::size_t tempIdx) + /*! + * \brief Specific internal energy of the gas \f$\mathrm{[J/kg]}\f$. + * + * \param temperature temperature of component in \f$\mathrm{[K]}\f$ + * \param pressure pressure of component in \f$\mathrm{[Pa]}\f$ + */ + static const Scalar gasInternalEnergy(Scalar temperature, Scalar pressure) { - Scalar densityMin = minGasDensity_[tempIdx]; - Scalar densityMax = maxGasDensity_[tempIdx]; - return (nDensity_ - 1) * (density - densityMin)/(densityMax - densityMin); + return gasEnthalpy(temperature, pressure) - pressure/gasDensity(temperature, pressure); } - //! returns the minimum tabularized liquid pressure at a given temperature index - static Scalar minLiquidPressure_(int tempIdx) + /*! + * \brief Specific internal energy of the liquid \f$\mathrm{[J/kg]}\f$. + * + * \param temperature temperature of component in \f$\mathrm{[K]}\f$ + * \param pressure pressure of component in \f$\mathrm{[Pa]}\f$ + */ + static const Scalar liquidInternalEnergy(Scalar temperature, Scalar pressure) { - using std::max; - if (!useVaporPressure) - return pressMin_; - else - return max(pressMin_, vaporPressure_[tempIdx] / 1.1); + return liquidEnthalpy(temperature, pressure) - pressure/liquidDensity(temperature, pressure); } - //! returns the maximum tabularized liquid pressure at a given temperature index - static Scalar maxLiquidPressure_(int tempIdx) + /*! + * \brief The pressure of gas in \f$\mathrm{[Pa]}\f$ at a given density and temperature. + * + * \param temperature temperature of component in \f$\mathrm{[K]}\f$ + * \param density density of component in \f$\mathrm{[kg/m^3]}\f$ + */ + static Scalar gasPressure(Scalar temperature, Scalar density) { - using std::max; - if (!useVaporPressure) - return pressMax_; - else - return max(pressMax_, vaporPressure_[tempIdx] * 1.1); + Scalar result = interpolateTRho_(table().gasPressure_, temperature, density, InterpolateGasPolicy{{ table() }}); + using std::isnan; + if (isnan(result)) + { + printWarningTRho_("gasPressure", temperature, density, InterpolateGasPolicy{{ table() }}); + return RawComponent::gasPressure(temperature, density); + } + return result; } - //! returns the minimum tabularized gas pressure at a given temperature index - static Scalar minGasPressure_(int tempIdx) + /*! + * \brief The pressure of liquid in \f$\mathrm{[Pa]}\f$ at a given density and temperature. + * + * \param temperature temperature of component in \f$\mathrm{[K]}\f$ + * \param density density of component in \f$\mathrm{[kg/m^3]}\f$ + */ + static Scalar liquidPressure(Scalar temperature, Scalar density) { - using std::min; - if (!useVaporPressure) - return pressMin_; - else - return min(pressMin_, vaporPressure_[tempIdx] / 1.1 ); + Scalar result = interpolateTRho_(table().liquidPressure_, temperature, density, InterpolateLiquidPolicy{{ table() }}); + using std::isnan; + if (isnan(result)) + { + printWarningTRho_("liquidPressure", temperature, density, InterpolateLiquidPolicy{{ table() }}); + return RawComponent::liquidPressure(temperature, density); + } + return result; } - //! returns the maximum tabularized gas pressure at a given temperature index - static Scalar maxGasPressure_(int tempIdx) - { - using std::min; - if (!useVaporPressure) - return pressMax_; - else - return min(pressMax_, vaporPressure_[tempIdx] * 1.1); - } + /*! + * \brief Returns true if the gas phase is assumed to be compressible + */ + static constexpr bool gasIsCompressible() + { return RawComponent::gasIsCompressible(); } - template<class RC = RawComponent> - static bool tabularizeGasEnthalpy_() + /*! + * \brief Returns true if the liquid phase is assumed to be compressible + */ + static constexpr bool liquidIsCompressible() + { return RawComponent::liquidIsCompressible(); } + + /*! + * \brief Returns true if the gas phase is assumed to be ideal + */ + static constexpr bool gasIsIdeal() + { return RawComponent::gasIsIdeal(); } + + + /*! + * \brief The density of gas at a given pressure and temperature + * \f$\mathrm{[kg/m^3]}\f$. + * + * \param temperature temperature of component in \f$\mathrm{[K]}\f$ + * \param pressure pressure of component in \f$\mathrm{[Pa]}\f$ + */ + static Scalar gasDensity(Scalar temperature, Scalar pressure) { - if constexpr (Detail::hasGasEnthalpy<RC>()) + Scalar result = interpolateTP_(table().gasDensity_, temperature, pressure, InterpolateGasPolicy{{ table() }}); + using std::isnan; + if (isnan(result)) { - auto gasEnth = [] (auto T, auto p) { return RC::gasEnthalpy(T, p); }; - initTPArray_(gasEnth, minGasPressure_, maxGasPressure_, gasEnthalpy_); - return true; + printWarningTP_("gasDensity", temperature, pressure, InterpolateGasPolicy{{ table() }}); + return RawComponent::gasDensity(temperature, pressure); } - - return false; + return result; } - template<class RC = RawComponent> - static bool tabularizeLiquidEnthalpy_() + /*! + * \brief The molar density of gas in \f$\mathrm{[mol/m^3]}\f$ + * at a given pressure and temperature. + * + * \param temperature temperature of component in \f$\mathrm{[K]}\f$ + * \param pressure pressure of component in \f$\mathrm{[Pa]}\f$ + * + */ + static Scalar gasMolarDensity(Scalar temperature, Scalar pressure) + { return gasDensity(temperature, pressure)/molarMass(); } + + /*! + * \brief The density of liquid at a given pressure and + * temperature \f$\mathrm{[kg/m^3]}\f$. + * + * \param temperature temperature of component in \f$\mathrm{[K]}\f$ + * \param pressure pressure of component in \f$\mathrm{[Pa]}\f$ + */ + static Scalar liquidDensity(Scalar temperature, Scalar pressure) { - if constexpr (Detail::hasLiquidEnthalpy<RC>()) + Scalar result = interpolateTP_(table().liquidDensity_, temperature, pressure, InterpolateLiquidPolicy{{ table() }}); + using std::isnan; + if (isnan(result)) { - auto liqEnth = [] (auto T, auto p) { return RC::liquidEnthalpy(T, p); }; - initTPArray_(liqEnth, minLiquidPressure_, maxLiquidPressure_, liquidEnthalpy_); - return true; + printWarningTP_("liquidDensity", temperature, pressure, InterpolateLiquidPolicy{{ table() }}); + return RawComponent::liquidDensity(temperature, pressure); } - return false; + return result; } - template<class RC = RawComponent> - static bool tabularizeGasHeatCapacity_() + /*! + * \brief The molar density of liquid in \f$\mathrm{[mol/m^3]}\f$ + * at a given pressure and temperature. + * + * \param temperature temperature of component in \f$\mathrm{[K]}\f$ + * \param pressure pressure of component in \f$\mathrm{[Pa]}\f$ + * + */ + static Scalar liquidMolarDensity(Scalar temperature, Scalar pressure) + { return liquidDensity(temperature, pressure)/molarMass(); } + + /*! + * \brief The dynamic viscosity \f$\mathrm{[Pa*s]}\f$ of gas. + * + * \param temperature temperature of component in \f$\mathrm{[K]}\f$ + * \param pressure pressure of component in \f$\mathrm{[Pa]}\f$ + */ + static Scalar gasViscosity(Scalar temperature, Scalar pressure) { - if constexpr (Detail::hasGasHeatCapacity<RC>()) + Scalar result = interpolateTP_(table().gasViscosity_, temperature, pressure, InterpolateGasPolicy{{ table() }}); + using std::isnan; + if (isnan(result)) { - auto gasHC = [] (auto T, auto p) { return RC::gasHeatCapacity(T, p); }; - initTPArray_(gasHC, minGasPressure_, maxGasPressure_, gasHeatCapacity_); - return true; + printWarningTP_("gasViscosity", temperature, pressure, InterpolateGasPolicy{{ table() }}); + return RawComponent::gasViscosity(temperature, pressure); } - - return false; + return result; } - template<class RC = RawComponent> - static bool tabularizeLiquidHeatCapacity_() + /*! + * \brief The dynamic viscosity \f$\mathrm{[Pa*s]}\f$ of liquid. + * + * \param temperature temperature of component in \f$\mathrm{[K]}\f$ + * \param pressure pressure of component in \f$\mathrm{[Pa]}\f$ + */ + static Scalar liquidViscosity(Scalar temperature, Scalar pressure) { - if constexpr (Detail::hasLiquidHeatCapacity<RC>()) + Scalar result = interpolateTP_(table().liquidViscosity_, temperature, pressure, InterpolateLiquidPolicy{{ table() }}); + using std::isnan; + if (isnan(result)) { - auto liqHC = [] (auto T, auto p) { return RC::liquidHeatCapacity(T, p); }; - initTPArray_(liqHC, minLiquidPressure_, maxLiquidPressure_, liquidHeatCapacity_); - return true; + printWarningTP_("liquidViscosity",temperature, pressure, InterpolateLiquidPolicy{{ table() }}); + return RawComponent::liquidViscosity(temperature, pressure); } - - return false; + return result; } - template<class RC = RawComponent> - static bool tabularizeMinMaxGasDensity_() + /*! + * \brief The thermal conductivity of gaseous water \f$\mathrm{[W/(m*K)]}\f$. + * + * \param temperature temperature of component in \f$\mathrm{[K]}\f$ + * \param pressure pressure of component in \f$\mathrm{[Pa]}\f$ + */ + static Scalar gasThermalConductivity(Scalar temperature, Scalar pressure) { - if constexpr (Detail::hasGasDensity<RC>()) + Scalar result = interpolateTP_(table().gasThermalConductivity_, temperature, pressure, InterpolateGasPolicy{{ table() }}); + using std::isnan; + if (isnan(result)) { - auto gasRho = [] (auto T, auto p) { return RC::gasDensity(T, p); }; - initMinMaxRhoArray_(gasRho, minGasPressure_, maxGasPressure_, minGasDensity_, maxGasDensity_); - return true; + printWarningTP_("gasThermalConductivity", temperature, pressure, InterpolateGasPolicy{{ table() }}); + return RawComponent::gasThermalConductivity(temperature, pressure); } - - return false; + return result; } - template<class RC = RawComponent> - static bool tabularizeMinMaxLiquidDensity_() + /*! + * \brief The thermal conductivity of liquid water \f$\mathrm{[W/(m*K)]}\f$. + * + * \param temperature temperature of component in \f$\mathrm{[K]}\f$ + * \param pressure pressure of component in \f$\mathrm{[Pa]}\f$ + */ + static Scalar liquidThermalConductivity(Scalar temperature, Scalar pressure) { - if constexpr (Detail::hasGasEnthalpy<RC>()) + Scalar result = interpolateTP_(table().liquidThermalConductivity_, temperature, pressure, InterpolateLiquidPolicy{{ table() }}); + using std::isnan; + if (isnan(result)) { - auto liqRho = [] (auto T, auto p) { return RC::liquidDensity(T, p); }; - initMinMaxRhoArray_(liqRho, minLiquidPressure_, maxLiquidPressure_, minLiquidDensity_, maxLiquidDensity_); - return true; + printWarningTP_("liquidThermalConductivity", temperature, pressure, InterpolateLiquidPolicy{{ table() }}); + return RawComponent::liquidThermalConductivity(temperature, pressure); } - - return false; + return result; } - template<class RC = RawComponent> - static bool tabularizeGasPressure_() + +private: + //! prints a warning if the result is not in range or the table has not been initialized + template<class Policy> + static void printWarningTP_(const std::string& quantity, Scalar T, Scalar p, Policy policy) { - // pressure is only defined if the gas is compressible (this is usually the case) - if constexpr (Detail::hasGasPressure<RC>() && RC::gasIsCompressible()) - { - auto gasPFunc = [] (auto T, auto rho) { return RC::gasPressure(T, rho); }; - initPressureArray_(gasPressure_, gasPFunc, minGasDensity_, maxGasDensity_); - return true; - } +#ifndef NDEBUG + if (warningPrinted_) + return; - return false; + if (!initialized_) + std::cerr << "Warning: tabulated component '" << name() + << "' has not been initialized. " + << "Call FluidSystem::init() to use the tabulation in order to reduce runtime. \n"; + else + std::cerr << "Warning: "<<quantity<<"(T="<<T<<", p="<<p<<") of component '"<<name() + << "' is outside tabulation range: ("<< policy.tempMin() <<"<=T<="<< policy.tempMax() <<"), (" + << policy.minP(0) <<"<=p<=" << policy.maxP(policy.nTemp()-1) <<"). " + << "Forwarded to FluidSystem for direct evaluation of "<<quantity<<". \n"; + warningPrinted_ = true; +#endif } - template<class RC = RawComponent> - static bool tabularizeLiquidPressure_() + //! prints a warning if the result is not in range or the table has not been initialized + template<class Policy> + static void printWarningTRho_(const std::string& quantity, Scalar T, Scalar rho, Policy policy) { - // pressure is only defined if the liquid is compressible (this is often not the case) - if constexpr (Detail::hasLiquidPressure<RC>() && RC::liquidIsCompressible()) +#ifndef NDEBUG + if (warningPrinted_) + return; + + if (!initialized_) + std::cerr << "Warning: tabulated component '" << name() + << "' has not been initialized. " + << "Call FluidSystem::init() to use the tabulation in order to reduce runtime. \n"; + else { - auto liqPFunc = [] (auto T, auto rho) { return RC::liquidPressure(T, rho); }; - initPressureArray_(liquidPressure_, liqPFunc, minLiquidDensity_, maxLiquidDensity_); - return true; + const auto [densityMin, densityMax] = [&] + { + const Scalar alphaT = policy.tempIdx(T); + using std::clamp; + const auto iT = clamp<int>(static_cast<int>(alphaT), 0, policy.nTemp() - 2); + return std::make_pair( policy.minRho(iT), policy.maxRho(iT) ); + }(); + + std::cerr << "Warning: "<<quantity<<"(T="<<T<<", density="<<rho<<") of component '"<<name() + << "' is outside tabulation range: ("<< policy.tempMin() <<"<=T<="<< policy.tempMax() <<"), (" + << densityMin <<"<=density<=" << densityMax <<"). " + << "Forwarded to FluidSystem for direct evaluation of "<<quantity<<". \n"; } - - return false; + warningPrinted_ = true; +#endif } - template<class RC = RawComponent> - static bool tabularizeGasDensity_() + //! returns an interpolated value depending on temperature + template<class Policy> + static Scalar interpolateT_(const std::vector<Scalar>& values, Scalar T, Policy policy) { - if constexpr (Detail::hasGasDensity<RC>()) - { - auto gasRho = [] (auto T, auto p) { return RC::gasDensity(T, p); }; - initTPArray_(gasRho, minGasPressure_, maxGasPressure_, gasDensity_); - return true; - } + Scalar alphaT = policy.tempIdx(T); + const auto nTemp = policy.nTemp(); + if (alphaT < 0 - 1e-7*nTemp || alphaT >= nTemp - 1 + 1e-7*nTemp) + return std::numeric_limits<Scalar>::quiet_NaN(); - return false; + using std::clamp; + const auto iT = clamp<int>(static_cast<int>(alphaT), 0, nTemp - 2); + alphaT -= iT; + + return values[iT ]*(1 - alphaT) + + values[iT + 1]*( alphaT); } - template<class RC = RawComponent> - static bool tabularizeLiquidDensity_() + //! returns an interpolated value depending on temperature and pressure + template<class Policy> + static Scalar interpolateTP_(const std::vector<Scalar>& values, const Scalar T, const Scalar p, Policy policy) { - if constexpr (Detail::hasLiquidDensity<RC>()) - { - // TODO: we could get rid of the lambdas and pass the functor directly. But, - // currently Brine is a component (and not a fluid system) expecting a - // third argument with a default, which cannot be wrapped in a function pointer. - // For this reason we have to wrap this into a lambda here. - auto liqRho = [] (auto T, auto p) { return RC::liquidDensity(T, p); }; - initTPArray_(liqRho, minLiquidPressure_, maxLiquidPressure_, liquidDensity_); - return true; - } + const auto nTemp = policy.nTemp(); + const auto nPress = policy.nPress(); - return false; - } + Scalar alphaT = policy.tempIdx(T); + if (alphaT < 0 - 1e-7*nTemp || alphaT >= nTemp - 1 + 1e-7*nTemp) + return std::numeric_limits<Scalar>::quiet_NaN(); - template<class RC = RawComponent> - static bool tabularizeGasViscosity_() - { - if constexpr (Detail::hasGasViscosity<RC>()) - { - auto gasVisc = [] (auto T, auto p) { return RC::gasViscosity(T, p); }; - initTPArray_(gasVisc, minGasPressure_, maxGasPressure_, gasViscosity_); - return true; - } + using std::clamp; + const auto iT = clamp<int>(static_cast<int>(alphaT), 0, nTemp - 2); + alphaT -= iT; - return false; - } + Scalar alphaP1 = policy.pressIdx(p, iT); + Scalar alphaP2 = policy.pressIdx(p, iT + 1); - template<class RC = RawComponent> - static bool tabularizeLiquidViscosity_() - { - if constexpr (Detail::hasLiquidViscosity<RC>()) - { - auto liqVisc = [] (auto T, auto p) { return RC::liquidViscosity(T, p); }; - initTPArray_(liqVisc, minLiquidPressure_, maxLiquidPressure_, liquidViscosity_); - return true; - } + const auto iP1 = clamp<int>(static_cast<int>(alphaP1), 0, nPress - 2); + const auto iP2 = clamp<int>(static_cast<int>(alphaP2), 0, nPress - 2); + alphaP1 -= iP1; + alphaP2 -= iP2; - return false; +#if 0 && !defined NDEBUG + const auto tempMin = policy.tempMin(); + const auto tempMin = policy.tempMax(); + if(!(0 <= alphaT && alphaT <= 1.0)) + DUNE_THROW(NumericalProblem, "Temperature out of range: " + << "T=" << T << " range: [" << tempMin_ << ", " << tempMax_ << "]"); + if(!(0 <= alphaP1 && alphaP1 <= 1.0)) + DUNE_THROW(NumericalProblem, "First pressure out of range: " + << "p=" << p << " range: [" << policy.minP(policy.tempIdx(T)) << ", " << policy.maxP(policy.tempIdx(T)) << "]"); + if(!(0 <= alphaP2 && alphaP2 <= 1.0)) + DUNE_THROW(NumericalProblem, "Second pressure out of range: " + << "p=" << p << " range: [" << policy.minP(policy.tempIdx(T) + 1) << ", " << policy.maxP(policy.tempIdx(T) + 1) << "]"); +#endif + return values[(iT ) + (iP1 )*nTemp]*(1 - alphaT)*(1 - alphaP1) + + values[(iT ) + (iP1 + 1)*nTemp]*(1 - alphaT)*( alphaP1) + + values[(iT + 1) + (iP2 )*nTemp]*( alphaT)*(1 - alphaP2) + + values[(iT + 1) + (iP2 + 1)*nTemp]*( alphaT)*( alphaP2); } - template<class RC = RawComponent> - static bool tabularizeGasThermalConductivity_() + //! returns an interpolated value for gas depending on temperature and density + template<class Policy> + static Scalar interpolateTRho_(const std::vector<Scalar>& values, const Scalar T, const Scalar rho, Policy policy) { - if constexpr (Detail::hasGasThermalConductivity<RC>()) - { - auto gasTC = [] (auto T, auto p) { return RC::gasThermalConductivity(T, p); }; - initTPArray_(gasTC, minGasPressure_, maxGasPressure_, gasThermalConductivity_); - return true; - } + const auto nTemp = policy.nTemp(); + const auto nDensity = policy.nDensity(); - return false; - } + using std::clamp; + Scalar alphaT = policy.tempIdx(T); + if (alphaT < 0 - 1e-7*nTemp || alphaT >= nTemp - 1 + 1e-7*nTemp) + return std::numeric_limits<Scalar>::quiet_NaN(); - template<class RC = RawComponent> - static bool tabularizeLiquidThermalConductivity_() - { - if constexpr (Detail::hasLiquidThermalConductivity<RC>()) - { - auto liqTC = [] (auto T, auto p) { return RC::liquidThermalConductivity(T, p); }; - initTPArray_(liqTC, minLiquidPressure_, maxLiquidPressure_, liquidThermalConductivity_); - return true; - } + const auto iT = clamp<int>(static_cast<int>(alphaT), 0, nTemp - 2); + alphaT -= iT; - return false; + Scalar alphaP1 = policy.rhoIdx(rho, iT); + Scalar alphaP2 = policy.rhoIdx(rho, iT + 1); + const auto iP1 = clamp<int>(static_cast<int>(alphaP1), 0, nDensity - 2); + const auto iP2 = clamp<int>(static_cast<int>(alphaP2), 0, nDensity - 2); + alphaP1 -= iP1; + alphaP2 -= iP2; + + return values[(iT ) + (iP1 )*nTemp]*(1 - alphaT)*(1 - alphaP1) + + values[(iT ) + (iP1 + 1)*nTemp]*(1 - alphaT)*( alphaP1) + + values[(iT + 1) + (iP2 )*nTemp]*( alphaT)*(1 - alphaP2) + + values[(iT + 1) + (iP2 + 1)*nTemp]*( alphaT)*( alphaP2); } // specifies whether the table was initialized @@ -1248,64 +1251,13 @@ private: static bool warningPrinted_; #endif - // 1D fields with the temperature as degree of freedom - static std::vector<typename RawComponent::Scalar> vaporPressure_; - - static std::vector<typename RawComponent::Scalar> minLiquidDensity_; - static std::vector<typename RawComponent::Scalar> maxLiquidDensity_; - static bool minMaxLiquidDensityInitialized_; - - static std::vector<typename RawComponent::Scalar> minGasDensity_; - static std::vector<typename RawComponent::Scalar> maxGasDensity_; - static bool minMaxGasDensityInitialized_; - - // 2D fields with the temperature and pressure as degrees of freedom - static std::vector<typename RawComponent::Scalar> gasEnthalpy_; - static std::vector<typename RawComponent::Scalar> liquidEnthalpy_; - static bool gasEnthalpyInitialized_; - static bool liquidEnthalpyInitialized_; - - static std::vector<typename RawComponent::Scalar> gasHeatCapacity_; - static std::vector<typename RawComponent::Scalar> liquidHeatCapacity_; - static bool gasHeatCapacityInitialized_; - static bool liquidHeatCapacityInitialized_; - - static std::vector<typename RawComponent::Scalar> gasDensity_; - static std::vector<typename RawComponent::Scalar> liquidDensity_; - static bool gasDensityInitialized_; - static bool liquidDensityInitialized_; - - static std::vector<typename RawComponent::Scalar> gasViscosity_; - static std::vector<typename RawComponent::Scalar> liquidViscosity_; - static bool gasViscosityInitialized_; - static bool liquidViscosityInitialized_; - - static std::vector<typename RawComponent::Scalar> gasThermalConductivity_; - static std::vector<typename RawComponent::Scalar> liquidThermalConductivity_; - static bool gasThermalConductivityInitialized_; - static bool liquidThermalConductivityInitialized_; - - // 2D fields with the temperature and density as degrees of freedom - static std::vector<typename RawComponent::Scalar> gasPressure_; - static std::vector<typename RawComponent::Scalar> liquidPressure_; - static bool gasPressureInitialized_; - static bool liquidPressureInitialized_; - - // temperature, pressure and density ranges - static Scalar tempMin_; - static Scalar tempMax_; - static std::size_t nTemp_; - - static Scalar pressMin_; - static Scalar pressMax_; - static std::size_t nPress_; - - static Scalar densityMin_; - static Scalar densityMax_; - static std::size_t nDensity_; + static Table& table() + { + static Table t; + return t; + } }; - template <class RawComponent, bool useVaporPressure> bool TabulatedComponent<RawComponent, useVaporPressure>::initialized_ = false; @@ -1314,88 +1266,6 @@ template <class RawComponent, bool useVaporPressure> bool TabulatedComponent<RawComponent, useVaporPressure>::warningPrinted_ = false; #endif -template <class RawComponent, bool useVaporPressure> -bool TabulatedComponent<RawComponent, useVaporPressure>::minMaxLiquidDensityInitialized_ = false; -template <class RawComponent, bool useVaporPressure> -bool TabulatedComponent<RawComponent, useVaporPressure>::minMaxGasDensityInitialized_ = false; -template <class RawComponent, bool useVaporPressure> -bool TabulatedComponent<RawComponent, useVaporPressure>::gasEnthalpyInitialized_ = false; -template <class RawComponent, bool useVaporPressure> -bool TabulatedComponent<RawComponent, useVaporPressure>::liquidEnthalpyInitialized_ = false; -template <class RawComponent, bool useVaporPressure> -bool TabulatedComponent<RawComponent, useVaporPressure>::gasHeatCapacityInitialized_ = false; -template <class RawComponent, bool useVaporPressure> -bool TabulatedComponent<RawComponent, useVaporPressure>::liquidHeatCapacityInitialized_ = false; -template <class RawComponent, bool useVaporPressure> -bool TabulatedComponent<RawComponent, useVaporPressure>::gasDensityInitialized_ = false; -template <class RawComponent, bool useVaporPressure> -bool TabulatedComponent<RawComponent, useVaporPressure>::liquidDensityInitialized_ = false; -template <class RawComponent, bool useVaporPressure> -bool TabulatedComponent<RawComponent, useVaporPressure>::gasViscosityInitialized_ = false; -template <class RawComponent, bool useVaporPressure> -bool TabulatedComponent<RawComponent, useVaporPressure>::liquidViscosityInitialized_ = false; -template <class RawComponent, bool useVaporPressure> -bool TabulatedComponent<RawComponent, useVaporPressure>::gasThermalConductivityInitialized_ = false; -template <class RawComponent, bool useVaporPressure> -bool TabulatedComponent<RawComponent, useVaporPressure>::liquidThermalConductivityInitialized_ = false; -template <class RawComponent, bool useVaporPressure> -bool TabulatedComponent<RawComponent, useVaporPressure>::gasPressureInitialized_ = false; -template <class RawComponent, bool useVaporPressure> -bool TabulatedComponent<RawComponent, useVaporPressure>::liquidPressureInitialized_ = false; - -template <class RawComponent, bool useVaporPressure> -std::vector<typename RawComponent::Scalar> TabulatedComponent<RawComponent, useVaporPressure>::vaporPressure_; -template <class RawComponent, bool useVaporPressure> -std::vector<typename RawComponent::Scalar> TabulatedComponent<RawComponent, useVaporPressure>::minLiquidDensity_; -template <class RawComponent, bool useVaporPressure> -std::vector<typename RawComponent::Scalar> TabulatedComponent<RawComponent, useVaporPressure>::maxLiquidDensity_; -template <class RawComponent, bool useVaporPressure> -std::vector<typename RawComponent::Scalar> TabulatedComponent<RawComponent, useVaporPressure>::minGasDensity_; -template <class RawComponent, bool useVaporPressure> -std::vector<typename RawComponent::Scalar> TabulatedComponent<RawComponent, useVaporPressure>::maxGasDensity_; -template <class RawComponent, bool useVaporPressure> -std::vector<typename RawComponent::Scalar> TabulatedComponent<RawComponent, useVaporPressure>::gasEnthalpy_; -template <class RawComponent, bool useVaporPressure> -std::vector<typename RawComponent::Scalar> TabulatedComponent<RawComponent, useVaporPressure>::liquidEnthalpy_; -template <class RawComponent, bool useVaporPressure> -std::vector<typename RawComponent::Scalar> TabulatedComponent<RawComponent, useVaporPressure>::gasHeatCapacity_; -template <class RawComponent, bool useVaporPressure> -std::vector<typename RawComponent::Scalar> TabulatedComponent<RawComponent, useVaporPressure>::liquidHeatCapacity_; -template <class RawComponent, bool useVaporPressure> -std::vector<typename RawComponent::Scalar> TabulatedComponent<RawComponent, useVaporPressure>::gasDensity_; -template <class RawComponent, bool useVaporPressure> -std::vector<typename RawComponent::Scalar> TabulatedComponent<RawComponent, useVaporPressure>::liquidDensity_; -template <class RawComponent, bool useVaporPressure> -std::vector<typename RawComponent::Scalar> TabulatedComponent<RawComponent, useVaporPressure>::gasViscosity_; -template <class RawComponent, bool useVaporPressure> -std::vector<typename RawComponent::Scalar> TabulatedComponent<RawComponent, useVaporPressure>::liquidViscosity_; -template <class RawComponent, bool useVaporPressure> -std::vector<typename RawComponent::Scalar> TabulatedComponent<RawComponent, useVaporPressure>::gasThermalConductivity_; -template <class RawComponent, bool useVaporPressure> -std::vector<typename RawComponent::Scalar> TabulatedComponent<RawComponent, useVaporPressure>::liquidThermalConductivity_; -template <class RawComponent, bool useVaporPressure> -std::vector<typename RawComponent::Scalar> TabulatedComponent<RawComponent, useVaporPressure>::gasPressure_; -template <class RawComponent, bool useVaporPressure> -std::vector<typename RawComponent::Scalar> TabulatedComponent<RawComponent, useVaporPressure>::liquidPressure_; -template <class RawComponent, bool useVaporPressure> -typename RawComponent::Scalar TabulatedComponent<RawComponent, useVaporPressure>::tempMin_; -template <class RawComponent, bool useVaporPressure> -typename RawComponent::Scalar TabulatedComponent<RawComponent, useVaporPressure>::tempMax_; -template <class RawComponent, bool useVaporPressure> -std::size_t TabulatedComponent<RawComponent, useVaporPressure>::nTemp_; -template <class RawComponent, bool useVaporPressure> -typename RawComponent::Scalar TabulatedComponent<RawComponent, useVaporPressure>::pressMin_; -template <class RawComponent, bool useVaporPressure> -typename RawComponent::Scalar TabulatedComponent<RawComponent, useVaporPressure>::pressMax_; -template <class RawComponent, bool useVaporPressure> -std::size_t TabulatedComponent<RawComponent, useVaporPressure>::nPress_; -template <class RawComponent, bool useVaporPressure> -typename RawComponent::Scalar TabulatedComponent<RawComponent, useVaporPressure>::densityMin_; -template <class RawComponent, bool useVaporPressure> -typename RawComponent::Scalar TabulatedComponent<RawComponent, useVaporPressure>::densityMax_; -template <class RawComponent, bool useVaporPressure> -std::size_t TabulatedComponent<RawComponent, useVaporPressure>::nDensity_; - // forward declaration template <class Component> struct IsAqueous; -- GitLab