Skip to content

fix: Correct CO2-brine thermodynamic properties - #4172

Open
bd713 wants to merge 17 commits into
developfrom
fix/bd713/pvt
Open

bd713 wants to merge 17 commits into
developfrom
fix/bd713/pvt

Conversation

@bd713

@bd713 bd713 commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor
  • Correct the non-analytic Span–Wagner terms used in CO₂ enthalpy calculations
  • Fix mass-to-molar enthalpy conversions
  • Convert dissolved CO2 solubility from mol/kg H2O to mol/kg brine before the flash, for analytic models and user tables (leave the Phillips density kernel unchanged). Closes PhillipsBrineDensity: CO2 concentration on a water-mass basis overstates the CO2 density effect in saline brine by 1/(1-S) #4171.
  • Correct phase-index mapping so compositional phase models use the intended equation of state
  • Restart unstable flashes that collapse to the trivial solution using the incipient composition from the stability test
  • Clarify doc
  • Update affected reference values in the 3 CO2-brine test suite

Rebaseline needed

Cause Affected decks
CO2 enthalpy: Span–Wagner non-analytic exponents and mass-to-molar conversion (*_CO2_enthalpy_table values change) co2_thermal_2d, co2_thermal_2d_iterative, co2_thermal_injwell_2d, co2_thermal_prdwell_2d, co2_thermal_well_2d, staircase_co2_wells_thermal_3d_iterative, ThermoPoroElastic_staircase_co2_smoke, ThermoPoroElastic_staircase_co2_iterative_smoke, testPVT_CO2Brine
CO2 solubility converted from mol/kg H2O to mol/kg brine when salinity > 0, for analytic models and user tables (*_co2Dissolution_table values change) class09_pb3_smoke_3d, PoroElastic_hybridHexPrism_co2_{fim,sequential}_3d, PoroElastic_staircase_co2_3d_{fim,fim_iterative,sequential}, thermalLeakyWell_smoke_3d, ThermoPoroElastic_staircase_co2*, testPVT, testPVT_CO2Brine, testPVT_CO2BrineTables
Compositional fluids: phase-model mapping and flash restart from the trivial solution dome_kvalue_smoke, dome_soreide_whitson_smoke, initialization_2phase_4comp
New test testPVT_Compositional

@bd713 bd713 self-assigned this Oct 31, 2026
@bd713 bd713 added type: bug Something isn't working flag: requires rebaseline Requires rebaseline branch in integratedTests ci: run integrated tests Allows to run the integrated tests in GEOS CI ci: run code coverage enables running of the code coverage CI jobs labels Oct 31, 2026
@bd713
bd713 requested a review from rrsettgast as a code owner October 31, 2026 02:16
@bd713 bd713 added the ci: run device builds Allows to run the device (CUDA/HIP) builds label Oct 31, 2026

@dkachuma dkachuma left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good.

I think initially the salinity and solubility were supposed to be per kg of brine (not kg of water). But I think somewhere down the line we started mixing the 2 up. It is worthwhile to check that we don't have dangling references to moles per kg of brine in the code and doc.

================================================================================

At initialization, GEOS performs a preprocessing step to construct a two-dimensional table storing the values of CO2 solubility in brine and water solubility in the gas phase as functions of pressure, temperature, and a constant salinity. Solubility is calculated and provided as moles of the component dissolved or vaporized per mass of the phase. For CO2 this will be moles of CO2 dissolved per kg of water and for water this will be moles of H2O vaporized in the CO2 phase per kg of CO2.
At initialization, GEOS performs a preprocessing step to construct a two-dimensional table storing the values of CO2 solubility in brine and water solubility in the gas phase as functions of pressure, temperature, and a constant salinity. Solubility is stored as moles of the component dissolved or vaporized per kg of the other component. The ``water`` component of these fluids is the brine, salt included: its density is the brine density, so one kg of it holds :math:`1/(1 + m M_{NaCl})` kg of H2O, where :math:`m` is the NaCl molality (mol per kg of H2O) and :math:`M_{NaCl}` the molar weight of NaCl. The solubility models return the equilibrium loading :math:`b_{CO2}` in moles of CO2 per kg of H2O; GEOS divides it by :math:`1 + m M_{NaCl}` so that the stored CO2 solubility is in moles of CO2 per kg of the water (brine) component. For a saturated liquid in two-phase equilibrium, the liquid pseudo-mole fraction :math:`y_{CO2,\ell}` produced by the flash therefore satisfies

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Solubility is stored as moles of the component dissolved or vaporized per kg of the other component.

Solubility in the brine phase is stored as moles of CO2 dissolved per kg of H2O whereas vaporization in the gas phase is stored as moles of H2O vaporized per kg of CO2.

temperatureInterval="5"
salinity="1.901285269"
ezrokhiDensityCoefficients="{0.1033, -2.2991e-5, -2.3658e-6}"
ezrokhiDensityCoefficients="{0.1003, -2.2991e-5, -2.3658e-6}"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a correction based on the salinity?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a correction to align the documentation with the actual default value 0.1003 used in BrineFluidParameters.cpp

Image

I don't have the original reference to determine which of the two values is the correct one.

real64 const CO2MW = m_componentMolarWeight[m_CO2Index];

value = (1.0 - C ) * CO2Enthalpy * CO2MWInv + C * brineEnthalpy * waterMWInv;
value = (1.0 - C ) * CO2Enthalpy * CO2MW + C * brineEnthalpy * waterMW;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the molar formulation, C will be a mole fraction. I'm guessing the assumption here is that CO2Enthalpy and brineEnthalpy are in mass variables (i.e. J/kg). In that case I think we should average them by using a mass fraction i.e.
C_new = C*waterMW/(CO2MW + waterMW)
I could be missing something though.

In any case, what we had before still doesn't feel correct.

phaseFraction.value[m_vapourIndex],
phaseCompFraction.value[m_liquidIndex],
phaseCompFraction.value[m_vapourIndex] );
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we do this always not only when the flash fails?

DENEL Bertrand and others added 3 commits November 4, 2026 18:39
@jhuang2601

Copy link
Copy Markdown
Contributor

@jafranc @OmarDuran Can you please review this PR?

@bd713
bd713 requested a review from tjb-ltk as a code owner November 6, 2026 22:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci: run code coverage enables running of the code coverage CI jobs ci: run device builds Allows to run the device (CUDA/HIP) builds ci: run integrated tests Allows to run the integrated tests in GEOS CI flag: requires rebaseline Requires rebaseline branch in integratedTests type: bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

PhillipsBrineDensity: CO2 concentration on a water-mass basis overstates the CO2 density effect in saline brine by 1/(1-S)

3 participants