From 0d829a30d13b9c95740e50198e8efcb0c8e1435e Mon Sep 17 00:00:00 2001 From: "Halvor M. Nilsen" Date: Tue, 4 Aug 2026 09:51:43 +0200 Subject: [PATCH 1/4] Make the standard-well control equation tolerances parameters The tolerances for the standard-well control equation residual were hardcoded as a brace-initialised literal at the call site, {1e3, 1e4, 1e-4, 1e-6}, i.e. BHP 0.01 bar, THP 0.1 bar, rates 1e-4 m3/s and group 1e-6 m3/s. Multisegment wells have had the equivalent values as parameters all along (and use a 10x stricter pressure tolerance), so the two well types could not be configured or studied consistently. Add --tolerance-well-{bhp,thp,grup}-eq with defaults equal to the previous literals, and reuse the existing --tolerance-wells for the rate entry (its default already matched the literal). Purely a plumbing change: runs are numerically identical at default settings. --- opm/simulators/flow/BlackoilModelParameters.cpp | 9 +++++++++ opm/simulators/flow/BlackoilModelParameters.hpp | 12 ++++++++++++ opm/simulators/wells/StandardWellEval.cpp | 5 ++++- opm/simulators/wells/StandardWellEval.hpp | 3 +++ opm/simulators/wells/StandardWell_impl.hpp | 3 +++ 5 files changed, 31 insertions(+), 1 deletion(-) diff --git a/opm/simulators/flow/BlackoilModelParameters.cpp b/opm/simulators/flow/BlackoilModelParameters.cpp index 1e05f78a23c..4af5cc8c779 100644 --- a/opm/simulators/flow/BlackoilModelParameters.cpp +++ b/opm/simulators/flow/BlackoilModelParameters.cpp @@ -60,6 +60,9 @@ BlackoilModelParameters::BlackoilModelParameters() max_welleq_iter_ = Parameters::Get(); use_multisegment_well_ = Parameters::Get(); tolerance_pressure_ms_wells_ = Parameters::Get>(); + tolerance_well_bhp_eq_ = Parameters::Get>(); + tolerance_well_thp_eq_ = Parameters::Get>(); + tolerance_well_grup_eq_ = Parameters::Get>(); relaxed_tolerance_flow_well_ = Parameters::Get>(); relaxed_tolerance_pressure_ms_well_ = Parameters::Get>(); max_pressure_change_ms_wells_ = Parameters::Get>(); @@ -198,6 +201,12 @@ void BlackoilModelParameters::registerParameters() "or 'per-connection' (one linear tubing, a segment per connection)"); Parameters::Register> ("Tolerance for the pressure equations for multi-segment wells"); + Parameters::Register> + ("Tolerance for the BHP control equation residual of standard wells [Pa]"); + Parameters::Register> + ("Tolerance for the THP control equation residual of standard wells [Pa]"); + Parameters::Register> + ("Tolerance for the group-control equation residual of standard wells [m3/s]"); Parameters::Register> ("Relaxed tolerance for the well flow residual"); Parameters::Register> diff --git a/opm/simulators/flow/BlackoilModelParameters.hpp b/opm/simulators/flow/BlackoilModelParameters.hpp index caadd0fc100..6777a703822 100644 --- a/opm/simulators/flow/BlackoilModelParameters.hpp +++ b/opm/simulators/flow/BlackoilModelParameters.hpp @@ -116,6 +116,14 @@ struct ConvertToMultisegmentWell { static constexpr auto value = "none"; }; template struct TolerancePressureMsWells { static constexpr Scalar value = 0.01*1e5; }; +/// Control-equation tolerances for standard wells. Defaults reproduce the values that +/// used to be hardcoded at the call site; MSW has had these as parameters all along. +template +struct ToleranceWellBhpEq { static constexpr Scalar value = 1.0e3; }; +template +struct ToleranceWellThpEq { static constexpr Scalar value = 1.0e4; }; +template +struct ToleranceWellGrupEq { static constexpr Scalar value = 1.0e-6; }; template struct MaxPressureChangeMsWells { static constexpr Scalar value = 10*1e5; }; @@ -252,6 +260,10 @@ struct BlackoilModelParameters Scalar tolerance_well_control_; /// Tolerance for the pressure equations for multisegment wells Scalar tolerance_pressure_ms_wells_; + /// standard-well control-equation tolerances (BHP/THP in Pa, GRUP in m3/s) + Scalar tolerance_well_bhp_eq_; + Scalar tolerance_well_thp_eq_; + Scalar tolerance_well_grup_eq_; /// Relaxed tolerance for for the well flow residual Scalar relaxed_tolerance_flow_well_; diff --git a/opm/simulators/wells/StandardWellEval.cpp b/opm/simulators/wells/StandardWellEval.cpp index f97649ed701..f807eb47ffd 100644 --- a/opm/simulators/wells/StandardWellEval.cpp +++ b/opm/simulators/wells/StandardWellEval.cpp @@ -157,6 +157,9 @@ getWellConvergence(const WellState& well_state, const std::vector& B_avg, const Scalar maxResidualAllowed, const Scalar tol_wells, + const Scalar tol_bhp_eq, + const Scalar tol_thp_eq, + const Scalar tol_grup_eq, const Scalar relaxed_tolerance_flow, const bool relax_tolerance, const bool well_is_stopped, @@ -208,7 +211,7 @@ getWellConvergence(const WellState& well_state, WellConvergence(baseif_). checkConvergenceControlEq(well_state, - {1.e3, 1.e4, 1.e-4, 1.e-6, maxResidualAllowed}, + {tol_bhp_eq, tol_thp_eq, tol_wells, tol_grup_eq, maxResidualAllowed}, std::abs(this->linSys_.residual()[0][Bhp]), well_is_stopped, report, diff --git a/opm/simulators/wells/StandardWellEval.hpp b/opm/simulators/wells/StandardWellEval.hpp index 371f2e9a580..abd20275307 100644 --- a/opm/simulators/wells/StandardWellEval.hpp +++ b/opm/simulators/wells/StandardWellEval.hpp @@ -101,6 +101,9 @@ class StandardWellEval const std::vector& B_avg, const Scalar maxResidualAllowed, const Scalar tol_wells, + const Scalar tol_bhp_eq, + const Scalar tol_thp_eq, + const Scalar tol_grup_eq, const Scalar relaxed_tolerance_flow, const bool relax_tolerance, const bool well_is_stopped, diff --git a/opm/simulators/wells/StandardWell_impl.hpp b/opm/simulators/wells/StandardWell_impl.hpp index de9084e5a4b..cd1260426f5 100644 --- a/opm/simulators/wells/StandardWell_impl.hpp +++ b/opm/simulators/wells/StandardWell_impl.hpp @@ -1252,6 +1252,9 @@ namespace Opm B_avg, this->param_.max_residual_allowed_, tol_wells, + this->param_.tolerance_well_bhp_eq_, + this->param_.tolerance_well_thp_eq_, + this->param_.tolerance_well_grup_eq_, this->param_.relaxed_tolerance_flow_well_, relax_tolerance, this->wellIsStopped(), From 3aab09d3fbcae64ec3fdb7601e2bfbefcc153224 Mon Sep 17 00:00:00 2001 From: hnil Date: Wed, 5 Aug 2026 15:48:48 +0200 Subject: [PATCH 2/4] Expose hardcoded convergence/well tuning constants as parameters Five values that steer the nonlinear and well solves were written at the call site and could not be studied without a rebuild: --min-iter-after-switch-wells (4) / --min-iter-after-switch-ms-wells (3) Iterations forced between two control switches in the inner well solve. The existing comment notes that values <= 1 make the solve cycle, i.e. a tuning constant with a known failure mode. Standard and multi-segment wells used different values with no stated reason. --newton-relax-increment (0.1) / --newton-relax-rel-tol (0.2) The Newton oscillation-damping schedule. Set in reset() only, so --newton-max-relax could be tuned but the schedule reaching it could not. --tolerance-wells-stopped-factor (1e-4) / --tolerance-wells-dynamic-thp-factor (1e-1) Multipliers on the well tolerance for stopped/zero-rate wells and for wells on a dynamic THP limit. Defaults reproduce the previous values; INFOSTEP is unchanged on SPE1CASE2_RADIAL, STONE2_MOD1, 3B_WSEGVALV_MODEL3, 2_GCONINJE_NETV and GASLIFT-06. Co-Authored-By: Claude Fable 5 --- opm/simulators/flow/BlackoilModelParameters.cpp | 16 ++++++++++++++++ opm/simulators/flow/BlackoilModelParameters.hpp | 17 +++++++++++++++++ opm/simulators/flow/NonlinearSolver.cpp | 8 ++++++++ opm/simulators/flow/NonlinearSolver.hpp | 6 ++++++ opm/simulators/wells/MultisegmentWell_impl.hpp | 2 +- opm/simulators/wells/StandardWell_impl.hpp | 6 +++--- 6 files changed, 51 insertions(+), 4 deletions(-) diff --git a/opm/simulators/flow/BlackoilModelParameters.cpp b/opm/simulators/flow/BlackoilModelParameters.cpp index 4af5cc8c779..31a037980ab 100644 --- a/opm/simulators/flow/BlackoilModelParameters.cpp +++ b/opm/simulators/flow/BlackoilModelParameters.cpp @@ -57,6 +57,8 @@ BlackoilModelParameters::BlackoilModelParameters() tolerance_max_drv_ = Parameters::Get>(); tolerance_wells_ = Parameters::Get>(); tolerance_well_control_ = Parameters::Get>(); + tolerance_wells_stopped_factor_ = Parameters::Get>(); + tolerance_wells_dynamic_thp_factor_ = Parameters::Get>(); max_welleq_iter_ = Parameters::Get(); use_multisegment_well_ = Parameters::Get(); tolerance_pressure_ms_wells_ = Parameters::Get>(); @@ -73,6 +75,8 @@ BlackoilModelParameters::BlackoilModelParameters() max_niter_inner_well_iter_ = Parameters::Get(); shut_unsolvable_wells_ = Parameters::Get(); max_inner_iter_wells_ = Parameters::Get(); + min_iter_after_switch_wells_ = Parameters::Get(); + min_iter_after_switch_ms_wells_ = Parameters::Get(); max_well_status_switch_inner_iter_ = Parameters::Get(); max_well_status_switch_ = Parameters::Get(); maxSinglePrecisionTimeStep_ = Parameters::Get>() * 24 * 60 * 60; @@ -190,6 +194,12 @@ void BlackoilModelParameters::registerParameters() ("Well convergence tolerance"); Parameters::Register> ("Tolerance for the well control equations"); + Parameters::Register> + ("Multiplier applied to the well convergence tolerance for stopped wells " + "and wells under a zero rate target"); + Parameters::Register> + ("Multiplier applied to the well convergence tolerance for wells on a " + "dynamic THP limit, to help network convergence"); Parameters::Register ("Maximum number of iterations to determine solution the well equations"); Parameters::Register @@ -226,6 +236,12 @@ void BlackoilModelParameters::registerParameters() ("Shut unsolvable wells"); Parameters::Register ("Maximum number of inner iterations for standard wells"); + Parameters::Register + ("Inner iterations forced between two control switches of a standard well. " + "Values <= 1 let the inner solve cycle between controls."); + Parameters::Register + ("Inner iterations forced between two control switches of a multi-segment well. " + "Values <= 1 let the inner solve cycle between controls."); Parameters::Register ("Maximum number of status switching (stop<->open) for a well during inner iterations."); Parameters::Register diff --git a/opm/simulators/flow/BlackoilModelParameters.hpp b/opm/simulators/flow/BlackoilModelParameters.hpp index 6777a703822..ea276f620c5 100644 --- a/opm/simulators/flow/BlackoilModelParameters.hpp +++ b/opm/simulators/flow/BlackoilModelParameters.hpp @@ -93,6 +93,12 @@ struct ToleranceWells { static constexpr Scalar value = 1e-4; }; template struct ToleranceWellControl { static constexpr Scalar value = 1e-7; }; +template +struct ToleranceWellsStoppedFactor { static constexpr Scalar value = 1e-4; }; + +template +struct ToleranceWellsDynamicThpFactor { static constexpr Scalar value = 1e-1; }; + struct MaxWelleqIter { static constexpr int value = 30; }; template @@ -131,6 +137,8 @@ struct MaxPressureChangeMsWells { static constexpr Scalar value = 10*1e5; }; struct MaxNewtonIterationsWithInnerWellIterations { static constexpr int value = 99; }; struct MaxInnerIterMsWells { static constexpr int value = 100; }; struct MaxInnerIterWells { static constexpr int value = 50; }; +struct MinIterAfterSwitchWells { static constexpr int value = 4; }; +struct MinIterAfterSwitchMsWells { static constexpr int value = 3; }; struct MaxWellStatusSwitchInInnerIterWells { static constexpr int value = 99; }; struct MaxWellStatusSwitchForWells { static constexpr int value = 99; }; struct ShutUnsolvableWells { static constexpr bool value = true; }; @@ -258,6 +266,10 @@ struct BlackoilModelParameters /// Tolerance for the well control equations // TODO: it might need to distinguish between rate control and pressure control later Scalar tolerance_well_control_; + /// Multipliers applied to tolerance_wells_ for stopped/zero-rate wells and for + /// wells on a dynamic THP limit (the latter to help network convergence). + Scalar tolerance_wells_stopped_factor_; + Scalar tolerance_wells_dynamic_thp_factor_; /// Tolerance for the pressure equations for multisegment wells Scalar tolerance_pressure_ms_wells_; /// standard-well control-equation tolerances (BHP/THP in Pa, GRUP in m3/s) @@ -294,6 +306,11 @@ struct BlackoilModelParameters /// Maximum inner iteration number for standard wells int max_inner_iter_wells_; + /// Inner iterations forced between two control switches of the same well. + /// Values <= 1 are known to let the inner solve cycle between controls. + int min_iter_after_switch_wells_; + int min_iter_after_switch_ms_wells_; + /// Maximum iteration number of the well equation solution int max_welleq_iter_; diff --git a/opm/simulators/flow/NonlinearSolver.cpp b/opm/simulators/flow/NonlinearSolver.cpp index c2a194c30ea..130df95abab 100644 --- a/opm/simulators/flow/NonlinearSolver.cpp +++ b/opm/simulators/flow/NonlinearSolver.cpp @@ -146,6 +146,8 @@ NonlinearSolverParameters() // overload with given parameters relaxMax_ = Parameters::Get>(); + relaxIncrement_ = Parameters::Get>(); + relaxRelTol_ = Parameters::Get>(); const auto& relaxationTypeString = Parameters::Get(); if (relaxationTypeString == "dampen") { @@ -175,6 +177,12 @@ registerParameters() { Parameters::Register> ("The maximum relaxation factor of a Newton iteration"); + Parameters::Register> + ("Amount by which the Newton relaxation factor is reduced each time an " + "oscillation is detected"); + Parameters::Register> + ("Relative tolerance used to classify a residual history as oscillating " + "rather than stagnating"); Parameters::Register ("The type of relaxation used by Newton method. Valid options are: dampen or sor"); } diff --git a/opm/simulators/flow/NonlinearSolver.hpp b/opm/simulators/flow/NonlinearSolver.hpp index 5d51e634866..ddc36c63505 100644 --- a/opm/simulators/flow/NonlinearSolver.hpp +++ b/opm/simulators/flow/NonlinearSolver.hpp @@ -45,6 +45,12 @@ namespace Opm::Parameters { template struct NewtonMaxRelax { static constexpr Scalar value = 0.5; }; +template +struct NewtonRelaxIncrement { static constexpr Scalar value = 0.1; }; + +template +struct NewtonRelaxRelTol { static constexpr Scalar value = 0.2; }; + struct NewtonRelaxationType { static constexpr auto value = "dampen"; }; } // namespace Opm::Parameters diff --git a/opm/simulators/wells/MultisegmentWell_impl.hpp b/opm/simulators/wells/MultisegmentWell_impl.hpp index 7c2c6a4d90b..775276f609d 100644 --- a/opm/simulators/wells/MultisegmentWell_impl.hpp +++ b/opm/simulators/wells/MultisegmentWell_impl.hpp @@ -1705,7 +1705,7 @@ namespace Opm // 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 // that unless this number is >1, we may get stuck in a cycle - const int min_its_after_switch = 3; + const int min_its_after_switch = this->param_.min_iter_after_switch_ms_wells_; // We also want to restrict the number of status switches to avoid oscillation between STOP<->OPEN const int max_status_switch = this->param_.max_well_status_switch_inner_iter_; int its_since_last_switch = min_its_after_switch; diff --git a/opm/simulators/wells/StandardWell_impl.hpp b/opm/simulators/wells/StandardWell_impl.hpp index cd1260426f5..cbe9c19a4d8 100644 --- a/opm/simulators/wells/StandardWell_impl.hpp +++ b/opm/simulators/wells/StandardWell_impl.hpp @@ -1238,9 +1238,9 @@ namespace Opm auto& deferred_logger = groupStateHelper.deferredLogger(); Scalar tol_wells = this->param_.tolerance_wells_; // use stricter tolerance for stopped wells and wells under zero rate target control. - constexpr Scalar stopped_factor = 1.e-4; + const Scalar stopped_factor = this->param_.tolerance_wells_stopped_factor_; // use stricter tolerance for dynamic thp to ameliorate network convergence - constexpr Scalar dynamic_thp_factor = 1.e-1; + const Scalar dynamic_thp_factor = this->param_.tolerance_wells_dynamic_thp_factor_; if (this->stoppedOrZeroRateTarget(groupStateHelper)) { tol_wells = tol_wells*stopped_factor; } else if (this->getDynamicThpLimit()) { @@ -2482,7 +2482,7 @@ namespace Opm // 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 // that unless this number is >1, we may get stuck in a cycle - constexpr int min_its_after_switch = 4; + const int min_its_after_switch = this->param_.min_iter_after_switch_wells_; // We also want to restrict the number of status switches to avoid oscillation between STOP<->OPEN const int max_status_switch = this->param_.max_well_status_switch_inner_iter_; int its_since_last_switch = min_its_after_switch; From 2d602f85b9e6f73c7312aa5e578210a7f37889ea Mon Sep 17 00:00:00 2001 From: hnil Date: Wed, 5 Aug 2026 15:49:04 +0200 Subject: [PATCH 3/4] Remove the dead --tolerance-well-control knob and document the -1 sentinel --tolerance-well-control ("Tolerance for the well control equations") is registered and read into tolerance_well_control_, but that member is never used anywhere. It is precisely the knob a user reaches for to tighten the well control equations, and it silently does nothing; the real values live in --tolerance-well-{bhp,thp,grup}-eq. Also: - --min-strict-cnv-iter now documents that the default -1 is a mode switch (relax only at the iteration cap), matching the text --min-strict-mb-iter already carries for the same sentinel. Read plainly, "minimum number of iterations before relaxed tolerances can be used ... -1" suggests the opposite of what it does. - Correct the comment on the solution-change branch of the CNV test: 1e20 disables CNV rather than relaxing it. Co-Authored-By: Claude Fable 5 --- opm/simulators/flow/BlackoilModelParameters.cpp | 7 +++---- opm/simulators/flow/BlackoilModelParameters.hpp | 6 ------ .../flow/NonlinearSystemBlackOilReservoir_impl.hpp | 4 ++-- 3 files changed, 5 insertions(+), 12 deletions(-) diff --git a/opm/simulators/flow/BlackoilModelParameters.cpp b/opm/simulators/flow/BlackoilModelParameters.cpp index 31a037980ab..5a3b4499171 100644 --- a/opm/simulators/flow/BlackoilModelParameters.cpp +++ b/opm/simulators/flow/BlackoilModelParameters.cpp @@ -56,7 +56,6 @@ BlackoilModelParameters::BlackoilModelParameters() tolerance_max_drs_ = Parameters::Get>(); tolerance_max_drv_ = Parameters::Get>(); tolerance_wells_ = Parameters::Get>(); - tolerance_well_control_ = Parameters::Get>(); tolerance_wells_stopped_factor_ = Parameters::Get>(); tolerance_wells_dynamic_thp_factor_ = Parameters::Get>(); max_welleq_iter_ = Parameters::Get(); @@ -192,8 +191,6 @@ void BlackoilModelParameters::registerParameters() "of residual tolerances. Use with care!"); Parameters::Register> ("Well convergence tolerance"); - Parameters::Register> - ("Tolerance for the well control equations"); Parameters::Register> ("Multiplier applied to the well convergence tolerance for stopped wells " "and wells under a zero rate target"); @@ -255,7 +252,9 @@ void BlackoilModelParameters::registerParameters() "arithmetic can be used solving for the linear systems of equations"); Parameters::Register ("Minimum number of Newton iterations before relaxed tolerances " - "can be used for the CNV convergence criterion"); + "can be used for the CNV convergence criterion. " + "Default -1 means that the relaxed tolerance is used when maximum " + "number of Newton iterations are reached."); Parameters::Register ("Minimum number of Newton iterations before relaxed tolerances " "can be used for the MB convergence criterion. " diff --git a/opm/simulators/flow/BlackoilModelParameters.hpp b/opm/simulators/flow/BlackoilModelParameters.hpp index ea276f620c5..6b10b231bdf 100644 --- a/opm/simulators/flow/BlackoilModelParameters.hpp +++ b/opm/simulators/flow/BlackoilModelParameters.hpp @@ -90,9 +90,6 @@ struct ToleranceMaxDrv { static constexpr Scalar value = 0.0; }; template struct ToleranceWells { static constexpr Scalar value = 1e-4; }; -template -struct ToleranceWellControl { static constexpr Scalar value = 1e-7; }; - template struct ToleranceWellsStoppedFactor { static constexpr Scalar value = 1e-4; }; @@ -263,9 +260,6 @@ struct BlackoilModelParameters Scalar tolerance_max_drv_; /// Well convergence tolerance. Scalar tolerance_wells_; - /// Tolerance for the well control equations - // TODO: it might need to distinguish between rate control and pressure control later - Scalar tolerance_well_control_; /// Multipliers applied to tolerance_wells_ for stopped/zero-rate wells and for /// wells on a dynamic THP limit (the latter to help network convergence). Scalar tolerance_wells_stopped_factor_; diff --git a/opm/simulators/flow/NonlinearSystemBlackOilReservoir_impl.hpp b/opm/simulators/flow/NonlinearSystemBlackOilReservoir_impl.hpp index e39e61b4df0..ea7af498562 100644 --- a/opm/simulators/flow/NonlinearSystemBlackOilReservoir_impl.hpp +++ b/opm/simulators/flow/NonlinearSystemBlackOilReservoir_impl.hpp @@ -810,8 +810,8 @@ getReservoirConvergence(const double reportTime, || relax_iter_cnv || relax_dsol_cnv; - // Ensure that CNV convergence criteria is met when max. - // solution change tolerances have been fulfilled + // The solution-change criterion overrides CNV entirely rather than merely relaxing it: + // 1e20 is never exceeded, so a converged solution update accepts any CNV. Scalar tolerance_cnv_relaxed = relax_dsol_cnv ? 1e20 : this->param_.tolerance_cnv_relaxed_; const auto tol_cnv = use_relaxed_cnv ? tolerance_cnv_relaxed : this->param_.tolerance_cnv_; From 7209421ed8be5acd1e7587539fc415ce1305f15c Mon Sep 17 00:00:00 2001 From: hnil Date: Tue, 11 Aug 2026 10:25:48 +0200 Subject: [PATCH 4/4] flowexp: stop hiding the removed tolerance-well-control parameter The knob is dead as a tolerance, but flowexp still registered a Hide for it, so dropping the parameter broke that build. Co-Authored-By: Claude Opus 5 --- flowexperimental/flowexp.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/flowexperimental/flowexp.hpp b/flowexperimental/flowexp.hpp index 25d25a4c06e..0fbd12650bc 100644 --- a/flowexperimental/flowexp.hpp +++ b/flowexperimental/flowexp.hpp @@ -160,7 +160,6 @@ class FlowExpProblem : public FlowProblemBlackoil //, public FvBaseProb Parameters::Hide>(); Parameters::Hide>(); Parameters::Hide>(); - Parameters::Hide>(); Parameters::Hide(); Parameters::Hide(); Parameters::Hide>();