From df009eec54b8114c1105ee175626b4ea3e87a665 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A5rd=20Skaflestad?= Date: Mon, 13 Jul 2026 17:30:03 +0200 Subject: [PATCH] Track Per-Region Cumulative Oil Production From Wells This commit maintains a record of the cumulative produced volume of oil attributable to each region of each defined region set. To this end, we define a special purpose "ConnOPT" region-level summary variable and update this at the end of each converged time step as needed. The variable is defined only if the run requests any of the *OEW* recovery factor summary vectors. In that case, we forward the dynamic values to the summary vector calculation engine in the I/O layer. --- flowexperimental/comp/wells/CompWellModel.hpp | 8 +- opm/simulators/flow/EclGenericWriter.hpp | 11 ++- opm/simulators/flow/EclGenericWriter_impl.hpp | 38 ++++++---- opm/simulators/flow/EclWriter.hpp | 23 ++++++ opm/simulators/flow/GenericOutputModule.cpp | 75 ++++++++++++++----- opm/simulators/flow/GenericOutputModule.hpp | 34 +++++++++ opm/simulators/flow/OutputBlackoilModule.hpp | 3 + .../wells/BlackoilWellModelGeneric.cpp | 31 ++++++++ .../wells/BlackoilWellModelGeneric.hpp | 5 ++ opm/simulators/wells/WellState.cpp | 44 +++++++++++ opm/simulators/wells/WellState.hpp | 4 + 11 files changed, 240 insertions(+), 36 deletions(-) diff --git a/flowexperimental/comp/wells/CompWellModel.hpp b/flowexperimental/comp/wells/CompWellModel.hpp index 940dc7bee0a..2391942bdda 100644 --- a/flowexperimental/comp/wells/CompWellModel.hpp +++ b/flowexperimental/comp/wells/CompWellModel.hpp @@ -38,12 +38,13 @@ #include - +#include #include #include namespace Opm { +class RegionVariableCollection; class Schedule; struct NewtonIterationContext; @@ -126,6 +127,11 @@ class CompWellModel : public WellConnectionAuxiliaryModule class LevelCartesianIndexMapper; struct NNCdata; +class RegionVariableCollection; class Schedule; class SummaryConfig; class SummaryState; @@ -58,9 +59,13 @@ class UDQState; } // namespace Opm -namespace Opm { namespace Action { +namespace Opm::Action { class State; -}} // namespace Opm::Action +} // namespace Opm::Action + +namespace Opm::data { +class RegionVariableMapping; +} // namespace Opm::data namespace Opm { @@ -149,6 +154,8 @@ class EclGenericWriter const std::map, double>& lgrBlockData, const std::map& miscSummaryData, const std::map>& regionData, + const data::RegionVariableMapping& regVarMap, + const RegionVariableCollection& regVars, const Inplace& inplace, const Inplace* initialInPlace, const InterRegFlowMap& interRegFlows, diff --git a/opm/simulators/flow/EclGenericWriter_impl.hpp b/opm/simulators/flow/EclGenericWriter_impl.hpp index dc178d77acb..1abf41c79d4 100644 --- a/opm/simulators/flow/EclGenericWriter_impl.hpp +++ b/opm/simulators/flow/EclGenericWriter_impl.hpp @@ -45,7 +45,10 @@ #include +#include + #include +#include #include #include @@ -1028,6 +1031,8 @@ evalSummary(const int reportStepNum, const std::map, double>& lgrBlockData, const std::map& miscSummaryData, const std::map>& regionData, + const data::RegionVariableMapping& regVarMap, + const RegionVariableCollection& regVars, const Inplace& inplace, const Inplace* initialInPlace, const InterRegFlowMap& interRegFlows, @@ -1036,8 +1041,6 @@ evalSummary(const int reportStepNum, const data::ReservoirCouplingGroupRates* rcGroupRates) { if (collectOnIORank_.isIORank()) { - const auto& summary = eclIO_->summary(); - const auto& wellData = this->collectOnIORank_.isParallel() ? this->collectOnIORank_.globalWellData() : localWellData; @@ -1057,23 +1060,26 @@ evalSummary(const int reportStepNum, const auto interreg_flows = getInterRegFlowsAsMap(interRegFlows); const auto values = out::Summary::DynamicSimulatorState { - /* well_solution = */ &wellData, - /* wbp = */ &wbpData, - /* group_and_nwrk_solution = */ &groupAndNetworkData, - /* single_values = */ &miscSummaryData, - /* region_values = */ ®ionData, - /* block_values = */ &blockData, - /* aquifer_values = */ &aquiferData, - /* interreg_flows = */ &interreg_flows, - /* rc_group_rates = */ rcGroupRates, - /* inplace = */ { - /* current = */ &inplace, - /* initial = */ initialInPlace + .well_solution = &wellData, + .wbp = &wbpData, + .group_and_nwrk_solution = &groupAndNetworkData, + .single_values = &miscSummaryData, + .region_values = ®ionData, + .reg_var_map = ®VarMap, + .reg_var_coll = ®Vars, + .block_values = &blockData, + .aquifer_values = &aquiferData, + .interreg_flows = &interreg_flows, + .rc_group_rates = rcGroupRates, + .inplace = { + .current = &inplace, + .initial = initialInPlace }, - /* lgr_block_values = */ &lgrBlockData + .lgr_block_values = &lgrBlockData }; - summary.eval(reportStepNum, curTime, values, summaryState); + this->eclIO_->summary() + .eval(reportStepNum, curTime, values, summaryState); // Off-by-one-fun: The reportStepNum argument corresponds to the // report step these results will be written to, whereas the diff --git a/opm/simulators/flow/EclWriter.hpp b/opm/simulators/flow/EclWriter.hpp index 434a87e764a..b0a3bedcde8 100644 --- a/opm/simulators/flow/EclWriter.hpp +++ b/opm/simulators/flow/EclWriter.hpp @@ -38,6 +38,7 @@ #include #include +#include #include #include // Properties::EnableMech, EnableSolvent @@ -59,6 +60,8 @@ #include +#include +#include #include #include #include @@ -238,6 +241,19 @@ class EclWriter : public EclGenericWriter simulator_.setupTimer().realTimeElapsed() + simulator_.vanguard().setupTime(); + auto& regVars = this->outputModule_->regionVariables(); + + regVars.prepareValueAccumulation(); + + if (const auto conn_opt_ix = regVars + .variableIndex(this->outputModule_->regVarMapping(), "ConnOPT"); + conn_opt_ix.has_value()) + { + this->simulator_.problem() + .wellModel().reportIntervalConnectionOilProduction + (this->simulator_.timeStepSize(), *conn_opt_ix, regVars); + } + const auto localWellData = simulator_.problem().wellModel().wellData(); const auto localWBP = simulator_.problem().wellModel().wellBlockAveragePressures(); const auto localGroupAndNetworkData = simulator_.problem().wellModel() @@ -333,6 +349,11 @@ class EclWriter : public EclGenericWriter { OPM_TIMEBLOCK(evalSummary); + // Note: This statement sums one value per registered region + // variable per region per registered region set across all MPI + // ranks. + regVars.commitValues(); + const auto& blockData = this->collectOnIORank_.isParallel() ? this->collectOnIORank_.globalBlockData() : this->outputModule_->getBlockData(); @@ -355,6 +376,8 @@ class EclWriter : public EclGenericWriter lgrBlockData, miscSummaryData, regionData, + this->outputModule_->regVarMapping(), + regVars, inplace, this->outputModule_->initialInplace(), interRegFlows, diff --git a/opm/simulators/flow/GenericOutputModule.cpp b/opm/simulators/flow/GenericOutputModule.cpp index b249e1074a6..7dc8e2dece0 100644 --- a/opm/simulators/flow/GenericOutputModule.cpp +++ b/opm/simulators/flow/GenericOutputModule.cpp @@ -35,6 +35,7 @@ #include #include +#include #include #include @@ -49,8 +50,15 @@ #include +#include +#include #include +#include + +#include +#include +#include #include #include @@ -58,6 +66,7 @@ #include #include #include +#include #include #include #include @@ -157,6 +166,8 @@ GenericOutputModule(const EclipseState& eclState, , enableExtbo_(enableExtbo) , enableBioeffects_(enableBioeffects) , enableGeochemistry_(enableGeochemistry) + , regionVars_ { std::make_unique(comm), + std::make_unique(comm) } , flowsC_(schedule, summaryConfig, isInterior) , rftC_(eclState_, schedule_, [this](const std::string& wname) { return this->isOwnedByCurrentRank(wname); }, @@ -164,25 +175,13 @@ GenericOutputModule(const EclipseState& eclState, , rst_conv_(std::move(globalCell), comm) , local_data_valid_(false) { - const auto& fp = eclState_.fieldProps(); - - this->regions_["FIPNUM"] = fp.get_int("FIPNUM"); - for (const auto& region : fp.fip_regions()) { - this->regions_[region] = fp.get_int(region); - } - - this->RPRNodes_ = summaryConfig_.keywords("RPR*"); - this->RPRPNodes_ = summaryConfig_.keywords("RPRP*"); - - for (const auto& phase : Inplace::phases()) { - std::string key_pattern = "R" + Inplace::EclString(phase) + "*"; - this->regionNodes_[phase] = summaryConfig_.keywords(key_pattern); - } - - forceDisableFipOutput_ = + this->forceDisableFipOutput_ = Parameters::Get(); - forceDisableFipresvOutput_ = + + this->forceDisableFipresvOutput_ = Parameters::Get(); + + this->initialiseRegionVariableSupport(); } template @@ -1274,6 +1273,48 @@ assignGlobalFieldsToSolution(data::Solution& sol) this->rst_conv_.outputRestart(sol); } +template +void GenericOutputModule:: +initialiseRegionVariableSupport() +{ + const auto& fp = this->eclState_.fieldProps(); + + this->regions_.insert_or_assign("FIPNUM", fp.get_int("FIPNUM")); + for (const auto& region : fp.fip_regions()) { + this->regions_.insert_or_assign(region, fp.get_int(region)); + } + + this->RPRNodes_ = this->summaryConfig_.keywords("RPR*"); + this->RPRPNodes_ = this->summaryConfig_.keywords("RPRP*"); + + for (const auto& phase : Inplace::phases()) { + const auto key_pattern = fmt::format("R{}*", Inplace::EclString(phase)); + this->regionNodes_.insert_or_assign(phase, this->summaryConfig_.keywords(key_pattern)); + } + + // ----------------------------------------------------------------------- + + this->regVarMap_.prepareRegistration(); + + populateRegVarMapping(this->summaryConfig_, this->regVarMap_); + + this->regVarMap_.commitStructure(); + + { + const auto declaredMaxRegID = + declaredMaxRegionID(this->eclState_.runspec()); + + this->regionVars_.initialise(static_cast(declaredMaxRegID), + fp, this->regVarMap_); + } +} + +// =========================================================================== +// Explicit template specialisations below separator +// +// No other code permitted here. +// =========================================================================== + template using FS = BlackOilFluidSystem; #define INSTANTIATE_TYPE(T) \ diff --git a/opm/simulators/flow/GenericOutputModule.hpp b/opm/simulators/flow/GenericOutputModule.hpp index 8815096b36b..e1a00795ccd 100644 --- a/opm/simulators/flow/GenericOutputModule.hpp +++ b/opm/simulators/flow/GenericOutputModule.hpp @@ -30,8 +30,11 @@ #include #include +#include #include + #include +#include #include #include @@ -54,6 +57,7 @@ #include #include #include +#include #include #include #include @@ -294,6 +298,16 @@ class GenericOutputModule { const RSTConv& getConv() const { return this->rst_conv_; } + const data::RegionVariableMapping& regVarMapping() const + { + return this->regVarMap_; + } + + RegionVariableCollection& regionVariables() + { + return this->regionVars_; + } + //! \brief Assign fields that are in global numbering to the solution. //! \detail This is used to add fields that for some reason cannot be collected //! using the regular collect mechanism to the solution. In particular this @@ -468,6 +482,15 @@ class GenericOutputModule { bool forceDisableFipresvOutput_{false}; bool computeFip_{false}; + /// Current values of input data for region-level summary vectors. + /// + /// Acceleration structure for region-level summary vector calculations, + /// aimed at minimising MPI overhead for calculating these vectors. + /// Contents depend on runtime configuration in summaryConfig_. + /// + /// Populated by initialiseRegionVariableSupport(). + RegionVariableCollection regionVars_; + FIPContainer fipC_; std::unordered_map> regions_; std::unordered_map> regionNodes_; @@ -475,6 +498,11 @@ class GenericOutputModule { std::vector RPRNodes_; std::vector RPRPNodes_; + /// Map region variable names and region set names to numeric indices. + /// + /// Populated by initialiseRegionVariableSupport(). + data::RegionVariableMapping regVarMap_{}; + std::vector failedCellsPb_; std::vector failedCellsPd_; @@ -554,6 +582,12 @@ class GenericOutputModule { bool local_data_valid_{false}; std::optional regionAvgDensity_; + +private: + /// Initialise acceleration structures for region-level summary vector calculations. + /// + /// Populates regionVars_ and regVarMap_. + void initialiseRegionVariableSupport(); }; } // namespace Opm diff --git a/opm/simulators/flow/OutputBlackoilModule.hpp b/opm/simulators/flow/OutputBlackoilModule.hpp index 93b7e251643..a3141c1795c 100644 --- a/opm/simulators/flow/OutputBlackoilModule.hpp +++ b/opm/simulators/flow/OutputBlackoilModule.hpp @@ -51,8 +51,11 @@ #include #include +#include + #include #include +#include #include #include diff --git a/opm/simulators/wells/BlackoilWellModelGeneric.cpp b/opm/simulators/wells/BlackoilWellModelGeneric.cpp index f92b7b2879a..2c6b0967fc7 100644 --- a/opm/simulators/wells/BlackoilWellModelGeneric.cpp +++ b/opm/simulators/wells/BlackoilWellModelGeneric.cpp @@ -26,9 +26,12 @@ #include #include + #include #include #include + +#include #include #include @@ -1256,6 +1259,34 @@ groupAndNetworkData(const int reportStepIdx) const return grp_nwrk_values; } +template +void BlackoilWellModelGeneric:: +reportIntervalConnectionOilProduction(const double dt, + const std::size_t conn_opt_ix, + RegionVariableCollection& regVars) const +{ + auto wasDynamicallyClosed = [this](const std::string& wname) + { + return this->wasDynamicallyShutThisTimeStep(wname); + }; + + auto wellEffFac = [this](const std::string& wname) + { + auto wellPtrPos = std::ranges::find_if + (this->well_container_generic_, + [&wname](const auto* wellPtr) { return wellPtr->name() == wname; }); + + return (wellPtrPos == this->well_container_generic_.end()) + ? 0.0 + : (*wellPtrPos)->wellEfficiencyFactor(); + }; + + this->wellState().reportIntervalConnectionOilProduction + (dt, wasDynamicallyClosed, wellEffFac, + [conn_opt_ix, ®Vars](const std::size_t cell_ix, const double copt) + { regVars.addCellValue(conn_opt_ix, cell_ix, copt); }); +} + template void BlackoilWellModelGeneric:: updateAndCommunicateGroupData(const int reportStepIdx, diff --git a/opm/simulators/wells/BlackoilWellModelGeneric.hpp b/opm/simulators/wells/BlackoilWellModelGeneric.hpp index 977ee65ea14..432f0b94808 100644 --- a/opm/simulators/wells/BlackoilWellModelGeneric.hpp +++ b/opm/simulators/wells/BlackoilWellModelGeneric.hpp @@ -68,6 +68,7 @@ namespace Opm { template class GasLiftWellState; class Group; class GuideRateConfig; + class RegionVariableCollection; class RestartValue; class Schedule; struct SimulatorUpdate; @@ -215,6 +216,10 @@ class BlackoilWellModelGeneric data::GroupAndNetworkValues groupAndNetworkData(const int reportStepIdx) const; + void reportIntervalConnectionOilProduction(const double dt, + const std::size_t conn_opt_ix, + RegionVariableCollection& regVars) const; + /// Shut down any single well /// Returns true if the well was actually found and shut. bool forceShutWellByName(const std::string& wellname, diff --git a/opm/simulators/wells/WellState.cpp b/opm/simulators/wells/WellState.cpp index c805a0e8155..1198137e7f4 100644 --- a/opm/simulators/wells/WellState.cpp +++ b/opm/simulators/wells/WellState.cpp @@ -672,6 +672,50 @@ report(const int* globalCellIdxMap, return res; } +template +void WellState:: +reportIntervalConnectionOilProduction(const double dt, + const std::function& wasDynamicallyClosed, + const std::function& wellEffFac, + const std::function& copt) const +{ + const auto& pu = this->phaseUsageInfo_; + + if (! pu.phaseIsActive(oilPhaseIdx)) { + return; + } + + const auto np = static_cast(this->numPhases()); + const auto opos = pu.canonicalToActivePhaseIdx(oilPhaseIdx); + + for (const auto& xw : this->wells_) { + if (! xw.producer || + ! xw.parallel_info.get().hasLocalCells() || + ((xw.status == Well::Status::SHUT) && + ! wasDynamicallyClosed(xw.name))) + { + // Not a producing well, not connected to this rank, or the well + // is shut. In other words, there is nothing to do for this + // well on this rank. + continue; + } + + const auto effective_dt = dt * wellEffFac(xw.name); + + if (! (effective_dt > 0.0)) { + continue; + } + + const auto* qo = &xw.perf_data.phase_rates[0*np + opos]; + + for (const auto& cell : xw.perf_data.cell_index) { + // Note sign of *qo to convert from injection to production. + copt(cell, (- *qo) * effective_dt); + qo += np; + } + } +} + template void WellState:: reportConnections(std::vector& connections, diff --git a/opm/simulators/wells/WellState.hpp b/opm/simulators/wells/WellState.hpp index d390c4c38af..27a7fee03dc 100644 --- a/opm/simulators/wells/WellState.hpp +++ b/opm/simulators/wells/WellState.hpp @@ -179,6 +179,10 @@ class WellState const std::function& wasDynamicallyClosed, const RsConstInfo& rsConst = RsConstInfo{}) const; + void reportIntervalConnectionOilProduction(const double dt, + const std::function& wasDynamicallyClosed, + const std::function& wellEffFac, + const std::function& copt) const; /// init the MS well related. /// \param wells_ecl Vector of ECL well definitions