From 2e1ae1e91d2b07367c05da763a80394b3c4d2ddd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A5rd=20Skaflestad?= Date: Mon, 13 Jul 2026 15:32:29 +0200 Subject: [PATCH 1/2] Populate Region Variable Mapping From Summary Config This commit adds a special purpose helper function, populateRegVarMapping() that, based on the run's configured summary vectors, populates an object of type data::RegionVariableMapping. In this initial implementation, we define a variable named ConnOPT if any of the *OEW* summary vectors are configured in the run's SUMMARY section. Those OEW vectors require tracking the per-region cumulative oil production from wells and it's easier to have a dedicated variable for this than to introduce extra "COPT" summary vectors that are visible to the user through the result set's summary files (e.g., .SMSPEC and .UNSMRY). --- CMakeLists_files.cmake | 2 + .../SummaryConfig/RegionVariableSupport.cpp | 64 +++++++++++++++++++ .../SummaryConfig/RegionVariableSupport.hpp | 36 +++++++++++ 3 files changed, 102 insertions(+) create mode 100644 opm/input/eclipse/EclipseState/SummaryConfig/RegionVariableSupport.cpp create mode 100644 opm/input/eclipse/EclipseState/SummaryConfig/RegionVariableSupport.hpp diff --git a/CMakeLists_files.cmake b/CMakeLists_files.cmake index 50068dbb244..b69afeb7795 100644 --- a/CMakeLists_files.cmake +++ b/CMakeLists_files.cmake @@ -146,6 +146,7 @@ list(APPEND MAIN_SOURCE_FILES opm/input/eclipse/EclipseState/SimulationConfig/RockConfig.cpp opm/input/eclipse/EclipseState/SimulationConfig/SimulationConfig.cpp opm/input/eclipse/EclipseState/SimulationConfig/ThresholdPressure.cpp + opm/input/eclipse/EclipseState/SummaryConfig/RegionVariableSupport.cpp opm/input/eclipse/EclipseState/SummaryConfig/SummaryConfig.cpp opm/input/eclipse/EclipseState/Tables/Aqudims.cpp opm/input/eclipse/EclipseState/Tables/ColumnSchema.cpp @@ -1009,6 +1010,7 @@ list(APPEND PUBLIC_HEADER_FILES opm/input/eclipse/EclipseState/SimulationConfig/RockConfig.hpp opm/input/eclipse/EclipseState/SimulationConfig/SimulationConfig.hpp opm/input/eclipse/EclipseState/SimulationConfig/ThresholdPressure.hpp + opm/input/eclipse/EclipseState/SummaryConfig/RegionVariableSupport.hpp opm/input/eclipse/EclipseState/SummaryConfig/SummaryConfig.hpp opm/input/eclipse/EclipseState/Tables/Aqudims.hpp opm/input/eclipse/EclipseState/Tables/AqutabTable.hpp diff --git a/opm/input/eclipse/EclipseState/SummaryConfig/RegionVariableSupport.cpp b/opm/input/eclipse/EclipseState/SummaryConfig/RegionVariableSupport.cpp new file mode 100644 index 00000000000..c9cf37081ce --- /dev/null +++ b/opm/input/eclipse/EclipseState/SummaryConfig/RegionVariableSupport.cpp @@ -0,0 +1,64 @@ +/* + Copyright 2026 Equinor ASA. + + This file is part of the Open Porous Media project (OPM). + + OPM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . +*/ + +#include + +#include + +#include + +namespace { + void populateOilEfficiencyVariables(const Opm::SummaryConfig& sumcfg, + Opm::data::RegionVariableMapping& regVarMap) + { + // FOEW, ROEW, ROEW_ + const auto oew_kws = sumcfg.keywords(R"(*OEW*)"); + + if (oew_kws.empty()) { + // No *OEW* vectors requested. Nothing to do. + return; + } + + using RegSet = Opm::data::RegionVariableMapping::RegionSet; + + regVarMap.add(Opm::data::RegionVariableMapping::Variable { "ConnOPT" }, + /* is_cumulative = */ true); + + for (const auto& oew_kw : oew_kws) { + if (oew_kw.category() == Opm::SummaryConfigNode::Category::Region) { + regVarMap.add(RegSet { oew_kw.fip_region() }); + } + else { + // Not a region level vector. Typically FOEW. Ensure that + // we have FIPNUM for this case. + regVarMap.add(RegSet { "FIPNUM" }); + } + } + } +} // Anonymous namespace + +// =========================================================================== +// Public interface below separator +// =========================================================================== + +void Opm::populateRegVarMapping(const SummaryConfig& sumcfg, + data::RegionVariableMapping& regVarMap) +{ + populateOilEfficiencyVariables(sumcfg, regVarMap); +} diff --git a/opm/input/eclipse/EclipseState/SummaryConfig/RegionVariableSupport.hpp b/opm/input/eclipse/EclipseState/SummaryConfig/RegionVariableSupport.hpp new file mode 100644 index 00000000000..0af8803782f --- /dev/null +++ b/opm/input/eclipse/EclipseState/SummaryConfig/RegionVariableSupport.hpp @@ -0,0 +1,36 @@ +/* + Copyright 2026 Equinor ASA. + + This file is part of the Open Porous Media project (OPM). + + OPM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . +*/ + +#ifndef OPM_REGION_VARIABLE_SUPPORT_HPP +#define OPM_REGION_VARIABLE_SUPPORT_HPP + +namespace Opm { + class SummaryConfig; +} // namespace Opm + +namespace Opm::data { + class RegionVariableMapping; +} // namespace Opm::data + +namespace Opm { + void populateRegVarMapping(const SummaryConfig& sumcfg, + data::RegionVariableMapping& regVarMap); +} + +#endif // OPM_REGION_VARIABLE_SUPPORT_HPP From 10f54fa812948d46eba65ada1a15681263fa8302 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A5rd=20Skaflestad?= Date: Mon, 13 Jul 2026 16:53:43 +0200 Subject: [PATCH 2/2] Calculate ROEW From External Per-Region Contributions This commit switches the ROEW calculation away from using the ad-hoc mechanism of defining additional "COPT" vectors and mapping these to region contributions using the RegionCache. Instead, we rely on client code to provide a dedicated "ConnOPT" region variable defined for all regions in all region sets and use that value, combined with the existing "initial in-place" container, to infer the ROEW recovery factor per region and region set. The practical benefit of this change is that users no longer see the COPT vectors that were added only as a means of calculating ROEW and which were not requested in the run's SUMMARY section. --- .../SummaryConfig/SummaryConfig.cpp | 15 -- opm/output/eclipse/Summary.cpp | 176 ++++++++++++------ opm/output/eclipse/Summary.hpp | 12 ++ 3 files changed, 134 insertions(+), 69 deletions(-) diff --git a/opm/input/eclipse/EclipseState/SummaryConfig/SummaryConfig.cpp b/opm/input/eclipse/EclipseState/SummaryConfig/SummaryConfig.cpp index 0c8eb29a241..29121206247 100644 --- a/opm/input/eclipse/EclipseState/SummaryConfig/SummaryConfig.cpp +++ b/opm/input/eclipse/EclipseState/SummaryConfig/SummaryConfig.cpp @@ -1502,21 +1502,6 @@ void keywordR(SummaryConfig::keyword_list& list, regions = context.activeRegions(*region_name); } - // See comment on function roew() in Summary.cpp for this weirdness. - if (keyword.rfind("ROEW", 0) == 0) { - auto copt_node = SummaryConfigNode("COPT", SummaryConfigNode::Category::Connection, {}); - copt_node.parameterType(SummaryConfigNode::Type::Total); - for (const auto& wname : schedule.wellNames()) { - copt_node.namedEntity(wname); - - const auto& well = schedule.getWellatEnd(wname); - for (const auto& connection : well.getConnections()) { - copt_node.number(connection.global_index() + 1); - list.push_back(copt_node); - } - } - } - auto param = SummaryConfigNode { keyword, SummaryConfigNode::Category::Region, deck_keyword.location() } diff --git a/opm/output/eclipse/Summary.cpp b/opm/output/eclipse/Summary.cpp index 028b2344a56..72ef07c52b7 100644 --- a/opm/output/eclipse/Summary.cpp +++ b/opm/output/eclipse/Summary.cpp @@ -65,9 +65,15 @@ #include #include #include +#include +#include +#include +#include #include + #include #include +#include #include #include @@ -698,6 +704,9 @@ struct fn_args // every non-connection keyword -- existing dispatch is unchanged. const std::optional lgr_grid_filter{}; const std::optional lgr_cell_filter{}; + + const Opm::data::RegionVariableMapping* reg_var_map{nullptr}; + const Opm::RegionVariableCollection* reg_var_coll{nullptr}; }; /* Since there are several enums in opm scattered about more-or-less @@ -1758,12 +1767,52 @@ inline quantity bhp( const fn_args& args ) { return { p->second.bhp, measure::pressure }; } -/* - This function is slightly ugly - the evaluation of ROEW uses the already - calculated COPT results. We do not really have any formalism for such - dependencies between the summary vectors. For this particualar case there is a - hack in SummaryConfig which should ensure that this is safe. -*/ +quantity oew_value(const fn_args& args, + const std::string& region_name, + const int regionId, + const double oiip) +{ + const auto zero = quantity { 0.0, measure::identity }; + + if (oiip < 1.0*Opm::prefix::micro*Opm::unit::liter) { + // One micro liter (1e-9 SM3) of initial oil in place + // is a very small region of the reservoir. We will + // not report ROEW for this case. + return zero; + } + + const auto varIx = args.reg_var_coll + ->variableIndex(*args.reg_var_map, "ConnOPT"); + if (! varIx.has_value()) { + return zero; + } + + const auto regsetIx = args.reg_var_coll + ->regionSetIndex(*args.reg_var_map, region_name); + if (! regsetIx.has_value()) { + return zero; + } + + const auto roptValues = args.reg_var_coll + ->regionVariableValues().values(*varIx); + if (! roptValues.has_value()) { + return zero; + } + + const auto copt = roptValues->element(*regsetIx, regionId); + + // No unit conversion needed here since both "copt" and + // "oiip" are in the same units (i.e., SM3). + return { copt / oiip, measure::identity }; +} + +quantity roew_value(const fn_args& args, const std::string& region_name) +{ + const auto roiip = args.initial_inplace-> + get(region_name, Opm::Inplace::Phase::OIL, args.num); + + return oew_value(args, region_name, args.num, roiip); +} quantity roew(const fn_args& args) { @@ -1782,21 +1831,13 @@ quantity roew(const fn_args& args) return zero; } - double oil_prod = 0; - for (const auto& [well, global_index] : args.regionCache.connections(region_name, args.num)) { - const auto copt_key = fmt::format("COPT:{}:{}", well, global_index + 1); - - if (args.st.has(copt_key)) { - oil_prod += args.st.get(copt_key); - } + if ((args.reg_var_coll != nullptr) && (args.reg_var_map != nullptr)) { + return roew_value(args, region_name); } - oil_prod = args.unit_system.to_si(Opm::UnitSystem::measure::volume, oil_prod); - - return { - oil_prod / args.initial_inplace->get(region_name, Opm::Inplace::Phase::OIL, args.num), - measure::identity - }; + // If we don't have dedicated region variable collection or mapping, + // we cannot calculate ROEW. + return zero; } quantity roe(const fn_args& args) @@ -4379,6 +4420,9 @@ namespace Evaluator { const Opm::data::Aquifers& aquifers; const std::unordered_map& ireg; const Opm::data::ReservoirCouplingGroupRates* rc_rates; + + const Opm::data::RegionVariableMapping* reg_var_map{nullptr}; + const Opm::RegionVariableCollection* reg_var_coll{nullptr}; }; class Base @@ -4436,16 +4480,29 @@ namespace Evaluator { eFac.setFactors(this->node_, input.sched, wells, sim_step, simRes.wellSol); const fn_args args { - wells, this->group_name(), this->node_.keyword, - stepSize, static_cast(sim_step), - this->number(), this->node_.fip_region, - st, - simRes.wellSol, simRes.wbp, simRes.grpNwrkSol, - input.reg, input.grid, input.sched, - std::move(eFac.factors), - input.initial_inplace, simRes.inplace, - input.sched.getUnits(), - simRes.rc_rates + .schedule_wells = wells, + .group_name = this->group_name(), + .keyword_name = this->node_.keyword, + .duration = stepSize, + .sim_step = static_cast(sim_step), + .num = this->number(), + .extra_data = this->node_.fip_region, + .st = st, + .wells = simRes.wellSol, + .wbp = simRes.wbp, + .grp_nwrk = simRes.grpNwrkSol, + .regionCache = input.reg, + .grid = input.grid, + .schedule = input.sched, + .eff_factors = std::move(eFac.factors), + .initial_inplace = input.initial_inplace, + .inplace = simRes.inplace, + .unit_system = input.sched.getUnits(), + .rc_rates = simRes.rc_rates, + .lgr_grid_filter = std::nullopt, + .lgr_cell_filter = std::nullopt, + .reg_var_map = simRes.reg_var_map, + .reg_var_coll = simRes.reg_var_coll }; const auto& usys = input.es.getUnits(); @@ -4581,18 +4638,27 @@ namespace Evaluator { simRes.wellSol); const fn_args args { - wells, "", this->node_.keyword, - stepSize, static_cast(sim_step), - /*num=*/0, /*extra_data=*/std::nullopt, - st, - simRes.wellSol, simRes.wbp, simRes.grpNwrkSol, - input.reg, input.grid, input.sched, - std::move(eFac.factors), - input.initial_inplace, simRes.inplace, - input.sched.getUnits(), - simRes.rc_rates, - /*lgr_grid_filter=*/lgr_id, - /*lgr_cell_filter=*/gridLocalCellIndex, + .schedule_wells = wells, + .group_name = "", + .keyword_name = this->node_.keyword, + .duration = stepSize, + .sim_step = static_cast(sim_step), + .num = 0, + .extra_data = std::nullopt, + .st = st, + .wells = simRes.wellSol, + .wbp = simRes.wbp, + .grp_nwrk = simRes.grpNwrkSol, + .regionCache = input.reg, + .grid = input.grid, + .schedule = input.sched, + .eff_factors = std::move(eFac.factors), + .initial_inplace = input.initial_inplace, + .inplace = simRes.inplace, + .unit_system = input.sched.getUnits(), + .rc_rates = simRes.rc_rates, + .lgr_grid_filter = lgr_id, + .lgr_cell_filter = gridLocalCellIndex, }; const auto& usys = input.es.getUnits(); @@ -6307,17 +6373,19 @@ eval(const int sim_step, ? *values.interreg_flows : DynamicSimulatorState::InterRegFlowValues{}; const Evaluator::SimulatorResults simRes { - well_solution, - wbp, - group_and_nwrk_solution, - single_values, - inplace, - region_values, - block_values, - lgr_block_values, - aquifer_values, - interreg_flows, - values.rc_group_rates + .wellSol = well_solution, + .wbp = wbp, + .grpNwrkSol = group_and_nwrk_solution, + .single = single_values, + .inplace = inplace, + .region = region_values, + .block = block_values, + .lgr_block = lgr_block_values, + .aquifers = aquifer_values, + .ireg = interreg_flows, + .rc_rates = values.rc_group_rates, + .reg_var_map = values.reg_var_map, + .reg_var_coll = values.reg_var_coll }; for (auto& evalPtr : this->outputParameters_.getEvaluators()) { @@ -6826,6 +6894,8 @@ void Summary::eval(const int report_step, this->pImpl_->eval(sim_step, secs_elapsed, values, st); } +Summary::~Summary() = default; + void Summary::add_timestep(const SummaryState& st, const int report_step, const int ministep_id, @@ -6839,6 +6909,4 @@ void Summary::write(const bool is_final_summary) const this->pImpl_->write(is_final_summary); } -Summary::~Summary() {} - } // namespace Opm::out diff --git a/opm/output/eclipse/Summary.hpp b/opm/output/eclipse/Summary.hpp index ff18f026624..86da2f84054 100644 --- a/opm/output/eclipse/Summary.hpp +++ b/opm/output/eclipse/Summary.hpp @@ -37,6 +37,7 @@ namespace Opm { class EclipseGrid; class EclipseState; class Inplace; + class RegionVariableCollection; class Schedule; class SummaryConfig; class SummaryState; @@ -45,6 +46,7 @@ namespace Opm { namespace Opm::data { class GroupAndNetworkValues; class InterRegFlowMap; + class RegionVariableMapping; struct WellBlockAveragePressures; class Wells; } // namespace Opm::data @@ -137,6 +139,16 @@ class Summary /// Nullptr if unavailable. const RegionParameters* region_values {nullptr}; + /// Name-to-index mapping for region variables. + /// + /// Nullptr if unavailable. + const data::RegionVariableMapping* reg_var_map {nullptr}; + + /// Collection of region variables. + /// + /// Nullptr if unavailable. + const RegionVariableCollection* reg_var_coll {nullptr}; + /// Block (cell) level dynamic state values. /// /// Selection configured by the SummaryConfig object. Nullptr if