diff --git a/dumux/material/fluidsystems/brineco2.hh b/dumux/material/fluidsystems/brineco2.hh index d01417d8ef2ca24c67d81f7c2af3241080af853a..42662e751257e17d93b490ec7e2d6c56473a0341 100644 --- a/dumux/material/fluidsystems/brineco2.hh +++ b/dumux/material/fluidsystems/brineco2.hh @@ -21,79 +21,183 @@ * \ingroup Fluidsystems * \brief @copybrief Dumux::FluidSystems::BrineCO2 */ -#ifndef DUMUX_BRINE_CO2_SYSTEM_HH -#define DUMUX_BRINE_CO2_SYSTEM_HH +#ifndef DUMUX_BRINE_CO2_FLUID_SYSTEM_HH +#define DUMUX_BRINE_CO2_FLUID_SYSTEM_HH + +#include <type_traits> + +#include <dune/common/exceptions.hh> #include <dumux/common/parameters.hh> #include <dumux/material/idealgas.hh> #include <dumux/material/fluidsystems/base.hh> +#include <dumux/material/fluidsystems/brine.hh> +#include <dumux/material/fluidstates/adapter.hh> +#include <dumux/material/components/brine.hh> #include <dumux/material/components/co2.hh> #include <dumux/material/components/co2tablereader.hh> #include <dumux/material/components/tabulatedcomponent.hh> #include <dumux/material/binarycoefficients/brine_co2.hh> -namespace Dumux -{ +namespace Dumux { + // include the default tables for CO2 #include <dumux/material/components/co2tables.inc> -namespace FluidSystems +namespace FluidSystems { +namespace Detail { + + /*! + * \brief Class that exports some indices that should + * be provided by the BrineAir fluid system. + * The indices are chosen dependent on the policy, + * i.e. if a simplified pseudo component Brine is + * used or salt is considered an individual component. + */ + template<bool useConstantSalinity> + struct BrineCO2Indices; + + /*! + * \brief Specialization for the case of brine being + * a pseudo component with a constant salinity. + * \note This specialization exports brine as component + */ + template<> + struct BrineCO2Indices<true> + { + static constexpr int BrineIdx = 0; + }; + + /*! + * \brief Specialization for the case of brine being + * a fluid system with NaCl as individual component. + * \note This specialization exports both water and NaCl as components + */ + template<> + struct BrineCO2Indices<false> + { + static constexpr int H2OIdx = 0; + static constexpr int NaClIdx = 2; + static constexpr int comp2Idx = 2; + }; +} // end namespace Detail + +/*! + * \ingroup Fluidsystems + * \brief Default policy for the Brine-CO2 fluid system + */ +template<bool salinityIsConstant> +struct BrineCO2DefaultPolicy { + static constexpr bool useConstantSalinity() { return salinityIsConstant; } +}; + /*! * \ingroup Fluidsystems - * \brief A compositional fluid with brine and carbon as - * components in both, the liquid and the gas (supercritical) phase. + * \brief A compositional fluid with brine (H2O & NaCl) and carbon dioxide as + * components in both the liquid and the gas (supercritical) phase. + * + * \note Depending on the chosen policy, the salinity is assumed to be constant + * (in which case Brine is used as a pseudo component) or salt (here NaCl) + * is considered as an individual component. + * \note This implemetation always assumes NaCl stays in the liquid phase. */ -template<class Scalar, - class CO2Table, - class H2Otype = Components::TabulatedComponent<Components::H2O<Scalar> >, - class BrineRawComponent = Components::Brine<Scalar, Components::H2O<Scalar> >, - class Brinetype = Components::TabulatedComponent<BrineRawComponent> > +template< class Scalar, + class CO2Table, + class H2OType = Components::TabulatedComponent<Components::H2O<Scalar>>, + class Policy = BrineCO2DefaultPolicy</*constantSalinity?*/true> > class BrineCO2 -: public Base<Scalar, BrineCO2<Scalar, CO2Table, H2Otype, BrineRawComponent, Brinetype> > +: public Base<Scalar, BrineCO2<Scalar, CO2Table, H2OType, Policy>> +, public Detail::BrineCO2Indices<Policy::useConstantSalinity()> { - using ThisType = BrineCO2<Scalar, CO2Table, H2Otype, BrineRawComponent, Brinetype>; + using ThisType = BrineCO2<Scalar, CO2Table, H2OType, Policy>; using Base = Dumux::FluidSystems::Base<Scalar, ThisType>; - + // binary coefficients using Brine_CO2 = BinaryCoeff::Brine_CO2<Scalar, CO2Table>; + // use constant salinity brine? + static constexpr bool useConstantSalinity = Policy::useConstantSalinity(); + + // The possible brine types + using VariableSalinityBrine = Dumux::FluidSystems::Brine<Scalar, H2OType>; + using ConstantSalinityBrine = Dumux::Components::Brine<Scalar, H2OType>; + using BrineType = typename std::conditional_t< useConstantSalinity, + ConstantSalinityBrine, + VariableSalinityBrine >; + + ///////////////////////////////////////////////////////////////////////////////// + //! The following two indices are only used internally and are not part of the + //! public interface. Depending on the chosen policy, i.e. if brine is used as + //! a pseudo component or a fluid system with NaCl as a separate component, the + //! indices that are part of the public interface are chosen by inheritance from + //! Detail::BrineCO2Indices (see documentation). + //! + //! depending on the implementation this is either brine (pseudo-component) or H2O + static constexpr int BrineOrH2OIdx = 0; + //! if the implementation considers NaCl as a real compoent, it gets the index 2 + static constexpr int NaClIdx = 2; + public: using ParameterCache = NullParameterCache; - using H2O = H2Otype; - using Brine = Brinetype; + + using H2O = H2OType; + using Brine = BrineType; using CO2 = Dumux::Components::CO2<Scalar, CO2Table>; - static constexpr int numComponents = 2; + static constexpr int numComponents = useConstantSalinity ? 2 : 3; static constexpr int numPhases = 2; static constexpr int liquidPhaseIdx = 0; //!< index of the liquid phase - static constexpr int gasPhaseIdx = 1; //!< index of the gas phase + static constexpr int gasPhaseIdx = 1; //!< index of the gas phase static constexpr int phase0Idx = liquidPhaseIdx; //!< index of the first phase - static constexpr int phase1Idx = gasPhaseIdx; //!< index of the second phase + static constexpr int phase1Idx = gasPhaseIdx; //!< index of the second phase static constexpr int comp0Idx = 0; static constexpr int comp1Idx = 1; - static constexpr int BrineIdx = comp0Idx; + // CO2 is always the second component static constexpr int CO2Idx = comp1Idx; +private: + + // Adapter policy for the fluid state corresponding to the brine fluid system + struct BrineAdapterPolicy + { + using FluidSystem = VariableSalinityBrine; + + static constexpr int phaseIdx(int brinePhaseIdx) { return liquidPhaseIdx; } + static constexpr int compIdx(int brineCompIdx) + { + switch (brineCompIdx) + { + assert(brineCompIdx == VariableSalinityBrine::H2OIdx || brineCompIdx == VariableSalinityBrine::NaClIdx); + case VariableSalinityBrine::H2OIdx: return BrineOrH2OIdx; + case VariableSalinityBrine::NaClIdx: return NaClIdx; + default: return 0; // this will never be reached, only needed to suppress compiler warning + } + } + }; + + template<class FluidState> + using BrineAdapter = FluidStateAdapter<FluidState, BrineAdapterPolicy>; + +public: + /*! * \brief Return the human readable name of a fluid phase - * * \param phaseIdx The index of the fluid phase to consider */ static std::string phaseName(int phaseIdx) { - static std::string name[] = { - std::string("l"), - std::string("g") - }; - - assert(0 <= phaseIdx && phaseIdx < numPhases); - return name[phaseIdx]; + switch (phaseIdx) + { + case liquidPhaseIdx: return "l"; + case gasPhaseIdx: return "g"; + } + DUNE_THROW(Dune::InvalidStateException, "Invalid phase index " << phaseIdx); } /*! @@ -131,7 +235,8 @@ public: static bool isIdealMixture(int phaseIdx) { assert(0 <= phaseIdx && phaseIdx < numPhases); - + if (!useConstantSalinity && phaseIdx == liquidPhaseIdx) + return VariableSalinityBrine::isIdealMixture(VariableSalinityBrine::liquidPhaseIdx); return true; } @@ -147,40 +252,52 @@ public: static constexpr bool isCompressible(int phaseIdx) { assert(0 <= phaseIdx && phaseIdx < numPhases); - + if (phaseIdx == liquidPhaseIdx) + return useConstantSalinity ? ConstantSalinityBrine::liquidIsCompressible() + : VariableSalinityBrine::isCompressible(VariableSalinityBrine::liquidPhaseIdx); return true; } /*! * \brief Return the human readable name of a component - * * \param compIdx The index of the component to consider */ static std::string componentName(int compIdx) { - static std::string name[] = { - Brine::name(), - CO2::name(), - }; - assert(0 <= compIdx && compIdx < numComponents); - return name[compIdx]; + if (useConstantSalinity) + { + static std::string name[] = { ConstantSalinityBrine::name(), CO2::name() }; + return name[compIdx]; + } + else + { + static std::string name[] = { VariableSalinityBrine::componentName(VariableSalinityBrine::H2OIdx), + CO2::name(), + VariableSalinityBrine::NaCl::name() }; + return name[compIdx]; + } } /*! * \brief Return the molar mass of a component in \f$\mathrm{[kg/mol]}\f$. - * * \param compIdx The index of the component to consider */ static Scalar molarMass(int compIdx) { - static const Scalar M[] = { - Brine::molarMass(), - CO2::molarMass(), - }; - assert(0 <= compIdx && compIdx < numComponents); - return M[compIdx]; + if (useConstantSalinity) + { + static const Scalar M[] = { ConstantSalinityBrine::molarMass(), CO2::molarMass() }; + return M[compIdx]; + } + else + { + static const Scalar M[] = { VariableSalinityBrine::molarMass(VariableSalinityBrine::H2OIdx), + CO2::molarMass(), + VariableSalinityBrine::molarMass(VariableSalinityBrine::NaClIdx) }; + return M[compIdx]; + } } /**************************************** @@ -188,62 +305,22 @@ public: ****************************************/ // Initializing with salinity and default tables - static void init(Scalar salinity) - { - init(/*startTemp=*/273.15, /*endTemp=*/623.15, /*tempSteps=*/100, - /*startPressure=*/1e4, /*endPressure=*/40e6, /*pressureSteps=*/200, salinity); - } - - // Initializing with salinity and custom tables - static void init(Scalar startTemp, Scalar endTemp, int tempSteps, - Scalar startPressure, Scalar endPressure, int pressureSteps, - Scalar salinity) - { - if(H2O::isTabulated) - { - std::cout << "Initializing tables for the pure-water properties.\n"; - H2O::init(startTemp, endTemp, tempSteps, - startPressure, endPressure, pressureSteps); - } - // set the salinity of brine - BrineRawComponent::constantSalinity = salinity; - - if(Brine::isTabulated) - { - std::cout << "Initializing tables for the brine fluid properties.\n"; - Brine::init(startTemp, endTemp, tempSteps, - startPressure, endPressure, pressureSteps); - } - } - - // Initializing with input parameter salinity and default tables static void init() { - const auto salinity = getParam<Scalar>("FluidSystem.Salinity", 0.3); init(/*startTemp=*/273.15, /*endTemp=*/623.15, /*tempSteps=*/100, - /*startPressure=*/1e4, /*endPressure=*/40e6, /*pressureSteps=*/200, salinity); + /*startPressure=*/1e4, /*endPressure=*/40e6, /*pressureSteps=*/200); } - // Initializing with input parameter salinity and custom tables + // Initializing with custom tables static void init(Scalar startTemp, Scalar endTemp, int tempSteps, Scalar startPressure, Scalar endPressure, int pressureSteps) { - const auto salinity = getParam<Scalar>("FluidSystem.Salinity", 0.3); - if(H2O::isTabulated) - { - std::cout << "Initializing tables for the pure-water properties.\n"; - H2O::init(startTemp, endTemp, tempSteps, - startPressure, endPressure, pressureSteps); - } - // set the salinity of brine - BrineRawComponent::constantSalinity = salinity; + // maybe set salinity of the constant salinity brine + if (useConstantSalinity) + ConstantSalinityBrine::constantSalinity = getParam<Scalar>("FluidSystem.Salinity", 0.3); - if(Brine::isTabulated) - { - std::cout << "Initializing tables for the brine fluid properties.\n"; - Brine::init(startTemp, endTemp, tempSteps, - startPressure, endPressure, pressureSteps); - } + if (H2O::isTabulated) + H2O::init(startTemp, endTemp, tempSteps, startPressure, endPressure, pressureSteps); } using Base::density; @@ -256,40 +333,16 @@ public: * \param phaseIdx The index of the phase */ template <class FluidState> - static Scalar density(const FluidState &fluidState, - int phaseIdx) + static Scalar density(const FluidState& fluidState, int phaseIdx) { - assert(0 <= phaseIdx && phaseIdx < numPhases); - - Scalar temperature = fluidState.temperature(phaseIdx); - Scalar pressure = fluidState.pressure(phaseIdx); - - if (phaseIdx == liquidPhaseIdx) { - // use normalized composition for to calculate the density - // (the relations don't seem to take non-normalized - // compositions too well...) - using std::min; - using std::max; - Scalar xlBrine = min(1.0, max(0.0, fluidState.moleFraction(liquidPhaseIdx, BrineIdx))); - Scalar xlCO2 = min(1.0, max(0.0, fluidState.moleFraction(liquidPhaseIdx, CO2Idx))); - Scalar sumx = xlBrine + xlCO2; - xlBrine /= sumx; - xlCO2 /= sumx; + if (phaseIdx == liquidPhaseIdx) + return liquidDensity_(fluidState); - Scalar result = liquidDensity_(temperature, - pressure, - xlBrine, - xlCO2); + // in the end it comes down to a simplification of just CO2 + else if (phaseIdx == gasPhaseIdx) + return CO2::gasDensity(fluidState.temperature(phaseIdx), fluidState.pressure(phaseIdx)); - Valgrind::CheckDefined(result); - return result; - } - else { - assert(phaseIdx == gasPhaseIdx); - - //in the end it comes down to a simplification of just CO2 - return CO2::gasDensity(temperature, pressure); - } + DUNE_THROW(Dune::InvalidStateException, "Invalid phase index."); } using Base::molarDensity; @@ -303,17 +356,13 @@ public: * \f[\rho_{mol,\alpha} = \frac{\rho_\alpha}{\overline M_\alpha} \;.\f] */ template <class FluidState> - static Scalar molarDensity(const FluidState &fluidState, int phaseIdx) + static Scalar molarDensity(const FluidState& fluidState, int phaseIdx) { - Scalar temperature = fluidState.temperature(phaseIdx); - Scalar pressure = fluidState.pressure(phaseIdx); - if (phaseIdx == liquidPhaseIdx) - { return density(fluidState, phaseIdx)/fluidState.averageMolarMass(phaseIdx); - } - else - return CO2::gasMolarDensity(temperature,pressure); + else if (phaseIdx == gasPhaseIdx) + return CO2::gasMolarDensity(fluidState.temperature(phaseIdx), fluidState.pressure(phaseIdx)); + DUNE_THROW(Dune::InvalidStateException, "Invalid phase index."); } using Base::viscosity; @@ -328,22 +377,19 @@ public: * would have to find out its influence. */ template <class FluidState> - static Scalar viscosity(const FluidState &fluidState, - int phaseIdx) + static Scalar viscosity(const FluidState& fluidState, int phaseIdx) { - assert(0 <= phaseIdx && phaseIdx < numPhases); - - Scalar temperature = fluidState.temperature(phaseIdx); - Scalar pressure = fluidState.pressure(phaseIdx); - Scalar result = 0; + Scalar T = fluidState.temperature(phaseIdx); + Scalar p = fluidState.pressure(phaseIdx); if (phaseIdx == liquidPhaseIdx) - result = Brine::liquidViscosity(temperature, pressure); - else - result = CO2::gasViscosity(temperature, pressure); + return useConstantSalinity ? ConstantSalinityBrine::liquidViscosity(T, p) + : VariableSalinityBrine::viscosity( BrineAdapter<FluidState>(fluidState), + VariableSalinityBrine::liquidPhaseIdx ); + else if (phaseIdx == gasPhaseIdx) + return CO2::gasViscosity(T, p); - Valgrind::CheckDefined(result); - return result; + DUNE_THROW(Dune::InvalidStateException, "Invalid phase index."); } using Base::fugacityCoefficient; @@ -374,11 +420,10 @@ public: * \param compIdx The index of the component */ template <class FluidState> - static Scalar fugacityCoefficient(const FluidState &fluidState, + static Scalar fugacityCoefficient(const FluidState& fluidState, int phaseIdx, int compIdx) { - assert(0 <= phaseIdx && phaseIdx < numPhases); assert(0 <= compIdx && compIdx < numComponents); if (phaseIdx == gasPhaseIdx) @@ -387,40 +432,42 @@ public: // as the relative fluid compositions are observed, return 1.0; - Scalar temperature = fluidState.temperature(phaseIdx); - Scalar pressure = fluidState.pressure(phaseIdx); - assert(temperature > 0); - assert(pressure > 0); + else if (phaseIdx == liquidPhaseIdx) + { + Scalar T = fluidState.temperature(phaseIdx); + Scalar p = fluidState.pressure(phaseIdx); + assert(temperature > 0); + assert(pressure > 0); + + // calulate the equilibrium composition for given T & p + Scalar xlH2O, xgH2O; + Scalar xlCO2, xgCO2; + const Scalar salinity = useConstantSalinity ? ConstantSalinityBrine::constantSalinity + : fluidState.massFraction(liquidPhaseIdx, NaClIdx); + Brine_CO2::calculateMoleFractions(T, p, salinity, /*knownGasPhaseIdx=*/-1, xlCO2, xgH2O); + + // normalize the phase compositions + using std::min; + using std::max; + xlCO2 = max(0.0, min(1.0, xlCO2)); + xgH2O = max(0.0, min(1.0, xgH2O)); + xlH2O = 1.0 - xlCO2; + xgCO2 = 1.0 - xgH2O; - // calulate the equilibrium composition for the given - // temperature and pressure. - Scalar xlH2O, xgH2O; - Scalar xlCO2, xgCO2; - Brine_CO2::calculateMoleFractions(temperature, - pressure, - BrineRawComponent::constantSalinity, - /*knowgasPhaseIdx=*/-1, - xlCO2, - xgH2O); - - // normalize the phase compositions - using std::min; - using std::max; - xlCO2 = max(0.0, min(1.0, xlCO2)); - xgH2O = max(0.0, min(1.0, xgH2O)); + if (compIdx == BrineOrH2OIdx) + return /*phigH2O=*/1.0*xgH2O/xlH2O; - xlH2O = 1.0 - xlCO2; - xgCO2 = 1.0 - xgH2O; + else if (compIdx == CO2Idx) + return /*phigCO2=*/1.0*xgCO2/xlCO2; - if (compIdx == BrineIdx) { - Scalar phigH2O = 1.0; - return phigH2O * xgH2O / xlH2O; - } + // NaCl is assumed to stay in the liquid! + else if (!useConstantSalinity && compIdx == NaClIdx) + return 0.0; - assert(compIdx == CO2Idx); + DUNE_THROW(Dune::InvalidStateException, "Invalid component index."); + } - Scalar phigCO2 = 1.0; - return phigCO2 * xgCO2 / xlCO2; + DUNE_THROW(Dune::InvalidStateException, "Invalid phase index."); } /*! @@ -431,37 +478,29 @@ public: * \param phaseIdx The index of the fluid phase to consider */ template <class FluidState> - static Scalar equilibriumMoleFraction(const FluidState &fluidState, - const ParameterCache ¶mCache, - int phaseIdx) + static Scalar equilibriumMoleFraction(const FluidState& fluidState, + const ParameterCache& paramCache, + int phaseIdx) { - assert(0 <= phaseIdx && phaseIdx < numPhases); - - Scalar temperature = fluidState.temperature(phaseIdx); - Scalar pressure = fluidState.pressure(phaseIdx); + Scalar T = fluidState.temperature(phaseIdx); + Scalar p = fluidState.pressure(phaseIdx); assert(temperature > 0); assert(pressure > 0); Scalar xgH2O; Scalar xlCO2; - // calulate the equilibrium composition for the given - // temperature and pressure. - Brine_CO2::calculateMoleFractions(temperature, - pressure, - BrineRawComponent::constantSalinity, - /*knowgasPhaseIdx=*/-1, - xlCO2, - xgH2O); + // calulate the equilibrium composition for given T & p + const Scalar salinity = useConstantSalinity ? ConstantSalinityBrine::constantSalinity + : fluidState.massFraction(liquidPhaseIdx, NaClIdx); + Brine_CO2::calculateMoleFractions(T, p, salinity, /*knowgasPhaseIdx=*/-1, xlCO2, xgH2O); - if(phaseIdx == gasPhaseIdx) - { + if (phaseIdx == gasPhaseIdx) return xgH2O; - } - else - { + else if (phaseIdx == liquidPhaseIdx) return xlCO2; - } + + DUNE_THROW(Dune::InvalidStateException, "Invalid phase index."); } @@ -492,12 +531,8 @@ public: * \param compIdx The index of the component to consider */ template <class FluidState> - static Scalar diffusionCoefficient(const FluidState &fluidState, - int phaseIdx, - int compIdx) - { - DUNE_THROW(Dune::NotImplemented, "Diffusion coefficients"); - } + static Scalar diffusionCoefficient(const FluidState& fluidState, int phaseIdx, int compIdx) + { DUNE_THROW(Dune::NotImplemented, "Diffusion coefficients"); } using Base::binaryDiffusionCoefficient; /*! @@ -509,12 +544,11 @@ public: * \param compJIdx Index of the component j */ template <class FluidState> - static Scalar binaryDiffusionCoefficient(const FluidState &fluidState, + static Scalar binaryDiffusionCoefficient(const FluidState& fluidState, int phaseIdx, int compIIdx, int compJIdx) { - assert(0 <= phaseIdx && phaseIdx < numPhases); assert(0 <= compIIdx && compIIdx < numComponents); assert(0 <= compJIdx && compJIdx < numComponents); @@ -524,25 +558,37 @@ public: swap(compIIdx, compJIdx); } - Scalar temperature = fluidState.temperature(phaseIdx); - Scalar pressure = fluidState.pressure(phaseIdx); - if (phaseIdx == liquidPhaseIdx) { - assert(compIIdx == BrineIdx); - assert(compJIdx == CO2Idx); - - Scalar result = Brine_CO2::liquidDiffCoeff(temperature, pressure); - Valgrind::CheckDefined(result); - return result; + Scalar T = fluidState.temperature(phaseIdx); + Scalar p = fluidState.pressure(phaseIdx); + if (phaseIdx == liquidPhaseIdx) + { + if (compIIdx == BrineOrH2OIdx && compJIdx == CO2Idx) + return Brine_CO2::liquidDiffCoeff(T, p); + if (!useConstantSalinity && compIIdx == BrineOrH2OIdx && compJIdx == NaClIdx) + return VariableSalinityBrine::binaryDiffusionCoefficient( BrineAdapter<FluidState>(fluidState), + VariableSalinityBrine::liquidPhaseIdx, + VariableSalinityBrine::H2OIdx, + VariableSalinityBrine::NaClIdx ); + + DUNE_THROW(Dune::NotImplemented, "Binary diffusion coefficient of components " << + compIIdx << " and " << compJIdx << " in phase " << phaseIdx); } - else { - assert(phaseIdx == gasPhaseIdx); - assert(compIIdx == BrineIdx); - assert(compJIdx == CO2Idx); + else if (phaseIdx == gasPhaseIdx) + { + if (compIIdx == BrineOrH2OIdx && compJIdx == CO2Idx) + return Brine_CO2::gasDiffCoeff(T, p); - Scalar result = Brine_CO2::gasDiffCoeff(temperature, pressure); - Valgrind::CheckDefined(result); - return result; + // NaCl is expected to never be present in the gas phase. we need to + // return a diffusion coefficient that does not case numerical problems. + // We choose a very small value here. + else if (!useConstantSalinity && compIIdx == CO2Idx && compJIdx == NaClIdx) + return 1e-12; + + DUNE_THROW(Dune::NotImplemented, "Binary diffusion coefficient of components " << + compIIdx << " and " << compJIdx << " in phase " << phaseIdx); } + + DUNE_THROW(Dune::InvalidStateException, "Invalid phase index."); } using Base::enthalpy; @@ -553,35 +599,43 @@ public: * \param phaseIdx The index of the fluid phase to consider */ template <class FluidState> - static Scalar enthalpy(const FluidState &fluidState, - int phaseIdx) + static Scalar enthalpy(const FluidState& fluidState, int phaseIdx) { - assert(0 <= phaseIdx && phaseIdx < numPhases); + Scalar T = fluidState.temperature(phaseIdx); + Scalar p = fluidState.pressure(phaseIdx); + + if (phaseIdx == liquidPhaseIdx) + { + // Convert J/kg to kJ/kg + const Scalar h_ls1 = useConstantSalinity ? ConstantSalinityBrine::liquidEnthalpy(T, p)/1e3 + : VariableSalinityBrine::enthalpy( BrineAdapter<FluidState>(fluidState), + VariableSalinityBrine::liquidPhaseIdx )/1e3; - Scalar temperature = fluidState.temperature(phaseIdx); - Scalar pressure = fluidState.pressure(phaseIdx); + // mass fraction of CO2 in Brine + const Scalar X_CO2_w = fluidState.massFraction(liquidPhaseIdx, CO2Idx); - if (phaseIdx == liquidPhaseIdx) { - Scalar XlCO2 = fluidState.massFraction(phaseIdx, CO2Idx); + // heat of dissolution for CO2 according to Fig. 6 in Duan and Sun 2003. (kJ/kg) + // In the relevant temperature ranges CO2 dissolution is exothermal + const Scalar delta_hCO2 = (-57.4375 + T * 0.1325) * 1000/44; - Scalar result = liquidEnthalpyBrineCO2_(temperature, - pressure, - BrineRawComponent::constantSalinity, - XlCO2); - Valgrind::CheckDefined(result); - return result; + // enthalpy contribution of water and CO2 (kJ/kg) + const Scalar hw = H2O::liquidEnthalpy(T, p)/1e3; + const Scalar hg = CO2::liquidEnthalpy(T, p)/1e3 + delta_hCO2; + + // Enthalpy of brine with dissolved CO2 (kJ/kg) + return (h_ls1 - X_CO2_w*hw + hg*X_CO2_w)*1e3; } - else { + else if (phaseIdx == gasPhaseIdx) + { Scalar result = 0; - result += - Brine::gasEnthalpy(temperature, pressure) * - fluidState.massFraction(gasPhaseIdx, BrineIdx); - result += - CO2::gasEnthalpy(temperature, pressure) * - fluidState.massFraction(gasPhaseIdx, CO2Idx); + // we assume NaCl to not enter the gas phase, only consider H2O and CO2 + result += H2O::gasEnthalpy(T, p)*fluidState.massFraction(gasPhaseIdx, BrineOrH2OIdx); + result += CO2::gasEnthalpy(T, p) *fluidState.massFraction(gasPhaseIdx, CO2Idx); Valgrind::CheckDefined(result); return result; } + + DUNE_THROW(Dune::InvalidStateException, "Invalid phase index."); } using Base::thermalConductivity; @@ -595,17 +649,17 @@ public: * would have to find out its influence. */ template <class FluidState> - static Scalar thermalConductivity(const FluidState &fluidState, - int phaseIdx) + static Scalar thermalConductivity(const FluidState& fluidState, int phaseIdx) { if (phaseIdx == liquidPhaseIdx) - { - return H2O::liquidThermalConductivity(fluidState.temperature(phaseIdx), - fluidState.pressure(phaseIdx)); - } - else // gas phase - return CO2::gasThermalConductivity(fluidState.temperature(phaseIdx), - fluidState.pressure(phaseIdx)); + return useConstantSalinity ? ConstantSalinityBrine::liquidThermalConductivity( fluidState.temperature(phaseIdx), + fluidState.pressure(phaseIdx) ) + : VariableSalinityBrine::thermalConductivity( BrineAdapter<FluidState>(fluidState), + VariableSalinityBrine::liquidPhaseIdx ); + else if (phaseIdx == gasPhaseIdx) + return CO2::gasThermalConductivity(fluidState.temperature(phaseIdx), fluidState.pressure(phaseIdx)); + + DUNE_THROW(Dune::InvalidStateException, "Invalid phase index."); } using Base::heatCapacity; @@ -614,7 +668,7 @@ public: * * \note We employ the heat capacity of the pure phases. * - * \todo Implement heat capacity for gaseous CO2 + * \todo TODO Implement heat capacity for gaseous CO2 * * \param fluidState An arbitrary fluid state * \param phaseIdx The index of the fluid phase to consider @@ -624,11 +678,15 @@ public: int phaseIdx) { if(phaseIdx == liquidPhaseIdx) - return H2O::liquidHeatCapacity(fluidState.temperature(phaseIdx), - fluidState.pressure(phaseIdx)); - else + return useConstantSalinity ? ConstantSalinityBrine::liquidHeatCapacity( fluidState.temperature(phaseIdx), + fluidState.pressure(phaseIdx) ) + : VariableSalinityBrine::heatCapacity( BrineAdapter<FluidState>(fluidState), + VariableSalinityBrine::liquidPhaseIdx ); + else if (phaseIdx == gasPhaseIdx) return CO2::liquidHeatCapacity(fluidState.temperature(phaseIdx), fluidState.pressure(phaseIdx)); + + DUNE_THROW(Dune::InvalidStateException, "Invalid phase index."); } private: @@ -639,35 +697,69 @@ private: /* rho_{b,CO2} = rho_w + contribution(salt) + contribution(CO2) */ /* */ /***********************************************************************/ - static Scalar liquidDensity_(Scalar T, - Scalar pl, - Scalar xlH2O, - Scalar xlCO2) + // computes the density of the liquid phase + template<class FluidState> + static Scalar liquidDensity_(const FluidState& fluidState) { + const auto T = fluidState.temperature(liquidPhaseIdx); + const auto p = fluidState.pressure(liquidPhaseIdx); + Valgrind::CheckDefined(T); - Valgrind::CheckDefined(pl); - Valgrind::CheckDefined(xlH2O); - Valgrind::CheckDefined(xlCO2); - - if(T < 273.15) { - DUNE_THROW(NumericalProblem, - "Liquid density for Brine and CO2 is only " - "defined above 273.15K (is" << T << ")"); + Valgrind::CheckDefined(p); + + if (T < 273.15) + DUNE_THROW(NumericalProblem, "Liquid density for Brine and CO2 is only " + "defined above 273.15K (T = " << T << ")"); + + if (p >= 2.5e8) + DUNE_THROW(NumericalProblem, "Liquid density for Brine and CO2 is only " + "defined below 250MPa (p = " << p << ")"); + + // density of pure water + Scalar rho_pure = H2O::liquidDensity(T, p); + + // density of water with dissolved CO2 (neglect NaCl) + Scalar rho_lCO2; + if (useConstantSalinity) + { + // use normalized composition for to calculate the density + // (the relations don't seem to take non-normalized compositions too well...) + // TODO Do we really need this normalization??? + using std::min; + using std::max; + Scalar xlBrine = min(1.0, max(0.0, fluidState.moleFraction(liquidPhaseIdx, BrineOrH2OIdx))); + Scalar xlCO2 = min(1.0, max(0.0, fluidState.moleFraction(liquidPhaseIdx, CO2Idx))); + Scalar sumx = xlBrine + xlCO2; + xlBrine /= sumx; + xlCO2 /= sumx; + + rho_lCO2 = liquidDensityWaterCO2_(T, p, xlBrine, xlCO2); } - if(pl >= 2.5e8) { - DUNE_THROW(NumericalProblem, - "Liquid density for Brine and CO2 is only " - "defined below 250MPa (is" << pl << ")"); + else + { + // rescale mole fractions + auto xlH2O = fluidState.moleFraction(liquidPhaseIdx, BrineOrH2OIdx); + auto xlCO2 = fluidState.moleFraction(liquidPhaseIdx, NaClIdx); + const auto sumMoleFrac = xlH2O + xlCO2; + xlH2O = xlH2O/sumMoleFrac; + xlCO2 = xlCO2/sumMoleFrac; + rho_lCO2 = liquidDensityWaterCO2_(T, p, xlH2O, xlCO2); } - Scalar rho_brine = Brine::liquidDensity(T, pl); - Scalar rho_pure = H2O::liquidDensity(T, pl); - Scalar rho_lCO2 = liquidDensityWaterCO2_(T, pl, xlH2O, xlCO2); + // density of brine (water with nacl) + Scalar rho_brine = useConstantSalinity ? ConstantSalinityBrine::liquidDensity(T, p) + : VariableSalinityBrine::density( BrineAdapter<FluidState>(fluidState), + VariableSalinityBrine::liquidPhaseIdx ); + + // contribution of co2 to the density Scalar contribCO2 = rho_lCO2 - rho_pure; + // Total brine density with dissolved CO2 + // rho_{b, CO2} = rho_pure + contribution(salt) + contribution(CO2) return rho_brine + contribCO2; } + // computes the density of the liquid water/CO2 mixture static Scalar liquidDensityWaterCO2_(Scalar temperature, Scalar pl, Scalar xlH2O, @@ -678,40 +770,16 @@ private: const Scalar tempC = temperature - 273.15; /* tempC : temperature in °C */ const Scalar rho_pure = H2O::liquidDensity(temperature, pl); - xlH2O = 1.0 - xlCO2; // xlH2O is available, but in case of a pure gas phase - // the value of M_T for the virtual liquid phase can become very large - const Scalar M_T = M_H2O * xlH2O + M_CO2 * xlCO2; - const Scalar V_phi = - (37.51 + - tempC*(-9.585e-2 + - tempC*(8.74e-4 - - tempC*5.044e-7))) / 1.0e6; - return 1/ (xlCO2 * V_phi/M_T + M_H2O * xlH2O / (rho_pure * M_T)); - } - - static Scalar liquidEnthalpyBrineCO2_(Scalar T, - Scalar p, - Scalar S, - Scalar X_CO2_w) - { - /* X_CO2_w : mass fraction of CO2 in brine */ - - const Scalar h_ls1 = BrineRawComponent::liquidEnthalpy(T, p, S)/1E3; /* J/kg */ - - /* heat of dissolution for CO2 according to Fig. 6 in Duan and Sun 2003. (kJ/kg) - In the relevant temperature ranges CO2 dissolution is - exothermal */ - const Scalar delta_hCO2 = (-57.4375 + T * 0.1325) * 1000/44; - const Scalar hw = H2O::liquidEnthalpy(T, p) /1E3; /* kJ/kg */ - - /* enthalpy contribution of CO2 (kJ/kg) */ - const Scalar hg = CO2::liquidEnthalpy(T, p)/1E3 + delta_hCO2; - - /* Enthalpy of brine with dissolved CO2 */ - const Scalar h_ls = (h_ls1 - X_CO2_w*hw + hg*X_CO2_w)*1E3; /*J/kg*/ - - return h_ls; + // xlH2O is available, but in case of a pure gas phase + // the value of M_T for the virtual liquid phase can become very large + xlH2O = 1.0 - xlCO2; + const Scalar M_T = M_H2O * xlH2O + M_CO2 * xlCO2; + const Scalar V_phi = (37.51 + + tempC*(-9.585e-2 + + tempC*(8.74e-4 - + tempC*5.044e-7))) / 1.0e6; + return 1/(xlCO2 * V_phi/M_T + M_H2O * xlH2O / (rho_pure * M_T)); } };