From 4f8af0681335608eaa0958da21c429d42d7a1b5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5kon=20H=C3=A6gland?= Date: Tue, 25 Nov 2025 13:21:52 +0100 Subject: [PATCH 1/3] Avoid hidden dual state pattern The solveWellWithZeroRate() method created an empty group state and pushed it into the wgHelper object, then passed wgHelper (along with the simulator object) to iterateWellEqWithSwitching(). Further down the call chain in wellUnderZeroGroupRateTarget(), the real group state was recovered by accessing the simulator object, defeating the convention that wgHelper should be the single source of truth for the current group state. solveWellWithZeroRate() needs to use both an empty group state when assembling the control equation and the real group state when querying the well's actual operational status in wellUnderZeroGroupRateTarget() (to check whether its parent group has a zero rate target). We solve this by passing a flag "solving_with_zero_rate" down from solveWellWithZeroRate() and letting the assembleWellEqWithoutIterationImpl() method check the flag and create the empty group state locally when the flag is set. This eliminates the need for pushing/popping group state in wgHelper while maintaining the distinction between assembly (uses empty group state) and queries (uses real group state). The issue of having both simulator and wgHelper as sources for the real group state will be addressed in a follow-up PR. --- .../wells/BlackoilWellModel_impl.hpp | 2 +- opm/simulators/wells/MultisegmentWell.hpp | 11 ++-- .../wells/MultisegmentWell_impl.hpp | 36 +++++++---- opm/simulators/wells/StandardWell.hpp | 14 +++-- opm/simulators/wells/StandardWell_impl.hpp | 60 +++++++++++-------- opm/simulators/wells/WellInterface.hpp | 17 ++++-- opm/simulators/wells/WellInterface_impl.hpp | 46 +++++++------- 7 files changed, 112 insertions(+), 74 deletions(-) diff --git a/opm/simulators/wells/BlackoilWellModel_impl.hpp b/opm/simulators/wells/BlackoilWellModel_impl.hpp index 4a4a3878113..ec66d96b9b0 100644 --- a/opm/simulators/wells/BlackoilWellModel_impl.hpp +++ b/opm/simulators/wells/BlackoilWellModel_impl.hpp @@ -1599,7 +1599,7 @@ namespace Opm { OPM_BEGIN_PARALLEL_TRY_CATCH(); for (auto& well: well_container_) { - well->assembleWellEqWithoutIteration(simulator_, dt, this->wellState(), this->groupState(), + well->assembleWellEqWithoutIteration(simulator_, this->wgHelper(), dt, this->wellState(), deferred_logger); } OPM_END_PARALLEL_TRY_CATCH_LOG(deferred_logger, "BlackoilWellModel::assembleWellEqWithoutIteration failed: ", diff --git a/opm/simulators/wells/MultisegmentWell.hpp b/opm/simulators/wells/MultisegmentWell.hpp index f4d79dfc11c..e93db297ad8 100644 --- a/opm/simulators/wells/MultisegmentWell.hpp +++ b/opm/simulators/wells/MultisegmentWell.hpp @@ -137,6 +137,7 @@ namespace Opm { DeferredLogger& deferred_logger) override; // should be const? void updateIPRImplicit(const Simulator& simulator, + const WellGroupHelperType& wgHelper, WellStateType& well_state, DeferredLogger& deferred_logger) override; @@ -234,7 +235,7 @@ namespace Opm { void getTransMult(Value& trans_mult, const Simulator& simulator, const int cell_indx) const; - + // get the mobility for specific perforation template void getMobility(const Simulator& simulator, @@ -287,15 +288,17 @@ namespace Opm { WellStateType& well_state, DeferredLogger& deferred_logger, const bool fixed_control = false, - const bool fixed_status = false) override; + const bool fixed_status = false, + const bool solving_with_zero_rate = false) override; void assembleWellEqWithoutIteration(const Simulator& simulator, + const WellGroupHelperType& wgHelper, const double dt, const Well::InjectionControls& inj_controls, const Well::ProductionControls& prod_controls, WellStateType& well_state, - const GroupState& group_state, - DeferredLogger& deferred_logger) override; + DeferredLogger& deferred_logger, + const bool solving_with_zero_rate = false) override; void updateWaterThroughput(const double dt, WellStateType& well_state) const override; diff --git a/opm/simulators/wells/MultisegmentWell_impl.hpp b/opm/simulators/wells/MultisegmentWell_impl.hpp index 31f667056ce..388423d2555 100644 --- a/opm/simulators/wells/MultisegmentWell_impl.hpp +++ b/opm/simulators/wells/MultisegmentWell_impl.hpp @@ -1397,6 +1397,7 @@ namespace Opm void MultisegmentWell:: updateIPRImplicit(const Simulator& simulator, + const WellGroupHelperType& wgHelper, WellStateType& well_state, DeferredLogger& deferred_logger) { @@ -1427,7 +1428,6 @@ namespace Opm return; */ } - const auto& group_state = simulator.problem().wellModel().groupState(); std::fill(ws.implicit_ipr_a.begin(), ws.implicit_ipr_a.end(), 0.); std::fill(ws.implicit_ipr_b.begin(), ws.implicit_ipr_b.end(), 0.); @@ -1441,7 +1441,7 @@ namespace Opm const auto cmode = ws.production_cmode; ws.production_cmode = Well::ProducerCMode::BHP; const double dt = simulator.timeStepSize(); - assembleWellEqWithoutIteration(simulator, dt, inj_controls, prod_controls, well_state, group_state, deferred_logger); + assembleWellEqWithoutIteration(simulator, wgHelper, dt, inj_controls, prod_controls, well_state, deferred_logger); BVectorWell rhs(this->numberOfSegments()); rhs = 0.0; @@ -1526,7 +1526,6 @@ namespace Opm WellStateType& well_state, DeferredLogger& deferred_logger) { - const auto& group_state = wgHelper.groupState(); if (!this->isOperableAndSolvable() && !this->wellIsStopped()) return true; const int max_iter_number = this->param_.max_inner_iter_ms_wells_; @@ -1555,8 +1554,8 @@ namespace Opm this->regularize_ = true; } - assembleWellEqWithoutIteration(simulator, dt, inj_controls, prod_controls, - well_state, group_state, deferred_logger); + assembleWellEqWithoutIteration(simulator, wgHelper, dt, inj_controls, prod_controls, + well_state, deferred_logger); const auto report = getWellConvergence(simulator, well_state, Base::B_avg_, deferred_logger, relax_convergence); if (report.converged()) { @@ -1651,9 +1650,9 @@ namespace Opm WellStateType& well_state, DeferredLogger& deferred_logger, const bool fixed_control /*false*/, - const bool fixed_status /*false*/) + const bool fixed_status /*false*/, + const bool solving_with_zero_rate /*false*/) { - const auto& group_state = wgHelper.groupState(); const int max_iter_number = this->param_.max_inner_iter_ms_wells_; { @@ -1673,7 +1672,7 @@ namespace Opm bool converged = false; bool relax_convergence = false; this->regularize_ = false; - const auto& summary_state = simulator.vanguard().summaryState(); + const auto& summary_state = wgHelper.summaryState(); // Always take a few (more than one) iterations after a switch before allowing a new switch // The optimal number here is subject to further investigation, but it has been observerved @@ -1704,7 +1703,8 @@ namespace Opm const Scalar wqTotal = this->primary_variables_.getWQTotal().value(); bool changed = this->updateWellControlAndStatusLocalIteration( simulator, wgHelper, inj_controls, prod_controls, wqTotal, - well_state, deferred_logger, fixed_control, fixed_status + well_state, deferred_logger, fixed_control, fixed_status, + solving_with_zero_rate ); if (changed) { its_since_last_switch = 0; @@ -1729,8 +1729,8 @@ namespace Opm this->regularize_ = true; } - assembleWellEqWithoutIteration(simulator, dt, inj_controls, prod_controls, - well_state, group_state, deferred_logger); + assembleWellEqWithoutIteration(simulator, wgHelper, dt, inj_controls, prod_controls, + well_state, deferred_logger, solving_with_zero_rate); const auto report = getWellConvergence(simulator, well_state, Base::B_avg_, deferred_logger, relax_convergence); @@ -1822,15 +1822,17 @@ namespace Opm void MultisegmentWell:: assembleWellEqWithoutIteration(const Simulator& simulator, + const WellGroupHelperType& wgHelper, const double dt, const Well::InjectionControls& inj_controls, const Well::ProductionControls& prod_controls, WellStateType& well_state, - const GroupState& group_state, - DeferredLogger& deferred_logger) + DeferredLogger& deferred_logger, + const bool solving_with_zero_rate) { if (!this->isOperableAndSolvable() && !this->wellIsStopped()) return; + // update the upwinding segments this->segments_.updateUpwindingSegments(this->primary_variables_); @@ -1975,6 +1977,14 @@ namespace Opm const auto& summaryState = simulator.vanguard().summaryState(); const Schedule& schedule = simulator.vanguard().schedule(); const bool stopped_or_zero_target = this->stoppedOrZeroRateTarget(simulator, well_state, deferred_logger); + // When solving with zero rate (well isolation), use empty group_state to isolate + // from group constraints in assembly. + // Otherwise use real group state from wgHelper. + const GroupState empty_group_state; + const auto& group_state = solving_with_zero_rate + ? empty_group_state + : wgHelper.groupState(); + MultisegmentWellAssemble(*this). assembleControlEq(well_state, group_state, diff --git a/opm/simulators/wells/StandardWell.hpp b/opm/simulators/wells/StandardWell.hpp index c060ff21251..fb40bb9dfeb 100644 --- a/opm/simulators/wells/StandardWell.hpp +++ b/opm/simulators/wells/StandardWell.hpp @@ -212,7 +212,8 @@ namespace Opm WellStateType& well_state, DeferredLogger& deferred_logger, const bool fixed_control = false, - const bool fixed_status = false) override; + const bool fixed_status = false, + const bool solving_with_zero_rate = false) override; /* returns BHP */ Scalar computeWellRatesAndBhpWithThpAlqProd(const Simulator& ebos_simulator, @@ -238,6 +239,7 @@ namespace Opm bool iterate_if_no_solution) const override; void updateIPRImplicit(const Simulator& simulator, + const WellGroupHelperType& wgHelper, WellStateType& well_state, DeferredLogger& deferred_logger) override; @@ -360,20 +362,22 @@ namespace Opm DeferredLogger& deferred_logger) const; void assembleWellEqWithoutIteration(const Simulator& simulator, + const WellGroupHelperType& wgHelper, const double dt, const Well::InjectionControls& inj_controls, const Well::ProductionControls& prod_controls, WellStateType& well_state, - const GroupState& group_state, - DeferredLogger& deferred_logger) override; + DeferredLogger& deferred_logger, + const bool solving_with_zero_rate = false) override; void assembleWellEqWithoutIterationImpl(const Simulator& simulator, + const WellGroupHelperType& wgHelper, const double dt, const Well::InjectionControls& inj_controls, const Well::ProductionControls& prod_controls, WellStateType& well_state, - const GroupState& group_state, - DeferredLogger& deferred_logger); + DeferredLogger& deferred_logger, + const bool solving_with_zero_rate); void calculateSinglePerf(const Simulator& simulator, const int perf, diff --git a/opm/simulators/wells/StandardWell_impl.hpp b/opm/simulators/wells/StandardWell_impl.hpp index 48c08d847a5..d9ff924689c 100644 --- a/opm/simulators/wells/StandardWell_impl.hpp +++ b/opm/simulators/wells/StandardWell_impl.hpp @@ -339,12 +339,13 @@ namespace Opm void StandardWell:: assembleWellEqWithoutIteration(const Simulator& simulator, + const WellGroupHelperType& wgHelper, const double dt, const Well::InjectionControls& inj_controls, const Well::ProductionControls& prod_controls, WellStateType& well_state, - const GroupState& group_state, - DeferredLogger& deferred_logger) + DeferredLogger& deferred_logger, + const bool solving_with_zero_rate) { // TODO: only_wells should be put back to save some computation // for example, the matrices B C does not need to update if only_wells @@ -353,9 +354,9 @@ namespace Opm // clear all entries this->linSys_.clear(); - assembleWellEqWithoutIterationImpl(simulator, dt, inj_controls, + assembleWellEqWithoutIterationImpl(simulator, wgHelper, dt, inj_controls, prod_controls, well_state, - group_state, deferred_logger); + deferred_logger, solving_with_zero_rate); } @@ -365,12 +366,13 @@ namespace Opm void StandardWell:: assembleWellEqWithoutIterationImpl(const Simulator& simulator, + const WellGroupHelperType& wgHelper, const double dt, const Well::InjectionControls& inj_controls, const Well::ProductionControls& prod_controls, WellStateType& well_state, - const GroupState& group_state, - DeferredLogger& deferred_logger) + DeferredLogger& deferred_logger, + const bool solving_with_zero_rate) { // try to regularize equation if the well does not converge const Scalar regularization_factor = this->regularize_? this->param_.regularization_factor_wells_ : 1.0; @@ -468,16 +470,24 @@ namespace Opm const auto& summaryState = simulator.vanguard().summaryState(); const Schedule& schedule = simulator.vanguard().schedule(); const bool stopped_or_zero_target = this->stoppedOrZeroRateTarget(simulator, well_state, deferred_logger); - StandardWellAssemble(*this). - assembleControlEq(well_state, group_state, - schedule, summaryState, - inj_controls, prod_controls, - this->primary_variables_, - this->getRefDensity(), - this->linSys_, - stopped_or_zero_target, - deferred_logger); - + { + // When solving_with_zero_rate=true (called from solveWellWithZeroRate), + // we use an empty GroupState to isolate the well from group constraints during assembly. + // This allows us to solve the well equations independently of group controls/targets. + const GroupState empty_group_state; + const auto& group_state = solving_with_zero_rate + ? empty_group_state + : wgHelper.groupState(); + StandardWellAssemble(*this). + assembleControlEq(well_state, group_state, + schedule, summaryState, + inj_controls, prod_controls, + this->primary_variables_, + this->getRefDensity(), + this->linSys_, + stopped_or_zero_target, + deferred_logger); + } // do the local inversion of D. try { @@ -909,6 +919,7 @@ namespace Opm void StandardWell:: updateIPRImplicit(const Simulator& simulator, + const WellGroupHelperType& wgHelper, WellStateType& well_state, DeferredLogger& deferred_logger) { @@ -939,7 +950,6 @@ namespace Opm return; */ } - const auto& group_state = simulator.problem().wellModel().groupState(); std::fill(ws.implicit_ipr_a.begin(), ws.implicit_ipr_a.end(), 0.); std::fill(ws.implicit_ipr_b.begin(), ws.implicit_ipr_b.end(), 0.); @@ -953,7 +963,7 @@ namespace Opm const auto cmode = ws.production_cmode; ws.production_cmode = Well::ProducerCMode::BHP; const double dt = simulator.timeStepSize(); - assembleWellEqWithoutIteration(simulator, dt, inj_controls, prod_controls, well_state, group_state, deferred_logger); + assembleWellEqWithoutIteration(simulator, wgHelper, dt, inj_controls, prod_controls, well_state, deferred_logger); const size_t nEq = this->primary_variables_.numWellEq(); BVectorWell rhs(1); @@ -2401,7 +2411,6 @@ namespace Opm WellStateType& well_state, DeferredLogger& deferred_logger) { - const auto& group_state = wgHelper.groupState(); updatePrimaryVariables(simulator, well_state, deferred_logger); const int max_iter = this->param_.max_inner_iter_wells_; @@ -2410,7 +2419,7 @@ namespace Opm bool relax_convergence = false; this->regularize_ = false; do { - assembleWellEqWithoutIteration(simulator, dt, inj_controls, prod_controls, well_state, group_state, deferred_logger); + assembleWellEqWithoutIteration(simulator, wgHelper, dt, inj_controls, prod_controls, well_state, deferred_logger); if (it > this->param_.strict_inner_iter_wells_) { relax_convergence = true; @@ -2464,9 +2473,9 @@ namespace Opm WellStateType& well_state, DeferredLogger& deferred_logger, const bool fixed_control /*false*/, - const bool fixed_status /*false*/) + const bool fixed_status /*false*/, + const bool solving_with_zero_rate /*false*/) { - const auto& group_state = wgHelper.groupState(); updatePrimaryVariables(simulator, well_state, deferred_logger); const int max_iter = this->param_.max_inner_iter_wells_; @@ -2474,7 +2483,7 @@ namespace Opm bool converged = false; bool relax_convergence = false; this->regularize_ = false; - const auto& summary_state = simulator.vanguard().summaryState(); + const auto& summary_state = wgHelper.summaryState(); // Always take a few (more than one) iterations after a switch before allowing a new switch // The optimal number here is subject to further investigation, but it has been observerved @@ -2507,7 +2516,8 @@ namespace Opm const Scalar wqTotal = this->primary_variables_.eval(WQTotal).value(); changed = this->updateWellControlAndStatusLocalIteration( simulator, wgHelper, inj_controls, prod_controls, wqTotal, - well_state, deferred_logger, fixed_control, fixed_status + well_state, deferred_logger, fixed_control, fixed_status, + solving_with_zero_rate ); if (changed){ its_since_last_switch = 0; @@ -2527,7 +2537,7 @@ namespace Opm } } - assembleWellEqWithoutIteration(simulator, dt, inj_controls, prod_controls, well_state, group_state, deferred_logger); + assembleWellEqWithoutIteration(simulator, wgHelper, dt, inj_controls, prod_controls, well_state, deferred_logger, solving_with_zero_rate); if (it > this->param_.strict_inner_iter_wells_) { relax_convergence = true; diff --git a/opm/simulators/wells/WellInterface.hpp b/opm/simulators/wells/WellInterface.hpp index a1262c66ba6..1e76dfd350c 100644 --- a/opm/simulators/wells/WellInterface.hpp +++ b/opm/simulators/wells/WellInterface.hpp @@ -174,10 +174,11 @@ class WellInterface : public WellInterfaceIndices& group_state, - DeferredLogger& deferred_logger); + DeferredLogger& deferred_logger, + const bool solving_with_zero_rate = false); // TODO: better name or further refactoring the function to make it more clear void prepareWellBeforeAssembling(const Simulator& simulator, @@ -265,7 +266,8 @@ class WellInterface : public WellInterfaceIndices connectionRates_; @@ -404,12 +407,13 @@ class WellInterface : public WellInterfaceIndices& group_state, - DeferredLogger& deferred_logger) = 0; + DeferredLogger& deferred_logger, + const bool solving_with_zero_rate = false) = 0; // iterate well equations with the specified control until converged virtual bool iterateWellEqWithControl(const Simulator& simulator, @@ -421,6 +425,7 @@ class WellInterface : public WellInterfaceIndicesisInjector() ? inj_controls.hasControl(Well::InjectorCMode::GRUP) : prod_controls.hasControl(Well::ProducerCMode::GRUP); bool isGroupControl = ws.production_cmode == Well::ProducerCMode::GRUP || ws.injection_cmode == Well::InjectorCMode::GRUP; @@ -626,7 +630,7 @@ namespace Opm if (converged && !stoppedOrZeroRateTarget(simulator, well_state, deferred_logger) && isThp) { auto rates = well_state.well(this->index_of_well_).surface_rates; this->adaptRatesForVFP(rates); - this->updateIPRImplicit(simulator, well_state, deferred_logger); + this->updateIPRImplicit(simulator, wgHelper, well_state, deferred_logger); bool is_stable = WellBhpThpCalculator(*this).isStableSolution(well_state, this->well_ecl_, rates, summary_state); if (!is_stable) { // solution converged to an unstable point! @@ -721,7 +725,7 @@ namespace Opm if (!converged || this->wellIsStopped()) { return std::nullopt; } - this->updateIPRImplicit(simulator, well_state, deferred_logger); + this->updateIPRImplicit(simulator, wgHelper, well_state, deferred_logger); auto rates = well_state.well(this->index_of_well_).surface_rates; this->adaptRatesForVFP(rates); return WellBhpThpCalculator(*this).estimateStableBhp(well_state, this->well_ecl_, rates, this->getRefDensity(), summary_state); @@ -784,22 +788,24 @@ namespace Opm DeferredLogger& deferred_logger) { OPM_TIMEFUNCTION(); - // Solve a well as stopped + // Solve a well as stopped with isolation (empty group state for assembly) const auto well_status_orig = this->wellStatus_; this->stopWell(); - auto group_state = GroupState(); // empty group - WellGroupHelperType wgHelper_copy = wgHelper; - // Ensure that wgHelper_copy uses the empty group state as GroupState for iterateWellEqWithSwitching() - // and the guard ensures that the original group state is restored at scope exit, i.e. at - // the end of this function. - auto group_guard = wgHelper_copy.pushGroupState(group_state); - auto inj_controls = Well::InjectionControls(0); auto prod_controls = Well::ProductionControls(0); - const bool converged = this->iterateWellEqWithSwitching( - simulator, dt, inj_controls, prod_controls, wgHelper_copy, well_state, - deferred_logger, /*fixed_control*/true, /*fixed_status*/ true + + // Solve with well isolation - the flag "solving_with_zero_rate=true" will be passed down to + // assembleWellEqWithoutIterationImpl() to create an empty group state when assembling the + // well equations. + const bool converged = this->iterateWellEqWithSwitching( + simulator, dt, inj_controls, prod_controls, + wgHelper, // Real wgHelper - single source of truth + well_state, + deferred_logger, + /*fixed_control*/true, + /*fixed_status*/true, + /*solving_with_zero_rate*/true ); this->wellStatus_ = well_status_orig; return converged; @@ -929,9 +935,8 @@ namespace Opm DeferredLogger& deferred_logger) { OPM_TIMEFUNCTION(); - const auto& group_state = wgHelper.groupState(); prepareWellBeforeAssembling(simulator, dt, wgHelper, well_state, deferred_logger); - assembleWellEqWithoutIteration(simulator, dt, well_state, group_state, deferred_logger); + assembleWellEqWithoutIteration(simulator, wgHelper, dt, well_state, deferred_logger); } @@ -940,10 +945,11 @@ namespace Opm void WellInterface:: assembleWellEqWithoutIteration(const Simulator& simulator, + const WellGroupHelperType& wgHelper, const double dt, WellStateType& well_state, - const GroupState& group_state, - DeferredLogger& deferred_logger) + DeferredLogger& deferred_logger, + const bool solving_with_zero_rate) { OPM_TIMEFUNCTION(); const auto& summary_state = simulator.vanguard().summaryState(); @@ -951,7 +957,7 @@ namespace Opm const auto prod_controls = this->well_ecl_.isProducer() ? this->well_ecl_.productionControls(summary_state) : Well::ProductionControls(0); // TODO: the reason to have inj_controls and prod_controls in the arguments, is that we want to change the control used for the well functions // TODO: maybe we can use std::optional or pointers to simplify here - assembleWellEqWithoutIteration(simulator, dt, inj_controls, prod_controls, well_state, group_state, deferred_logger); + assembleWellEqWithoutIteration(simulator, wgHelper, dt, inj_controls, prod_controls, well_state, deferred_logger, solving_with_zero_rate); } From f9cfbf0c68bb8e69f01916499ea87bcec51b92b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5kon=20H=C3=A6gland?= Date: Thu, 27 Nov 2025 14:02:42 +0100 Subject: [PATCH 2/3] Remove default arguments Default arguments have maintenance issues so avoid using them if possible. --- .../wells/BlackoilWellModel_impl.hpp | 8 ++++-- opm/simulators/wells/MultisegmentWell.hpp | 8 +++--- .../wells/MultisegmentWell_impl.hpp | 11 +++++--- opm/simulators/wells/StandardWell.hpp | 8 +++--- opm/simulators/wells/StandardWell_impl.hpp | 11 +++++--- opm/simulators/wells/WellInterface.hpp | 16 +++++------ opm/simulators/wells/WellInterface_impl.hpp | 27 ++++++++++++++----- 7 files changed, 58 insertions(+), 31 deletions(-) diff --git a/opm/simulators/wells/BlackoilWellModel_impl.hpp b/opm/simulators/wells/BlackoilWellModel_impl.hpp index ec66d96b9b0..e8d2ff952dd 100644 --- a/opm/simulators/wells/BlackoilWellModel_impl.hpp +++ b/opm/simulators/wells/BlackoilWellModel_impl.hpp @@ -1456,7 +1456,10 @@ namespace Opm { const auto inj_controls = Well::InjectionControls(0); const auto prod_controls = well_ecl.productionControls(summary_state); well->iterateWellEqWithSwitching( - this->simulator_, dt, inj_controls, prod_controls, this->wgHelper(), this->wellState(), local_deferredLogger, false, false + this->simulator_, dt, inj_controls, prod_controls, this->wgHelper(), this->wellState(), local_deferredLogger, + /*fixed_control=*/false, + /*fixed_status=*/false, + /*solving_with_zero_rate=*/false ); rate = -tcalc.calcModeRateFromRates(ws.surface_rates); group_rate += rate; @@ -1600,7 +1603,8 @@ namespace Opm { for (auto& well: well_container_) { well->assembleWellEqWithoutIteration(simulator_, this->wgHelper(), dt, this->wellState(), - deferred_logger); + deferred_logger, + /*solving_with_zero_rate=*/false); } OPM_END_PARALLEL_TRY_CATCH_LOG(deferred_logger, "BlackoilWellModel::assembleWellEqWithoutIteration failed: ", this->terminal_output_, grid().comm()); diff --git a/opm/simulators/wells/MultisegmentWell.hpp b/opm/simulators/wells/MultisegmentWell.hpp index e93db297ad8..4da8092af61 100644 --- a/opm/simulators/wells/MultisegmentWell.hpp +++ b/opm/simulators/wells/MultisegmentWell.hpp @@ -287,9 +287,9 @@ namespace Opm { const WellGroupHelperType& wgHelper, WellStateType& well_state, DeferredLogger& deferred_logger, - const bool fixed_control = false, - const bool fixed_status = false, - const bool solving_with_zero_rate = false) override; + const bool fixed_control, + const bool fixed_status, + const bool solving_with_zero_rate) override; void assembleWellEqWithoutIteration(const Simulator& simulator, const WellGroupHelperType& wgHelper, @@ -298,7 +298,7 @@ namespace Opm { const Well::ProductionControls& prod_controls, WellStateType& well_state, DeferredLogger& deferred_logger, - const bool solving_with_zero_rate = false) override; + const bool solving_with_zero_rate) override; void updateWaterThroughput(const double dt, WellStateType& well_state) const override; diff --git a/opm/simulators/wells/MultisegmentWell_impl.hpp b/opm/simulators/wells/MultisegmentWell_impl.hpp index 388423d2555..a5b8869feee 100644 --- a/opm/simulators/wells/MultisegmentWell_impl.hpp +++ b/opm/simulators/wells/MultisegmentWell_impl.hpp @@ -593,7 +593,10 @@ namespace Opm ); } else { converged = well_copy.iterateWellEqWithSwitching( - simulator, dt, inj_controls, prod_controls, wgHelper_copy, well_state_copy, deferred_logger + simulator, dt, inj_controls, prod_controls, wgHelper_copy, well_state_copy, deferred_logger, + /*fixed_control=*/false, + /*fixed_status=*/false, + /*solving_with_zero_rate=*/false ); } @@ -1441,7 +1444,8 @@ namespace Opm const auto cmode = ws.production_cmode; ws.production_cmode = Well::ProducerCMode::BHP; const double dt = simulator.timeStepSize(); - assembleWellEqWithoutIteration(simulator, wgHelper, dt, inj_controls, prod_controls, well_state, deferred_logger); + assembleWellEqWithoutIteration(simulator, wgHelper, dt, inj_controls, prod_controls, well_state, deferred_logger, + /*solving_with_zero_rate=*/false); BVectorWell rhs(this->numberOfSegments()); rhs = 0.0; @@ -1555,7 +1559,8 @@ namespace Opm } assembleWellEqWithoutIteration(simulator, wgHelper, dt, inj_controls, prod_controls, - well_state, deferred_logger); + well_state, deferred_logger, + /*solving_with_zero_rate=*/false); const auto report = getWellConvergence(simulator, well_state, Base::B_avg_, deferred_logger, relax_convergence); if (report.converged()) { diff --git a/opm/simulators/wells/StandardWell.hpp b/opm/simulators/wells/StandardWell.hpp index fb40bb9dfeb..5eea16d1729 100644 --- a/opm/simulators/wells/StandardWell.hpp +++ b/opm/simulators/wells/StandardWell.hpp @@ -211,9 +211,9 @@ namespace Opm const WellGroupHelperType& wgHelper, WellStateType& well_state, DeferredLogger& deferred_logger, - const bool fixed_control = false, - const bool fixed_status = false, - const bool solving_with_zero_rate = false) override; + const bool fixed_control, + const bool fixed_status, + const bool solving_with_zero_rate) override; /* returns BHP */ Scalar computeWellRatesAndBhpWithThpAlqProd(const Simulator& ebos_simulator, @@ -368,7 +368,7 @@ namespace Opm const Well::ProductionControls& prod_controls, WellStateType& well_state, DeferredLogger& deferred_logger, - const bool solving_with_zero_rate = false) override; + const bool solving_with_zero_rate) override; void assembleWellEqWithoutIterationImpl(const Simulator& simulator, const WellGroupHelperType& wgHelper, diff --git a/opm/simulators/wells/StandardWell_impl.hpp b/opm/simulators/wells/StandardWell_impl.hpp index d9ff924689c..8290627f22c 100644 --- a/opm/simulators/wells/StandardWell_impl.hpp +++ b/opm/simulators/wells/StandardWell_impl.hpp @@ -963,7 +963,8 @@ namespace Opm const auto cmode = ws.production_cmode; ws.production_cmode = Well::ProducerCMode::BHP; const double dt = simulator.timeStepSize(); - assembleWellEqWithoutIteration(simulator, wgHelper, dt, inj_controls, prod_controls, well_state, deferred_logger); + assembleWellEqWithoutIteration(simulator, wgHelper, dt, inj_controls, prod_controls, well_state, deferred_logger, + /*solving_with_zero_rate=*/false); const size_t nEq = this->primary_variables_.numWellEq(); BVectorWell rhs(1); @@ -1712,7 +1713,10 @@ namespace Opm ); } else { converged = well_copy.iterateWellEqWithSwitching( - simulator, dt, inj_controls, prod_controls, wgHelper_copy, well_state_copy, deferred_logger + simulator, dt, inj_controls, prod_controls, wgHelper_copy, well_state_copy, deferred_logger, + /*fixed_control=*/false, + /*fixed_status=*/false, + /*solving_with_zero_rate=*/false ); } @@ -2419,7 +2423,8 @@ namespace Opm bool relax_convergence = false; this->regularize_ = false; do { - assembleWellEqWithoutIteration(simulator, wgHelper, dt, inj_controls, prod_controls, well_state, deferred_logger); + assembleWellEqWithoutIteration(simulator, wgHelper, dt, inj_controls, prod_controls, well_state, deferred_logger, + /*solving_with_zero_rate=*/false); if (it > this->param_.strict_inner_iter_wells_) { relax_convergence = true; diff --git a/opm/simulators/wells/WellInterface.hpp b/opm/simulators/wells/WellInterface.hpp index 1e76dfd350c..1073594a219 100644 --- a/opm/simulators/wells/WellInterface.hpp +++ b/opm/simulators/wells/WellInterface.hpp @@ -178,7 +178,7 @@ class WellInterface : public WellInterfaceIndices connectionRates_; @@ -413,7 +413,7 @@ class WellInterface : public WellInterfaceIndicesiterateWellEqWithSwitching( - simulator, dt, inj_controls, prod_controls, wgHelper, well_state, deferred_logger + simulator, dt, inj_controls, prod_controls, wgHelper, well_state, deferred_logger, + /*fixed_control=*/false, /*fixed_status=*/false, /*solving_with_zero_rate=*/false ); } } @@ -621,7 +622,8 @@ namespace Opm } // solve well-equation converged = this->iterateWellEqWithSwitching( - simulator, dt, inj_controls, prod_controls, wgHelper, well_state, deferred_logger + simulator, dt, inj_controls, prod_controls, wgHelper, well_state, deferred_logger, + /*fixed_control=*/false, /*fixed_status=*/false, /*solving_with_zero_rate=*/false ); @@ -648,7 +650,8 @@ namespace Opm // re-solve with hopefully good initial guess ws.thp = this->getTHPConstraint(summary_state); converged = this->iterateWellEqWithSwitching( - simulator, dt, inj_controls, prod_controls, wgHelper, well_state, deferred_logger + simulator, dt, inj_controls, prod_controls, wgHelper, well_state, deferred_logger, + /*fixed_control=*/false, /*fixed_status=*/false, /*solving_with_zero_rate=*/false ); } } @@ -684,7 +687,10 @@ namespace Opm prod_controls, wgHelper, well_state, - deferred_logger); + deferred_logger, + /*fixed_control=*/false, + /*fixed_status=*/false, + /*solving_with_zero_rate=*/false); } } // update operability @@ -771,7 +777,10 @@ namespace Opm // solve const bool converged = this->iterateWellEqWithSwitching( simulator, dt, inj_controls, prod_controls, wgHelper_copy, - well_state, deferred_logger, /*fixed_control*/true + well_state, deferred_logger, + /*fixed_control=*/true, + /*fixed_status=*/false, + /*solving_with_zero_rate=*/false ); ws.injection_cmode = cmode_inj; ws.production_cmode = cmode_prod; @@ -841,7 +850,10 @@ namespace Opm ); } else { converged = this->iterateWellEqWithSwitching( - simulator, dt, inj_controls, prod_controls, wgHelper, well_state, deferred_logger + simulator, dt, inj_controls, prod_controls, wgHelper, well_state, deferred_logger, + /*fixed_control=*/false, + /*fixed_status=*/false, + /*solving_with_zero_rate=*/false ); } } @@ -936,7 +948,8 @@ namespace Opm { OPM_TIMEFUNCTION(); prepareWellBeforeAssembling(simulator, dt, wgHelper, well_state, deferred_logger); - assembleWellEqWithoutIteration(simulator, wgHelper, dt, well_state, deferred_logger); + assembleWellEqWithoutIteration(simulator, wgHelper, dt, well_state, deferred_logger, + /*solving_with_zero_rate=*/false); } From 5be44f3802ba629f2d3142e5941bbae98171a51d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5kon=20H=C3=A6gland?= Date: Thu, 27 Nov 2025 14:17:29 +0100 Subject: [PATCH 3/3] Convert assert() to throw Convert assert() to throw to be consistent with usage elsewhere in the code base where external interface violations should cause thrown exceptions instead of causing assertion failures which will not even be enforced in release builds. --- opm/simulators/wells/WellInterface_impl.hpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/opm/simulators/wells/WellInterface_impl.hpp b/opm/simulators/wells/WellInterface_impl.hpp index 0bc979f80ee..6cc7f62b8ec 100644 --- a/opm/simulators/wells/WellInterface_impl.hpp +++ b/opm/simulators/wells/WellInterface_impl.hpp @@ -311,7 +311,11 @@ namespace Opm if (!fixed_control) { // When solving_with_zero_rate=true, fixed_control=true, so this block should never // be entered. - assert(!solving_with_zero_rate); + if (solving_with_zero_rate) { + OPM_DEFLOG_THROW(std::runtime_error, fmt::format( + "Well {}: solving_with_zero_rate should not be true when fixed_control is false", + this->name()), deferred_logger); + } // Changing to group controls here may lead to inconsistencies in the group handling which in turn // may result in excessive back and forth switching. However, we currently allow this by default.