LCOV - code coverage report
Current view: top level - src/userobjects - PorousFlowBrineCO2.C (source / functions) Hit Total Coverage
Test: idaholab/moose porous_flow: #31706 (f8ed4a) with base bb0a08 Lines: 461 482 95.6 %
Date: 2025-11-03 17:27:11 Functions: 37 37 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 "PorousFlowBrineCO2.h"
      11             : #include "BrineFluidProperties.h"
      12             : #include "SinglePhaseFluidProperties.h"
      13             : #include "MathUtils.h"
      14             : #include "Conversion.h"
      15             : 
      16             : registerMooseObject("PorousFlowApp", PorousFlowBrineCO2);
      17             : 
      18             : InputParameters
      19         938 : PorousFlowBrineCO2::validParams()
      20             : {
      21         938 :   InputParameters params = PorousFlowFluidStateMultiComponentBase::validParams();
      22        1876 :   params.addRequiredParam<UserObjectName>("brine_fp", "The name of the user object for brine");
      23        1876 :   params.addRequiredParam<UserObjectName>("co2_fp", "The name of the user object for CO2");
      24        1876 :   params.addParam<unsigned int>("salt_component", 2, "The component number of salt");
      25         938 :   params.addClassDescription("Fluid state class for brine and CO2");
      26         938 :   return params;
      27           0 : }
      28             : 
      29         469 : PorousFlowBrineCO2::PorousFlowBrineCO2(const InputParameters & parameters)
      30             :   : PorousFlowFluidStateMultiComponentBase(parameters),
      31         469 :     _salt_component(getParam<unsigned int>("salt_component")),
      32         469 :     _brine_fp(getUserObject<BrineFluidProperties>("brine_fp")),
      33         469 :     _co2_fp(getUserObject<SinglePhaseFluidProperties>("co2_fp")),
      34         469 :     _Mh2o(_brine_fp.molarMassH2O()),
      35         469 :     _invMh2o(1.0 / _Mh2o),
      36         469 :     _Mco2(_co2_fp.molarMass()),
      37         469 :     _Mnacl(_brine_fp.molarMassNaCl()),
      38         469 :     _Rbar(_R * 10.0),
      39         469 :     _Tlower(372.15),
      40         469 :     _Tupper(382.15),
      41         469 :     _Zmin(1.0e-4),
      42         938 :     _co2_henry(_co2_fp.henryCoefficients())
      43             : {
      44             :   // Check that the correct FluidProperties UserObjects have been provided
      45         938 :   if (_co2_fp.fluidName() != "co2")
      46           0 :     paramError("co2_fp", "A valid CO2 FluidProperties UserObject must be provided");
      47             : 
      48         938 :   if (_brine_fp.fluidName() != "brine")
      49           0 :     paramError("brine_fp", "A valid Brine FluidProperties UserObject must be provided");
      50             : 
      51             :   // Set the number of phases and components, and their indexes
      52         469 :   _num_phases = 2;
      53         469 :   _num_components = 3;
      54         469 :   _gas_phase_number = 1 - _aqueous_phase_number;
      55         469 :   _gas_fluid_component = 3 - _aqueous_fluid_component - _salt_component;
      56             : 
      57             :   // Check that _aqueous_phase_number is <= total number of phases
      58         469 :   if (_aqueous_phase_number >= _num_phases)
      59           0 :     paramError("liquid_phase_number",
      60             :                "This value is larger than the possible number of phases ",
      61             :                _num_phases);
      62             : 
      63             :   // Check that _aqueous_fluid_component is <= total number of fluid components
      64         469 :   if (_aqueous_fluid_component >= _num_components)
      65           0 :     paramError("liquid_fluid_component",
      66             :                "This value is larger than the possible number of fluid components",
      67             :                _num_components);
      68             : 
      69             :   // Check that the salt component index is not identical to the liquid fluid component
      70         469 :   if (_salt_component == _aqueous_fluid_component)
      71           0 :     paramError(
      72             :         "salt_component",
      73             :         "The value provided must be different from the value entered in liquid_fluid_component");
      74             : 
      75             :   // Check that _salt_component is <= total number of fluid components
      76         469 :   if (_salt_component >= _num_components)
      77           0 :     paramError("salt_component",
      78             :                "The value provided is larger than the possible number of fluid components",
      79             :                _num_components);
      80             : 
      81         469 :   _empty_fsp = FluidStateProperties(_num_components);
      82         469 : }
      83             : 
      84             : std::string
      85           1 : PorousFlowBrineCO2::fluidStateName() const
      86             : {
      87           1 :   return "brine-co2";
      88             : }
      89             : 
      90             : void
      91      376495 : PorousFlowBrineCO2::thermophysicalProperties(Real pressure,
      92             :                                              Real temperature,
      93             :                                              Real Xnacl,
      94             :                                              Real Z,
      95             :                                              unsigned int qp,
      96             :                                              std::vector<FluidStateProperties> & fsp) const
      97             : {
      98             :   // Make AD versions of primary variables then call AD thermophysicalProperties()
      99      376495 :   ADReal p = pressure;
     100      376495 :   Moose::derivInsert(p.derivatives(), _pidx, 1.0);
     101      376495 :   ADReal T = temperature;
     102      376495 :   Moose::derivInsert(T.derivatives(), _Tidx, 1.0);
     103      376495 :   ADReal Zco2 = Z;
     104      376495 :   Moose::derivInsert(Zco2.derivatives(), _Zidx, 1.0);
     105      376495 :   ADReal X = Xnacl;
     106      376495 :   Moose::derivInsert(X.derivatives(), _Xidx, 1.0);
     107             : 
     108      376495 :   thermophysicalProperties(p, T, X, Zco2, qp, fsp);
     109      376372 : }
     110             : 
     111             : void
     112      636106 : PorousFlowBrineCO2::thermophysicalProperties(const ADReal & pressure,
     113             :                                              const ADReal & temperature,
     114             :                                              const ADReal & Xnacl,
     115             :                                              const ADReal & Z,
     116             :                                              unsigned int qp,
     117             :                                              std::vector<FluidStateProperties> & fsp) const
     118             : {
     119      636106 :   FluidStateProperties & liquid = fsp[_aqueous_phase_number];
     120      636106 :   FluidStateProperties & gas = fsp[_gas_phase_number];
     121             : 
     122             :   // Check whether the input temperature is within the region of validity
     123      636106 :   checkVariables(pressure.value(), temperature.value());
     124             : 
     125             :   // Clear all of the FluidStateProperties data
     126      636106 :   clearFluidStateProperties(fsp);
     127             : 
     128             :   FluidStatePhaseEnum phase_state;
     129      636106 :   massFractions(pressure, temperature, Xnacl, Z, phase_state, fsp);
     130             : 
     131      636106 :   switch (phase_state)
     132             :   {
     133        8120 :     case FluidStatePhaseEnum::GAS:
     134             :     {
     135             :       // Set the gas saturations
     136        8120 :       gas.saturation = 1.0;
     137             : 
     138             :       // Calculate gas properties
     139        8120 :       gasProperties(pressure, temperature, fsp);
     140             : 
     141             :       break;
     142             :     }
     143             : 
     144      593207 :     case FluidStatePhaseEnum::LIQUID:
     145             :     {
     146             :       // Calculate the liquid properties
     147      593207 :       const ADReal liquid_pressure = pressure - _pc.capillaryPressure(1.0, qp);
     148      593207 :       liquidProperties(liquid_pressure, temperature, Xnacl, fsp);
     149             : 
     150             :       break;
     151             :     }
     152             : 
     153       34779 :     case FluidStatePhaseEnum::TWOPHASE:
     154             :     {
     155             :       // Calculate the gas and liquid properties in the two phase region
     156       34779 :       twoPhaseProperties(pressure, temperature, Xnacl, Z, qp, fsp);
     157             : 
     158             :       break;
     159             :     }
     160             :   }
     161             : 
     162             :   // Liquid saturations can now be set
     163     1271966 :   liquid.saturation = 1.0 - gas.saturation;
     164             : 
     165             :   // Save pressures to FluidStateProperties object
     166      635983 :   gas.pressure = pressure;
     167     1271966 :   liquid.pressure = pressure - _pc.capillaryPressure(liquid.saturation, qp);
     168      635983 : }
     169             : 
     170             : void
     171      636135 : PorousFlowBrineCO2::massFractions(const ADReal & pressure,
     172             :                                   const ADReal & temperature,
     173             :                                   const ADReal & Xnacl,
     174             :                                   const ADReal & Z,
     175             :                                   FluidStatePhaseEnum & phase_state,
     176             :                                   std::vector<FluidStateProperties> & fsp) const
     177             : {
     178      636135 :   FluidStateProperties & liquid = fsp[_aqueous_phase_number];
     179      636135 :   FluidStateProperties & gas = fsp[_gas_phase_number];
     180             : 
     181      636135 :   ADReal Xco2 = 0.0;
     182      636135 :   ADReal Yh2o = 0.0;
     183      636135 :   ADReal Yco2 = 0.0;
     184             : 
     185             :   // If the amount of CO2 is less than the smallest solubility, then all CO2 will
     186             :   // be dissolved, and the equilibrium mass fractions do not need to be computed
     187      636135 :   if (Z.value() < _Zmin)
     188      501912 :     phase_state = FluidStatePhaseEnum::LIQUID;
     189             : 
     190             :   else
     191             :   {
     192             :     // Equilibrium mass fraction of CO2 in liquid and H2O in gas phases
     193      134223 :     equilibriumMassFractions(pressure, temperature, Xnacl, Xco2, Yh2o);
     194             : 
     195      268446 :     Yco2 = 1.0 - Yh2o;
     196             : 
     197             :     // Determine which phases are present based on the value of z
     198      134223 :     phaseState(Z.value(), Xco2.value(), Yco2.value(), phase_state);
     199             :   }
     200             : 
     201             :   // The equilibrium mass fractions calculated above are only correct in the two phase
     202             :   // state. If only liquid or gas phases are present, the mass fractions are given by
     203             :   // the total mass fraction z
     204      636135 :   ADReal Xh2o = 0.0;
     205             : 
     206      636135 :   switch (phase_state)
     207             :   {
     208      593211 :     case FluidStatePhaseEnum::LIQUID:
     209             :     {
     210      593211 :       Xco2 = Z;
     211      593211 :       Yco2 = 0.0;
     212     1186422 :       Xh2o = 1.0 - Z;
     213      593211 :       Yh2o = 0.0;
     214      593211 :       break;
     215             :     }
     216             : 
     217        8124 :     case FluidStatePhaseEnum::GAS:
     218             :     {
     219        8124 :       Xco2 = 0.0;
     220        8124 :       Yco2 = Z;
     221       16248 :       Yh2o = 1.0 - Z;
     222        8124 :       break;
     223             :     }
     224             : 
     225       34800 :     case FluidStatePhaseEnum::TWOPHASE:
     226             :     {
     227             :       // Keep equilibrium mass fractions
     228       69600 :       Xh2o = 1.0 - Xco2;
     229       34800 :       break;
     230             :     }
     231             :   }
     232             : 
     233             :   // Save the mass fractions in the FluidStateProperties object
     234      636135 :   liquid.mass_fraction[_aqueous_fluid_component] = Xh2o;
     235      636135 :   liquid.mass_fraction[_gas_fluid_component] = Xco2;
     236      636135 :   liquid.mass_fraction[_salt_component] = Xnacl;
     237      636135 :   gas.mass_fraction[_aqueous_fluid_component] = Yh2o;
     238      636135 :   gas.mass_fraction[_gas_fluid_component] = Yco2;
     239      636135 : }
     240             : 
     241             : void
     242       42944 : PorousFlowBrineCO2::gasProperties(const ADReal & pressure,
     243             :                                   const ADReal & temperature,
     244             :                                   std::vector<FluidStateProperties> & fsp) const
     245             : {
     246       42944 :   FluidStateProperties & gas = fsp[_gas_phase_number];
     247             : 
     248             :   // Gas density, viscosity and enthalpy are approximated with pure CO2 - no correction due
     249             :   // to the small amount of water vapor is made
     250             :   ADReal co2_density, co2_viscosity;
     251       42944 :   _co2_fp.rho_mu_from_p_T(pressure, temperature, co2_density, co2_viscosity);
     252             : 
     253       42944 :   ADReal co2_enthalpy = _co2_fp.h_from_p_T(pressure, temperature);
     254             : 
     255             :   // Save the values to the FluidStateProperties object. Note that derivatives wrt z are 0
     256       42944 :   gas.density = co2_density;
     257       42944 :   gas.viscosity = co2_viscosity;
     258       42944 :   gas.enthalpy = co2_enthalpy;
     259             : 
     260             :   mooseAssert(gas.density.value() > 0.0, "Gas density must be greater than zero");
     261       42944 :   gas.internal_energy = gas.enthalpy - pressure / gas.density;
     262       42944 : }
     263             : 
     264             : void
     265      628042 : PorousFlowBrineCO2::liquidProperties(const ADReal & pressure,
     266             :                                      const ADReal & temperature,
     267             :                                      const ADReal & Xnacl,
     268             :                                      std::vector<FluidStateProperties> & fsp) const
     269             : {
     270      628042 :   FluidStateProperties & liquid = fsp[_aqueous_phase_number];
     271             : 
     272             :   // The liquid density includes the density increase due to dissolved CO2
     273      628042 :   const ADReal brine_density = _brine_fp.rho_from_p_T_X(pressure, temperature, Xnacl);
     274             : 
     275             :   // Mass fraction of CO2 in liquid phase
     276      627919 :   const ADReal Xco2 = liquid.mass_fraction[_gas_fluid_component];
     277             : 
     278             :   // The liquid density
     279      627919 :   const ADReal co2_partial_density = partialDensityCO2(temperature);
     280             : 
     281     1883757 :   const ADReal liquid_density = 1.0 / (Xco2 / co2_partial_density + (1.0 - Xco2) / brine_density);
     282             : 
     283             :   // Assume that liquid viscosity is just the brine viscosity
     284      627919 :   const ADReal liquid_viscosity = _brine_fp.mu_from_p_T_X(pressure, temperature, Xnacl);
     285             : 
     286             :   // Liquid enthalpy (including contribution due to the enthalpy of dissolution)
     287      627919 :   const ADReal brine_enthalpy = _brine_fp.h_from_p_T_X(pressure, temperature, Xnacl);
     288             : 
     289             :   // Enthalpy of CO2
     290      627919 :   const ADReal co2_enthalpy = _co2_fp.h_from_p_T(pressure, temperature);
     291             : 
     292             :   // Enthalpy of dissolution
     293      627919 :   const ADReal hdis = enthalpyOfDissolution(temperature);
     294             : 
     295      627919 :   const ADReal liquid_enthalpy = (1.0 - Xco2) * brine_enthalpy + Xco2 * (co2_enthalpy + hdis);
     296             : 
     297             :   // Save the values to the FluidStateProperties object
     298      627919 :   liquid.density = liquid_density;
     299      627919 :   liquid.viscosity = liquid_viscosity;
     300      627919 :   liquid.enthalpy = liquid_enthalpy;
     301             : 
     302             :   mooseAssert(liquid.density.value() > 0.0, "Liquid density must be greater than zero");
     303      627919 :   liquid.internal_energy = liquid.enthalpy - pressure / liquid.density;
     304      627919 : }
     305             : 
     306             : ADReal
     307       34790 : PorousFlowBrineCO2::saturation(const ADReal & pressure,
     308             :                                const ADReal & temperature,
     309             :                                const ADReal & Xnacl,
     310             :                                const ADReal & Z,
     311             :                                std::vector<FluidStateProperties> & fsp) const
     312             : {
     313       34790 :   auto & gas = fsp[_gas_phase_number];
     314       34790 :   auto & liquid = fsp[_aqueous_fluid_component];
     315             : 
     316             :   // Approximate liquid density as saturation isn't known yet, by using the gas
     317             :   // pressure rather than the liquid pressure. This does result in a small error
     318             :   // in the calculated saturation, but this is below the error associated with
     319             :   // the correlations. A more accurate saturation could be found iteraviely,
     320             :   // at the cost of increased computational expense
     321             : 
     322             :   // Gas density
     323       34790 :   const ADReal gas_density = _co2_fp.rho_from_p_T(pressure, temperature);
     324             : 
     325             :   // Approximate liquid density as saturation isn't known yet
     326       34790 :   const ADReal brine_density = _brine_fp.rho_from_p_T_X(pressure, temperature, Xnacl);
     327             : 
     328             :   // Mass fraction of CO2 in liquid phase
     329       34790 :   const ADReal Xco2 = liquid.mass_fraction[_gas_fluid_component];
     330             : 
     331             :   // The liquid density
     332       34790 :   const ADReal co2_partial_density = partialDensityCO2(temperature);
     333             : 
     334      104370 :   const ADReal liquid_density = 1.0 / (Xco2 / co2_partial_density + (1.0 - Xco2) / brine_density);
     335             : 
     336       34790 :   const ADReal Yco2 = gas.mass_fraction[_gas_fluid_component];
     337             : 
     338             :   // Set mass equilibrium constants used in the calculation of vapor mass fraction
     339             :   const ADReal K0 = Yco2 / Xco2;
     340       69580 :   const ADReal K1 = (1.0 - Yco2) / (1.0 - Xco2);
     341       34790 :   const ADReal vapor_mass_fraction = vaporMassFraction(Z, K0, K1);
     342             : 
     343             :   // The gas saturation in the two phase case
     344       34790 :   const ADReal saturation = vapor_mass_fraction * liquid_density /
     345           0 :                             (gas_density + vapor_mass_fraction * (liquid_density - gas_density));
     346             : 
     347       34790 :   return saturation;
     348             : }
     349             : 
     350             : void
     351       34779 : PorousFlowBrineCO2::twoPhaseProperties(const ADReal & pressure,
     352             :                                        const ADReal & temperature,
     353             :                                        const ADReal & Xnacl,
     354             :                                        const ADReal & Z,
     355             :                                        unsigned int qp,
     356             :                                        std::vector<FluidStateProperties> & fsp) const
     357             : {
     358       34779 :   auto & gas = fsp[_gas_phase_number];
     359             : 
     360             :   // Calculate all of the gas phase properties, as these don't depend on saturation
     361       34779 :   gasProperties(pressure, temperature, fsp);
     362             : 
     363             :   // The gas saturation in the two phase case
     364       34779 :   gas.saturation = saturation(pressure, temperature, Xnacl, Z, fsp);
     365             : 
     366             :   // The liquid pressure and properties can now be calculated
     367       69558 :   const ADReal liquid_pressure = pressure - _pc.capillaryPressure(1.0 - gas.saturation, qp);
     368       34779 :   liquidProperties(liquid_pressure, temperature, Xnacl, fsp);
     369       34779 : }
     370             : 
     371             : void
     372      134269 : PorousFlowBrineCO2::equilibriumMassFractions(const ADReal & pressure,
     373             :                                              const ADReal & temperature,
     374             :                                              const ADReal & Xnacl,
     375             :                                              ADReal & Xco2,
     376             :                                              ADReal & Yh2o) const
     377             : {
     378             :   // Mole fractions at equilibrium
     379             :   ADReal xco2, yh2o;
     380      134269 :   equilibriumMoleFractions(pressure, temperature, Xnacl, xco2, yh2o);
     381             : 
     382             :   // The mass fraction of H2O in gas (assume no salt in gas phase) and derivatives
     383             :   // wrt p, T, and X
     384      402807 :   Yh2o = yh2o * _Mh2o / (yh2o * _Mh2o + (1.0 - yh2o) * _Mco2);
     385             : 
     386             :   // NaCl molality (mol/kg)
     387      134269 :   const ADReal mnacl = Xnacl / (1.0 - Xnacl) / _Mnacl;
     388             : 
     389             :   // The molality of CO2 in 1kg of H2O
     390      402807 :   const ADReal mco2 = xco2 * (2.0 * mnacl + _invMh2o) / (1.0 - xco2);
     391             :   // The mass fraction of CO2 in brine is then
     392      268538 :   const ADReal denominator = (1.0 + mnacl * _Mnacl + mco2 * _Mco2);
     393      134269 :   Xco2 = mco2 * _Mco2 / denominator;
     394      134269 : }
     395             : 
     396             : void
     397      133865 : PorousFlowBrineCO2::fugacityCoefficientsLowTemp(const ADReal & pressure,
     398             :                                                 const ADReal & temperature,
     399             :                                                 const ADReal & co2_density,
     400             :                                                 ADReal & fco2,
     401             :                                                 ADReal & fh2o) const
     402             : {
     403             :   using std::log, std::exp, std::pow;
     404             : 
     405      133865 :   if (temperature.value() > 373.15)
     406           0 :     mooseError(name(),
     407             :                ": fugacityCoefficientsLowTemp() is not valid for T > 373.15K. Use "
     408             :                "fugacityCoefficientsHighTemp() instead");
     409             : 
     410             :   // Need pressure in bar
     411      133865 :   const ADReal pbar = pressure * 1.0e-5;
     412             : 
     413             :   // Molar volume in cm^3/mol
     414      133865 :   const ADReal V = _Mco2 / co2_density * 1.0e6;
     415             : 
     416             :   // Redlich-Kwong parameters
     417      401595 :   const ADReal aCO2 = 7.54e7 - 4.13e4 * temperature;
     418      133865 :   const Real bCO2 = 27.8;
     419      133865 :   const Real aCO2H2O = 7.89e7;
     420      133865 :   const Real bH2O = 18.18;
     421             : 
     422      133865 :   const ADReal t15 = pow(temperature, 1.5);
     423             : 
     424             :   // The fugacity coefficients for H2O and CO2
     425      535460 :   auto lnPhi = [V, aCO2, bCO2, t15, this](ADReal a, ADReal b)
     426             :   {
     427      267730 :     return log(V / (V - bCO2)) + b / (V - bCO2) -
     428      535460 :            2.0 * a / (_Rbar * t15 * bCO2) * log((V + bCO2) / V) +
     429     1338650 :            aCO2 * b / (_Rbar * t15 * bCO2 * bCO2) * (log((V + bCO2) / V) - bCO2 / (V + bCO2));
     430      133865 :   };
     431             : 
     432      401595 :   const ADReal lnPhiH2O = lnPhi(aCO2H2O, bH2O) - log(pbar * V / (_Rbar * temperature));
     433      133865 :   const ADReal lnPhiCO2 = lnPhi(aCO2, bCO2) - log(pbar * V / (_Rbar * temperature));
     434             : 
     435      133865 :   fh2o = exp(lnPhiH2O);
     436      133865 :   fco2 = exp(lnPhiCO2);
     437      133865 : }
     438             : 
     439             : void
     440       10402 : PorousFlowBrineCO2::fugacityCoefficientsHighTemp(const ADReal & pressure,
     441             :                                                  const ADReal & temperature,
     442             :                                                  const ADReal & co2_density,
     443             :                                                  const ADReal & xco2,
     444             :                                                  const ADReal & yh2o,
     445             :                                                  ADReal & fco2,
     446             :                                                  ADReal & fh2o) const
     447             : {
     448       10402 :   if (temperature.value() <= 373.15)
     449           0 :     mooseError(name(),
     450             :                ": fugacityCoefficientsHighTemp() is not valid for T <= 373.15K. Use "
     451             :                "fugacityCoefficientsLowTemp() instead");
     452             : 
     453       10402 :   fh2o = fugacityCoefficientH2OHighTemp(pressure, temperature, co2_density, xco2, yh2o);
     454       10402 :   fco2 = fugacityCoefficientCO2HighTemp(pressure, temperature, co2_density, xco2, yh2o);
     455       10402 : }
     456             : 
     457             : ADReal
     458       10403 : PorousFlowBrineCO2::fugacityCoefficientH2OHighTemp(const ADReal & pressure,
     459             :                                                    const ADReal & temperature,
     460             :                                                    const ADReal & co2_density,
     461             :                                                    const ADReal & xco2,
     462             :                                                    const ADReal & yh2o) const
     463             : {
     464             :   using std::sqrt, std::pow, std::log, std::exp;
     465             : 
     466             :   // Need pressure in bar
     467       10403 :   const ADReal pbar = pressure * 1.0e-5;
     468             :   // Molar volume in cm^3/mol
     469       10403 :   const ADReal V = _Mco2 / co2_density * 1.0e6;
     470             : 
     471             :   // Redlich-Kwong parameters
     472       20806 :   const ADReal yco2 = 1.0 - yh2o;
     473       10403 :   const ADReal xh2o = 1.0 - xco2;
     474             : 
     475       31209 :   const ADReal aCO2 = 8.008e7 - 4.984e4 * temperature;
     476       31209 :   const ADReal aH2O = 1.337e8 - 1.4e4 * temperature;
     477       10403 :   const Real bCO2 = 28.25;
     478       10403 :   const Real bH2O = 15.7;
     479       31209 :   const ADReal KH2OCO2 = 1.427e-2 - 4.037e-4 * temperature;
     480       31209 :   const ADReal KCO2H2O = 0.4228 - 7.422e-4 * temperature;
     481       10403 :   const ADReal kH2OCO2 = KH2OCO2 * yh2o + KCO2H2O * yco2;
     482       10403 :   const ADReal kCO2H2O = KCO2H2O * yh2o + KH2OCO2 * yco2;
     483             : 
     484       20806 :   const ADReal aH2OCO2 = sqrt(aCO2 * aH2O) * (1.0 - kH2OCO2);
     485       31209 :   const ADReal aCO2H2O = sqrt(aCO2 * aH2O) * (1.0 - kCO2H2O);
     486             : 
     487       10403 :   const ADReal amix = yh2o * yh2o * aH2O + yh2o * yco2 * (aH2OCO2 + aCO2H2O) + yco2 * yco2 * aCO2;
     488       10403 :   const ADReal bmix = yh2o * bH2O + yco2 * bCO2;
     489             : 
     490       10403 :   const ADReal t15 = pow(temperature, 1.5);
     491             : 
     492           0 :   ADReal lnPhiH2O = bH2O / bmix * (pbar * V / (_Rbar * temperature) - 1.0) -
     493       31209 :                     log(pbar * (V - bmix) / (_Rbar * temperature));
     494       10403 :   ADReal term3 = (2.0 * yh2o * aH2O + yco2 * (aH2OCO2 + aCO2H2O) -
     495       10403 :                   yh2o * yco2 * sqrt(aH2O * aCO2) * (kH2OCO2 - kCO2H2O) * (yh2o - yco2) +
     496       20806 :                   xh2o * xco2 * sqrt(aH2O * aCO2) * (kH2OCO2 - kCO2H2O)) /
     497             :                  amix;
     498       10403 :   term3 -= bH2O / bmix;
     499       20806 :   term3 *= amix / (bmix * _Rbar * t15) * log(V / (V + bmix));
     500       10403 :   lnPhiH2O += term3;
     501             : 
     502       10403 :   return exp(lnPhiH2O);
     503             : }
     504             : 
     505             : ADReal
     506       10403 : PorousFlowBrineCO2::fugacityCoefficientCO2HighTemp(const ADReal & pressure,
     507             :                                                    const ADReal & temperature,
     508             :                                                    const ADReal & co2_density,
     509             :                                                    const ADReal & xco2,
     510             :                                                    const ADReal & yh2o) const
     511             : {
     512             :   using std::sqrt, std::pow, std::log, std::exp;
     513             : 
     514             :   // Need pressure in bar
     515       10403 :   const ADReal pbar = pressure * 1.0e-5;
     516             :   // Molar volume in cm^3/mol
     517       10403 :   const ADReal V = _Mco2 / co2_density * 1.0e6;
     518             : 
     519             :   // Redlich-Kwong parameters
     520       10403 :   const ADReal yco2 = 1.0 - yh2o;
     521       10403 :   const ADReal xh2o = 1.0 - xco2;
     522             : 
     523       31209 :   const ADReal aCO2 = 8.008e7 - 4.984e4 * temperature;
     524       31209 :   const ADReal aH2O = 1.337e8 - 1.4e4 * temperature;
     525       10403 :   const Real bCO2 = 28.25;
     526       10403 :   const Real bH2O = 15.7;
     527       31209 :   const ADReal KH2OCO2 = 1.427e-2 - 4.037e-4 * temperature;
     528       31209 :   const ADReal KCO2H2O = 0.4228 - 7.422e-4 * temperature;
     529       10403 :   const ADReal kH2OCO2 = KH2OCO2 * yh2o + KCO2H2O * yco2;
     530       10403 :   const ADReal kCO2H2O = KCO2H2O * yh2o + KH2OCO2 * yco2;
     531             : 
     532       20806 :   const ADReal aH2OCO2 = sqrt(aCO2 * aH2O) * (1.0 - kH2OCO2);
     533       31209 :   const ADReal aCO2H2O = sqrt(aCO2 * aH2O) * (1.0 - kCO2H2O);
     534             : 
     535       10403 :   const ADReal amix = yh2o * yh2o * aH2O + yh2o * yco2 * (aH2OCO2 + aCO2H2O) + yco2 * yco2 * aCO2;
     536       10403 :   const ADReal bmix = yh2o * bH2O + yco2 * bCO2;
     537             : 
     538       10403 :   const ADReal t15 = pow(temperature, 1.5);
     539             : 
     540           0 :   ADReal lnPhiCO2 = bCO2 / bmix * (pbar * V / (_Rbar * temperature) - 1.0) -
     541       31209 :                     log(pbar * (V - bmix) / (_Rbar * temperature));
     542             : 
     543       10403 :   ADReal term3 = (2.0 * yco2 * aCO2 + yh2o * (aH2OCO2 + aCO2H2O) -
     544       10403 :                   yh2o * yco2 * sqrt(aH2O * aCO2) * (kH2OCO2 - kCO2H2O) * (yh2o - yco2) +
     545       20806 :                   xh2o * xco2 * sqrt(aH2O * aCO2) * (kCO2H2O - kH2OCO2)) /
     546             :                  amix;
     547             : 
     548       20806 :   lnPhiCO2 += (term3 - bCO2 / bmix) * amix / (bmix * _Rbar * t15) * log(V / (V + bmix));
     549             : 
     550       10403 :   return exp(lnPhiCO2);
     551             : }
     552             : 
     553             : ADReal
     554       10403 : PorousFlowBrineCO2::activityCoefficientH2O(const ADReal & temperature, const ADReal & xco2) const
     555             : {
     556             :   using std::exp;
     557             : 
     558       10403 :   if (temperature.value() <= 373.15)
     559           1 :     return 1.0;
     560             :   else
     561             :   {
     562             :     const ADReal Tref = temperature - 373.15;
     563       10402 :     const ADReal xh2o = 1.0 - xco2;
     564       20804 :     const ADReal Am = -3.084e-2 * Tref + 1.927e-5 * Tref * Tref;
     565             : 
     566       20804 :     return exp((Am - 2.0 * Am * xh2o) * xco2 * xco2);
     567             :   }
     568             : }
     569             : 
     570             : ADReal
     571       10403 : PorousFlowBrineCO2::activityCoefficientCO2(const ADReal & temperature, const ADReal & xco2) const
     572             : {
     573             :   using std::exp;
     574             : 
     575       10403 :   if (temperature.value() <= 373.15)
     576           1 :     return 1.0;
     577             :   else
     578             :   {
     579             :     const ADReal Tref = temperature - 373.15;
     580       10402 :     const ADReal xh2o = 1.0 - xco2;
     581       20804 :     const ADReal Am = -3.084e-2 * Tref + 1.927e-5 * Tref * Tref;
     582             : 
     583       20804 :     return exp(2.0 * Am * xco2 * xh2o * xh2o);
     584             :   }
     585             : }
     586             : 
     587             : ADReal
     588      133869 : PorousFlowBrineCO2::activityCoefficient(const ADReal & pressure,
     589             :                                         const ADReal & temperature,
     590             :                                         const ADReal & Xnacl) const
     591             : {
     592             :   using std::log, std::exp;
     593             : 
     594             :   // Need pressure in bar
     595      133869 :   const ADReal pbar = pressure * 1.0e-5;
     596             :   // Need NaCl molality (mol/kg)
     597      133869 :   const ADReal mnacl = Xnacl / (1.0 - Xnacl) / _Mnacl;
     598             : 
     599      401607 :   const ADReal lambda = -0.411370585 + 6.07632013e-4 * temperature + 97.5347708 / temperature -
     600      133869 :                         0.0237622469 * pbar / temperature +
     601      267738 :                         0.0170656236 * pbar / (630.0 - temperature) +
     602      133869 :                         1.41335834e-5 * temperature * log(pbar);
     603             : 
     604      267738 :   const ADReal xi = 3.36389723e-4 - 1.9829898e-5 * temperature +
     605      133869 :                     2.12220830e-3 * pbar / temperature -
     606      401607 :                     5.24873303e-3 * pbar / (630.0 - temperature);
     607             : 
     608      267738 :   return exp(2.0 * lambda * mnacl + xi * mnacl * mnacl);
     609             : }
     610             : 
     611             : ADReal
     612       10405 : PorousFlowBrineCO2::activityCoefficientHighTemp(const ADReal & temperature,
     613             :                                                 const ADReal & Xnacl) const
     614             : {
     615             :   using std::exp;
     616             : 
     617             :   // Need NaCl molality (mol/kg)
     618       20810 :   const ADReal mnacl = Xnacl / (1.0 - Xnacl) / _Mnacl;
     619             : 
     620             :   const ADReal T2 = temperature * temperature;
     621             :   const ADReal T3 = temperature * T2;
     622             : 
     623       31215 :   const ADReal lambda = 2.217e-4 * temperature + 1.074 / temperature + 2648.0 / T2;
     624       41620 :   const ADReal xi = 1.3e-5 * temperature - 20.12 / temperature + 5259.0 / T2;
     625             : 
     626       41620 :   return (1.0 - mnacl / _invMh2o) * exp(2.0 * lambda * mnacl + xi * mnacl * mnacl);
     627             : }
     628             : 
     629             : ADReal
     630      144269 : PorousFlowBrineCO2::equilibriumConstantH2O(const ADReal & temperature) const
     631             : {
     632             :   using std::pow;
     633             : 
     634             :   // Uses temperature in Celsius
     635             :   const ADReal Tc = temperature - _T_c2k;
     636             :   const ADReal Tc2 = Tc * Tc;
     637             :   const ADReal Tc3 = Tc2 * Tc;
     638             :   const ADReal Tc4 = Tc3 * Tc;
     639             : 
     640             :   ADReal logK0H2O;
     641             : 
     642      144269 :   if (Tc <= 99.0)
     643      669330 :     logK0H2O = -2.209 + 3.097e-2 * Tc - 1.098e-4 * Tc2 + 2.048e-7 * Tc3;
     644             : 
     645       10403 :   else if (Tc > 99.0 && Tc < 109.0)
     646             :   {
     647           0 :     const ADReal Tint = (Tc - 99.0) / 10.0;
     648             :     const ADReal Tint2 = Tint * Tint;
     649           0 :     logK0H2O = -0.0204026 + 0.0152513 * Tint + 0.417565 * Tint2 - 0.278636 * Tint * Tint2;
     650             :   }
     651             : 
     652             :   else // 109 <= Tc <= 300
     653       62418 :     logK0H2O = -2.1077 + 2.8127e-2 * Tc - 8.4298e-5 * Tc2 + 1.4969e-7 * Tc3 - 1.1812e-10 * Tc4;
     654             : 
     655      144269 :   return pow(10.0, logK0H2O);
     656             : }
     657             : 
     658             : ADReal
     659      144269 : PorousFlowBrineCO2::equilibriumConstantCO2(const ADReal & temperature) const
     660             : {
     661             :   using std::pow;
     662             : 
     663             :   // Uses temperature in Celsius
     664             :   const ADReal Tc = temperature - _T_c2k;
     665             :   const ADReal Tc2 = Tc * Tc;
     666             :   const ADReal Tc3 = Tc2 * Tc;
     667             : 
     668             :   ADReal logK0CO2;
     669             : 
     670      144269 :   if (Tc <= 99.0)
     671      535464 :     logK0CO2 = 1.189 + 1.304e-2 * Tc - 5.446e-5 * Tc2;
     672             : 
     673       10403 :   else if (Tc > 99.0 && Tc < 109.0)
     674             :   {
     675           0 :     const ADReal Tint = (Tc - 99.0) / 10.0;
     676             :     const ADReal Tint2 = Tint * Tint;
     677           0 :     logK0CO2 = 1.9462 + 2.25692e-2 * Tint - 9.49577e-3 * Tint2 - 6.77721e-3 * Tint * Tint2;
     678             :   }
     679             : 
     680             :   else // 109 <= Tc <= 300
     681       52015 :     logK0CO2 = 1.668 + 3.992e-3 * Tc - 1.156e-5 * Tc2 + 1.593e-9 * Tc3;
     682             : 
     683      144269 :   return pow(10.0, logK0CO2);
     684             : }
     685             : 
     686             : void
     687      134276 : PorousFlowBrineCO2::equilibriumMoleFractions(const ADReal & pressure,
     688             :                                              const ADReal & temperature,
     689             :                                              const ADReal & Xnacl,
     690             :                                              ADReal & xco2,
     691             :                                              ADReal & yh2o) const
     692             : {
     693      134276 :   if (temperature.value() <= _Tlower)
     694             :   {
     695      133862 :     equilibriumMoleFractionsLowTemp(pressure, temperature, Xnacl, xco2, yh2o);
     696             :   }
     697         414 :   else if (temperature.value() > _Tlower && temperature.value() < _Tupper)
     698             :   {
     699             :     // Cubic polynomial in this regime
     700           2 :     const Real Tint = (temperature.value() - _Tlower) / 10.0;
     701             : 
     702             :     // Equilibrium mole fractions and derivatives at the lower temperature
     703           2 :     ADReal Tlower = _Tlower;
     704           2 :     Moose::derivInsert(Tlower.derivatives(), _Tidx, 1.0);
     705             : 
     706             :     ADReal xco2_lower, yh2o_lower;
     707           2 :     equilibriumMoleFractionsLowTemp(pressure, Tlower, Xnacl, xco2_lower, yh2o_lower);
     708             : 
     709           2 :     const Real dxco2_dT_lower = xco2_lower.derivatives()[_Tidx];
     710           2 :     const Real dyh2o_dT_lower = yh2o_lower.derivatives()[_Tidx];
     711             : 
     712             :     // Equilibrium mole fractions and derivatives at the upper temperature
     713             :     Real xco2_upper, yh2o_upper;
     714           2 :     Real co2_density_upper = _co2_fp.rho_from_p_T(pressure.value(), _Tupper);
     715             : 
     716           2 :     solveEquilibriumMoleFractionHighTemp(
     717           2 :         pressure.value(), _Tupper, Xnacl.value(), co2_density_upper, xco2_upper, yh2o_upper);
     718             : 
     719             :     Real A, dA_dp, dA_dT, B, dB_dp, dB_dT, dB_dX;
     720           2 :     funcABHighTemp(pressure.value(),
     721           2 :                    _Tupper,
     722             :                    Xnacl.value(),
     723             :                    co2_density_upper,
     724             :                    xco2_upper,
     725             :                    yh2o_upper,
     726             :                    A,
     727             :                    dA_dp,
     728             :                    dA_dT,
     729             :                    B,
     730             :                    dB_dp,
     731             :                    dB_dT,
     732             :                    dB_dX);
     733             : 
     734           2 :     const Real dyh2o_dT_upper =
     735           2 :         ((1.0 - B) * dA_dT + (A - 1.0) * A * dB_dT) / (1.0 - A * B) / (1.0 - A * B);
     736           2 :     const Real dxco2_dT_upper = dB_dT * (1.0 - yh2o_upper) - B * dyh2o_dT_upper;
     737             : 
     738             :     // The mole fractions in this regime are then found by interpolation
     739             :     Real xco2r, yh2or, dxco2_dT, dyh2o_dT;
     740           2 :     smoothCubicInterpolation(
     741             :         Tint, xco2_lower.value(), dxco2_dT_lower, xco2_upper, dxco2_dT_upper, xco2r, dxco2_dT);
     742           2 :     smoothCubicInterpolation(
     743             :         Tint, yh2o_lower.value(), dyh2o_dT_lower, yh2o_upper, dyh2o_dT_upper, yh2or, dyh2o_dT);
     744             : 
     745           2 :     xco2 = xco2r;
     746           4 :     Moose::derivInsert(xco2.derivatives(), _pidx, xco2_lower.derivatives()[_pidx]);
     747           2 :     Moose::derivInsert(xco2.derivatives(), _Tidx, dxco2_dT);
     748           4 :     Moose::derivInsert(xco2.derivatives(), _Xidx, xco2_lower.derivatives()[_Xidx]);
     749             : 
     750           2 :     yh2o = yh2or;
     751           4 :     Moose::derivInsert(yh2o.derivatives(), _pidx, yh2o_lower.derivatives()[_pidx]);
     752           2 :     Moose::derivInsert(yh2o.derivatives(), _Tidx, dyh2o_dT);
     753           4 :     Moose::derivInsert(yh2o.derivatives(), _Xidx, yh2o_lower.derivatives()[_Xidx]);
     754             :   }
     755             :   else
     756             :   {
     757             :     // CO2 density and derivatives wrt pressure and temperature
     758         412 :     const Real co2_density = _co2_fp.rho_from_p_T(pressure.value(), temperature.value());
     759             : 
     760             :     // Equilibrium mole fractions solved using iteration in this regime
     761             :     Real xco2r, yh2or;
     762         412 :     solveEquilibriumMoleFractionHighTemp(
     763             :         pressure.value(), temperature.value(), Xnacl.value(), co2_density, xco2r, yh2or);
     764             : 
     765             :     // Can use these in funcABHighTemp() to get derivatives analytically rather than by iteration
     766             :     Real A, dA_dp, dA_dT, B, dB_dp, dB_dT, dB_dX;
     767         412 :     funcABHighTemp(pressure.value(),
     768             :                    temperature.value(),
     769             :                    Xnacl.value(),
     770             :                    co2_density,
     771             :                    xco2r,
     772             :                    yh2or,
     773             :                    A,
     774             :                    dA_dp,
     775             :                    dA_dT,
     776             :                    B,
     777             :                    dB_dp,
     778             :                    dB_dT,
     779             :                    dB_dX);
     780             : 
     781         412 :     const Real dyh2o_dp =
     782         412 :         ((1.0 - B) * dA_dp + (A - 1.0) * A * dB_dp) / (1.0 - A * B) / (1.0 - A * B);
     783         412 :     const Real dxco2_dp = dB_dp * (1.0 - yh2or) - B * dyh2o_dp;
     784             : 
     785         412 :     const Real dyh2o_dT =
     786         412 :         ((1.0 - B) * dA_dT + (A - 1.0) * A * dB_dT) / (1.0 - A * B) / (1.0 - A * B);
     787         412 :     const Real dxco2_dT = dB_dT * (1.0 - yh2or) - B * dyh2o_dT;
     788             : 
     789         412 :     const Real dyh2o_dX = ((A - 1.0) * A * dB_dX) / (1.0 - A * B) / (1.0 - A * B);
     790         412 :     const Real dxco2_dX = dB_dX * (1.0 - yh2or) - B * dyh2o_dX;
     791             : 
     792         412 :     xco2 = xco2r;
     793         412 :     Moose::derivInsert(xco2.derivatives(), _pidx, dxco2_dp);
     794         412 :     Moose::derivInsert(xco2.derivatives(), _Tidx, dxco2_dT);
     795         412 :     Moose::derivInsert(xco2.derivatives(), _Xidx, dxco2_dX);
     796             : 
     797         412 :     yh2o = yh2or;
     798         412 :     Moose::derivInsert(yh2o.derivatives(), _pidx, dyh2o_dp);
     799         412 :     Moose::derivInsert(yh2o.derivatives(), _Tidx, dyh2o_dT);
     800         412 :     Moose::derivInsert(yh2o.derivatives(), _Xidx, dyh2o_dX);
     801             :   }
     802      134276 : }
     803             : 
     804             : void
     805      133864 : PorousFlowBrineCO2::equilibriumMoleFractionsLowTemp(const ADReal & pressure,
     806             :                                                     const ADReal & temperature,
     807             :                                                     const ADReal & Xnacl,
     808             :                                                     ADReal & xco2,
     809             :                                                     ADReal & yh2o) const
     810             : {
     811      133864 :   if (temperature.value() > 373.15)
     812           0 :     mooseError(name(),
     813             :                ": equilibriumMoleFractionsLowTemp() is not valid for T > 373.15K. Use "
     814             :                "equilibriumMoleFractions() instead");
     815             : 
     816             :   // CO2 density and derivatives wrt pressure and temperature
     817      133864 :   const ADReal co2_density = _co2_fp.rho_from_p_T(pressure, temperature);
     818             : 
     819             :   // Assume infinite dilution (yh20 = 0 and xco2 = 0) in low temperature regime
     820             :   ADReal A, B;
     821      133864 :   funcABLowTemp(pressure, temperature, co2_density, A, B);
     822             : 
     823             :   // As the activity coefficient for CO2 in brine used in this regime isn't a 'true'
     824             :   // activity coefficient, we instead calculate the molality of CO2 in water, then
     825             :   // correct it for brine, and then calculate the mole fractions.
     826             :   // The mole fraction in pure water is
     827      267728 :   const ADReal yh2ow = (1.0 - B) / (1.0 / A - B);
     828      133864 :   const ADReal xco2w = B * (1.0 - yh2ow);
     829             : 
     830             :   // Molality of CO2 in pure water
     831      267728 :   const ADReal mco2w = xco2w * _invMh2o / (1.0 - xco2w);
     832             :   // Molality of CO2 in brine is then calculated using gamma
     833      133864 :   const ADReal gamma = activityCoefficient(pressure, temperature, Xnacl);
     834             :   const ADReal mco2 = mco2w / gamma;
     835             : 
     836             :   // Need NaCl molality (mol/kg)
     837      133864 :   const ADReal mnacl = Xnacl / (1.0 - Xnacl) / _Mnacl;
     838             : 
     839             :   // Mole fractions of CO2 and H2O in liquid and gas phases
     840      267728 :   const ADReal total_moles = 2.0 * mnacl + _invMh2o + mco2;
     841      133864 :   xco2 = mco2 / total_moles;
     842      401592 :   yh2o = A * (1.0 - xco2 - 2.0 * mnacl / total_moles);
     843      133864 : }
     844             : 
     845             : void
     846      133864 : PorousFlowBrineCO2::funcABLowTemp(const ADReal & pressure,
     847             :                                   const ADReal & temperature,
     848             :                                   const ADReal & co2_density,
     849             :                                   ADReal & A,
     850             :                                   ADReal & B) const
     851             : {
     852             :   using std::exp;
     853             : 
     854      133864 :   if (temperature.value() > 373.15)
     855           0 :     mooseError(name(),
     856             :                ": funcABLowTemp() is not valid for T > 373.15K. Use funcABHighTemp() instead");
     857             : 
     858             :   // Pressure in bar
     859      133864 :   const ADReal pbar = pressure * 1.0e-5;
     860             : 
     861             :   // Reference pressure and partial molar volumes
     862             :   const Real pref = 1.0;
     863      133864 :   const Real vCO2 = 32.6;
     864      133864 :   const Real vH2O = 18.1;
     865             : 
     866             :   const ADReal delta_pbar = pbar - pref;
     867      133864 :   const ADReal Rt = _Rbar * temperature;
     868             : 
     869             :   // Equilibrium constants
     870      133864 :   const ADReal K0H2O = equilibriumConstantH2O(temperature);
     871      133864 :   const ADReal K0CO2 = equilibriumConstantCO2(temperature);
     872             : 
     873             :   // Fugacity coefficients
     874             :   ADReal phiH2O, phiCO2;
     875      133864 :   fugacityCoefficientsLowTemp(pressure, temperature, co2_density, phiCO2, phiH2O);
     876             : 
     877      267728 :   A = K0H2O / (phiH2O * pbar) * exp(delta_pbar * vH2O / Rt);
     878      401592 :   B = phiCO2 * pbar / (_invMh2o * K0CO2) * exp(-delta_pbar * vCO2 / Rt);
     879      133864 : }
     880             : 
     881             : void
     882       10401 : PorousFlowBrineCO2::funcABHighTemp(Real pressure,
     883             :                                    Real temperature,
     884             :                                    Real Xnacl,
     885             :                                    Real co2_density,
     886             :                                    Real xco2,
     887             :                                    Real yh2o,
     888             :                                    Real & A,
     889             :                                    Real & B) const
     890             : {
     891             :   using std::exp;
     892             : 
     893       10401 :   if (temperature <= 373.15)
     894           0 :     mooseError(name(),
     895             :                ": funcABHighTemp() is not valid for T <= 373.15K. Use funcABLowTemp() instead");
     896             : 
     897             :   // Pressure in bar
     898       10401 :   const Real pbar = pressure * 1.0e-5;
     899             :   // Temperature in C
     900       10401 :   const Real Tc = temperature - _T_c2k;
     901             : 
     902             :   // Reference pressure and partial molar volumes
     903       10401 :   const Real pref = -1.9906e-1 + 2.0471e-3 * Tc + 1.0152e-4 * Tc * Tc - 1.4234e-6 * Tc * Tc * Tc +
     904       10401 :                     1.4168e-8 * Tc * Tc * Tc * Tc;
     905       10401 :   const Real vCO2 = 32.6 + 3.413e-2 * (Tc - 100.0);
     906       10401 :   const Real vH2O = 18.1 + 3.137e-2 * (Tc - 100.0);
     907             : 
     908       10401 :   const Real delta_pbar = pbar - pref;
     909       10401 :   const Real Rt = _Rbar * temperature;
     910             : 
     911             :   // Equilibrium constants
     912             :   // Use dummy ADReal temperature as derivatives aren't required
     913       10401 :   const ADReal T = temperature;
     914       10401 :   Real K0H2O = equilibriumConstantH2O(T).value();
     915       10401 :   Real K0CO2 = equilibriumConstantCO2(T).value();
     916             : 
     917             :   // Fugacity coefficients
     918             :   // Use dummy ADReal variables as derivatives aren't required
     919       10401 :   const ADReal p = pressure;
     920       10401 :   const ADReal rhoco2 = co2_density;
     921       10401 :   const ADReal x = xco2;
     922       10401 :   const ADReal y = yh2o;
     923             : 
     924             :   ADReal phiH2O, phiCO2;
     925       10401 :   fugacityCoefficientsHighTemp(p, T, rhoco2, x, y, phiCO2, phiH2O);
     926             : 
     927             :   // Activity coefficients
     928       10401 :   const Real gammaH2O = activityCoefficientH2O(T, x).value();
     929       10401 :   const Real gammaCO2 = activityCoefficientCO2(T, x).value();
     930             : 
     931             :   // Activity coefficient for CO2 in brine
     932             :   // Use dummy ADReal Xnacl as derivatives aren't required
     933       10401 :   const ADReal X = Xnacl;
     934       10401 :   const Real gamma = activityCoefficientHighTemp(T, X).value();
     935             : 
     936       10401 :   A = K0H2O * gammaH2O / (phiH2O.value() * pbar) * exp(delta_pbar * vH2O / Rt);
     937       10401 :   B = phiCO2.value() * pbar / (_invMh2o * K0CO2 * gamma * gammaCO2) * exp(-delta_pbar * vCO2 / Rt);
     938       10401 : }
     939             : 
     940             : void
     941         414 : PorousFlowBrineCO2::funcABHighTemp(Real pressure,
     942             :                                    Real temperature,
     943             :                                    Real Xnacl,
     944             :                                    Real co2_density,
     945             :                                    Real xco2,
     946             :                                    Real yh2o,
     947             :                                    Real & A,
     948             :                                    Real & dA_dp,
     949             :                                    Real & dA_dT,
     950             :                                    Real & B,
     951             :                                    Real & dB_dp,
     952             :                                    Real & dB_dT,
     953             :                                    Real & dB_dX) const
     954             : {
     955         414 :   funcABHighTemp(pressure, temperature, Xnacl, co2_density, xco2, yh2o, A, B);
     956             : 
     957             :   // Use finite differences for derivatives in the high temperature regime
     958             :   const Real dp = 1.0e-2;
     959             :   const Real dT = 1.0e-6;
     960             :   const Real dX = 1.0e-8;
     961             : 
     962             :   Real A2, B2;
     963         414 :   funcABHighTemp(pressure + dp, temperature, Xnacl, co2_density, xco2, yh2o, A2, B2);
     964         414 :   dA_dp = (A2 - A) / dp;
     965         414 :   dB_dp = (B2 - B) / dp;
     966             : 
     967         414 :   funcABHighTemp(pressure, temperature + dT, Xnacl, co2_density, xco2, yh2o, A2, B2);
     968         414 :   dA_dT = (A2 - A) / dT;
     969         414 :   dB_dT = (B2 - B) / dT;
     970             : 
     971         414 :   funcABHighTemp(pressure, temperature, Xnacl + dX, co2_density, xco2, yh2o, A2, B2);
     972         414 :   dB_dX = (B2 - B) / dX;
     973         414 : }
     974             : 
     975             : void
     976         418 : PorousFlowBrineCO2::solveEquilibriumMoleFractionHighTemp(
     977             :     Real pressure, Real temperature, Real Xnacl, Real co2_density, Real & xco2, Real & yh2o) const
     978             : {
     979             :   // Initial guess for yh2o and xco2 (from Spycher and Pruess (2010))
     980         418 :   Real y = _brine_fp.vaporPressure(temperature, 0.0) / pressure;
     981             :   Real x = 0.009;
     982             : 
     983             :   // Need salt mass fraction in molality
     984         418 :   const Real mnacl = Xnacl / (1.0 - Xnacl) / _Mnacl;
     985             : 
     986             :   // If y > 1, then just use y = 1, x = 0 (only a gas phase)
     987         418 :   if (y >= 1.0)
     988             :   {
     989             :     y = 1.0;
     990             :     x = 0.0;
     991             :   }
     992             :   else
     993             :   {
     994             :     // Residual function for Newton-Raphson
     995        8745 :     auto fy = [mnacl, this](Real y, Real A, Real B) {
     996             :       return y -
     997        8745 :              (1.0 - B) * _invMh2o / ((1.0 / A - B) * (2.0 * mnacl + _invMh2o) + 2.0 * mnacl * B);
     998         417 :     };
     999             : 
    1000             :     // Derivative of fy wrt y
    1001        4164 :     auto dfy = [mnacl, this](Real A, Real B, Real dA, Real dB)
    1002             :     {
    1003        4164 :       const Real denominator = (1.0 / A - B) * (2.0 * mnacl + _invMh2o) + 2.0 * mnacl * B;
    1004        4164 :       return 1.0 + _invMh2o * dB / denominator +
    1005        4164 :              (1.0 - B) * _invMh2o *
    1006        4164 :                  (2.0 * mnacl * dB - (2.0 * mnacl + _invMh2o) * (dB + dA / A / A)) / denominator /
    1007        4164 :                  denominator;
    1008         417 :     };
    1009             : 
    1010             :     Real A, B;
    1011             :     Real dA, dB;
    1012             :     const Real dy = 1.0e-8;
    1013             : 
    1014             :     // Solve for yh2o using Newton-Raphson method
    1015             :     unsigned int iter = 0;
    1016             :     const Real tol = 1.0e-12;
    1017             :     const unsigned int max_its = 10;
    1018         417 :     funcABHighTemp(pressure, temperature, Xnacl, co2_density, x, y, A, B);
    1019             : 
    1020        4581 :     while (std::abs(fy(y, A, B)) > tol)
    1021             :     {
    1022        4164 :       funcABHighTemp(pressure, temperature, Xnacl, co2_density, x, y, A, B);
    1023             :       // Finite difference derivatives of A and B wrt y
    1024        4164 :       funcABHighTemp(pressure, temperature, Xnacl, co2_density, x, y + dy, dA, dB);
    1025        4164 :       dA = (dA - A) / dy;
    1026        4164 :       dB = (dB - B) / dy;
    1027             : 
    1028        4164 :       y = y - fy(y, A, B) / dfy(A, B, dA, dB);
    1029             : 
    1030        4164 :       x = B * (1.0 - y);
    1031             : 
    1032             :       // Break if not converged and just use the value
    1033             :       if (iter > max_its)
    1034             :         break;
    1035             :     }
    1036             :   }
    1037             : 
    1038         418 :   yh2o = y;
    1039         418 :   xco2 = x;
    1040         418 : }
    1041             : 
    1042             : ADReal
    1043      662712 : PorousFlowBrineCO2::partialDensityCO2(const ADReal & temperature) const
    1044             : {
    1045             :   // This correlation uses temperature in C
    1046             :   const ADReal Tc = temperature - _T_c2k;
    1047             :   // The parial molar volume
    1048     2650848 :   const ADReal V = 37.51 - 9.585e-2 * Tc + 8.74e-4 * Tc * Tc - 5.044e-7 * Tc * Tc * Tc;
    1049             : 
    1050     1325424 :   return 1.0e6 * _Mco2 / V;
    1051             : }
    1052             : 
    1053             : Real
    1054          37 : PorousFlowBrineCO2::totalMassFraction(
    1055             :     Real pressure, Real temperature, Real Xnacl, Real saturation, unsigned int qp) const
    1056             : {
    1057             :   // Check whether the input pressure and temperature are within the region of validity
    1058          37 :   checkVariables(pressure, temperature);
    1059             : 
    1060             :   // As we do not require derivatives, we can simply ignore their initialisation
    1061          37 :   const ADReal p = pressure;
    1062          37 :   const ADReal T = temperature;
    1063          37 :   const ADReal X = Xnacl;
    1064             : 
    1065             :   // FluidStateProperties data structure
    1066          37 :   std::vector<FluidStateProperties> fsp(_num_phases, FluidStateProperties(_num_components));
    1067          37 :   FluidStateProperties & liquid = fsp[_aqueous_phase_number];
    1068          37 :   FluidStateProperties & gas = fsp[_gas_phase_number];
    1069             : 
    1070             :   // Calculate equilibrium mass fractions in the two-phase state
    1071             :   ADReal Xco2, Yh2o;
    1072          37 :   equilibriumMassFractions(p, T, X, Xco2, Yh2o);
    1073             : 
    1074             :   // Save the mass fractions in the FluidStateMassFractions object
    1075          37 :   const ADReal Yco2 = 1.0 - Yh2o;
    1076          74 :   liquid.mass_fraction[_aqueous_fluid_component] = 1.0 - Xco2;
    1077          37 :   liquid.mass_fraction[_gas_fluid_component] = Xco2;
    1078          37 :   gas.mass_fraction[_aqueous_fluid_component] = Yh2o;
    1079          37 :   gas.mass_fraction[_gas_fluid_component] = Yco2;
    1080             : 
    1081             :   // Gas properties
    1082          37 :   gasProperties(pressure, temperature, fsp);
    1083             : 
    1084             :   // Liquid properties
    1085          37 :   const ADReal liquid_saturation = 1.0 - saturation;
    1086          37 :   const ADReal liquid_pressure = p - _pc.capillaryPressure(liquid_saturation, qp);
    1087          37 :   liquidProperties(liquid_pressure, T, X, fsp);
    1088             : 
    1089             :   // The total mass fraction of ncg (z) can now be calculated
    1090          37 :   const ADReal Z = (saturation * gas.density * Yco2 + liquid_saturation * liquid.density * Xco2) /
    1091          74 :                    (saturation * gas.density + liquid_saturation * liquid.density);
    1092             : 
    1093          37 :   return Z.value();
    1094          37 : }
    1095             : 
    1096             : ADReal
    1097          24 : PorousFlowBrineCO2::henryConstant(const ADReal & temperature, const ADReal & Xnacl) const
    1098             : {
    1099             :   using std::pow;
    1100             : 
    1101             :   // Henry's constant for dissolution in water
    1102          24 :   const ADReal Kh_h2o = _brine_fp.henryConstant(temperature, _co2_henry);
    1103             : 
    1104             :   // The correction to salt is obtained through the salting out coefficient
    1105          24 :   const std::vector<Real> b{1.19784e-1, -7.17823e-4, 4.93854e-6, -1.03826e-8, 1.08233e-11};
    1106             : 
    1107             :   // Need temperature in Celsius
    1108             :   const ADReal Tc = temperature - _T_c2k;
    1109             : 
    1110          24 :   ADReal kb = 0.0;
    1111         144 :   for (unsigned int i = 0; i < b.size(); ++i)
    1112         240 :     kb += b[i] * pow(Tc, i);
    1113             : 
    1114             :   // Need salt mass fraction in molality
    1115          48 :   const ADReal xmol = Xnacl / (1.0 - Xnacl) / _Mnacl;
    1116             : 
    1117             :   // Henry's constant and its derivative wrt temperature and salt mass fraction
    1118          48 :   return Kh_h2o * pow(10.0, xmol * kb);
    1119          24 : }
    1120             : 
    1121             : ADReal
    1122           6 : PorousFlowBrineCO2::enthalpyOfDissolutionGas(const ADReal & temperature, const ADReal & Xnacl) const
    1123             : {
    1124             :   // Henry's constant
    1125           6 :   const ADReal Kh = henryConstant(temperature, Xnacl);
    1126             : 
    1127          12 :   ADReal hdis = -_R * temperature * temperature * Kh.derivatives()[_Tidx] / Kh / _Mco2;
    1128             : 
    1129             :   // Derivative of enthalpy of dissolution wrt temperature and xnacl requires the second
    1130             :   // derivatives of Henry's constant. For simplicity, approximate these numerically
    1131           6 :   const Real dT = temperature.value() * 1.0e-8;
    1132             :   const ADReal T2 = temperature + dT;
    1133           6 :   const ADReal Kh2 = henryConstant(T2, Xnacl);
    1134             : 
    1135             :   const Real dhdis_dT =
    1136          18 :       (-_R * T2 * T2 * Kh2.derivatives()[_Tidx] / Kh2 / _Mco2 - hdis).value() / dT;
    1137             : 
    1138           6 :   const Real dX = Xnacl.value() * 1.0e-8;
    1139             :   const ADReal X2 = Xnacl + dX;
    1140           6 :   const ADReal Kh3 = henryConstant(temperature, X2);
    1141             : 
    1142             :   const Real dhdis_dX =
    1143          12 :       (-_R * temperature * temperature * Kh3.derivatives()[_Tidx] / Kh3 / _Mco2 - hdis).value() /
    1144           6 :       dX;
    1145             : 
    1146          12 :   hdis.derivatives() = temperature.derivatives() * dhdis_dT + Xnacl.derivatives() * dhdis_dX;
    1147             : 
    1148           6 :   return hdis;
    1149             : }
    1150             : 
    1151             : ADReal
    1152      627924 : PorousFlowBrineCO2::enthalpyOfDissolution(const ADReal & temperature) const
    1153             : {
    1154             :   // Linear fit to model of Duan and Sun (2003) (in kJ/mol)
    1155     1883772 :   const ADReal delta_h = -58.3533 + 0.134519 * temperature;
    1156             : 
    1157             :   // Convert to J/kg
    1158      627924 :   return delta_h * 1000.0 / _Mco2;
    1159             : }
    1160             : 
    1161             : void
    1162           4 : PorousFlowBrineCO2::smoothCubicInterpolation(
    1163             :     Real temperature, Real f0, Real df0, Real f1, Real df1, Real & value, Real & deriv) const
    1164             : {
    1165             :   // Coefficients of cubic polynomial
    1166           4 :   const Real dT = _Tupper - _Tlower;
    1167             : 
    1168             :   const Real a = f0;
    1169           4 :   const Real b = df0 * dT;
    1170           4 :   const Real c = 3.0 * (f1 - f0) - (2.0 * df0 + df1) * dT;
    1171           4 :   const Real d = 2.0 * (f0 - f1) + (df0 + df1) * dT;
    1172             : 
    1173           4 :   const Real t2 = temperature * temperature;
    1174           4 :   const Real t3 = temperature * t2;
    1175             : 
    1176           4 :   value = a + b * temperature + c * t2 + d * t3;
    1177           4 :   deriv = b + 2.0 * c * temperature + 3.0 * d * t2;
    1178           4 : }
    1179             : 
    1180             : void
    1181      636143 : PorousFlowBrineCO2::checkVariables(Real pressure, Real temperature) const
    1182             : {
    1183             :   // The calculation of mass fractions is valid from 12C <= T <= 300C, and
    1184             :   // pressure less than 60 MPa
    1185      636143 :   if (temperature < 285.15 || temperature > 573.15)
    1186           0 :     mooseException(name() + ": temperature " + Moose::stringify(temperature) +
    1187             :                    " is outside range 285.15 K <= T <= 573.15 K");
    1188             : 
    1189      636143 :   if (pressure > 6.0e7)
    1190           0 :     mooseException(name() + ": pressure " + Moose::stringify(pressure) +
    1191             :                    " must be less than 60 MPa");
    1192      636143 : }

Generated by: LCOV version 1.14