Add per-region SALINITY support for CO2STORE multi-PVT decks#5107
Add per-region SALINITY support for CO2STORE multi-PVT decks#5107dlmorenob wants to merge 1 commit into
Conversation
|
Thanks for your work. Since |
We might consider implementing the |
Yes, that is a good suggestion. If an implementation based on |
|
Thanks for the direction — SALINITR is OK. However, It's not in OPM's parser yet so this would be an addition, right? I'll check the implementation using EOSNUM as you suggest, that said, I have not seen CO2STORE decks that set EOSNUM. Should SALINITR require EOSNUM in the deck, or default to PVTNUM when EOSNUM is absent? |
|
Following up on EOSNUM for SALINITR: the OPM Flow manual notes that EOSNUM is currently "parsed but its data ignored." Implementing SALINITR per EOSNUM would therefore require first wiring EOSNUM? |
Yes, SALINITR might be difficult to implement due to the current state of EOSNUM. I propose then to make an entirely new keyword (not named SALINITR, since that is also defined in the commercial simulator) and implement PVTNUM-based salinity. |
Introduces SALINITP, an OPM-specific keyword sized by NTPVT, allowing different brine salinities per PVT region in CO2STORE/H2STORE decks. Avoids using a commercial keyword name (e.g. SALINITR) since EOSNUM plumbing is not yet supported in OPM. Co2StoreConfig now stores salt as std::vector<double>; the existing scalar salinity() accessor returns salt[0] (or 0.0 if unset). A new salinity(regionIdx) accessor reads per-region values, broadcasting single-record decks (SALINITY/SALTMF) to any region for backward compatibility. BrineCo2Pvt, Co2GasPvt and BlackOilFluidSystem now consult salinity(regionIdx) in their per-region init loops, fixing a silent bug where multi-region PVT tables and brine component molar masses used only the first region's salt. SALINITP lives under 900_OPM (not Eclipse 300) and prohibits SALINITY, SALTMF and BRINE; SALINITY continues to behave as a single-scalar broadcast and remains accepted for compatibility.
b970e94 to
4dbe890
Compare
|
Reworked per discussion: I introduce SALINITP under 900_OPM/S/, sized by NTPVT, prohibits SALINITY/SALTMF/BRINE. Existing scalar SALINITY/SALTMF unchanged. Per-region indexing wired through BrineCo2Pvt, Co2GasPvt, and the existing CO2STORE-guarded init in BlackOilFluidSystem. New tests for per-region behavior, scalar broadcast, and parser rejection of conflicting keywords. |
svenn-t
left a comment
There was a problem hiding this comment.
I have some minor comments, mostly related to enforcing the salt vector to be NTPVT size regardless of keyword.
| else if (props_section.hasKeyword<ParserKeywords::SALINITY>()) { | ||
| const auto& molality = deck["SALINITY"].back().getRecord(0).getItem("MOLALITY").get<double>(0); | ||
| salt = 1.0 / (1.0 + 1.0 / (molality * MmNaCl)); | ||
| salt.resize(1); |
There was a problem hiding this comment.
Resize according to NTPVT from TABDIMS and fill vector with the one value from SALINITY.
| else if (props_section.hasKeyword<ParserKeywords::SALTMF>()) { | ||
| const auto& mole_frac = deck["SALTMF"].back().getRecord(0).getItem("MOLE_FRACTION").get<double>(0); | ||
| salt = mole_frac * MmNaCl / (mole_frac * (MmNaCl - MmH2O) + MmH2O); | ||
| salt.resize(1); |
There was a problem hiding this comment.
Resize according to NTPVT from TABDIMS and fill vector with the one value from SALTMF.
| if (props_section.hasKeyword<ParserKeywords::SALINITY>()) { | ||
| // SALINITP / SALINITY / SALTMF convert to mass fraction. | ||
| // SALINITP (OPM-specific): per-PVT-region salinity, sized by NTPVT. | ||
| // SALINITY (commercial Eclipse): single scalar value, broadcast to all regions. |
| double Co2StoreConfig::salinity() const { | ||
| return salt; | ||
| return salt.empty() ? 0.0 : salt[0]; | ||
| } | ||
|
|
There was a problem hiding this comment.
Remove salinity() function without any input. We will only use the version with regionIdx input.
| if (salt.empty()) | ||
| return 0.0; | ||
| // Broadcast single-record (SALINITY/SALTMF) value to all regions. | ||
| if (regionIdx >= salt.size()) |
There was a problem hiding this comment.
Remove broadcasting. SALINITY and SALTMF should be the same size as NTPVT.
| return salt[regionIdx]; | ||
| } | ||
|
|
||
| std::size_t Co2StoreConfig::numSalinityRegions() const { |
There was a problem hiding this comment.
Remove. We can use NTPVT information if necessary.
Summary
The
SALINITYkeyword currently accepts only a single record, so all PVT regions in a CO2STORE model use the same brine salinity. This prevents accurate modelling of stackedaquifer storage targets with different formation-water TDS, or formations with caprock/reservoir salinity contrasts.
This PR allows one
SALINITYrecord per PVT region (size tied toTABDIMSitemNTPVT, mirroring howPVTWis handled).Changes
share/keywords/001_Eclipse300/S/SALINITY— keyword size:1→{"keyword": "TABDIMS", "item": "NTPVT"}Co2StoreConfig.hpp—double salt→std::vector<double> salt; addedsalinity(regionIdx)andnumSalinityRegions()accessorsCo2StoreConfig.cpp— parser loops over records; backward-compatsalinity()returnssalt[0]; out-of-rangeregionIdxbroadcastssalt[0]BrineCo2Pvt.cpp—salinity()→salinity(regionIdx)in per-region init loopCo2GasPvt.cpp— same one-line fix for gas-phase PVT pathtests/parser/EclipseStateTests.cpp— addedTABDIMS /to existingSALTMFTestBackward compatibility
A deck with a single
SALINITYrecord (the only form currently in use) works unchanged.NTPVTdefaults to 1,salinity()(no args) returnssalt[0], andsalinity(regionIdx)broadcasts
salt[0]for out-of-range indices.Testing
python_testsfailure on clean master)Context
Second of two follow-ups requested by the reviewer of #5097. Independent of the BRINE/fugacity gating fix (to be filed separately).