Reworked implementation of group control feasibility#6963
Conversation
|
jenkins build this failure_report please |
413e7e4 to
a29892b
Compare
|
jenkins build this failure_report please |
|
I'll make a relevant test since none of the current tests are influenced by this change. |
|
Thank you for the request, I will look into this by Monday. |
There was a problem hiding this comment.
Pull request overview
This PR reworks “group control feasibility” for producer wells by introducing a per-well group-target fallback and a runtime flag that switches which group target/mode is used when the current group control mode becomes numerically infeasible.
Changes:
- Extend
SingleWellStateto storegroup_targetcontrol mode metadata plusgroup_target_fallbackanduse_group_target_fallback. - Add logic to compute and select fallback group targets/modes (including guide-rate diagnostics and a new tolerance parameter).
- Add per-well feasibility checks based on guide-rate ratio and scaled phase fractions (from primary variables) to avoid singular control equations.
Reviewed changes
Copilot reviewed 23 out of 23 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| opm/simulators/wells/WellInterface_impl.hpp | Adds debug logging for fallback usage; adds convenience overload to update fallback flag and calls it under GRUP control. |
| opm/simulators/wells/WellInterface.hpp | Adds a convenience overload and a new pure-virtual hook to retrieve scaled well fractions. |
| opm/simulators/wells/WellInterfaceGeneric.hpp | Declares base helper to update use_group_target_fallback using scaled fractions. |
| opm/simulators/wells/WellInterfaceGeneric.cpp | Implements fallback-flag decision logic based on guide-rate ratio and scaled well fractions. |
| opm/simulators/wells/WellGroupControls.cpp | Switches group-control equation and scaling to use either current target or fallback target based on the flag. |
| opm/simulators/wells/StandardWell_impl.hpp | Calls fallback-flag update before assembling control equations; wires scaled fractions from primary variables. |
| opm/simulators/wells/StandardWell.hpp | Implements required getScaledWellFractions() override for standard wells. |
| opm/simulators/wells/StandardWellPrimaryVariables.hpp | Declares helper to compute scaled phase fractions from current primary variables. |
| opm/simulators/wells/StandardWellPrimaryVariables.cpp | Implements scaled fraction extraction for standard wells. |
| opm/simulators/wells/MultisegmentWell_impl.hpp | Calls fallback-flag update before assembling control equations; wires scaled fractions from MSW primary variables. |
| opm/simulators/wells/MultisegmentWell.hpp | Implements required getScaledWellFractions() override for multisegment wells. |
| opm/simulators/wells/MultisegmentWellPrimaryVariables.hpp | Declares helper to compute scaled fractions for the top segment. |
| opm/simulators/wells/MultisegmentWellPrimaryVariables.cpp | Implements scaled fraction extraction for multisegment wells (top segment). |
| opm/simulators/wells/SingleWellState.hpp | Extends GroupTarget with mode + guide-rate diagnostics; adds fallback target + flag and serializes them. |
| opm/simulators/wells/SingleWellState.cpp | Initializes the new use_group_target_fallback member; extends equality to cover new fields. |
| opm/simulators/wells/GroupStateHelper.hpp | Adds helpers to map control mode to guide target, adds optional fallback cmode translation parameter, and adds prev-rate update API. |
| opm/simulators/wells/GroupStateHelper.cpp | Implements guide-target mapping by mode; computes guide-rate ratio diagnostics; translates targets using stored previous group production rates. |
| opm/simulators/wells/GroupState.hpp | Adds storage + serialization for previous production rates. |
| opm/simulators/wells/GroupState.cpp | Implements storage/accessors for previous production rates and extends equality. |
| opm/simulators/wells/FractionCalculator.hpp | Makes guideRate() available for diagnostics used by group target computation. |
| opm/simulators/wells/BlackoilWellModelGeneric.cpp | Updates group data flow to snapshot previous production rates and compute per-well fallback targets when group mode differs from scheduled/original mode. |
| opm/simulators/flow/BlackoilModelParameters.hpp | Adds group_control_fraction_tolerance_ parameter to model parameters. |
| opm/simulators/flow/BlackoilModelParameters.cpp | Registers and loads the new group_control_fraction_tolerance parameter. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (group_target.has_value()) { | ||
| // check whether group giving target is operating at its original cmode, and if not, compute the target | ||
| // corresponding to original cmode for potential later use in case current cmode is/becomes infeasible | ||
| const Group& target_group = this->schedule().getGroup(group_target->group_name, reportStepIdx); | ||
| const auto cmode_orig = target_group.productionControls(summaryState_).cmode; | ||
| if (cmode_orig != group_target->production_cmode && | ||
| cmode_orig != Group::ProductionCMode::FLD && | ||
| cmode_orig != Group::ProductionCMode::NONE) { | ||
| auto& group_target_fallback = this->wellState().well(well->indexOfWell()).group_target_fallback; | ||
| group_target_fallback = group_state_helper.getWellGroupTargetProducer( | ||
| well->name(), | ||
| well->wellEcl().groupName(), | ||
| group, | ||
| ws.surface_rates.data(), | ||
| efficiencyFactor, | ||
| resv_coeff, | ||
| cmode_orig); | ||
| } | ||
| } |
There was a problem hiding this comment.
group_target_fallback is only assigned when cmode_orig != group_target->production_cmode, but it is never cleared when that condition is false (or when group_target becomes nullopt). This can leave a stale fallback target from a previous iteration/step, and later logic (e.g., fallback-flag updates) may incorrectly use it or even assume group_target is present when only the stale fallback is set. Clear group_target_fallback (and ideally use_group_target_fallback) when no fallback is needed / when group_target is not set, to keep the well state consistent.
There was a problem hiding this comment.
Made sure to reset group_target_fallback and flag
| auto& ws = well_state.well(this->index_of_well_); | ||
| if (!wellUnderGroupControl(ws) || this->isInjector() || !ws.group_target_fallback.has_value()) | ||
| { | ||
| ws.use_group_target_fallback = false; | ||
| return; | ||
| } | ||
| // If group_target_fallback is not empty, group_target should not be empty either | ||
| assert(ws.group_target.has_value()); | ||
|
|
||
| // We only need to check modes that can result in small fractions | ||
| // i.e. ORAT, WRAT, GRAT, LRAT | ||
| const auto cmode = ws.group_target->production_cmode; | ||
| const bool do_check = (cmode == Group::ProductionCMode::ORAT || |
There was a problem hiding this comment.
This function relies on assert(ws.group_target.has_value()) before dereferencing ws.group_target. In release builds assertions are typically disabled, so if a stale group_target_fallback is present while group_target is empty, this becomes undefined behavior/crash. Add a runtime guard (e.g., treat as no-fallback / clear fallback) so dereferencing ws.group_target is always safe.
There was a problem hiding this comment.
Removed assert, if no value function will now return early
| GroupState<Scalar>::prev_production_rates(const std::string& gname) const | ||
| { | ||
| auto group_iter = this->m_prev_production_rates.find(gname); | ||
| if (group_iter == this->m_prev_production_rates.end()) | ||
| throw std::logic_error("No such group"); | ||
|
|
||
| return group_iter->second; | ||
| } | ||
|
|
||
| template<class Scalar> | ||
| void GroupState<Scalar>::update_prev_production_rates(const std::string& gname, | ||
| const std::vector<Scalar>& rates) | ||
| { | ||
| if (rates.size() != this->num_phases) | ||
| throw std::logic_error("Wrong number of phases"); | ||
|
|
||
| this->m_prev_production_rates[gname] = rates; |
There was a problem hiding this comment.
The thrown std::logic_error messages here are very generic ("No such group" / "Wrong number of phases"). When these exceptions surface, it will be hard to diagnose which group triggered it and what sizes were expected/received. Include the group name (and expected vs actual phase counts) in the error message to make failures actionable.
There was a problem hiding this comment.
Did not update this as multiple of functions here have exactly the same error logging
| Parameters ::Register<Parameters::GroupControlFractionTolerance<Scalar>> | ||
| ("Tolerance for minimal allowed fraction for controlled phase before employing a fallback control"); |
There was a problem hiding this comment.
Parameter registration formatting is inconsistent with the rest of this function (Parameters ::Register has an extra space, and the continuation indentation differs). Keeping this consistent helps readability and avoids churn with auto-formatters.
| Parameters ::Register<Parameters::GroupControlFractionTolerance<Scalar>> | |
| ("Tolerance for minimal allowed fraction for controlled phase before employing a fallback control"); | |
| Parameters::Register<Parameters::GroupControlFractionTolerance<Scalar>> | |
| ("Tolerance for minimal allowed fraction for controlled phase before employing a fallback control"); |
|
Added a test where the PR has an efect here: OPM/opm-tests#1512 |
5e14009 to
c5bf0f3
Compare
|
jenkins build this failure_report please |
1 similar comment
|
jenkins build this failure_report please |
GitPaean
left a comment
There was a problem hiding this comment.
This is a well structured pull request, and it fixes the issue as stated in OPM/opm-tests#1512 . I left some minor comments and questions.
|
|
||
| template<class FluidSystem, class Indices> | ||
| void StandardWellPrimaryVariables<FluidSystem,Indices>:: | ||
| scaledWellFractions(std::vector<Scalar>& fractions) const |
There was a problem hiding this comment.
With this way, it might be a untouched 0 value fractions at the end of the std::vector<Scalar>& fractions. It should not be an issue, since we do not handle solvent with group control, I believe. And with group controls, there is no guide rate or target will be related to solvent either.
There was a problem hiding this comment.
Thanks, might not have throught of this. Agree that it's not an issue with the current code, but might become an issue if the function should used for some other purpuse in the future. I can include the solvent-fraction if you think that's more clean or we can deal with it later if it becomes relevant?
There was a problem hiding this comment.
I also struggle with solvent related sometimes. Maybe at least some comment for later record or even throw to say solvent related should not use this function if we want to leave this to be fixed later.
There was a problem hiding this comment.
OK, I'll include a throw. Will update shortly, just need to resolve some building issues locally.
There was a problem hiding this comment.
Now updated, please have a look and see if you agree.
| this->well_control_log_.push_back(from); | ||
| } | ||
| } | ||
| // Add debug info for problematic group targets |
There was a problem hiding this comment.
With this way, we can only see this message when the local solve of the well equation is converged. It can be good to see the message whether the solving is converged or not.
There was a problem hiding this comment.
Thanks, moved to end so message appears also for non-converged
| } | ||
|
|
||
| template<typename Scalar, typename IndexTraits> | ||
| void WellInterfaceGeneric<Scalar, IndexTraits>::updateGroupTargetFallbackFlag(WellState<Scalar, IndexTraits>& well_state, |
There was a problem hiding this comment.
It might be good to break the long line here, similar to other function definitions.
| void WellInterfaceGeneric<Scalar, IndexTraits>::updateGroupTargetFallbackFlag(WellState<Scalar, IndexTraits>& well_state, | ||
| const std::vector<Scalar>& scaled_well_fractions, | ||
| DeferredLogger& deferred_logger) const | ||
| { |
| // Finally, we provide the well-to-group guide-rate ratio for subsequent diagnostics of | ||
| // target feasibility. Note that we cannot use the target directly for this, since a ~zero | ||
| // target may very well just be the result of non-converged group tree. | ||
| Scalar guide_rate_fraction = 0.0; |
There was a problem hiding this comment.
The guide_rate_fraction and also the guiderate_ratio in the class GroupTarget by default will 0. The usage is that if it is smaller than a small value (1.e-4 by default), it will use the fallback mode. Will initializing the values be 0. makes it easier to enter the fallback mode by accident? Will it be better to initialize it to be some big value?
There was a problem hiding this comment.
Thanks, agree. Now intializes to 1.0
| // Check whether guiderate ratio or well fraction of production_cmode is too small, | ||
| // if so, switch to fallback (original) control mode | ||
| const Scalar fraction_tolerance = this->param_.group_control_fraction_tolerance_; | ||
| if (ws.group_target->guiderate_ratio < fraction_tolerance) { |
There was a problem hiding this comment.
I understand that why the well rate ratio for one phase is two low, it means it has the problem of singular well matrix. I do not understand well, why the small guiderate_ratio can also be a problem (I probably did not get it when you tried to explain to me). At least, it is not related to the singular well matrix, right? It does not look affecting the case in OPM/opm-tests#1512 .
There was a problem hiding this comment.
Thanks, this is a good point. It might not be strictly neccessary, but the rationale was that we never attempt to solve a well with e.g., water-rate control if it has aprox zero water potential no matter what it's current (unconverged) water fraction is. I beleive this is more robust than only considering current fractions. I think that a well that cannot produce water, but for some reason is in a state with non-zero water fraction (in top segment), can result in a rather dramatic Newton update if solved with water constraint.
bb17a4d to
df601d2
Compare
|
jenkins build this failure_report please |
fa1cd05 to
54d7e0e
Compare
|
jenkins build this failure_report please |
GitPaean
left a comment
There was a problem hiding this comment.
Thanks for the update. Leaving two small minor comments for you to address. Feel free to merge after jenkins approves.
| { | ||
| if constexpr (Indices::enableSolvent) { | ||
| OPM_DEFLOG_THROW(std::runtime_error, | ||
| fmt::format("Function scaledWellFractions does not support solvent yet."), |
There was a problem hiding this comment.
I do not think fmt::format is necessary here.
| Scalar target_value{0.0}; | ||
| Group::ProductionCMode production_cmode {Group::ProductionCMode::NONE}; | ||
| Group::InjectionCMode injection_cmode {Group::InjectionCMode::NONE}; | ||
| Scalar guiderate_ratio{0.0}; // well to group guide rate ratio for diagnostics |
There was a problem hiding this comment.
maybe there also should be 1.0, because by default it should not trigger the fallback mode.
There was a problem hiding this comment.
Thanks, agreed. Fixed.
99b0af7 to
16227fc
Compare
|
jenkins build this failure_report please |
|
Did something go wrong in conflict resolution here? This PR appears to remove the new regression tests that were introduced in #6968. Was that intentional? |
Thanks for discovering! Absolutely not intentional. |
Rewritten #6889 with better functionality and hopefully less impact on cases where it should not be relevant. Main differences from #6889:
group_targetgroup_target_fallbackto well-stateuse_group_target_fallbackto well-stategetGroupProductionTargetRateandgetGroupProductionControlselects group-target based on above flagupdateWellStateWithTarget(to prevent bad scaling)group_control_fraction_tolerancegroup_control_fraction_tolerancewhich is currently defaulted to 1e-4.Will do some more testing before asking for review