Refactor logic based on iteration index

This commit is contained in:
Jakob Torben committed 2026-02-26 17:35:14 +01:00
1 parent 79eb2a0d8f
commit 61113726d8
32 files changed
+519 -271

No files matched your search

+4 -5
View File
@@ -135,10 +135,9 @@ public:
*/
bool converged() const
{
if (errorPvFraction_ < relaxedMaxPvFraction_) {
return (this->error_ < relaxedTolerance_ && errorSum_ < sumTolerance_) ;
} else if (this->numIterations() > numStrictIterations_) {
return (this->error_ < relaxedTolerance_ && errorSum_ < sumTolerance_) ;
if (errorPvFraction_ < relaxedMaxPvFraction_
|| this->problem().iterationContext().shouldRelax(numStrictIterations_)) {
return (this->error_ < relaxedTolerance_ && errorSum_ < sumTolerance_);
}
return this->error_ <= this->tolerance() && errorSum_ <= sumTolerance_;
@@ -257,7 +256,7 @@ public:
const SolutionVector& currentSolution)
{
ParentType::endIteration_(nextSolution, currentSolution);
OpmLog::debug( "Newton iteration " + std::to_string(this->numIterations_) + ""
OpmLog::debug( "Newton iteration " + std::to_string(this->problem().iterationContext().iteration()) + ""
+ " error: " + std::to_string(double(this->error_))
+ this->endIterMsg().str());
this->endIterMsg().str("");
@@ -161,8 +161,7 @@ private:
void initWellConnectionData();
void initWellState();
void assemble(const int iterationIdx,
const double dt);
void assemble(const double dt);
void calculateExplicitQuantities();
};
@@ -232,15 +232,15 @@ beginIteration()
elemCtx.updatePrimaryIntensiveQuantities(/*timeIdx=*/0);
}
assemble(simulator_.model().newtonMethod().numIterations(), simulator_.timeStepSize());
assemble(simulator_.timeStepSize());
}
template <typename TypeTag>
void
CompWellModel<TypeTag>::
assemble(const int iterationIdx,
const double dt)
assemble(const double dt)
{
const int iterationIdx = simulator_.problem().iterationContext().iteration();
if (iterationIdx == 0) {
this->calculateExplicitQuantities();
@@ -528,7 +528,7 @@ private:
// before the first iteration of each time step, we need to update the
// constraints. (i.e., we assume that constraints can be time dependent, but they
// can't depend on the solution.)
if (model_().newtonMethod().numIterations() == 0) {
if (problem_().iterationContext().isFirstGlobalIteration()) {
updateConstraintsMap_();
}
@@ -542,7 +542,7 @@ protected:
if (elemCtx.enableStorageCache()) {
const auto& model = elemCtx.model();
const unsigned globalDofIdx = elemCtx.globalSpaceIndex(dofIdx, /*timeIdx=*/0);
if (model.newtonMethod().numIterations() == 0 &&
if (elemCtx.problem().iterationContext().isFirstGlobalIteration() &&
!elemCtx.haveStashedIntensiveQuantities())
{
if (!elemCtx.problem().recycleFirstIterationStorage()) {
@@ -34,6 +34,8 @@
#include <opm/models/discretization/common/fvbaseproperties.hh>
#include <opm/models/discretization/common/restrictprolong.hh>
#include <opm/simulators/flow/NewtonIterationContext.hpp>
#include <opm/models/io/vtkmultiwriter.hh>
#include <opm/models/io/restart.hpp>
@@ -816,8 +818,46 @@ public:
VtkMultiWriter& defaultVtkWriter() const
{ return *defaultVtkWriter_; }
/*!
* \brief Returns the iteration context for iteration-dependent decisions.
*/
const NewtonIterationContext& iterationContext() const
{ return iterationContext_; }
/*!
* \brief Reset the iteration context for a new timestep.
*/
void resetIterationForNewTimestep()
{ iterationContext_.resetForNewTimestep(); }
/*!
* \brief Advance the iteration counter.
*/
void advanceIteration()
{ iterationContext_.advanceIteration(); }
/*!
* \brief Mark timestep initialization as complete.
*/
void markTimestepInitialized()
{ iterationContext_.markTimestepInitialized(); }
private:
// LocalContextGuard needs mutable access to save/restore the context
// during NLDD domain-local solves.
template<class P> friend class LocalContextGuard;
/*!
* \brief Mutable access to the iteration context for LocalContextGuard.
*/
NewtonIterationContext& mutableIterationContext()
{ return iterationContext_; }
public:
protected:
Scalar nextTimeStepSize_;
NewtonIterationContext iterationContext_;
bool enableVtkOutput_() const
{ return Parameters::Get<Parameters::EnableVtkOutput>(); }
@@ -996,7 +996,7 @@ private:
// but the starting state may not be identical to the start-of-step state.
// Note that a full assembly must be done before local solves
// otherwise this will be left un-updated.
if (model_().newtonMethod().numIterations() == 0 && !isNlddLocalSolve) {
if (problem_().iterationContext().isFirstGlobalIteration() && !isNlddLocalSolve) {
// Need to update the storage cache.
if (problem_().recycleFirstIterationStorage()) {
// Assumes nothing have changed in the system which
+1 -1
View File
@@ -192,7 +192,7 @@ protected:
// do not become grossly unphysical in a single iteration for the first few
// iterations of a time step
if (this->numIterations_ < 3) {
if (this->problem().iterationContext().iteration() < 3) {
// fugacities
for (unsigned compIdx = 0; compIdx < numComponents; ++compIdx) {
Scalar& val = nextValue[fugacity0Idx + compIdx];
+15 -30
View File
@@ -122,7 +122,6 @@ public:
, endIterMsgStream_(std::ostringstream::out)
, error_(1e100)
, lastError_(1e100)
, numIterations_(0)
, linearSolver_(simulator)
, comm_(Dune::MPIHelper::getCommunicator())
, convergenceWriter_(asImp_())
@@ -179,23 +178,6 @@ public:
const Model& model() const
{ return simulator_.model(); }
/*!
* \brief Returns the number of iterations done since the Newton method
* was invoked.
*/
int numIterations() const
{ return numIterations_; }
/*!
* \brief Set the index of current iteration.
*
* Normally this does not need to be called, but if the non-linear solver is
* implemented externally, it needs to be set in order for the model to do the Right
* Thing (TM) while linearizing.
*/
void setIterationIndex(int value)
{ numIterations_ = value; }
/*!
* \brief Return the current tolerance at which the Newton method considers itself to
* be converged.
@@ -449,14 +431,16 @@ public:
// conservative when increasing it. the rationale is
// that we want to avoid failing in the next time
// integration which would be quite expensive
if (numIterations_ > params_.targetIterations_) {
Scalar percent = Scalar(numIterations_ - params_.targetIterations_) / params_.targetIterations_;
const int nit = problem().iterationContext().iteration();
if (lastSolveFailed_ || nit > params_.targetIterations_) {
const int overshoot = lastSolveFailed_ ? params_.targetIterations_ : nit - params_.targetIterations_;
Scalar percent = Scalar(overshoot) / params_.targetIterations_;
Scalar nextDt = std::max(problem().minTimeStepSize(),
oldDt / (Scalar{1.0} + percent));
return nextDt;
}
Scalar percent = Scalar(params_.targetIterations_ - numIterations_) / params_.targetIterations_;
Scalar percent = Scalar(params_.targetIterations_ - nit) / params_.targetIterations_;
Scalar nextDt = std::max(problem().minTimeStepSize(),
oldDt*(Scalar{1.0} + percent / Scalar{1.2}));
return nextDt;
@@ -515,7 +499,9 @@ protected:
*/
void begin_(const SolutionVector&)
{
numIterations_ = 0;
lastSolveFailed_ = false;
problem().resetIterationForNewTimestep();
if (params_.writeConvergence_) {
convergenceWriter_.beginTimeStep();
@@ -755,7 +741,7 @@ protected:
void endIteration_(const SolutionVector&,
const SolutionVector&)
{
++numIterations_;
problem().advanceIteration();
const auto& comm = simulator_.gridView().comm();
bool succeeded = true;
@@ -777,7 +763,7 @@ protected:
}
if (asImp_().verbose_()) {
std::cout << "Newton iteration " << numIterations_ << ""
std::cout << "Newton iteration " << problem().iterationContext().iteration() << ""
<< " error: " << error_
<< endIterMsg().str() << "\n" << std::flush;
}
@@ -788,7 +774,7 @@ protected:
*/
bool proceed_() const
{
if (asImp_().numIterations() < 1) {
if (problem().iterationContext().iteration() < 1) {
return true; // we always do at least one full iteration
}
else if (asImp_().converged()) {
@@ -796,7 +782,7 @@ protected:
// do more iterations
return false;
}
else if (asImp_().numIterations() >= params_.maxIterations_) {
else if (problem().iterationContext().iteration() >= params_.maxIterations_) {
// we have exceeded the allowed number of steps. If the
// error was reduced by a factor of at least 4,
// in the last iterations we proceed even if we are above
@@ -824,7 +810,7 @@ protected:
* This method is called _after_ end_()
*/
void failed_()
{ numIterations_ = params_.targetIterations_ * 2; }
{ lastSolveFailed_ = true; }
/*!
* \brief Called if the Newton method was successful.
@@ -850,9 +836,8 @@ protected:
Scalar lastError_;
NewtonMethodParams<Scalar> params_;
// actual number of iterations done so far
int numIterations_;
// Flags that the last solve failed, triggering a timestep reduction.
bool lastSolveFailed_ = false;
// the linear solver
LinearSolverBackend linearSolver_;
+1 -1
View File
@@ -90,7 +90,7 @@ protected:
nextValue -= update;
// do not clamp anything after 4 iterations
if (this->numIterations_ > 4) {
if (this->problem().iterationContext().iteration() > 4) {
return;
}
+12 -13
View File
@@ -155,7 +155,6 @@ public:
SimulatorReportSingle prepareStep(const SimulatorTimerInterface& timer);
void initialLinearization(SimulatorReportSingle& report,
const int iteration,
const int minIter,
const int maxIter,
const SimulatorTimerInterface& timer);
@@ -164,22 +163,19 @@ public:
/// This model will perform a Newton-Raphson update, changing reservoir_state
/// and well_state. It will also use the nonlinear_solver to do relaxation of
/// updates if necessary.
/// \param[in] iteration should be 0 for the first call of a new timestep
/// The method increases the counter in iterationContext at the end of the method.
/// \param[in] timer simulation timer
/// \param[in] nonlinear_solver nonlinear solver used (for oscillation/relaxation control)
template <class NonlinearSolverType>
SimulatorReportSingle nonlinearIteration(const int iteration,
const SimulatorTimerInterface& timer,
SimulatorReportSingle nonlinearIteration(const SimulatorTimerInterface& timer,
NonlinearSolverType& nonlinear_solver);
template <class NonlinearSolverType>
SimulatorReportSingle nonlinearIterationNewton(const int iteration,
const SimulatorTimerInterface& timer,
SimulatorReportSingle nonlinearIterationNewton(const SimulatorTimerInterface& timer,
NonlinearSolverType& nonlinear_solver);
/// Assemble the residual and Jacobian of the nonlinear system.
SimulatorReportSingle assembleReservoir(const SimulatorTimerInterface& /* timer */,
const int iterationIdx);
SimulatorReportSingle assembleReservoir(const SimulatorTimerInterface& timer);
// compute the "relative" change of the solution between time steps
Scalar relativeChange() const;
@@ -236,8 +232,7 @@ public:
void convergencePerCell(const std::vector<Scalar>& B_avg,
const double dt,
const double tol_cnv,
const double tol_cnv_energy,
const int iteration);
const double tol_cnv_energy);
void updateTUNING(const Tuning& tuning);
void updateTUNINGDP(const TuningDp& tuning_dp);
@@ -245,7 +240,6 @@ public:
ConvergenceReport
getReservoirConvergence(const double reportTime,
const double dt,
const int iteration,
const int maxIter,
std::vector<Scalar>& B_avg,
std::vector<Scalar>& residual_norms);
@@ -253,12 +247,10 @@ public:
/// Compute convergence based on total mass balance (tol_mb) and maximum
/// residual mass balance (tol_cnv).
/// \param[in] timer simulation timer
/// \param[in] iteration current iteration number
/// \param[in] maxIter maximum number of iterations
/// \param[out] residual_norms CNV residuals by phase
ConvergenceReport
getConvergence(const SimulatorTimerInterface& timer,
const int iteration,
const int maxIter,
std::vector<Scalar>& residual_norms);
@@ -298,6 +290,13 @@ public:
const std::vector<StepReport>& stepReports() const
{ return convergence_reports_; }
/// Remove the last convergence report entry and residual norms history entry.
void popLastConvergenceReport()
{
convergence_reports_.back().report.pop_back();
residual_norms_history_.pop_back();
}
void writePartitions(const std::filesystem::path& odir) const;
/// return the StandardWells object
+47 -52
View File
@@ -237,27 +237,33 @@ public:
//! \brief Do one non-linear NLDD iteration.
template <class NonlinearSolverType>
SimulatorReportSingle nonlinearIterationNldd(const int iteration,
const SimulatorTimerInterface& timer,
SimulatorReportSingle nonlinearIterationNldd(const SimulatorTimerInterface& timer,
NonlinearSolverType& nonlinear_solver)
{
// ----------- Set up reports and timer -----------
SimulatorReportSingle report;
if (iteration < model_.param().nldd_num_initial_newton_iter_) {
report = model_.nonlinearIterationNewton(iteration,
timer,
nonlinear_solver);
// When nldd_num_initial_newton_iter_ == 0, NLDD starts immediately but wells are still
// properly initialized via NewtonIterationContext::needsTimestepInit() which
// ensures prepareTimeStep() is called during the initial global linearization.
// If nldd_num_initial_newton_iter_ > 0, run global Newton iterations first.
// TODO: Investigate whether wells and other subsystems are correctly initialized
// for the following NLDD iteration.
if (model_.simulator().problem().iterationContext().iteration() < model_.param().nldd_num_initial_newton_iter_) {
report = model_.nonlinearIterationNewton(timer, nonlinear_solver);
return report;
}
model_.initialLinearization(report, iteration, model_.param().newton_min_iter_,
model_.initialLinearization(report, model_.param().newton_min_iter_,
model_.param().newton_max_iter_, timer);
if (report.converged) {
return report;
}
// Remove the initial linearization entry; the final Newton step will
// record its own, keeping one entry per outer iteration.
model_.popLastConvergenceReport();
// ----------- If not converged, do an NLDD iteration -----------
Dune::Timer localSolveTimer;
Dune::Timer detailTimer;
@@ -282,7 +288,7 @@ public:
detailTimer.reset();
detailTimer.start();
domain_needs_solving_[domain_index] = checkIfSubdomainNeedsSolving(domain, iteration);
domain_needs_solving_[domain_index] = checkIfSubdomainNeedsSolving(domain);
updateMobilities(domain);
@@ -295,12 +301,12 @@ public:
switch (model_.param().local_solve_approach_) {
case DomainSolveApproach::Jacobi:
solveDomainJacobi(solution, locally_solved, local_report, logger,
iteration, timer, domain);
timer, domain);
break;
default:
case DomainSolveApproach::GaussSeidel:
solveDomainGaussSeidel(solution, locally_solved, local_report, logger,
iteration, timer, domain);
timer, domain);
break;
}
// This should have updated the global matrix to be
@@ -408,12 +414,8 @@ public:
local_reports_accumulated_.success.total_time += total_local_solve_time;
local_reports_accumulated_.success.pre_post_time += detailTimer.stop();
// Finish with a Newton step.
// Note that the "iteration + 100" is a simple way to avoid entering
// "if (iteration == 0)" and similar blocks, and also makes it a little
// easier to spot the iteration residuals in the DBG file. A more sophisticated
// approach can be done later.
auto rep = model_.nonlinearIterationNewton(iteration + 100, timer, nonlinear_solver);
// Finish with a global Newton step.
auto rep = model_.nonlinearIterationNewton(timer, nonlinear_solver);
report += rep;
if (rep.converged) {
report.converged = true;
@@ -483,26 +485,24 @@ private:
const SimulatorTimerInterface& timer,
SimulatorReportSingle& local_report,
DeferredLogger& logger,
[[maybe_unused]] const int global_iteration,
const bool initial_assembly_required)
{
auto& modelSimulator = model_.simulator();
Dune::Timer detailTimer;
modelSimulator.model().newtonMethod().setIterationIndex(0);
// RAII guard: local iteration context for this domain, restored on scope exit.
LocalContextGuard localCtxGuard(modelSimulator.problem());
auto& localCtx = localCtxGuard.context();
// When called, if assembly has already been performed
// with the initial values, we only need to check
// for local convergence. Otherwise, we must do a local
// assembly.
int iter = 0;
if (initial_assembly_required) {
detailTimer.start();
modelSimulator.model().newtonMethod().setIterationIndex(iter);
// TODO: we should have a beginIterationLocal function()
// only handling the well model for now
wellModel_.assemble(modelSimulator.model().newtonMethod().numIterations(),
modelSimulator.timeStepSize(),
wellModel_.assemble(modelSimulator.timeStepSize(),
domain);
const double tt0 = detailTimer.stop();
local_report.assemble_time += tt0;
@@ -517,7 +517,7 @@ private:
detailTimer.reset();
detailTimer.start();
std::vector<Scalar> resnorms;
auto convreport = this->getDomainConvergence(domain, timer, 0, logger, resnorms);
auto convreport = this->getDomainConvergence(domain, timer, logger, resnorms);
local_report.update_time += detailTimer.stop();
if (convreport.converged()) {
// TODO: set more info, timing etc.
@@ -565,8 +565,8 @@ private:
local_report.total_linear_iterations = domain_linsolvers_[domain.index].iterations();
modelSimulator.problem().endIteration();
local_report.converged = false;
local_report.total_newton_iterations = iter;
local_report.total_linearizations += iter;
local_report.total_newton_iterations = localCtx.iteration();
local_report.total_linearizations += localCtx.iteration();
return convreport;
}
model_.wellModel().postSolveDomain(x, domain);
@@ -586,13 +586,11 @@ private:
// Assemble well and reservoir.
detailTimer.reset();
detailTimer.start();
++iter;
modelSimulator.model().newtonMethod().setIterationIndex(iter);
localCtx.advanceIteration();
// TODO: we should have a beginIterationLocal function()
// only handling the well model for now
// Assemble reservoir locally.
wellModel_.assemble(modelSimulator.model().newtonMethod().numIterations(),
modelSimulator.timeStepSize(),
wellModel_.assemble(modelSimulator.timeStepSize(),
domain);
const double tt3 = detailTimer.stop();
local_report.assemble_time += tt3;
@@ -606,7 +604,7 @@ private:
detailTimer.reset();
detailTimer.start();
resnorms.clear();
convreport = this->getDomainConvergence(domain, timer, iter, logger, resnorms);
convreport = this->getDomainConvergence(domain, timer, logger, resnorms);
convergence_history.push_back(resnorms);
local_report.update_time += detailTimer.stop();
@@ -626,20 +624,20 @@ private:
bool oscillate = false;
bool stagnate = false;
const auto num_residuals = convergence_history.front().size();
detail::detectOscillations(convergence_history, iter, num_residuals,
detail::detectOscillations(convergence_history, localCtx.iteration(), num_residuals,
Scalar{0.2}, 1, oscillate, stagnate);
if (oscillate) {
damping_factor *= 0.85;
logger.debug(fmt::format(fmt::runtime("| Damping factor is now {}"), damping_factor));
}
}
} while (!convreport.converged() && iter <= max_iter);
} while (!convreport.converged() && localCtx.iteration() <= max_iter);
modelSimulator.problem().endIteration();
local_report.converged = convreport.converged();
local_report.total_newton_iterations = iter;
local_report.total_linearizations += iter;
local_report.total_newton_iterations = localCtx.iteration();
local_report.total_linearizations += localCtx.iteration();
// TODO: set more info, timing etc.
return convreport;
}
@@ -766,7 +764,6 @@ private:
ConvergenceReport getDomainReservoirConvergence(const double reportTime,
const double dt,
const int iteration,
const Domain& domain,
DeferredLogger& logger,
std::vector<Scalar>& B_avg,
@@ -774,6 +771,8 @@ private:
{
using Vector = std::vector<Scalar>;
const auto& iterCtx = model_.simulator().problem().iterationContext();
const int numComp = numEq;
Vector R_sum(numComp, 0.0 );
Vector maxCoeff(numComp, std::numeric_limits<Scalar>::lowest() );
@@ -794,11 +793,11 @@ private:
const int maxLocalIter = model_.param().max_local_solve_iterations_;
const bool relax_final_iteration_mb =
model_.param().min_strict_mb_iter_ < 0 && iteration == maxLocalIter;
model_.param().min_strict_mb_iter_ < 0 && iterCtx.iteration() == maxLocalIter;
const bool relax_iter_mb =
model_.param().min_strict_mb_iter_ >= 0 &&
iteration >= model_.param().min_strict_mb_iter_;
iterCtx.shouldRelax(model_.param().min_strict_mb_iter_);
const bool use_relaxed_mb = relax_final_iteration_mb || relax_iter_mb;
@@ -810,11 +809,11 @@ private:
// tolerance after the given number of iterations. We also use relaxed tolerances
// for cells with total pore-volume less than relaxed_max_pv_fraction_.
const bool relax_final_iteration_cnv =
model_.param().min_strict_cnv_iter_ < 0 && iteration == maxLocalIter;
model_.param().min_strict_cnv_iter_ < 0 && iterCtx.iteration() == maxLocalIter;
const bool relax_iter_cnv =
model_.param().min_strict_cnv_iter_ >= 0 &&
iteration >= model_.param().min_strict_cnv_iter_;
iterCtx.shouldRelax(model_.param().min_strict_cnv_iter_);
const bool relax_pv_fraction_cnv =
cnvErrorPvFraction < model_.param().relaxed_max_pv_fraction_;
@@ -869,9 +868,9 @@ private:
}
// Output of residuals. If converged at initial state, log nothing.
const bool converged_at_initial_state = (report.converged() && iteration == 0);
const bool converged_at_initial_state = (report.converged() && iterCtx.iteration() == 0);
if (!converged_at_initial_state) {
if (iteration == 0) {
if (iterCtx.iteration() == 0) {
// Log header.
std::string msg = fmt::format("Domain {} on rank {}, size {}, containing cell {}\n| Iter",
domain.index, this->rank_, domain.cells.size(), domain.cells[0]);
@@ -892,7 +891,7 @@ private:
ss << "| ";
const std::streamsize oprec = ss.precision(3);
const std::ios::fmtflags oflags = ss.setf(std::ios::scientific);
ss << std::setw(4) << iteration;
ss << std::setw(4) << iterCtx.iteration();
for (int compIdx = 0; compIdx < numComp; ++compIdx) {
ss << std::setw(11) << mass_balance_residual[compIdx];
}
@@ -909,7 +908,6 @@ private:
ConvergenceReport getDomainConvergence(const Domain& domain,
const SimulatorTimerInterface& timer,
const int iteration,
DeferredLogger& logger,
std::vector<Scalar>& residual_norms)
{
@@ -917,7 +915,6 @@ private:
std::vector<Scalar> B_avg(numEq, 0.0);
auto report = this->getDomainReservoirConvergence(timer.simulationTimeElapsed(),
timer.currentStepLength(),
iteration,
domain,
logger,
B_avg,
@@ -996,13 +993,12 @@ private:
GlobalEqVector& locally_solved,
SimulatorReportSingle& local_report,
DeferredLogger& logger,
const int iteration,
const SimulatorTimerInterface& timer,
const Domain& domain)
{
auto initial_local_well_primary_vars = wellModel_.getPrimaryVarsDomain(domain.index);
auto initial_local_solution = Details::extractVector(solution, domain.cells);
auto convrep = solveDomain(domain, timer, local_report, logger, iteration, false);
auto convrep = solveDomain(domain, timer, local_report, logger, false);
if (local_report.converged) {
auto local_solution = Details::extractVector(solution, domain.cells);
Details::setGlobal(local_solution, domain.cells, locally_solved);
@@ -1020,13 +1016,12 @@ private:
GlobalEqVector& locally_solved,
SimulatorReportSingle& local_report,
DeferredLogger& logger,
const int iteration,
const SimulatorTimerInterface& timer,
const Domain& domain)
{
auto initial_local_well_primary_vars = wellModel_.getPrimaryVarsDomain(domain.index);
auto initial_local_solution = Details::extractVector(solution, domain.cells);
auto convrep = solveDomain(domain, timer, local_report, logger, iteration, true);
auto convrep = solveDomain(domain, timer, local_report, logger, true);
if (!local_report.converged) {
// We look at the detailed convergence report to evaluate
// if we should accept the unconverged solution.
@@ -1169,7 +1164,7 @@ private:
}
}
bool checkIfSubdomainNeedsSolving(const Domain& domain, const int iteration)
bool checkIfSubdomainNeedsSolving(const Domain& domain)
{
if (domain.skip) {
return false;
@@ -1186,8 +1181,8 @@ private:
return true;
}
// Skip mobility check on first iteration
if (iteration == 0) {
// Skip mobility check on first global iteration (no previous mobilities to compare)
if (model_.simulator().problem().iterationContext().isFirstGlobalIteration()) {
return true;
}
+40 -46
View File
@@ -101,13 +101,14 @@ prepareStep(const SimulatorTimerInterface& timer)
simulator_.model().advanceTimeLevel();
}
// Set the timestep size, episode index, and non-linear iteration index
// for the model explicitly. The model needs to know the report step/episode index
// because of timing dependent data despite the fact that flow uses its
// own time stepper. (The length of the episode does not matter, though.)
// Set the timestep size and episode index for the model explicitly.
// The model needs to know the report step/episode index because of
// timing dependent data despite the fact that flow uses its own time stepper.
// (The length of the episode does not matter, though.)
simulator_.setTime(timer.simulationTimeElapsed());
simulator_.setTimeStepSize(timer.currentStepLength());
simulator_.model().newtonMethod().setIterationIndex(0);
simulator_.problem().resetIterationForNewTimestep();
simulator_.problem().beginTimeStep();
@@ -153,7 +154,6 @@ template <class TypeTag>
void
BlackoilModel<TypeTag>::
initialLinearization(SimulatorReportSingle& report,
const int iteration,
const int minIter,
const int maxIter,
const SimulatorTimerInterface& timer)
@@ -167,8 +167,11 @@ initialLinearization(SimulatorReportSingle& report,
// ----------- Assemble -----------
try {
report += assembleReservoir(timer, iteration);
report += assembleReservoir(timer);
report.assemble_time += perfTimer.stop();
// Mark timestep initialized after assembleReservoir(), because the well model's
// assemble() also uses needsTimestepInit() to trigger prepareTimeStep().
simulator_.problem().markTimestepInitialized();
}
catch (...) {
report.assemble_time += perfTimer.stop();
@@ -182,8 +185,9 @@ initialLinearization(SimulatorReportSingle& report,
perfTimer.start();
// the step is not considered converged until at least minIter iterations is done
{
auto convrep = getConvergence(timer, iteration, maxIter, residual_norms);
report.converged = convrep.converged() && iteration >= minIter;
auto convrep = getConvergence(timer, maxIter, residual_norms);
report.converged = convrep.converged() &&
simulator_.problem().iterationContext().iteration() >= minIter;
ConvergenceReport::Severity severity = convrep.severityOfWorstFailure();
convergence_reports_.back().report.push_back(std::move(convrep));
@@ -211,13 +215,13 @@ template <class TypeTag>
template <class NonlinearSolverType>
SimulatorReportSingle
BlackoilModel<TypeTag>::
nonlinearIteration(const int iteration,
const SimulatorTimerInterface& timer,
nonlinearIteration(const SimulatorTimerInterface& timer,
NonlinearSolverType& nonlinear_solver)
{
if (iteration == 0) {
// For each iteration we store in a vector the norms of the residual of
// the mass balance for each active phase, the well flux and the well equations.
// Model-level timestep initialization (once per timestep).
// markTimestepInitialized() is called later in initialLinearization(),
// after assembleReservoir() has triggered the well model's prepareTimeStep().
if (simulator_.problem().iterationContext().needsTimestepInit()) {
residual_norms_history_.clear();
conv_monitor_.reset();
current_relaxation_ = 1.0;
@@ -229,19 +233,16 @@ nonlinearIteration(const int iteration,
SimulatorReportSingle result;
if (this->param_.nonlinear_solver_ != "nldd")
{
result = this->nonlinearIterationNewton(iteration,
timer,
nonlinear_solver);
result = this->nonlinearIterationNewton(timer, nonlinear_solver);
}
else {
result = this->nlddSolver_->nonlinearIterationNldd(iteration,
timer,
nonlinear_solver);
result = this->nlddSolver_->nonlinearIterationNldd(timer, nonlinear_solver);
}
auto& rst_conv = simulator_.problem().eclWriter().mutableOutputModule().getConv();
rst_conv.update(simulator_.model().linearizer().residual());
simulator_.problem().advanceIteration();
return result;
}
@@ -249,8 +250,7 @@ template <class TypeTag>
template <class NonlinearSolverType>
SimulatorReportSingle
BlackoilModel<TypeTag>::
nonlinearIterationNewton(const int iteration,
const SimulatorTimerInterface& timer,
nonlinearIterationNewton(const SimulatorTimerInterface& timer,
NonlinearSolverType& nonlinear_solver)
{
// ----------- Set up reports and timer -----------
@@ -258,7 +258,6 @@ nonlinearIterationNewton(const int iteration,
Dune::Timer perfTimer;
this->initialLinearization(report,
iteration,
this->param_.newton_min_iter_,
this->param_.newton_max_iter_,
timer);
@@ -341,11 +340,9 @@ nonlinearIterationNewton(const int iteration,
template <class TypeTag>
SimulatorReportSingle
BlackoilModel<TypeTag>::
assembleReservoir(const SimulatorTimerInterface& /* timer */,
const int iterationIdx)
assembleReservoir(const SimulatorTimerInterface& /* timer */)
{
// -------- Mass balance equations --------
simulator_.model().newtonMethod().setIterationIndex(iterationIdx);
simulator_.problem().beginIteration();
simulator_.model().linearizer().linearizeDomain();
simulator_.problem().endIteration();
@@ -850,7 +847,6 @@ ConvergenceReport
BlackoilModel<TypeTag>::
getReservoirConvergence(const double reportTime,
const double dt,
const int iteration,
const int maxIter,
std::vector<Scalar>& B_avg,
std::vector<Scalar>& residual_norms)
@@ -858,6 +854,8 @@ getReservoirConvergence(const double reportTime,
OPM_TIMEBLOCK(getReservoirConvergence);
using Vector = std::vector<Scalar>;
const auto& iterCtx = simulator_.problem().iterationContext();
ConvergenceReport report{reportTime};
const int numComp = numEq;
@@ -888,11 +886,10 @@ getReservoirConvergence(const double reportTime,
// iterations. For positive values we use the relaxed tolerance
// after the given number of iterations
const bool relax_final_iteration_mb =
this->param_.min_strict_mb_iter_ < 0 && iteration == maxIter;
this->param_.min_strict_mb_iter_ < 0 && iterCtx.iteration() == maxIter;
const bool relax_iter_mb =
this->param_.min_strict_mb_iter_ >= 0 &&
iteration >= this->param_.min_strict_mb_iter_;
const bool relax_iter_mb = this->param_.min_strict_mb_iter_ >= 0 &&
iterCtx.shouldRelax(this->param_.min_strict_mb_iter_);
const bool use_relaxed_mb = relax_final_iteration_mb
|| relax_iter_mb;
@@ -904,11 +901,10 @@ getReservoirConvergence(const double reportTime,
// pore-volume less than relaxed_max_pv_fraction_. Default
// value of relaxed_max_pv_fraction_ is 0.03
const bool relax_final_iteration_cnv =
this->param_.min_strict_cnv_iter_ < 0 && iteration == maxIter;
this->param_.min_strict_cnv_iter_ < 0 && iterCtx.iteration() == maxIter;
const bool relax_iter_cnv =
this->param_.min_strict_cnv_iter_ >= 0 &&
iteration >= this->param_.min_strict_cnv_iter_;
const bool relax_iter_cnv = this->param_.min_strict_cnv_iter_ >= 0 &&
iterCtx.shouldRelax(this->param_.min_strict_cnv_iter_);
// Note trailing parentheses here, just before the final
// semicolon. This is an immediately invoked function
@@ -935,7 +931,7 @@ getReservoirConvergence(const double reportTime,
const bool use_drv_tol = this->param_.tolerance_max_drv_ > 0.0;
const bool use_dsol_tol = use_dp_tol || use_ds_tol || use_drs_tol || use_drv_tol;
bool relax_dsol_cnv = false;
if (iteration > 0 && use_dsol_tol) {
if (!iterCtx.isFirstGlobalIteration() && use_dsol_tol) {
maxSolUpd = getMaxSolutionUpdate(cnvSplitData.ixCells);
relax_dsol_cnv =
(!use_dp_tol || (maxSolUpd.dPMax > 0.0 && maxSolUpd.dPMax < this->param_.tolerance_max_dp_)) &&
@@ -1014,12 +1010,12 @@ getReservoirConvergence(const double reportTime,
}
// Compute the Newton convergence per cell.
this->convergencePerCell(B_avg, dt, tol_cnv, tol_cnv_energy, iteration);
this->convergencePerCell(B_avg, dt, tol_cnv, tol_cnv_energy);
// Output of residuals.
if (this->terminal_output_) {
// Only rank 0 does print to std::cout
if (iteration == 0) {
if (iterCtx.isFirstGlobalIteration()) {
std::string msg = "Iter";
for (int compIdx = 0; compIdx < numComp; ++compIdx) {
msg += " MB(";
@@ -1050,7 +1046,7 @@ getReservoirConvergence(const double reportTime,
const std::streamsize oprec = ss.precision(3);
const std::ios::fmtflags oflags = ss.setf(std::ios::scientific);
ss << std::setw(4) << iteration;
ss << std::setw(4) << iterCtx.iteration();
for (int compIdx = 0; compIdx < numComp; ++compIdx) {
ss << std::setw(11) << mass_balance_residual[compIdx];
}
@@ -1065,7 +1061,7 @@ getReservoirConvergence(const double reportTime,
if (!use_tol) {
return;
}
if (iteration == 0 || dsol <= 0.0) {
if (iterCtx.isFirstGlobalIteration() || dsol <= 0.0) {
ss << std::string(5, ' ') << "-" << std::string(5, ' ');
}
else {
@@ -1099,15 +1095,14 @@ BlackoilModel<TypeTag>::
convergencePerCell(const std::vector<Scalar>& B_avg,
const double dt,
const double tol_cnv,
const double tol_cnv_energy,
const int iteration)
const double tol_cnv_energy)
{
auto& rst_conv = simulator_.problem().eclWriter().mutableOutputModule().getConv();
if (!rst_conv.hasConv()) {
return;
}
if (iteration == 0) {
if (simulator_.problem().iterationContext().isFirstGlobalIteration()) {
rst_conv.prepareConv();
}
@@ -1144,7 +1139,6 @@ template <class TypeTag>
ConvergenceReport
BlackoilModel<TypeTag>::
getConvergence(const SimulatorTimerInterface& timer,
const int iteration,
const int maxIter,
std::vector<Scalar>& residual_norms)
{
@@ -1153,14 +1147,14 @@ getConvergence(const SimulatorTimerInterface& timer,
std::vector<Scalar> B_avg(numEq, 0.0);
auto report = getReservoirConvergence(timer.simulationTimeElapsed(),
timer.currentStepLength(),
iteration, maxIter, B_avg, residual_norms);
maxIter, B_avg, residual_norms);
{
OPM_TIMEBLOCK(getWellConvergence);
report += wellModel().getWellConvergence(B_avg,
/*checkWellGroupControlsAndNetwork*/report.converged());
}
conv_monitor_.checkPenaltyCard(report, iteration);
conv_monitor_.checkPenaltyCard(report, simulator_.problem().iterationContext().iteration());
return report;
}
@@ -0,0 +1,181 @@
/*
Copyright 2026 SINTEF AS.
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 <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_NEWTON_ITERATION_CONTEXT_HPP
#define OPM_NEWTON_ITERATION_CONTEXT_HPP
#include <cassert>
namespace Opm {
/// \brief Context for iteration-dependent decisions in the Newton solver.
///
/// Provides explicit state for iteration-dependent behavior:
///
/// - Global iteration: The top-level Newton iteration count (0-based).
/// Used for NUPCOL checks, group controls, gas lift timing.
///
/// - Local iteration: Iteration count within a nested domain solve.
/// Starts at 0 for each domain.
///
/// - Timestep initialization: One-time setup at the start of each timestep,
/// independent of iteration number.
///
/// For domain-local solves, `forLocalSolve()` creates a context that
/// preserves the global iteration but resets local state.
///
struct NewtonIterationContext {
NewtonIterationContext() = default;
/// Getters
/// Current iteration number (0-based).
/// Returns the local iteration during domain-local solves,
/// and the global iteration otherwise.
int iteration() const
{
return inLocalSolve_ ? localIteration_ : globalIteration_;
}
/// Whether we are inside a domain-local solve (NLDD).
bool inLocalSolve() const { return inLocalSolve_; }
/// Whether timestep initialization has been performed.
bool timestepInitialized() const { return timestepInitialized_; }
/// Semantic Queries
/// Should timestep initialization run?
/// True only on first global iteration before initialization is done.
bool needsTimestepInit() const
{
return !timestepInitialized_ && !inLocalSolve_;
}
/// Is this the first iteration of the global solve (not a local solve)?
/// Use for one-time-per-timestep logic
bool isFirstGlobalIteration() const
{
return globalIteration_ == 0 && !inLocalSolve_;
}
/// Are we within the NUPCOL iteration window?
/// Always uses global iteration regardless of local solve state.
bool withinNupcol(int nupcol) const
{
return globalIteration_ < nupcol;
}
/// Should tolerances be relaxed based on iteration count?
/// Uses local iteration for local solves, global otherwise.
bool shouldRelax(int strictIterations) const
{
return iteration() >= strictIterations;
}
/// Whether inner well iterations should run.
/// Returns false during local solves, otherwise true while below maxIter.
bool shouldRunInnerWellIterations(int maxIter) const
{
if (inLocalSolve_) return false;
return globalIteration_ < maxIter;
}
/// State Mutations
/// Mark timestep initialization as complete.
void markTimestepInitialized()
{
timestepInitialized_ = true;
}
/// Advance the current iteration counter.
/// Advances the local iteration during domain-local solves,
/// and the global iteration otherwise.
void advanceIteration()
{
if (inLocalSolve_) {
localIteration_++;
} else {
globalIteration_++;
}
}
/// Reset all state for a new timestep.
void resetForNewTimestep()
{
globalIteration_ = 0;
localIteration_ = 0;
timestepInitialized_ = false;
inLocalSolve_ = false;
}
/// Create a context for a domain-local solve.
/// Preserves global iteration, resets local iteration to 0.
NewtonIterationContext forLocalSolve() const
{
return {globalIteration_, 0, /*inLocalSolve=*/true, /*timestepInitialized=*/true};
}
private:
NewtonIterationContext(int globalIter, int localIter, bool localSolve, bool tsInit)
: globalIteration_(globalIter), localIteration_(localIter)
, inLocalSolve_(localSolve), timestepInitialized_(tsInit)
{}
int globalIteration_ = 0;
int localIteration_ = 0;
bool inLocalSolve_ = false;
bool timestepInitialized_ = false;
};
/// RAII guard for NLDD domain-local iteration context.
/// Saves the current context on the problem, installs a local-solve
/// context, and restores the original on destruction.
template<class Problem>
class LocalContextGuard {
public:
LocalContextGuard(Problem& problem)
: problem_(problem)
, saved_(problem.iterationContext())
{
problem_.mutableIterationContext() = saved_.forLocalSolve();
}
~LocalContextGuard()
{
problem_.mutableIterationContext() = saved_;
}
LocalContextGuard(const LocalContextGuard&) = delete;
LocalContextGuard& operator=(const LocalContextGuard&) = delete;
LocalContextGuard(LocalContextGuard&&) = delete;
LocalContextGuard& operator=(LocalContextGuard&&) = delete;
NewtonIterationContext& context() { return problem_.mutableIterationContext(); }
const NewtonIterationContext& context() const { return problem_.iterationContext(); }
private:
Problem& problem_;
NewtonIterationContext saved_;
};
} // namespace Opm
#endif // OPM_NEWTON_ITERATION_CONTEXT_HPP
+3 -8
View File
@@ -135,10 +135,6 @@ struct NonlinearSolverParameters
// Do model-specific once-per-step calculations.
report += model_->prepareStep(timer);
int iteration = 0;
// Let the model do one nonlinear iteration.
// Set up for main solver loop.
bool converged = false;
@@ -148,13 +144,12 @@ struct NonlinearSolverParameters
// Do the nonlinear step. If we are in a converged state, the
// model will usually do an early return without an expensive
// solve, unless the newton_min_iter_ count has not been reached yet.
auto iterReport = model_->nonlinearIteration(iteration, timer, *this);
auto iterReport = model_->nonlinearIteration(timer, *this);
iterReport.global_time = timer.simulationTimeElapsed();
report += iterReport;
report.converged = iterReport.converged;
converged = report.converged;
iteration += 1;
}
catch (...) {
// if an iteration fails during a time step, all previous iterations
@@ -164,8 +159,8 @@ struct NonlinearSolverParameters
throw;
}
}
while ( (!converged && (iteration <= this->model().param().newton_max_iter_)) ||
(iteration <= this->model().param().newton_min_iter_));
while ( (!converged && (model_->simulator().problem().iterationContext().iteration() <= this->model().param().newton_max_iter_)) ||
(model_->simulator().problem().iterationContext().iteration() <= this->model().param().newton_min_iter_));
if (!converged) {
failureReport_ = report;
+1 -2
View File
@@ -533,8 +533,7 @@ std::unique_ptr<Matrix> blockJacobiAdjacency(const Grid& grid,
}
if (this->parameters_[activeSolverNum_].cpr_reuse_setup_ == 1) {
// Recreate solver on the first iteration of every timestep.
const int newton_iteration = this->simulator_.model().newtonMethod().numIterations();
return newton_iteration == 0;
return this->simulator_.problem().iterationContext().isFirstGlobalIteration();
}
if (this->parameters_[activeSolverNum_].cpr_reuse_setup_ == 2) {
// Recreate solver if the last solve used more than 10 iterations.
@@ -73,10 +73,11 @@ namespace Opm::Helper {
}
// Combine and return.
const auto& iterCtx = simulator.problem().iterationContext();
std::ostringstream oss;
oss << "prob_" << simulator.episodeIndex()
<< "_time_" << std::setprecision(15) << std::setw(12) << std::setfill('0') << simulator.time()
<< "_nit_" << simulator.model().newtonMethod().numIterations()
<< "_nit_" << iterCtx.iteration()
<< '_' << objName << "_istl";
const auto filename = (output_dir / oss.str()).generic_string();
+10 -15
View File
@@ -156,8 +156,7 @@ template<class Scalar> class WellContributions;
void beginIteration()
{
OPM_TIMEBLOCK(beginIteration);
assemble(simulator_.model().newtonMethod().numIterations(),
simulator_.timeStepSize());
assemble(simulator_.timeStepSize());
}
void endIteration()
@@ -263,26 +262,23 @@ template<class Scalar> class WellContributions;
// called at the beginning of a report step
void beginReportStep(const int time_step);
// it should be able to go to prepareTimeStep(), however, the updateWellControls()
// makes it a little more difficult. unless we introduce if (iterationIdx != 0) to avoid doing the above function
// twice at the beginning of the time step
/// Calculating the explict quantities used in the well calculation. By explicit, we mean they are cacluated
/// at the beginning of the time step and no derivatives are included in these quantities
/// Calculating the explicit quantities used in the well calculation. By explicit, we mean they are calculated
/// at the beginning of the time step and no derivatives are included in these quantities.
/// Called from assemble() when needsTimestepInit() returns true.
void calculateExplicitQuantities() const;
// some preparation work, mostly related to group control and RESV,
// at the beginning of each time step (Not report step)
/// One-time initialization at the start of each timestep.
/// Called once per timestep from assemble() when needsTimestepInit() returns true.
void prepareTimeStep(DeferredLogger& deferred_logger);
bool
updateWellControls(DeferredLogger& deferred_logger);
void updateAndCommunicate(const int reportStepIdx,
const int iterationIdx);
void updateAndCommunicate(const int reportStepIdx);
bool updateGroupControls(const Group& group,
DeferredLogger& deferred_logger,
const int reportStepIdx,
const int iterationIdx);
const int reportStepIdx);
const WellInterface<TypeTag>& getWell(const std::string& well_name) const;
@@ -535,8 +531,7 @@ template<class Scalar> class WellContributions;
// compute the well fluxes and assemble them in to the reservoir equations as source terms
// and in the well equations.
void assemble(const int iterationIdx,
const double dt);
void assemble(const double dt);
// well controls and network pressures affect each other and are solved in an iterative manner.
// the function handles one iteration of updating well controls and network pressures.
@@ -86,12 +86,13 @@ maybeDoGasLiftOptimize(const Simulator& simulator,
// them to the GasLiftGroupInfo constructor.
GLiftEclWells ecl_well_map;
initGliftEclWellMap(well_container, ecl_well_map);
const auto& iterCtx = simulator.problem().iterationContext();
GasLiftGroupInfo group_info {
ecl_well_map,
simulator.vanguard().schedule(),
simulator.vanguard().summaryState(),
simulator.episodeIndex(),
simulator.model().newtonMethod().numIterations(),
iterCtx,
deferred_logger,
wellState,
groupState,
@@ -280,7 +281,8 @@ gasLiftOptimizationStage1SingleWell(WellInterface<TypeTag>* well,
sync_groups,
simulator.vanguard().gridView().comm(),
this->glift_debug);
auto state = glift->runOptimize(simulator.model().newtonMethod().numIterations());
const auto& iterCtx = simulator.problem().iterationContext();
auto state = glift->runOptimize(iterCtx.iteration());
if (state) {
state_map.emplace(well->name(), std::move(state));
glift_wells.emplace(well->name(), std::move(glift));
@@ -1206,7 +1206,7 @@ groupAndNetworkData(const int reportStepIdx) const
template<typename Scalar, typename IndexTraits>
void BlackoilWellModelGeneric<Scalar, IndexTraits>::
updateAndCommunicateGroupData(const int reportStepIdx,
const int iterationIdx,
const NewtonIterationContext& iterCtx,
const Scalar tol_nupcol,
const bool update_wellgrouptarget)
{
@@ -1215,7 +1215,7 @@ updateAndCommunicateGroupData(const int reportStepIdx,
const int nupcol = schedule()[reportStepIdx].nupcol();
// Update accumulated group consumption/import rates for current report step
if(iterationIdx == 0) {
if (iterCtx.isFirstGlobalIteration()) {
this->groupState().update_gconsump(schedule(), reportStepIdx, this->summaryState_);
}
@@ -1232,7 +1232,7 @@ updateAndCommunicateGroupData(const int reportStepIdx,
this->wellState().updateGlobalIsGrup(comm_, well_status);
GroupStateHelperType &group_state_helper = this->groupStateHelper();
if (iterationIdx < nupcol) {
if (iterCtx.withinNupcol(nupcol)) {
OPM_TIMEBLOCK(updateNupcol);
this->updateNupcolWGState();
} else {
@@ -1282,7 +1282,7 @@ updateAndCommunicateGroupData(const int reportStepIdx,
const std::string control_str = is_vrep? "VREP" : "REIN";
const std::string msg = fmt::format("Group prodution relative change {} larger than tolerance {} "
"at iteration {}. Update {} for Group {} even if iteration is larger than {} given by NUPCOL." ,
rel_change, tol_nupcol, iterationIdx, control_str, gr_name, nupcol);
rel_change, tol_nupcol, iterCtx.iteration(), control_str, gr_name, nupcol);
group_state_helper.deferredLogger().debug(msg);
}
}
@@ -31,6 +31,7 @@
#include <opm/input/eclipse/Schedule/Well/PAvgCalculatorCollection.hpp>
#include <opm/input/eclipse/Schedule/Well/WellTestState.hpp>
#include <opm/simulators/flow/NewtonIterationContext.hpp>
#include <opm/simulators/utils/DeferredLoggingErrorHelpers.hpp>
#include <opm/simulators/wells/BlackoilWellModelWBP.hpp>
#include <opm/simulators/wells/ConnectionIndexMap.hpp>
@@ -304,7 +305,7 @@ public:
}
void updateAndCommunicateGroupData(const int reportStepIdx,
const int iterationIdx,
const NewtonIterationContext& iterCtx,
const Scalar tol_nupcol,
// we only want to update the wellgroup target
// after the groups have found their controls
@@ -105,7 +105,7 @@ needPreStepRebalance(const int report_step) const
template<typename Scalar, typename IndexTraits>
bool BlackoilWellModelNetworkGeneric<Scalar, IndexTraits>::
shouldBalance(const int reportStepIdx, const int iterationIdx) const
shouldBalance(const int reportStepIdx, const NewtonIterationContext& iterCtx) const
{
// if network is not active, we do not need to balance the network
const auto& network = well_model_.schedule()[reportStepIdx].network();
@@ -115,10 +115,10 @@ shouldBalance(const int reportStepIdx, const int iterationIdx) const
const auto& balance = well_model_.schedule()[reportStepIdx].network_balance();
if (balance.mode() == Network::Balance::CalcMode::TimeStepStart) {
return iterationIdx == 0;
return iterCtx.isFirstGlobalIteration();
} else if (balance.mode() == Network::Balance::CalcMode::NUPCOL) {
const int nupcol = well_model_.schedule()[reportStepIdx].nupcol();
return iterationIdx < nupcol;
return iterCtx.withinNupcol(nupcol);
} else {
// We do not support any other rebalancing modes,
// i.e. TimeInterval based rebalancing is not available.
@@ -27,6 +27,7 @@
#include <opm/output/data/Groups.hpp>
#include <opm/simulators/flow/NewtonIterationContext.hpp>
#include <opm/simulators/utils/ParallelCommunication.hpp>
#include <map>
@@ -80,7 +81,7 @@ public:
bool needPreStepRebalance(const int report_step) const;
bool shouldBalance(const int reportStepIndex,
const int iterationIdx) const;
const NewtonIterationContext& iterCtx) const;
Scalar updatePressures(const int reportStepIdx,
const Scalar damping_factor,
@@ -96,13 +96,13 @@ update(const bool mandatory_network_balance,
return {false, 0.0};
}
const int iterationIdx = well_model_.simulator().model().newtonMethod().numIterations();
const auto& iterCtx = well_model_.simulator().problem().iterationContext();
const auto& comm = well_model_.simulator().vanguard().grid().comm();
// network related
Scalar network_imbalance = 0.0;
bool more_network_update = false;
if (this->shouldBalance(episodeIdx, iterationIdx) || mandatory_network_balance) {
if (this->shouldBalance(episodeIdx, iterCtx) || mandatory_network_balance) {
OPM_TIMEBLOCK(BalanceNetwork);
const double dt = well_model_.simulator().timeStepSize();
// Calculate common THP for subsea manifold well group (item 3 of NODEPROP set to YES)
@@ -144,7 +144,7 @@ update(const bool mandatory_network_balance,
}
}
well_model_.updateAndCommunicateGroupData(episodeIdx,
iterationIdx,
iterCtx,
well_model_.param().nupcol_group_rate_tolerance_,
/*update_wellgrouptarget*/ true);
}
@@ -102,8 +102,7 @@ public:
// prototype for assemble function for ASPIN solveLocal()
// will try to merge back to assemble() when done prototyping
void assemble(const int iterationIdx,
const double dt,
void assemble(const double dt,
const Domain& domain);
void updateWellControls(const Domain& domain);
@@ -36,15 +36,55 @@ namespace Opm {
template<typename TypeTag>
void
BlackoilWellModelNldd<TypeTag>::
assemble(const int /*iterationIdx*/,
const double dt,
assemble(const double dt,
const Domain& domain)
{
OPM_TIMEBLOCK(assemble);
// We assume that calculateExplicitQuantities() and
// prepareTimeStep() have been called already for the entire
// well model, so we do not need to do it here (when
// iterationIdx is 0).
// NLDD domain well assembly for local solves.
//
// Call chain: BlackoilWellModelNldd::assemble()
// -> BlackoilWellModelNldd::assembleWellEq(dt, domain)
// -> WellInterface::assembleWellEq()
// -> WellInterface::prepareWellBeforeAssembling()
// -> WellInterface::assembleWellEqWithoutIteration()
//
// Key assumptions and design decisions:
//
// 1. Global well initialization (BlackoilWellModel::prepareTimeStep, including the
// optional initial well solve) has already been called via
// BlackoilWellModel::assemble() during the initial global linearization.
// This is ensured by NewtonIterationContext::needsTimestepInit().
//
// 2. Inner well iterations (BlackoilWellModel::iterateWellEquations) are skipped
// during local solves via NewtonIterationContext::shouldRunInnerWellIterations()
// (inLocalSolve=true).
// The NLDD local Newton loop already iterates wells to convergence.
//
// 3. No MPI collective operations (do_mpi_gather=false).
//
// 4. Only individual well constraints are checked (see
// BlackoilWellModelNldd::updateWellControls).
// Group controls, network balancing, gas lift, and NUPCOL/VREP updates
// are NOT performed during domain solves. This means:
// - Wells can switch between individual controls (BHP, THP, rate limits)
// to respect physical safety constraints.
// - Wells cannot be switched to/from GRUP control. Individual constraint
// checks (WellInterface::checkIndividualConstraints) never produce GRUP
// as a result; only WellInterface::checkGroupConstraints can do that,
// and it is not called here.
// - Group production targets and VREP/REIN injection targets use stale
// group state from the last global assembly.
//
// The final Newton step after NLDD domain solves re-synchronizes everything
// via a global BlackoilWellModel::assemble() which performs the full control
// update pipeline: BlackoilWellModelGeneric::updateAndCommunicateGroupData,
// BlackoilWellModelGeneric::updateGroupControls,
// group+individual well constraint checks, network balancing, and gas lift.
// Importantly, the global step also checks individual constraints (after
// group constraints), so if a domain solve switched a well to e.g. BHP
// because of a genuine BHP limit violation, the global step will reach
// the same conclusion -- the two levels are consistent for individual
// constraints.
// Use do_mpi_gather=false to avoid MPI collective operations in domain solves.
auto loggerGuard = wellModel_.groupStateHelper().pushLogger(/*do_mpi_gather=*/false);
@@ -132,8 +172,8 @@ getWellConvergence(const Domain& domain,
const std::vector<Scalar>& B_avg,
DeferredLogger& local_deferredLogger) const
{
const int iterationIdx = wellModel_.simulator().model().newtonMethod().numIterations();
const bool relax_tolerance = iterationIdx > wellModel_.numStrictIterations();
const auto& iterCtx = wellModel_.simulator().problem().iterationContext();
const bool relax_tolerance = iterCtx.shouldRelax(wellModel_.numStrictIterations());
ConvergenceReport report;
{
@@ -184,10 +224,15 @@ updateWellControls(const Domain& domain)
return;
}
// TODO: decide on and implement an approach to handling of
// group controls, network and similar for domain solves.
// Check only individual well constraints and communicate.
// Only individual well constraints (BHP, THP, rate limits) are checked.
// Group constraints, network balancing, and NUPCOL/VREP updates require
// MPI collectives and consistent global group state, which are not
// available during domain-local solves. The final global Newton step
// handles all group-level control decisions.
//
// Individual constraints are physical safety limits that are essential
// for well convergence (e.g., BHP minimum prevents infeasible operating
// points). Skipping them would cause domain convergence failures.
for (const auto& well : wellModel_.localNonshutWells()) {
if (this->well_domain().at(well->name()) == domain.index) {
constexpr auto mode = WellInterface<TypeTag>::IndividualOrGroup::Individual;
+39 -33
View File
@@ -388,8 +388,9 @@ namespace Opm {
// we need to update the group data after the well is created
// to make sure we get the correct mapping.
const auto& iterCtx = simulator_.problem().iterationContext();
this->updateAndCommunicateGroupData(reportStepIdx,
simulator_.model().newtonMethod().numIterations(),
iterCtx,
param_.nupcol_group_rate_tolerance_, /*update_wellgrouptarget*/ false);
// Wells are active if they are active wells on at least one process.
@@ -492,10 +493,13 @@ namespace Opm {
OPM_PARALLEL_CATCH_CLAUSE(exc_type, exc_msg);
}
this->updateAndCommunicateGroupData(reportStepIdx,
simulator_.model().newtonMethod().numIterations(),
param_.nupcol_group_rate_tolerance_,
/*update_wellgrouptarget*/ true);
{
const auto& iterCtx = simulator_.problem().iterationContext();
this->updateAndCommunicateGroupData(reportStepIdx,
iterCtx,
param_.nupcol_group_rate_tolerance_,
/*update_wellgrouptarget*/ true);
}
try {
// Compute initial well solution for new wells and injectors that change injection type i.e. WAG.
for (auto& well : well_container_) {
@@ -1155,17 +1159,18 @@ namespace Opm {
template<typename TypeTag>
void
BlackoilWellModel<TypeTag>::
assemble(const int iterationIdx,
const double dt)
assemble(const double dt)
{
OPM_TIMEFUNCTION();
auto logger_guard = this->groupStateHelper().pushLogger();
auto& local_deferredLogger = this->groupStateHelper().deferredLogger();
const auto& iterCtx = simulator_.problem().iterationContext();
if constexpr (BlackoilWellModelGasLift<TypeTag>::glift_debug) {
if (gaslift_.terminalOutput()) {
const std::string msg =
fmt::format(fmt::runtime("assemble() : iteration {}"), iterationIdx);
fmt::format(fmt::runtime("assemble() : iteration {}"), iterCtx.iteration());
gaslift_.gliftDebug(msg, local_deferredLogger);
}
}
@@ -1182,8 +1187,9 @@ namespace Opm {
}
}
if (iterationIdx == 0 && this->wellsActive()) {
OPM_TIMEBLOCK(firstIterationAssmble);
// Timestep initialization: should run once at the start of each timestep.
if (iterCtx.needsTimestepInit() && this->wellsActive()) {
OPM_TIMEBLOCK(firstIterationAssemble);
// try-catch is needed here as updateWellControls
// contains global communication and has either to
// be reached by all processes or all need to abort
@@ -1194,7 +1200,7 @@ namespace Opm {
prepareTimeStep(local_deferredLogger);
}
OPM_END_PARALLEL_TRY_CATCH_LOG(local_deferredLogger,
"assemble() failed (It=0): ",
"assemble() failed during well initialization: ",
this->terminal_output_, grid().comm());
}
@@ -1241,8 +1247,8 @@ namespace Opm {
if (network_update_iteration >= max_iteration ) {
// only output to terminal if we at the last newton iterations where we try to balance the network.
const int episodeIdx = simulator_.episodeIndex();
const int iterationIdx = simulator_.model().newtonMethod().numIterations();
if (this->network_.shouldBalance(episodeIdx, iterationIdx + 1)) {
const auto& iterCtx = simulator_.problem().iterationContext();
if (this->network_.shouldBalance(episodeIdx, iterCtx)) {
if (this->terminal_output_) {
const std::string msg = fmt::format("Maximum of {:d} network iterations has been used and we stop the update, \n"
"and try again after the next Newton iteration (imbalance = {:.2e} bar)",
@@ -1288,9 +1294,9 @@ namespace Opm {
DeferredLogger& local_deferredLogger)
{
OPM_TIMEFUNCTION();
const int iterationIdx = simulator_.model().newtonMethod().numIterations();
const auto& iterCtx = simulator_.problem().iterationContext();
const int reportStepIdx = simulator_.episodeIndex();
this->updateAndCommunicateGroupData(reportStepIdx, iterationIdx,
this->updateAndCommunicateGroupData(reportStepIdx, iterCtx,
param_.nupcol_group_rate_tolerance_, /*update_wellgrouptarget*/ true);
// We need to call updateWellControls before we update the network as
// network updates are only done on thp controlled wells.
@@ -1308,7 +1314,7 @@ namespace Opm {
if (optimize_gas_lift) {
// we need to update the potentials if the thp limit as been modified by
// the network balancing
const bool updatePotentials = (this->network_.shouldBalance(reportStepIdx, iterationIdx) ||
const bool updatePotentials = (this->network_.shouldBalance(reportStepIdx, iterCtx) ||
mandatory_network_balance);
alq_updated = gaslift_.maybeDoGasLiftOptimize(simulator_,
well_container_,
@@ -1337,7 +1343,7 @@ namespace Opm {
}
// we need to re-iterate the network when the well group controls changed or gaslift/alq is changed or
// the inner iterations are did not converge
const bool more_network_update = this->network_.shouldBalance(reportStepIdx, iterationIdx) &&
const bool more_network_update = this->network_.shouldBalance(reportStepIdx, iterCtx) &&
(more_inner_network_update || alq_updated);
return {well_group_control_changed, more_network_update, network_imbalance};
}
@@ -1582,14 +1588,15 @@ namespace Opm {
{
// Get global (from all processes) convergence report.
ConvergenceReport local_report;
const int iterationIdx = simulator_.model().newtonMethod().numIterations();
const auto& iterCtx = simulator_.problem().iterationContext();
const bool relaxTolerance = iterCtx.shouldRelax(param_.strict_outer_iter_wells_);
{
auto logger_guard = this->groupStateHelper().pushLogger();
for (const auto& well : well_container_) {
if (well->isOperableAndSolvable() || well->wellIsStopped()) {
local_report += well->getWellConvergence(
this->groupStateHelper(), B_avg,
iterationIdx > param_.strict_outer_iter_wells_);
relaxTolerance);
} else {
ConvergenceReport report;
using CR = ConvergenceReport;
@@ -1650,7 +1657,6 @@ namespace Opm {
return false;
}
const int episodeIdx = simulator_.episodeIndex();
const int iterationIdx = simulator_.model().newtonMethod().numIterations();
const auto& comm = simulator_.vanguard().grid().comm();
size_t iter = 0;
bool changed_well_group = false;
@@ -1659,7 +1665,7 @@ namespace Opm {
// iterate a few times to make sure all constraints are honored
const std::size_t max_iter = param_.well_group_constraints_max_iterations_;
while(!changed_well_group && iter < max_iter) {
changed_well_group = updateGroupControls(fieldGroup, deferred_logger, episodeIdx, iterationIdx);
changed_well_group = updateGroupControls(fieldGroup, deferred_logger, episodeIdx);
// Check wells' group constraints and communicate.
bool changed_well_to_group = false;
@@ -1683,7 +1689,7 @@ namespace Opm {
changed_well_to_group = comm.sum(static_cast<int>(changed_well_to_group));
if (changed_well_to_group) {
updateAndCommunicate(episodeIdx, iterationIdx);
updateAndCommunicate(episodeIdx);
changed_well_group = true;
}
@@ -1708,7 +1714,7 @@ namespace Opm {
changed_well_individual = comm.sum(static_cast<int>(changed_well_individual));
if (changed_well_individual) {
updateAndCommunicate(episodeIdx, iterationIdx);
updateAndCommunicate(episodeIdx);
changed_well_group = true;
}
iter++;
@@ -1724,11 +1730,11 @@ namespace Opm {
template<typename TypeTag>
void
BlackoilWellModel<TypeTag>::
updateAndCommunicate(const int reportStepIdx,
const int iterationIdx)
updateAndCommunicate(const int reportStepIdx)
{
const auto& iterCtx = simulator_.problem().iterationContext();
this->updateAndCommunicateGroupData(reportStepIdx,
iterationIdx,
iterCtx,
param_.nupcol_group_rate_tolerance_,
/*update_wellgrouptarget*/ true);
@@ -1750,7 +1756,7 @@ namespace Opm {
OPM_END_PARALLEL_TRY_CATCH("BlackoilWellModel::updateAndCommunicate failed: ",
simulator_.gridView().comm())
this->updateAndCommunicateGroupData(reportStepIdx,
iterationIdx,
iterCtx,
param_.nupcol_group_rate_tolerance_,
/*update_wellgrouptarget*/ true);
}
@@ -1760,19 +1766,19 @@ namespace Opm {
BlackoilWellModel<TypeTag>::
updateGroupControls(const Group& group,
DeferredLogger& deferred_logger,
const int reportStepIdx,
const int iterationIdx)
const int reportStepIdx)
{
OPM_TIMEFUNCTION();
const auto& iterCtx = simulator_.problem().iterationContext();
bool changed = false;
// restrict the number of group switches but only after nupcol iterations.
const int nupcol = this->schedule()[reportStepIdx].nupcol();
const int max_number_of_group_switches = param_.max_number_of_group_switches_;
const bool update_group_switching_log = iterationIdx >= nupcol;
const bool update_group_switching_log = !iterCtx.withinNupcol(nupcol);
const bool changed_hc = this->checkGroupHigherConstraints(group, deferred_logger, reportStepIdx, max_number_of_group_switches, update_group_switching_log);
if (changed_hc) {
changed = true;
updateAndCommunicate(reportStepIdx, iterationIdx);
updateAndCommunicate(reportStepIdx);
}
bool changed_individual =
@@ -1790,11 +1796,11 @@ namespace Opm {
if (changed_individual) {
changed = true;
updateAndCommunicate(reportStepIdx, iterationIdx);
updateAndCommunicate(reportStepIdx);
}
// call recursively down the group hierarchy
for (const std::string& groupName : group.groups()) {
bool changed_this = updateGroupControls( this->schedule().getGroup(groupName, reportStepIdx), deferred_logger, reportStepIdx,iterationIdx);
bool changed_this = updateGroupControls(this->schedule().getGroup(groupName, reportStepIdx), deferred_logger, reportStepIdx);
changed = changed || changed_this;
}
return changed;
+10 -8
View File
@@ -41,7 +41,7 @@ GasLiftGroupInfo(GLiftEclWells& ecl_wells,
const Schedule& schedule,
const SummaryState& summary_state,
const int report_step_idx,
const int iteration_idx,
const NewtonIterationContext& iterCtx,
DeferredLogger& deferred_logger,
WellState<Scalar, IndexTraits>& well_state,
const GroupState<Scalar>& group_state,
@@ -52,7 +52,7 @@ GasLiftGroupInfo(GLiftEclWells& ecl_wells,
, schedule_{schedule}
, summary_state_{summary_state}
, report_step_idx_{report_step_idx}
, iteration_idx_{iteration_idx}
, iterCtx_{iterCtx}
, phase_usage_{well_state.phaseUsageInfo()}
, glo_{schedule_.glo(report_step_idx_)}
{}
@@ -426,25 +426,27 @@ checkNewtonIterationIdxOk_(const std::string& well_name)
{
if (this->glo_.all_newton()) {
const int nupcol = this->schedule_[this->report_step_idx_].nupcol();
const bool optimize = this->iterCtx_.withinNupcol(nupcol);
if (this->debug) {
const std::string msg = fmt::format(
"LIFTOPT item4 == YES, it = {}, nupcol = {} --> GLIFT optimize = {}",
this->iteration_idx_,
this->iterCtx_.iteration(),
nupcol,
((this->iteration_idx_ < nupcol) ? "TRUE" : "FALSE"));
(optimize ? "TRUE" : "FALSE"));
displayDebugMessage_(msg, well_name);
}
return this->iteration_idx_ < nupcol;
return optimize;
}
else {
const bool optimize = this->iterCtx_.iteration() == 1;
if (this->debug) {
const std::string msg = fmt::format(
"LIFTOPT item4 == NO, it = {} --> GLIFT optimize = {}",
this->iteration_idx_,
((this->iteration_idx_ == 1) ? "TRUE" : "FALSE"));
this->iterCtx_.iteration(),
(optimize ? "TRUE" : "FALSE"));
displayDebugMessage_(msg, well_name);
}
return this->iteration_idx_ == 1;
return optimize;
}
}
+3 -2
View File
@@ -20,6 +20,7 @@
#ifndef OPM_GASLIFT_GROUP_INFO_HEADER_INCLUDED
#define OPM_GASLIFT_GROUP_INFO_HEADER_INCLUDED
#include <opm/simulators/flow/NewtonIterationContext.hpp>
#include <opm/simulators/wells/GasLiftCommon.hpp>
#include <map>
@@ -66,7 +67,7 @@ public:
const Schedule& schedule,
const SummaryState& summary_state,
const int report_step_idx,
const int iteration_idx,
const NewtonIterationContext& iterCtx,
DeferredLogger& deferred_logger,
WellState<Scalar, IndexTraits>& well_state,
const GroupState<Scalar>& group_state,
@@ -234,7 +235,7 @@ protected:
const Schedule& schedule_;
const SummaryState& summary_state_;
const int report_step_idx_;
const int iteration_idx_;
const NewtonIterationContext& iterCtx_;
const PhaseUsageInfo<IndexTraits>& phase_usage_;
const GasLiftOpt& glo_;
GroupRateMap group_rate_map_;
+15 -9
View File
@@ -212,7 +212,7 @@ namespace Opm
}
const int episodeIdx = simulator.episodeIndex();
const int iterationIdx = simulator.model().newtonMethod().numIterations();
const auto& iterCtx = simulator.problem().iterationContext();
const int nupcol = schedule[episodeIdx].nupcol();
const bool oscillating =
std::ranges::count(this->well_control_log_, from) >= this->param_.max_number_of_well_switches_;
@@ -255,6 +255,9 @@ namespace Opm
ss << " Switching control mode for well " << this->name()
<< " from " << from
<< " to " << to;
if (iterCtx.inLocalSolve()) {
ss << " (NLDD domain solve)";
}
if (cc.size() > 1) {
ss << " on rank " << cc.rank();
}
@@ -263,7 +266,7 @@ namespace Opm
// We always store the current control as it is used for output
// and only after iteration >= nupcol
// we log all switches to check if the well controls oscillates
if (iterationIdx >= nupcol || this->well_control_log_.empty()) {
if (!iterCtx.withinNupcol(nupcol) || this->well_control_log_.empty()) {
this->well_control_log_.push_back(from);
}
updateWellStateWithTarget(simulator, groupStateHelper, well_state);
@@ -572,13 +575,13 @@ namespace Opm
const auto msg = fmt::format(" Well {} switched from {} to {} during local solve", this->name(), from, to);
deferred_logger.debug(msg);
const int episodeIdx = simulator.episodeIndex();
const int iterationIdx = simulator.model().newtonMethod().numIterations();
const auto& iterCtx = simulator.problem().iterationContext();
const auto& schedule = simulator.vanguard().schedule();
const int nupcol = schedule[episodeIdx].nupcol();
// We always store the current control as it is used for output
// and only after iteration >= nupcol
// we log all switches to check if the well controls oscillates
if (iterationIdx >= nupcol || this->well_control_log_.empty()) {
if (!iterCtx.withinNupcol(nupcol) || this->well_control_log_.empty()) {
this->well_control_log_.push_back(from);
}
}
@@ -996,8 +999,8 @@ namespace Opm
checkWellOperability(simulator, well_state, groupStateHelper);
// only use inner well iterations for the first newton iterations.
const int iteration_idx = simulator.model().newtonMethod().numIterations();
if (iteration_idx < this->param_.max_niter_inner_well_iter_) {
const auto& iterCtx = simulator.problem().iterationContext();
if (iterCtx.shouldRunInnerWellIterations(this->param_.max_niter_inner_well_iter_)) {
const auto& ws = well_state.well(this->indexOfWell());
const bool nonzero_rate_original =
std::any_of(ws.surface_rates.begin(),
@@ -1089,13 +1092,15 @@ namespace Opm
}
}
if (old_well_operable) {
deferred_logger.debug(" well " + this->name() + " gets STOPPED during iteration ");
const std::string ctx = iterCtx.inLocalSolve() ? " (NLDD domain solve)" : "";
deferred_logger.debug(" well " + this->name() + " gets STOPPED during iteration" + ctx);
changed_to_stopped_this_step_ = true;
}
} else if (well_state.isOpen(this->name())) {
this->openWell();
if (!old_well_operable) {
deferred_logger.debug(" well " + this->name() + " gets REVIVED during iteration ");
const std::string ctx = iterCtx.inLocalSolve() ? " (NLDD domain solve)" : "";
deferred_logger.debug(" well " + this->name() + " gets REVIVED during iteration" + ctx);
this->changed_to_open_this_step_ = true;
}
}
@@ -2289,12 +2294,13 @@ namespace Opm
// Instantiate group info object (without initialization) since it is needed in GasLiftSingleWell
auto& comm = simulator.vanguard().grid().comm();
ecl_well_map.try_emplace(this->name(), &(this->wellEcl()), this->indexOfWell());
const auto& iterCtx = simulator.problem().iterationContext();
GasLiftGroupInfo<Scalar, IndexTraits> group_info {
ecl_well_map,
simulator.vanguard().schedule(),
simulator.vanguard().summaryState(),
simulator.episodeIndex(),
simulator.model().newtonMethod().numIterations(),
iterCtx,
deferred_logger,
well_state,
group_state,
+10 -8
View File
@@ -78,7 +78,8 @@ BOOST_AUTO_TEST_CASE(G1)
simulator->setEpisodeLength(0.0);
simulator->startNextEpisode(/*episodeStartTime=*/0.0, /*episodeLength=*/1e30);
simulator->setTimeStepSize(43200); // 12 hours
simulator->model().newtonMethod().setIterationIndex(0);
// Reset iteration context so code using problem().iterationContext() sees first iteration
simulator->problem().resetIterationForNewTimestep();
WellModel& well_model = simulator->problem().wellModel();
// we tests 4 different setups given in the .DATA file
@@ -117,14 +118,14 @@ BOOST_AUTO_TEST_CASE(G1)
GLiftEclWells ecl_well_map;
Opm::BlackoilWellModelGasLift<TypeTag>::
initGliftEclWellMap(well_model.localNonshutWells(), ecl_well_map);
const int iteration_idx = simulator->model().newtonMethod().numIterations();
const auto& iterCtx = simulator->problem().iterationContext();
const auto& comm = simulator->vanguard().grid().comm();
GasLiftGroupInfo group_info {
ecl_well_map,
schedule,
summary_state,
simulator->episodeIndex(),
iteration_idx,
iterCtx,
deferred_logger,
well_state,
group_state,
@@ -139,7 +140,7 @@ BOOST_AUTO_TEST_CASE(G1)
};
group_info.initialize();
const auto& controls = well.productionControls(summary_state);
auto state = glift.runOptimize(iteration_idx);
auto state = glift.runOptimize(iterCtx.iteration());
auto pot = well_state[well.name()].well_potentials;
const auto& glo = schedule.glo(report_step_idx);
@@ -230,7 +231,8 @@ BOOST_AUTO_TEST_CASE(G2)
simulator->setEpisodeLength(0.0);
simulator->startNextEpisode(/*episodeStartTime=*/0.0, /*episodeLength=*/1e30);
simulator->setTimeStepSize(43200); // 12 hours
simulator->model().newtonMethod().setIterationIndex(0);
// Reset iteration context so code using problem().iterationContext() sees first iteration
simulator->problem().resetIterationForNewTimestep();
WellModel& well_model = simulator->problem().wellModel();
const auto& comm = simulator->vanguard().grid().comm();
@@ -257,7 +259,7 @@ BOOST_AUTO_TEST_CASE(G2)
GLiftEclWells ecl_well_map;
Opm::BlackoilWellModelGasLift<TypeTag>::
initGliftEclWellMap(well_model.localNonshutWells(), ecl_well_map);
const int iteration_idx = simulator->model().newtonMethod().numIterations();
const auto& iterCtx = simulator->problem().iterationContext();
WellState& well_state = well_model.wellState();
const auto& group_state = well_model.groupState();
GasLiftGroupInfo group_info {
@@ -265,7 +267,7 @@ BOOST_AUTO_TEST_CASE(G2)
schedule,
summary_state,
report_step_idx,
iteration_idx,
iterCtx,
deferred_logger,
well_state,
group_state,
@@ -280,7 +282,7 @@ BOOST_AUTO_TEST_CASE(G2)
deferred_logger, well_state, group_state, group_info, sync_groups,
comm, /*glift_debug=*/false
);
auto state = glift->runOptimize(iteration_idx);
auto state = glift->runOptimize(iterCtx.iteration());
if (state) {
state_map.emplace(well->name(), std::move(state));
glift_wells.emplace(well->name(), std::move(glift));
+2 -1
View File
@@ -249,7 +249,8 @@ private:
simulator_->setEpisodeLength(0.0);
simulator_->startNextEpisode(/*episodeStartTime=*/0.0, /*episodeLength=*/1e30);
simulator_->setTimeStepSize(Opm::unit::day);
simulator_->model().newtonMethod().setIterationIndex(0);
// Reset iteration context so code using problem().iterationContext() sees first iteration
simulator_->problem().resetIterationForNewTimestep();
wellModel().beginReportStep(report_step_idx_);