LCOV - code coverage report
Current view: top level - src/userobjects - PorousFlowWaterNCG.C (source / functions) Hit Total Coverage
Test: idaholab/moose porous_flow: #31405 (292dce) with base fef103 Lines: 182 186 97.8 %
Date: 2025-09-04 07:55:56 Functions: 17 17 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : //* This file is part of the MOOSE framework
       2             : //* https://mooseframework.inl.gov
       3             : //*
       4             : //* All rights reserved, see COPYRIGHT for full restrictions
       5             : //* https://github.com/idaholab/moose/blob/master/COPYRIGHT
       6             : //*
       7             : //* Licensed under LGPL 2.1, please see LICENSE for details
       8             : //* https://www.gnu.org/licenses/lgpl-2.1.html
       9             : 
      10             : #include "PorousFlowWaterNCG.h"
      11             : #include "SinglePhaseFluidProperties.h"
      12             : #include "Water97FluidProperties.h"
      13             : #include "Conversion.h"
      14             : 
      15             : registerMooseObject("PorousFlowApp", PorousFlowWaterNCG);
      16             : 
      17             : InputParameters
      18         468 : PorousFlowWaterNCG::validParams()
      19             : {
      20         468 :   InputParameters params = PorousFlowFluidStateMultiComponentBase::validParams();
      21         936 :   params.addRequiredParam<UserObjectName>("water_fp", "The name of the user object for water");
      22         936 :   params.addRequiredParam<UserObjectName>(
      23             :       "gas_fp", "The name of the user object for the non-condensable gas");
      24         468 :   params.addClassDescription("Fluid state class for water and non-condensable gas");
      25         468 :   return params;
      26           0 : }
      27             : 
      28         234 : PorousFlowWaterNCG::PorousFlowWaterNCG(const InputParameters & parameters)
      29             :   : PorousFlowFluidStateMultiComponentBase(parameters),
      30         234 :     _water_fp(getUserObject<SinglePhaseFluidProperties>("water_fp")),
      31         234 :     _water97_fp(getUserObject<Water97FluidProperties>("water_fp")),
      32         234 :     _ncg_fp(getUserObject<SinglePhaseFluidProperties>("gas_fp")),
      33         234 :     _Mh2o(_water_fp.molarMass()),
      34         234 :     _Mncg(_ncg_fp.molarMass()),
      35         234 :     _water_triple_temperature(_water_fp.triplePointTemperature()),
      36         234 :     _water_critical_temperature(_water_fp.criticalTemperature()),
      37         468 :     _ncg_henry(_ncg_fp.henryCoefficients())
      38             : {
      39             :   // Check that the correct FluidProperties UserObjects have been provided
      40         468 :   if (_water_fp.fluidName() != "water")
      41           0 :     paramError("water_fp", "A valid water FluidProperties UserObject must be provided in water_fp");
      42             : 
      43             :   // Set the number of phases and components, and their indexes
      44         234 :   _num_phases = 2;
      45         234 :   _num_components = 2;
      46         234 :   _gas_phase_number = 1 - _aqueous_phase_number;
      47         234 :   _gas_fluid_component = 1 - _aqueous_fluid_component;
      48             : 
      49             :   // Check that _aqueous_phase_number is <= total number of phases
      50         234 :   if (_aqueous_phase_number >= _num_phases)
      51           0 :     paramError("liquid_phase_number",
      52             :                "This value is larger than the possible number of phases ",
      53             :                _num_phases);
      54             : 
      55             :   // Check that _aqueous_fluid_component is <= total number of fluid components
      56         234 :   if (_aqueous_fluid_component >= _num_components)
      57           0 :     paramError("liquid_fluid_component",
      58             :                "This value is larger than the possible number of fluid components",
      59             :                _num_components);
      60             : 
      61         234 :   _empty_fsp = FluidStateProperties(_num_components);
      62         234 : }
      63             : 
      64             : std::string
      65           1 : PorousFlowWaterNCG::fluidStateName() const
      66             : {
      67           1 :   return "water-ncg";
      68             : }
      69             : 
      70             : void
      71      912456 : PorousFlowWaterNCG::thermophysicalProperties(Real pressure,
      72             :                                              Real temperature,
      73             :                                              Real Xnacl,
      74             :                                              Real Z,
      75             :                                              unsigned int qp,
      76             :                                              std::vector<FluidStateProperties> & fsp) const
      77             : {
      78             :   // Make AD versions of primary variables then call AD thermophysicalProperties()
      79      912456 :   ADReal p = pressure;
      80      912456 :   Moose::derivInsert(p.derivatives(), _pidx, 1.0);
      81      912456 :   ADReal T = temperature;
      82      912456 :   Moose::derivInsert(T.derivatives(), _Tidx, 1.0);
      83      912456 :   ADReal Zncg = Z;
      84      912456 :   Moose::derivInsert(Zncg.derivatives(), _Zidx, 1.0);
      85             :   // X is not used, but needed for consistency with PorousFlowFluidState interface
      86      912456 :   ADReal X = Xnacl;
      87      912456 :   Moose::derivInsert(X.derivatives(), _Xidx, 1.0);
      88             : 
      89      912456 :   thermophysicalProperties(p, T, X, Zncg, qp, fsp);
      90      912445 : }
      91             : 
      92             : void
      93      912456 : PorousFlowWaterNCG::thermophysicalProperties(const ADReal & pressure,
      94             :                                              const ADReal & temperature,
      95             :                                              const ADReal & /* Xnacl */,
      96             :                                              const ADReal & Z,
      97             :                                              unsigned int qp,
      98             :                                              std::vector<FluidStateProperties> & fsp) const
      99             : {
     100      912456 :   FluidStateProperties & liquid = fsp[_aqueous_phase_number];
     101      912456 :   FluidStateProperties & gas = fsp[_gas_phase_number];
     102             : 
     103             :   // Check whether the input temperature is within the region of validity
     104      912456 :   checkVariables(temperature.value());
     105             : 
     106             :   // Clear all of the FluidStateProperties data
     107      912445 :   clearFluidStateProperties(fsp);
     108             : 
     109             :   FluidStatePhaseEnum phase_state;
     110      912445 :   massFractions(pressure, temperature, Z, phase_state, fsp);
     111             : 
     112      912445 :   switch (phase_state)
     113             :   {
     114        8248 :     case FluidStatePhaseEnum::GAS:
     115             :     {
     116             :       // Set the gas saturations
     117        8248 :       gas.saturation = 1.0;
     118             : 
     119             :       // Calculate gas properties
     120        8248 :       gasProperties(pressure, temperature, fsp);
     121             : 
     122             :       break;
     123             :     }
     124             : 
     125      852234 :     case FluidStatePhaseEnum::LIQUID:
     126             :     {
     127             :       // Calculate the liquid properties
     128      852234 :       const ADReal liquid_pressure = pressure - _pc.capillaryPressure(1.0, qp);
     129      852234 :       liquidProperties(liquid_pressure, temperature, fsp);
     130             : 
     131             :       break;
     132             :     }
     133             : 
     134       51963 :     case FluidStatePhaseEnum::TWOPHASE:
     135             :     {
     136             :       // Calculate the gas and liquid properties in the two phase region
     137       51963 :       twoPhaseProperties(pressure, temperature, Z, qp, fsp);
     138             : 
     139             :       break;
     140             :     }
     141             :   }
     142             : 
     143             :   // Liquid saturations can now be set
     144     1824890 :   liquid.saturation = 1.0 - gas.saturation;
     145             : 
     146             :   // Save pressures to FluidStateProperties object
     147      912445 :   gas.pressure = pressure;
     148     1824890 :   liquid.pressure = pressure - _pc.capillaryPressure(liquid.saturation, qp);
     149      912445 : }
     150             : 
     151             : void
     152      912481 : PorousFlowWaterNCG::massFractions(const ADReal & pressure,
     153             :                                   const ADReal & temperature,
     154             :                                   const ADReal & Z,
     155             :                                   FluidStatePhaseEnum & phase_state,
     156             :                                   std::vector<FluidStateProperties> & fsp) const
     157             : {
     158      912481 :   FluidStateProperties & liquid = fsp[_aqueous_phase_number];
     159      912481 :   FluidStateProperties & gas = fsp[_gas_phase_number];
     160             : 
     161             :   // Equilibrium mass fraction of NCG in liquid and H2O in gas phases
     162             :   ADReal Xncg, Yh2o;
     163      912481 :   equilibriumMassFractions(pressure, temperature, Xncg, Yh2o);
     164             : 
     165      912481 :   ADReal Yncg = 1.0 - Yh2o;
     166             : 
     167             :   // Determine which phases are present based on the value of Z
     168      912481 :   phaseState(Z.value(), Xncg.value(), Yncg.value(), phase_state);
     169             : 
     170             :   // The equilibrium mass fractions calculated above are only correct in the two phase
     171             :   // state. If only liquid or gas phases are present, the mass fractions are given by
     172             :   // the total mass fraction Z.
     173      912481 :   ADReal Xh2o = 0.0;
     174             : 
     175      912481 :   switch (phase_state)
     176             :   {
     177      852238 :     case FluidStatePhaseEnum::LIQUID:
     178             :     {
     179      852238 :       Xncg = Z;
     180      852238 :       Yncg = 0.0;
     181     1704476 :       Xh2o = 1.0 - Z;
     182      852238 :       Yh2o = 0.0;
     183      852238 :       break;
     184             :     }
     185             : 
     186        8253 :     case FluidStatePhaseEnum::GAS:
     187             :     {
     188        8253 :       Xncg = 0.0;
     189        8253 :       Yncg = Z;
     190       16506 :       Yh2o = 1.0 - Z;
     191        8253 :       break;
     192             :     }
     193             : 
     194       51990 :     case FluidStatePhaseEnum::TWOPHASE:
     195             :     {
     196             :       // Keep equilibrium mass fractions
     197      103980 :       Xh2o = 1.0 - Xncg;
     198       51990 :       break;
     199             :     }
     200             :   }
     201             : 
     202             :   // Save the mass fractions in the FluidStateMassFractions object
     203      912481 :   liquid.mass_fraction[_aqueous_fluid_component] = Xh2o;
     204      912481 :   liquid.mass_fraction[_gas_fluid_component] = Xncg;
     205      912481 :   gas.mass_fraction[_aqueous_fluid_component] = Yh2o;
     206      912481 :   gas.mass_fraction[_gas_fluid_component] = Yncg;
     207      912481 : }
     208             : 
     209             : void
     210       60228 : PorousFlowWaterNCG::gasProperties(const ADReal & pressure,
     211             :                                   const ADReal & temperature,
     212             :                                   std::vector<FluidStateProperties> & fsp) const
     213             : {
     214       60228 :   FluidStateProperties & liquid = fsp[_aqueous_phase_number];
     215       60228 :   FluidStateProperties & gas = fsp[_gas_phase_number];
     216             : 
     217       60228 :   const ADReal psat = _water_fp.vaporPressure(temperature);
     218             : 
     219       60228 :   const ADReal Yncg = gas.mass_fraction[_gas_fluid_component];
     220       60228 :   const ADReal Xncg = liquid.mass_fraction[_gas_fluid_component];
     221             : 
     222             :   // NCG density, viscosity and enthalpy calculated using partial pressure
     223             :   // Yncg * gas_poreressure (Dalton's law)
     224             :   ADReal ncg_density, ncg_viscosity;
     225      120456 :   _ncg_fp.rho_mu_from_p_T(Yncg * pressure, temperature, ncg_density, ncg_viscosity);
     226      120456 :   ADReal ncg_enthalpy = _ncg_fp.h_from_p_T(Yncg * pressure, temperature);
     227             : 
     228             :   // Vapor density, viscosity and enthalpy calculated using partial pressure
     229             :   // X1 * psat (Raoult's law)
     230             :   ADReal vapor_density, vapor_viscosity;
     231             : 
     232      120456 :   _water_fp.rho_mu_from_p_T((1.0 - Xncg) * psat, temperature, vapor_density, vapor_viscosity);
     233      120456 :   ADReal vapor_enthalpy = _water_fp.h_from_p_T((1.0 - Xncg) * psat, temperature);
     234             : 
     235             :   // Density is just the sum of individual component densities
     236       60228 :   gas.density = ncg_density + vapor_density;
     237             : 
     238             :   // Viscosity of the gas phase is a weighted sum of the individual viscosities
     239      120456 :   gas.viscosity = Yncg * ncg_viscosity + (1.0 - Yncg) * vapor_viscosity;
     240             : 
     241             :   // Enthalpy of the gas phase is a weighted sum of the individual enthalpies
     242      120456 :   gas.enthalpy = Yncg * ncg_enthalpy + (1.0 - Yncg) * vapor_enthalpy;
     243             : 
     244             :   //  Internal energy of the gas phase (e = h - pv)
     245             :   mooseAssert(gas.density.value() > 0.0, "Gas density must be greater than zero");
     246       60228 :   gas.internal_energy = gas.enthalpy - pressure / gas.density;
     247       60228 : }
     248             : 
     249             : void
     250      904215 : PorousFlowWaterNCG::liquidProperties(const ADReal & pressure,
     251             :                                      const ADReal & temperature,
     252             :                                      std::vector<FluidStateProperties> & fsp) const
     253             : {
     254      904215 :   FluidStateProperties & liquid = fsp[_aqueous_phase_number];
     255             : 
     256             :   // Calculate liquid density and viscosity if in the two phase or single phase
     257             :   // liquid region, assuming they are not affected by the presence of dissolved
     258             :   // NCG. Note: the (small) contribution due to derivative of capillary pressure
     259             :   // wrt pressure (using the chain rule) is not implemented.
     260             :   ADReal liquid_density, liquid_viscosity;
     261      904215 :   _water_fp.rho_mu_from_p_T(pressure, temperature, liquid_density, liquid_viscosity);
     262             : 
     263      904215 :   liquid.density = liquid_density;
     264      904215 :   liquid.viscosity = liquid_viscosity;
     265             : 
     266             :   // Enthalpy does include a contribution due to the enthalpy of dissolution
     267      904215 :   const ADReal hdis = enthalpyOfDissolution(temperature);
     268             : 
     269      904215 :   const ADReal water_enthalpy = _water_fp.h_from_p_T(pressure, temperature);
     270      904215 :   const ADReal ncg_enthalpy = _ncg_fp.h_from_p_T(pressure, temperature);
     271             : 
     272      904215 :   const ADReal Xncg = liquid.mass_fraction[_gas_fluid_component];
     273     1808430 :   liquid.enthalpy = (1.0 - Xncg) * water_enthalpy + Xncg * (ncg_enthalpy + hdis);
     274             : 
     275             :   //  Internal energy of the liquid phase (e = h - pv)
     276             :   mooseAssert(liquid.density.value() > 0.0, "Liquid density must be greater than zero");
     277      904215 :   liquid.internal_energy = liquid.enthalpy - pressure / liquid.density;
     278      904215 : }
     279             : 
     280             : ADReal
     281       52012 : PorousFlowWaterNCG::liquidDensity(const ADReal & pressure, const ADReal & temperature) const
     282             : {
     283       52012 :   return _water_fp.rho_from_p_T(pressure, temperature);
     284             : }
     285             : 
     286             : ADReal
     287       52012 : PorousFlowWaterNCG::gasDensity(const ADReal & pressure,
     288             :                                const ADReal & temperature,
     289             :                                std::vector<FluidStateProperties> & fsp) const
     290             : {
     291       52012 :   auto & liquid = fsp[_aqueous_phase_number];
     292       52012 :   auto & gas = fsp[_gas_phase_number];
     293             : 
     294       52012 :   ADReal psat = _water_fp.vaporPressure(temperature);
     295             : 
     296       52012 :   const ADReal Yncg = gas.mass_fraction[_gas_fluid_component];
     297       52012 :   const ADReal Xncg = liquid.mass_fraction[_gas_fluid_component];
     298             : 
     299      104024 :   ADReal ncg_density = _ncg_fp.rho_from_p_T(Yncg * pressure, temperature);
     300      104024 :   ADReal vapor_density = _water_fp.rho_from_p_T((1.0 - Xncg) * psat, temperature);
     301             : 
     302             :   // Density is just the sum of individual component densities
     303       52012 :   return ncg_density + vapor_density;
     304             : }
     305             : 
     306             : ADReal
     307       51974 : PorousFlowWaterNCG::saturation(const ADReal & pressure,
     308             :                                const ADReal & temperature,
     309             :                                const ADReal & Z,
     310             :                                std::vector<FluidStateProperties> & fsp) const
     311             : {
     312       51974 :   auto & gas = fsp[_gas_phase_number];
     313       51974 :   auto & liquid = fsp[_aqueous_fluid_component];
     314             : 
     315             :   // Approximate liquid density as saturation isn't known yet, by using the gas
     316             :   // pressure rather than the liquid pressure. This does result in a small error
     317             :   // in the calculated saturation, but this is below the error associated with
     318             :   // the correlations. A more accurate saturation could be found iteraviely,
     319             :   // at the cost of increased computational expense
     320             : 
     321             :   // The gas and liquid densities
     322       51974 :   const ADReal gas_density = gasDensity(pressure, temperature, fsp);
     323       51974 :   const ADReal liquid_density = liquidDensity(pressure, temperature);
     324             : 
     325             :   // Set mass equilibrium constants used in the calculation of vapor mass fraction
     326       51974 :   const ADReal Xncg = liquid.mass_fraction[_gas_fluid_component];
     327       51974 :   const ADReal Yncg = gas.mass_fraction[_gas_fluid_component];
     328             : 
     329             :   const ADReal K0 = Yncg / Xncg;
     330      103948 :   const ADReal K1 = (1.0 - Yncg) / (1.0 - Xncg);
     331       51974 :   const ADReal vapor_mass_fraction = vaporMassFraction(Z, K0, K1);
     332             : 
     333             :   // The gas saturation in the two phase case
     334       51974 :   const ADReal saturation = vapor_mass_fraction * liquid_density /
     335       51974 :                             (gas_density + vapor_mass_fraction * (liquid_density - gas_density));
     336             : 
     337       51974 :   return saturation;
     338             : }
     339             : 
     340             : void
     341       51964 : PorousFlowWaterNCG::twoPhaseProperties(const ADReal & pressure,
     342             :                                        const ADReal & temperature,
     343             :                                        const ADReal & Z,
     344             :                                        unsigned int qp,
     345             :                                        std::vector<FluidStateProperties> & fsp) const
     346             : {
     347       51964 :   auto & gas = fsp[_gas_phase_number];
     348             : 
     349             :   // Calculate all of the gas phase properties, as these don't depend on saturation
     350       51964 :   gasProperties(pressure, temperature, fsp);
     351             : 
     352             :   // The gas saturation in the two phase case
     353       51964 :   gas.saturation = saturation(pressure, temperature, Z, fsp);
     354             : 
     355             :   // The liquid pressure and properties can now be calculated
     356      103928 :   const ADReal liquid_pressure = pressure - _pc.capillaryPressure(1.0 - gas.saturation, qp);
     357       51964 :   liquidProperties(liquid_pressure, temperature, fsp);
     358       51964 : }
     359             : 
     360             : void
     361      912524 : PorousFlowWaterNCG::equilibriumMassFractions(const ADReal & pressure,
     362             :                                              const ADReal & temperature,
     363             :                                              ADReal & Xncg,
     364             :                                              ADReal & Yh2o) const
     365             : {
     366             :   // Equilibrium constants for each component (Henry's law for the NCG
     367             :   // component, and Raoult's law for water).
     368      912524 :   const ADReal Kh = _water97_fp.henryConstant(temperature, _ncg_henry);
     369      912524 :   const ADReal psat = _water_fp.vaporPressure(temperature);
     370             : 
     371             :   const ADReal Kncg = Kh / pressure;
     372             :   const ADReal Kh2o = psat / pressure;
     373             : 
     374             :   // The mole fractions for the NCG component in the two component
     375             :   // case can be expressed in terms of the equilibrium constants only
     376     1825048 :   const ADReal xncg = (1.0 - Kh2o) / (Kncg - Kh2o);
     377             :   const ADReal yncg = Kncg * xncg;
     378             : 
     379             :   // Convert mole fractions to mass fractions
     380      912524 :   Xncg = moleFractionToMassFraction(xncg);
     381     1825048 :   Yh2o = 1.0 - moleFractionToMassFraction(yncg);
     382      912524 : }
     383             : 
     384             : ADReal
     385     1825048 : PorousFlowWaterNCG::moleFractionToMassFraction(const ADReal & xmol) const
     386             : {
     387     5475144 :   return xmol * _Mncg / (xmol * _Mncg + (1.0 - xmol) * _Mh2o);
     388             : }
     389             : 
     390             : void
     391      912493 : PorousFlowWaterNCG::checkVariables(Real temperature) const
     392             : {
     393             :   // Check whether the input temperature is within the region of validity of this equation
     394             :   // of state (T_triple <= T <= T_critical)
     395      912493 :   if (temperature < _water_triple_temperature || temperature > _water_critical_temperature)
     396          33 :     mooseException(name() + ": temperature " + Moose::stringify(temperature) +
     397             :                    " is outside range 273.16 K <= T <= 647.096 K");
     398      912482 : }
     399             : 
     400             : ADReal
     401      904219 : PorousFlowWaterNCG::enthalpyOfDissolution(const ADReal & temperature) const
     402             : {
     403             :   // Henry's constant
     404      904219 :   const ADReal Kh = _water97_fp.henryConstant(temperature, _ncg_henry);
     405             : 
     406     1808438 :   ADReal hdis = -_R * temperature * temperature * Kh.derivatives()[_Tidx] / Kh / _Mncg;
     407             : 
     408             :   // Derivative of enthalpy of dissolution wrt temperature requires the second derivative of
     409             :   // Henry's constant wrt temperature. For simplicity, approximate this numerically
     410      904219 :   const Real dT = temperature.value() * 1.0e-8;
     411             :   const ADReal t2 = temperature + dT;
     412      904219 :   const ADReal Kh2 = _water97_fp.henryConstant(t2, _ncg_henry);
     413             : 
     414             :   const Real dhdis_dT =
     415     2712657 :       (-_R * t2 * t2 * Kh2.derivatives()[_Tidx] / Kh2 / _Mncg - hdis).value() / dT;
     416             : 
     417     1808438 :   hdis.derivatives() = temperature.derivatives() * dhdis_dT;
     418             : 
     419      904219 :   return hdis;
     420             : }
     421             : 
     422             : Real
     423          37 : PorousFlowWaterNCG::totalMassFraction(
     424             :     Real pressure, Real temperature, Real /* Xnacl */, Real saturation, unsigned int qp) const
     425             : {
     426             :   // Check whether the input temperature is within the region of validity
     427          37 :   checkVariables(temperature);
     428             : 
     429             :   // As we do not require derivatives, we can simply ignore their initialisation
     430          37 :   const ADReal p = pressure;
     431          37 :   const ADReal T = temperature;
     432             : 
     433             :   // FluidStateProperties data structure
     434          37 :   std::vector<FluidStateProperties> fsp(_num_phases, FluidStateProperties(_num_components));
     435          37 :   auto & liquid = fsp[_aqueous_phase_number];
     436          37 :   auto & gas = fsp[_gas_phase_number];
     437             : 
     438             :   // Calculate equilibrium mass fractions in the two-phase state
     439             :   ADReal Xncg, Yh2o;
     440          37 :   equilibriumMassFractions(p, T, Xncg, Yh2o);
     441             : 
     442             :   // Save the mass fractions in the FluidStateMassFractions object to calculate gas density
     443          37 :   const ADReal Yncg = 1.0 - Yh2o;
     444          74 :   liquid.mass_fraction[_aqueous_fluid_component] = 1.0 - Xncg;
     445          37 :   liquid.mass_fraction[_gas_fluid_component] = Xncg;
     446          37 :   gas.mass_fraction[_aqueous_fluid_component] = Yh2o;
     447          37 :   gas.mass_fraction[_gas_fluid_component] = Yncg;
     448             : 
     449             :   // Gas density
     450          37 :   const Real gas_density = gasDensity(p, T, fsp).value();
     451             : 
     452             :   // Liquid density
     453          37 :   const ADReal liquid_pressure = p - _pc.capillaryPressure(1.0 - saturation, qp);
     454          37 :   const Real liquid_density = liquidDensity(liquid_pressure, T).value();
     455             : 
     456             :   // The total mass fraction of ncg (Z) can now be calculated
     457          37 :   const Real Z = (saturation * gas_density * Yncg.value() +
     458          37 :                   (1.0 - saturation) * liquid_density * Xncg.value()) /
     459          37 :                  (saturation * gas_density + (1.0 - saturation) * liquid_density);
     460             : 
     461          37 :   return Z;
     462          37 : }

Generated by: LCOV version 1.14