LCOV - code coverage report
Current view: top level - src/userobjects - PorousFlowWaterNCG.C (source / functions) Hit Total Coverage
Test: idaholab/moose porous_flow: #32971 (54bef8) with base c6cf66 Lines: 182 186 97.8 %
Date: 2026-05-29 20:38: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         252 : PorousFlowWaterNCG::validParams()
      19             : {
      20         252 :   InputParameters params = PorousFlowFluidStateMultiComponentBase::validParams();
      21         504 :   params.addRequiredParam<UserObjectName>("water_fp", "The name of the user object for water");
      22         504 :   params.addRequiredParam<UserObjectName>(
      23             :       "gas_fp", "The name of the user object for the non-condensable gas");
      24         252 :   params.addClassDescription("Fluid state class for water and non-condensable gas");
      25         252 :   return params;
      26           0 : }
      27             : 
      28         126 : PorousFlowWaterNCG::PorousFlowWaterNCG(const InputParameters & parameters)
      29             :   : PorousFlowFluidStateMultiComponentBase(parameters),
      30         126 :     _water_fp(getUserObject<SinglePhaseFluidProperties>("water_fp")),
      31         126 :     _water97_fp(getUserObject<Water97FluidProperties>("water_fp")),
      32         126 :     _ncg_fp(getUserObject<SinglePhaseFluidProperties>("gas_fp")),
      33         126 :     _Mh2o(_water_fp.molarMass()),
      34         126 :     _Mncg(_ncg_fp.molarMass()),
      35         126 :     _water_triple_temperature(_water_fp.triplePointTemperature()),
      36         126 :     _water_critical_temperature(_water_fp.criticalTemperature()),
      37         252 :     _ncg_henry(_ncg_fp.henryCoefficients())
      38             : {
      39             :   // Check that the correct FluidProperties UserObjects have been provided
      40         252 :   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         126 :   _num_phases = 2;
      45         126 :   _num_components = 2;
      46         126 :   _gas_phase_number = 1 - _aqueous_phase_number;
      47         126 :   _gas_fluid_component = 1 - _aqueous_fluid_component;
      48             : 
      49             :   // Check that _aqueous_phase_number is <= total number of phases
      50         126 :   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         126 :   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         126 :   _empty_fsp = FluidStateProperties(_num_components);
      62         126 : }
      63             : 
      64             : std::string
      65           1 : PorousFlowWaterNCG::fluidStateName() const
      66             : {
      67           1 :   return "water-ncg";
      68             : }
      69             : 
      70             : void
      71      613382 : 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      613382 :   ADReal p = pressure;
      80      613382 :   Moose::derivInsert(p.derivatives(), _pidx, 1.0);
      81      613382 :   ADReal T = temperature;
      82      613382 :   Moose::derivInsert(T.derivatives(), _Tidx, 1.0);
      83      613382 :   ADReal Zncg = Z;
      84      613382 :   Moose::derivInsert(Zncg.derivatives(), _Zidx, 1.0);
      85             :   // X is not used, but needed for consistency with PorousFlowFluidState interface
      86      613382 :   ADReal X = Xnacl;
      87      613382 :   Moose::derivInsert(X.derivatives(), _Xidx, 1.0);
      88             : 
      89      613382 :   thermophysicalProperties(p, T, X, Zncg, qp, fsp);
      90      613375 : }
      91             : 
      92             : void
      93      613382 : 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      613382 :   FluidStateProperties & liquid = fsp[_aqueous_phase_number];
     101      613382 :   FluidStateProperties & gas = fsp[_gas_phase_number];
     102             : 
     103             :   // Check whether the input temperature is within the region of validity
     104      613382 :   checkVariables(temperature.value());
     105             : 
     106             :   // Clear all of the FluidStateProperties data
     107      613375 :   clearFluidStateProperties(fsp);
     108             : 
     109             :   FluidStatePhaseEnum phase_state;
     110      613375 :   massFractions(pressure, temperature, Z, phase_state, fsp);
     111             : 
     112      613375 :   switch (phase_state)
     113             :   {
     114        6544 :     case FluidStatePhaseEnum::GAS:
     115             :     {
     116             :       // Set the gas saturations
     117        6544 :       gas.saturation = 1.0;
     118             : 
     119             :       // Calculate gas properties
     120        6544 :       gasProperties(pressure, temperature, fsp);
     121             : 
     122             :       break;
     123             :     }
     124             : 
     125      569643 :     case FluidStatePhaseEnum::LIQUID:
     126             :     {
     127             :       // Calculate the liquid properties
     128      569643 :       const ADReal liquid_pressure = pressure - _pc.capillaryPressure(1.0, qp);
     129      569643 :       liquidProperties(liquid_pressure, temperature, fsp);
     130             : 
     131             :       break;
     132             :     }
     133             : 
     134       37188 :     case FluidStatePhaseEnum::TWOPHASE:
     135             :     {
     136             :       // Calculate the gas and liquid properties in the two phase region
     137       37188 :       twoPhaseProperties(pressure, temperature, Z, qp, fsp);
     138             : 
     139             :       break;
     140             :     }
     141             :   }
     142             : 
     143             :   // Liquid saturations can now be set
     144     1226750 :   liquid.saturation = 1.0 - gas.saturation;
     145             : 
     146             :   // Save pressures to FluidStateProperties object
     147      613375 :   gas.pressure = pressure;
     148     1226750 :   liquid.pressure = pressure - _pc.capillaryPressure(liquid.saturation, qp);
     149      613375 : }
     150             : 
     151             : void
     152      613411 : 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      613411 :   FluidStateProperties & liquid = fsp[_aqueous_phase_number];
     159      613411 :   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      613411 :   equilibriumMassFractions(pressure, temperature, Xncg, Yh2o);
     164             : 
     165      613411 :   ADReal Yncg = 1.0 - Yh2o;
     166             : 
     167             :   // Determine which phases are present based on the value of Z
     168      613411 :   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      613411 :   ADReal Xh2o = 0.0;
     174             : 
     175      613411 :   switch (phase_state)
     176             :   {
     177      569647 :     case FluidStatePhaseEnum::LIQUID:
     178             :     {
     179      569647 :       Xncg = Z;
     180      569647 :       Yncg = 0.0;
     181     1139294 :       Xh2o = 1.0 - Z;
     182      569647 :       Yh2o = 0.0;
     183      569647 :       break;
     184             :     }
     185             : 
     186        6549 :     case FluidStatePhaseEnum::GAS:
     187             :     {
     188        6549 :       Xncg = 0.0;
     189        6549 :       Yncg = Z;
     190       13098 :       Yh2o = 1.0 - Z;
     191        6549 :       break;
     192             :     }
     193             : 
     194       37215 :     case FluidStatePhaseEnum::TWOPHASE:
     195             :     {
     196             :       // Keep equilibrium mass fractions
     197       74430 :       Xh2o = 1.0 - Xncg;
     198       37215 :       break;
     199             :     }
     200             :   }
     201             : 
     202             :   // Save the mass fractions in the FluidStateMassFractions object
     203      613411 :   liquid.mass_fraction[_aqueous_fluid_component] = Xh2o;
     204      613411 :   liquid.mass_fraction[_gas_fluid_component] = Xncg;
     205      613411 :   gas.mass_fraction[_aqueous_fluid_component] = Yh2o;
     206      613411 :   gas.mass_fraction[_gas_fluid_component] = Yncg;
     207      613411 : }
     208             : 
     209             : void
     210       43749 : PorousFlowWaterNCG::gasProperties(const ADReal & pressure,
     211             :                                   const ADReal & temperature,
     212             :                                   std::vector<FluidStateProperties> & fsp) const
     213             : {
     214       43749 :   FluidStateProperties & liquid = fsp[_aqueous_phase_number];
     215       43749 :   FluidStateProperties & gas = fsp[_gas_phase_number];
     216             : 
     217       43749 :   const ADReal psat = _water_fp.vaporPressure(temperature);
     218             : 
     219       43749 :   const ADReal Yncg = gas.mass_fraction[_gas_fluid_component];
     220       43749 :   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       87498 :   _ncg_fp.rho_mu_from_p_T(Yncg * pressure, temperature, ncg_density, ncg_viscosity);
     226       87498 :   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       87498 :   _water_fp.rho_mu_from_p_T((1.0 - Xncg) * psat, temperature, vapor_density, vapor_viscosity);
     233       87498 :   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       43749 :   gas.density = ncg_density + vapor_density;
     237             : 
     238             :   // Viscosity of the gas phase is a weighted sum of the individual viscosities
     239       87498 :   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       87498 :   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       43749 :   gas.internal_energy = gas.enthalpy - pressure / gas.density;
     247       43749 : }
     248             : 
     249             : void
     250      606849 : PorousFlowWaterNCG::liquidProperties(const ADReal & pressure,
     251             :                                      const ADReal & temperature,
     252             :                                      std::vector<FluidStateProperties> & fsp) const
     253             : {
     254      606849 :   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      606849 :   _water_fp.rho_mu_from_p_T(pressure, temperature, liquid_density, liquid_viscosity);
     262             : 
     263      606849 :   liquid.density = liquid_density;
     264      606849 :   liquid.viscosity = liquid_viscosity;
     265             : 
     266             :   // Enthalpy does include a contribution due to the enthalpy of dissolution
     267      606849 :   const ADReal hdis = enthalpyOfDissolution(temperature);
     268             : 
     269      606849 :   const ADReal water_enthalpy = _water_fp.h_from_p_T(pressure, temperature);
     270      606849 :   const ADReal ncg_enthalpy = _ncg_fp.h_from_p_T(pressure, temperature);
     271             : 
     272      606849 :   const ADReal Xncg = liquid.mass_fraction[_gas_fluid_component];
     273     1213698 :   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      606849 :   liquid.internal_energy = liquid.enthalpy - pressure / liquid.density;
     278      606849 : }
     279             : 
     280             : ADReal
     281       37225 : PorousFlowWaterNCG::liquidDensity(const ADReal & pressure, const ADReal & temperature) const
     282             : {
     283       37225 :   return _water_fp.rho_from_p_T(pressure, temperature);
     284             : }
     285             : 
     286             : ADReal
     287       37225 : PorousFlowWaterNCG::gasDensity(const ADReal & pressure,
     288             :                                const ADReal & temperature,
     289             :                                std::vector<FluidStateProperties> & fsp) const
     290             : {
     291       37225 :   auto & liquid = fsp[_aqueous_phase_number];
     292       37225 :   auto & gas = fsp[_gas_phase_number];
     293             : 
     294       37225 :   ADReal psat = _water_fp.vaporPressure(temperature);
     295             : 
     296       37225 :   const ADReal Yncg = gas.mass_fraction[_gas_fluid_component];
     297       37225 :   const ADReal Xncg = liquid.mass_fraction[_gas_fluid_component];
     298             : 
     299       74450 :   ADReal ncg_density = _ncg_fp.rho_from_p_T(Yncg * pressure, temperature);
     300       74450 :   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       37225 :   return ncg_density + vapor_density;
     304             : }
     305             : 
     306             : ADReal
     307       37199 : PorousFlowWaterNCG::saturation(const ADReal & pressure,
     308             :                                const ADReal & temperature,
     309             :                                const ADReal & Z,
     310             :                                std::vector<FluidStateProperties> & fsp) const
     311             : {
     312       37199 :   auto & gas = fsp[_gas_phase_number];
     313       37199 :   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       37199 :   const ADReal gas_density = gasDensity(pressure, temperature, fsp);
     323       37199 :   const ADReal liquid_density = liquidDensity(pressure, temperature);
     324             : 
     325             :   // Set mass equilibrium constants used in the calculation of vapor mass fraction
     326       37199 :   const ADReal Xncg = liquid.mass_fraction[_gas_fluid_component];
     327       37199 :   const ADReal Yncg = gas.mass_fraction[_gas_fluid_component];
     328             : 
     329             :   const ADReal K0 = Yncg / Xncg;
     330       74398 :   const ADReal K1 = (1.0 - Yncg) / (1.0 - Xncg);
     331       37199 :   const ADReal vapor_mass_fraction = vaporMassFraction(Z, K0, K1);
     332             : 
     333             :   // The gas saturation in the two phase case
     334       37199 :   const ADReal saturation = vapor_mass_fraction * liquid_density /
     335       37199 :                             (gas_density + vapor_mass_fraction * (liquid_density - gas_density));
     336             : 
     337       37199 :   return saturation;
     338             : }
     339             : 
     340             : void
     341       37189 : 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       37189 :   auto & gas = fsp[_gas_phase_number];
     348             : 
     349             :   // Calculate all of the gas phase properties, as these don't depend on saturation
     350       37189 :   gasProperties(pressure, temperature, fsp);
     351             : 
     352             :   // The gas saturation in the two phase case
     353       37189 :   gas.saturation = saturation(pressure, temperature, Z, fsp);
     354             : 
     355             :   // The liquid pressure and properties can now be calculated
     356       74378 :   const ADReal liquid_pressure = pressure - _pc.capillaryPressure(1.0 - gas.saturation, qp);
     357       37189 :   liquidProperties(liquid_pressure, temperature, fsp);
     358       37189 : }
     359             : 
     360             : void
     361      613442 : 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      613442 :   const ADReal Kh = _water97_fp.henryConstant(temperature, _ncg_henry);
     369      613442 :   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     1226884 :   const ADReal xncg = (1.0 - Kh2o) / (Kncg - Kh2o);
     377             :   const ADReal yncg = Kncg * xncg;
     378             : 
     379             :   // Convert mole fractions to mass fractions
     380      613442 :   Xncg = moleFractionToMassFraction(xncg);
     381     1226884 :   Yh2o = 1.0 - moleFractionToMassFraction(yncg);
     382      613442 : }
     383             : 
     384             : ADReal
     385     1226884 : PorousFlowWaterNCG::moleFractionToMassFraction(const ADReal & xmol) const
     386             : {
     387     3680652 :   return xmol * _Mncg / (xmol * _Mncg + (1.0 - xmol) * _Mh2o);
     388             : }
     389             : 
     390             : void
     391      613407 : 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      613407 :   if (temperature < _water_triple_temperature || temperature > _water_critical_temperature)
     396          21 :     mooseException(name() + ": temperature " + Moose::stringify(temperature) +
     397             :                    " is outside range 273.16 K <= T <= 647.096 K");
     398      613400 : }
     399             : 
     400             : ADReal
     401      606853 : PorousFlowWaterNCG::enthalpyOfDissolution(const ADReal & temperature) const
     402             : {
     403             :   // Henry's constant
     404      606853 :   const ADReal Kh = _water97_fp.henryConstant(temperature, _ncg_henry);
     405             : 
     406     1213706 :   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      606853 :   const Real dT = temperature.value() * 1.0e-8;
     411             :   const ADReal t2 = temperature + dT;
     412      606853 :   const ADReal Kh2 = _water97_fp.henryConstant(t2, _ncg_henry);
     413             : 
     414             :   const Real dhdis_dT =
     415     1820559 :       (-_R * t2 * t2 * Kh2.derivatives()[_Tidx] / Kh2 / _Mncg - hdis).value() / dT;
     416             : 
     417     1213706 :   hdis.derivatives() = temperature.derivatives() * dhdis_dT;
     418             : 
     419      606853 :   return hdis;
     420             : }
     421             : 
     422             : Real
     423          25 : 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          25 :   checkVariables(temperature);
     428             : 
     429             :   // As we do not require derivatives, we can simply ignore their initialisation
     430          25 :   const ADReal p = pressure;
     431          25 :   const ADReal T = temperature;
     432             : 
     433             :   // FluidStateProperties data structure
     434          25 :   std::vector<FluidStateProperties> fsp(_num_phases, FluidStateProperties(_num_components));
     435          25 :   auto & liquid = fsp[_aqueous_phase_number];
     436          25 :   auto & gas = fsp[_gas_phase_number];
     437             : 
     438             :   // Calculate equilibrium mass fractions in the two-phase state
     439             :   ADReal Xncg, Yh2o;
     440          25 :   equilibriumMassFractions(p, T, Xncg, Yh2o);
     441             : 
     442             :   // Save the mass fractions in the FluidStateMassFractions object to calculate gas density
     443          25 :   const ADReal Yncg = 1.0 - Yh2o;
     444          50 :   liquid.mass_fraction[_aqueous_fluid_component] = 1.0 - Xncg;
     445          25 :   liquid.mass_fraction[_gas_fluid_component] = Xncg;
     446          25 :   gas.mass_fraction[_aqueous_fluid_component] = Yh2o;
     447          25 :   gas.mass_fraction[_gas_fluid_component] = Yncg;
     448             : 
     449             :   // Gas density
     450          25 :   const Real gas_density = gasDensity(p, T, fsp).value();
     451             : 
     452             :   // Liquid density
     453          25 :   const ADReal liquid_pressure = p - _pc.capillaryPressure(1.0 - saturation, qp);
     454          25 :   const Real liquid_density = liquidDensity(liquid_pressure, T).value();
     455             : 
     456             :   // The total mass fraction of ncg (Z) can now be calculated
     457          25 :   const Real Z = (saturation * gas_density * Yncg.value() +
     458          25 :                   (1.0 - saturation) * liquid_density * Xncg.value()) /
     459          25 :                  (saturation * gas_density + (1.0 - saturation) * liquid_density);
     460             : 
     461          25 :   return Z;
     462          25 : }

Generated by: LCOV version 1.14