AdaptiveTimeStepping_impl.hpp
Go to the documentation of this file.
1/*
2 Copyright 2024 Equinor ASA.
3
4 This file is part of the Open Porous Media project (OPM).
5
6 OPM is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 OPM is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with OPM. If not, see <http://www.gnu.org/licenses/>.
18*/
19
20#ifndef OPM_ADAPTIVE_TIME_STEPPING_IMPL_HPP
21#define OPM_ADAPTIVE_TIME_STEPPING_IMPL_HPP
22
23// Improve IDE experience
24#ifndef OPM_ADAPTIVE_TIME_STEPPING_HPP
25#include <config.h>
28#endif
29
30#include <dune/common/timer.hh>
31#include <dune/istl/istlexception.hh>
32
33#include <opm/common/Exceptions.hpp>
34#include <opm/common/ErrorMacros.hpp>
35#include <opm/common/OpmLog/OpmLog.hpp>
36#include <opm/common/TimingMacros.hpp>
37
38#include <opm/grid/utility/StopWatch.hpp>
39
40#include <opm/input/eclipse/Schedule/Tuning.hpp>
41
42#include <opm/input/eclipse/Units/Units.hpp>
43#include <opm/input/eclipse/Units/UnitSystem.hpp>
44
47
49
50#include <algorithm>
51#include <cassert>
52#include <cmath>
53#include <sstream>
54#include <stdexcept>
55
56#include <boost/date_time/posix_time/posix_time.hpp>
57#include <fmt/format.h>
58#include <fmt/ranges.h>
59namespace Opm {
60/*********************************************
61 * Public methods of AdaptiveTimeStepping
62 * ******************************************/
63
64
66template<class TypeTag>
68AdaptiveTimeStepping(const UnitSystem& unit_system,
69 const SimulatorReport& report,
70 const double max_next_tstep,
71 const bool terminal_output
72)
73 : time_step_control_{}
74 , restart_factor_{Parameters::Get<Parameters::SolverRestartFactor<Scalar>>()} // 0.33
75 , growth_factor_{Parameters::Get<Parameters::SolverGrowthFactor<Scalar>>()} // 2.0
76 , max_growth_{Parameters::Get<Parameters::SolverMaxGrowth<Scalar>>()} // 3.0
77 , max_time_step_{
78 Parameters::Get<Parameters::SolverMaxTimeStepInDays<Scalar>>() * 24 * 60 * 60} // 365.25
79 , min_time_step_{
80 unit_system.to_si(UnitSystem::measure::time,
81 Parameters::Get<Parameters::SolverMinTimeStep<Scalar>>())} // 1e-12;
82 , ignore_convergence_failure_{
83 Parameters::Get<Parameters::SolverContinueOnConvergenceFailure>()} // false;
84 , solver_restart_max_{Parameters::Get<Parameters::SolverMaxRestarts>()} // 10
85 , solver_verbose_{Parameters::Get<Parameters::SolverVerbosity>() > 0 && terminal_output} // 2
86 , timestep_verbose_{Parameters::Get<Parameters::TimeStepVerbosity>() > 0 && terminal_output} // 2
87 , suggested_next_timestep_{
88 (max_next_tstep <= 0 ? Parameters::Get<Parameters::InitialTimeStepInDays>()
89 : max_next_tstep) * 24 * 60 * 60} // 1.0
90 , full_timestep_initially_{Parameters::Get<Parameters::FullTimeStepInitially>()} // false
91 , timestep_after_event_{
92 Parameters::Get<Parameters::TimeStepAfterEventInDays<Scalar>>() * 24 * 60 * 60} // 1e30
93 , use_newton_iteration_{false}
94 , min_time_step_before_shutting_problematic_wells_{
95 Parameters::Get<Parameters::MinTimeStepBeforeShuttingProblematicWellsInDays>() * unit::day}
96 , report_(report)
97{
98 init_(unit_system);
99}
100
107template<class TypeTag>
109AdaptiveTimeStepping(double max_next_tstep,
110 const Tuning& tuning,
111 const UnitSystem& unit_system,
112 const SimulatorReport& report,
113 const bool terminal_output
114)
115 : time_step_control_{}
116 , restart_factor_{tuning.TSFCNV}
117 , growth_factor_{tuning.TFDIFF}
118 , max_growth_{tuning.TSFMAX}
119 , max_time_step_{tuning.TSMAXZ} // 365.25
120 , min_time_step_{tuning.TSMINZ} // 0.1;
121 , ignore_convergence_failure_{true}
122 , solver_restart_max_{Parameters::Get<Parameters::SolverMaxRestarts>()} // 10
123 , solver_verbose_{Parameters::Get<Parameters::SolverVerbosity>() > 0 && terminal_output} // 2
124 , timestep_verbose_{Parameters::Get<Parameters::TimeStepVerbosity>() > 0 && terminal_output} // 2
125 , suggested_next_timestep_{
126 max_next_tstep <= 0 ? Parameters::Get<Parameters::InitialTimeStepInDays>() * 24 * 60 * 60
127 : max_next_tstep} // 1.0
128 , full_timestep_initially_{Parameters::Get<Parameters::FullTimeStepInitially>()} // false
129 , timestep_after_event_{tuning.TMAXWC} // 1e30
130 , use_newton_iteration_{false}
131 , min_time_step_before_shutting_problematic_wells_{
132 Parameters::Get<Parameters::MinTimeStepBeforeShuttingProblematicWellsInDays>() * unit::day}
133 , report_(report)
134{
135 init_(unit_system);
136}
137
138template<class TypeTag>
139bool
142{
143 if (this->time_step_control_type_ != rhs.time_step_control_type_ ||
144 (this->time_step_control_ && !rhs.time_step_control_) ||
145 (!this->time_step_control_ && rhs.time_step_control_)) {
146 return false;
147 }
148
149 bool result = false;
150 switch (this->time_step_control_type_) {
152 result = castAndComp<HardcodedTimeStepControl>(rhs);
153 break;
155 result = castAndComp<PIDAndIterationCountTimeStepControl>(rhs);
156 break;
158 result = castAndComp<SimpleIterationCountTimeStepControl>(rhs);
159 break;
161 result = castAndComp<PIDTimeStepControl>(rhs);
162 break;
164 result = castAndComp<General3rdOrderController>(rhs);
165 break;
166 }
167
168 return result &&
169 this->restart_factor_ == rhs.restart_factor_ &&
170 this->growth_factor_ == rhs.growth_factor_ &&
171 this->max_growth_ == rhs.max_growth_ &&
172 this->max_time_step_ == rhs.max_time_step_ &&
173 this->min_time_step_ == rhs.min_time_step_ &&
174 this->ignore_convergence_failure_ == rhs.ignore_convergence_failure_ &&
175 this->solver_restart_max_== rhs.solver_restart_max_ &&
176 this->solver_verbose_ == rhs.solver_verbose_ &&
177 this->full_timestep_initially_ == rhs.full_timestep_initially_ &&
178 this->timestep_after_event_ == rhs.timestep_after_event_ &&
179 this->use_newton_iteration_ == rhs.use_newton_iteration_ &&
180 this->min_time_step_before_shutting_problematic_wells_ ==
182}
183
184template<class TypeTag>
185void
188{
189 registerEclTimeSteppingParameters<Scalar>();
191}
192
193// See Doxygen comment on the declaration in AdaptiveTimeStepping.hpp.
194template<class TypeTag>
195template <class Solver>
198step(const SimulatorTimer& simulator_timer,
199 Solver& solver,
200 const bool is_event,
201 const TuningUpdateCallback& tuning_updater)
202{
203 SubStepper<Solver> sub_stepper{
204 *this, simulator_timer, solver, is_event, tuning_updater,
205 };
206 return sub_stepper.run();
207}
208
209template<class TypeTag>
210template<class Serializer>
211void
213serializeOp(Serializer& serializer)
214{
215 serializer(this->time_step_control_type_);
216 switch (this->time_step_control_type_) {
218 allocAndSerialize<HardcodedTimeStepControl>(serializer);
219 break;
221 allocAndSerialize<PIDAndIterationCountTimeStepControl>(serializer);
222 break;
224 allocAndSerialize<SimpleIterationCountTimeStepControl>(serializer);
225 break;
227 allocAndSerialize<PIDTimeStepControl>(serializer);
228 break;
230 allocAndSerialize<General3rdOrderController>(serializer);
231 break;
232 }
233 serializer(this->restart_factor_);
234 serializer(this->growth_factor_);
235 serializer(this->max_growth_);
236 serializer(this->max_time_step_);
237 serializer(this->min_time_step_);
238 serializer(this->ignore_convergence_failure_);
239 serializer(this->solver_restart_max_);
240 serializer(this->solver_verbose_);
241 serializer(this->timestep_verbose_);
242 serializer(this->suggested_next_timestep_);
243 serializer(this->full_timestep_initially_);
244 serializer(this->timestep_after_event_);
245 serializer(this->use_newton_iteration_);
246 serializer(this->min_time_step_before_shutting_problematic_wells_);
247}
248
249template<class TypeTag>
252report()
253{
254 return report_;
255}
256
257template<class TypeTag>
261{
262 return serializationTestObject_<HardcodedTimeStepControl>();
263}
264
265template<class TypeTag>
269{
270 return serializationTestObject_<PIDTimeStepControl>();
271}
272
273template<class TypeTag>
277{
278 return serializationTestObject_<PIDAndIterationCountTimeStepControl>();
279}
280
281template<class TypeTag>
285{
286 return serializationTestObject_<SimpleIterationCountTimeStepControl>();
287}
288
289template<class TypeTag>
293{
294 return serializationTestObject_<General3rdOrderController>();
295}
296
297
298template<class TypeTag>
299void
301setSuggestedNextStep(const double x)
302{
303 this->suggested_next_timestep_ = x;
304}
305
306template<class TypeTag>
307double
309suggestedNextStep() const
310{
311 return this->suggested_next_timestep_;
312}
313
314template<class TypeTag>
317timeStepControl() const
318{
319 return *this->time_step_control_;
320}
321
322
323template<class TypeTag>
324void
326updateNEXTSTEP(double max_next_tstep)
327{
328 // \Note Only update next suggested step if TSINIT was explicitly
329 // set in TUNING or NEXTSTEP is active.
330 if (max_next_tstep > 0) {
331 this->suggested_next_timestep_ = max_next_tstep;
332 }
333}
334
335template<class TypeTag>
336void
338updateTUNING(double max_next_tstep, const Tuning& tuning)
339{
340 this->restart_factor_ = tuning.TSFCNV;
341 this->growth_factor_ = tuning.TFDIFF;
342 this->max_growth_ = tuning.TSFMAX;
343 this->max_time_step_ = tuning.TSMAXZ;
344 updateNEXTSTEP(max_next_tstep);
345 this->timestep_after_event_ = tuning.TMAXWC;
346}
347
348/*********************************************
349 * Private methods of AdaptiveTimeStepping
350 * ******************************************/
351
352template<class TypeTag>
353template<class T, class Serializer>
354void
356allocAndSerialize(Serializer& serializer)
357{
358 if (!serializer.isSerializing()) {
359 this->time_step_control_ = std::make_unique<T>();
360 }
361 serializer(*static_cast<T*>(this->time_step_control_.get()));
362}
363
364template<class TypeTag>
365template<class T>
366bool
367AdaptiveTimeStepping<TypeTag>::
368castAndComp(const AdaptiveTimeStepping<TypeTag>& Rhs) const
369{
370 const T* lhs = static_cast<const T*>(this->time_step_control_.get());
371 const T* rhs = static_cast<const T*>(Rhs.time_step_control_.get());
372 return *lhs == *rhs;
373}
374
375template<class TypeTag>
376void
377AdaptiveTimeStepping<TypeTag>::
378maybeModifySuggestedTimeStepAtBeginningOfReportStep_(const double original_time_step,
379 bool is_event)
380{
381 // init last time step as a fraction of the given time step
382 if (this->suggested_next_timestep_ < 0) {
383 this->suggested_next_timestep_ = this->restart_factor_ * original_time_step;
384 }
385
386 if (this->full_timestep_initially_) {
387 this->suggested_next_timestep_ = original_time_step;
388 }
389
390 // use seperate time step after event
391 if (is_event && this->timestep_after_event_ > 0) {
392 this->suggested_next_timestep_ = this->timestep_after_event_;
393 }
394}
395
396template<class TypeTag>
397template<class Controller>
398AdaptiveTimeStepping<TypeTag>
399AdaptiveTimeStepping<TypeTag>::
400serializationTestObject_()
401{
402 AdaptiveTimeStepping<TypeTag> result;
403
404 result.restart_factor_ = 1.0;
405 result.growth_factor_ = 2.0;
406 result.max_growth_ = 3.0;
407 result.max_time_step_ = 4.0;
408 result.min_time_step_ = 5.0;
409 result.ignore_convergence_failure_ = true;
410 result.solver_restart_max_ = 6;
411 result.solver_verbose_ = true;
412 result.timestep_verbose_ = true;
413 result.suggested_next_timestep_ = 7.0;
414 result.full_timestep_initially_ = true;
415 result.use_newton_iteration_ = true;
416 result.min_time_step_before_shutting_problematic_wells_ = 9.0;
417 result.time_step_control_type_ = Controller::Type;
418 result.time_step_control_ =
419 std::make_unique<Controller>(Controller::serializationTestObject());
420
421 return result;
422}
423
424/*********************************************
425 * Protected methods of AdaptiveTimeStepping
426 * ******************************************/
427
428template<class TypeTag>
430init_(const UnitSystem& unitSystem)
431{
432 std::tie(time_step_control_type_,
433 time_step_control_,
434 use_newton_iteration_) = detail::createController(unitSystem);
435 // make sure growth factor is something reasonable
436 if (this->growth_factor_ < 1.0) {
437 OPM_THROW(std::runtime_error,
438 "Growth factor cannot be less than 1.");
439 }
440}
441
442
443
444/************************************************
445 * Private class SubStepper public methods
446 ************************************************/
447
448template<class TypeTag>
449template<class Solver>
451SubStepper(AdaptiveTimeStepping<TypeTag>& adaptive_time_stepping,
452 const SimulatorTimer& simulator_timer,
453 Solver& solver,
454 const bool is_event,
455 const TuningUpdateCallback& tuning_updater)
456 : adaptive_time_stepping_{adaptive_time_stepping}
457 , simulator_timer_{simulator_timer}
458 , solver_{solver}
459 , is_event_{is_event}
460 , tuning_updater_{tuning_updater}
461{
462}
463
464template<class TypeTag>
465template<class Solver>
466AdaptiveTimeStepping<TypeTag>&
467AdaptiveTimeStepping<TypeTag>::SubStepper<Solver>::
468getAdaptiveTimerStepper()
469{
470 return adaptive_time_stepping_;
471}
472
473template<class TypeTag>
474template<class Solver>
475SimulatorReport
476AdaptiveTimeStepping<TypeTag>::SubStepper<Solver>::
477run()
478{
479#ifdef RESERVOIR_COUPLING_ENABLED
480 if (isReservoirCouplingSlave_() && reservoirCouplingSlave_().activated()) {
481 return runStepReservoirCouplingSlave_();
482 }
483 else if (isReservoirCouplingMaster_() && reservoirCouplingMaster_().activated()) {
484 return runStepReservoirCouplingMaster_();
485 }
486 else {
487 return runStepOriginal_();
488 }
489#else
490 return runStepOriginal_();
491#endif
492}
493
494/************************************************
495 * Private class SubStepper private methods
496 ************************************************/
497
498#ifdef RESERVOIR_COUPLING_ENABLED
499template<class TypeTag>
500template<class Solver>
501bool
502AdaptiveTimeStepping<TypeTag>::SubStepper<Solver>::
503isReservoirCouplingMaster_() const
504{
505 return this->solver_.model().simulator().reservoirCouplingMaster() != nullptr;
506}
507
508template<class TypeTag>
509template<class Solver>
510bool
511AdaptiveTimeStepping<TypeTag>::SubStepper<Solver>::
512isReservoirCouplingSlave_() const
513{
514 return this->solver_.model().simulator().reservoirCouplingSlave() != nullptr;
515}
516#endif
517
518template<class TypeTag>
519template<class Solver>
520void
521AdaptiveTimeStepping<TypeTag>::SubStepper<Solver>::
522maybeModifySuggestedTimeStepAtBeginningOfReportStep_(const double original_time_step)
523{
524 this->adaptive_time_stepping_.maybeModifySuggestedTimeStepAtBeginningOfReportStep_(
525 original_time_step, this->is_event_
526 );
527
528 if (this->adaptive_time_stepping_.time_step_control_type_ != TimeStepControlType::HardCodedTimeStep) {
529 return;
530 }
531
532 struct ZeroRelativeChange final : RelativeChangeInterface {
533 double relativeChange() const override { return 0.0; }
534 } zero_relative_change;
535
536 const auto* hardcoded_control = static_cast<const HardcodedTimeStepControl*>(
537 this->adaptive_time_stepping_.time_step_control_.get());
538 AdaptiveSimulatorTimer report_step_timer{
539 this->simulator_timer_.startDateTime(),
540 original_time_step,
541 this->simulator_timer_.simulationTimeElapsed(),
542 original_time_step,
543 this->simulator_timer_.reportStepNum(),
544 maxTimeStep_()
545 };
546
547 const double hardcoded_initial_step = hardcoded_control->computeTimeStepSize(
548 original_time_step,
549 0,
550 zero_relative_change,
551 report_step_timer);
552 if (std::isfinite(hardcoded_initial_step) && hardcoded_initial_step > 0.0) {
553 this->adaptive_time_stepping_.setSuggestedNextStep(hardcoded_initial_step);
554 }
555}
556
557// The maybeUpdateTuning_() lambda callback is defined in SimulatorFullyImplicit::runStep()
558// It has to be called for each substep since TUNING might have been changed for next sub step due
559// to ACTIONX (via NEXTSTEP) or WCYCLE keywords.
560template<class TypeTag>
561template<class Solver>
562bool
563AdaptiveTimeStepping<TypeTag>::SubStepper<Solver>::
564maybeUpdateTuning_(double elapsed, double substep_length, int sub_step_number) const
565{
566 return this->tuning_updater_(elapsed, substep_length, sub_step_number);
567}
568
569template<class TypeTag>
570template<class Solver>
571double
572AdaptiveTimeStepping<TypeTag>::SubStepper<Solver>::
573maxTimeStep_() const
574{
575 return this->adaptive_time_stepping_.max_time_step_;
576}
577
578template <class TypeTag>
579template <class Solver>
580SimulatorReport
581AdaptiveTimeStepping<TypeTag>::SubStepper<Solver>::
582runStepOriginal_()
583{
584 const auto elapsed = this->simulator_timer_.simulationTimeElapsed();
585 const auto original_time_step = this->simulator_timer_.currentStepLength();
586 const auto report_step = this->simulator_timer_.reportStepNum();
587 maybeUpdateTuning_(elapsed, suggestedNextTimestep_(), /*substep=*/0);
588 maybeModifySuggestedTimeStepAtBeginningOfReportStep_(original_time_step);
589
590 AdaptiveSimulatorTimer substep_timer{
591 this->simulator_timer_.startDateTime(),
592 original_time_step,
593 elapsed,
594 suggestedNextTimestep_(),
595 report_step,
596 maxTimeStep_()
597 };
598 SubStepIteration<Solver> substepIteration{*this, substep_timer, original_time_step, /*final_step=*/true};
599 return substepIteration.run();
600}
601
602template <class TypeTag>
603template <class Solver>
604double
605AdaptiveTimeStepping<TypeTag>::SubStepper<Solver>::
606suggestedNextTimestep_() const
607{
608 return this->adaptive_time_stepping_.suggestedNextStep();
609}
610
611
612#ifdef RESERVOIR_COUPLING_ENABLED
613// Throw if the slave has already been terminated by the master. A terminated slave has
614// disconnected its intercommunicator, so running another coupled substep loop would issue
615// an MPI_Recv on a null communicator and abort the job. The run loop in
616// SimulatorFullyImplicit::runStep() stops before this can happen, so this guards
617// against future regressions of that logic.
618template <class TypeTag>
619template <class Solver>
620void
621AdaptiveTimeStepping<TypeTag>::SubStepper<Solver>::
622checkIfSlaveIsTerminated_()
623{
624 if (reservoirCouplingSlave_().terminated()) {
625 OPM_THROW(ReservoirCouplingError,
626 "Internal error: attempt to run a coupled substep loop after the slave "
627 "has been terminated by the master process");
628 }
629}
630
631// Pick the master's sync-step length for the next outer-loop iteration of
632// `runStepReservoirCouplingMaster_()`. Includes the chop against slave-
633// report dates and emits the user-visible log line. See the block comment
634// above `runStepReservoirCouplingMaster_()` for TSYNC/RSYNC mode definitions.
635//
636// TSYNC (default):
637// Each iteration is one master time step. Start from the master's
638// adaptive suggestion, capped at the max time step and at the time
639// remaining until the report-step end, then chop.
640//
641// RSYNC:
642// The outer loop iterates per chunk between slave-report boundaries.
643// Re-use the previous iteration's chopped value as the candidate for
644// this iteration (the caller seeds the first iteration with
645// `original_time_step`), then chop.
646template <class TypeTag>
647template <class Solver>
648double
649AdaptiveTimeStepping<TypeTag>::SubStepper<Solver>::
650getRcMasterSyncStepLength_(double prev_step,
651 double current_time,
652 double step_end_time)
653{
654 const bool sync_at_report_steps = reservoirCouplingMaster_().syncAtReportSteps();
655 double current_step_length;
656 if (sync_at_report_steps) {
657 current_step_length = prev_step;
658 } else {
659 const double remaining = step_end_time - current_time;
660 current_step_length = std::min({suggestedNextTimestep_(), maxTimeStep_(), remaining});
661 // NOTE: The substep timer is later constructed (in the caller) with
662 // current_step_length as its span, and after construction,
663 // substep_timer.currentStepLength() should return the same as
664 // current_step_length. The timer's constructor calls
665 // provideTimeStepEstimate() with suggestedNextTimestep_() as the dt_
666 // estimate and current_step_length as the span. Since we took
667 // current_step_length = min(suggestedNextTimestep_(), maxTimeStep_(), remaining)
668 // here (and only shrink it further via maybeChopSubStep below), the
669 // estimate is always >= the span, so the timer's snap branch (see
670 // AdaptiveSimulatorTimer::provideTimeStepEstimate) fires and clamps
671 // dt_ to current_step_length.
672 }
673 current_step_length = reservoirCouplingMaster_().maybeChopSubStep(current_step_length, current_time);
674 auto num_active = reservoirCouplingMaster_().numCoupledSlaves();
675 OpmLog::info(fmt::format(
676 "\nChoosing next sync time{} between master and {} active slave {}: {:.2f} days",
677 sync_at_report_steps ? " (RSYNC)" : "",
678 num_active, (num_active == 1 ? "process" : "processes"),
679 current_step_length / unit::day
680 ));
681 return current_step_length;
682}
683
684template <class TypeTag>
685template <class Solver>
686ReservoirCouplingMaster<typename AdaptiveTimeStepping<TypeTag>::Scalar>&
687AdaptiveTimeStepping<TypeTag>::SubStepper<Solver>::
688reservoirCouplingMaster_()
689{
690 return *(this->solver_.model().simulator().reservoirCouplingMaster());
691}
692
693template <class TypeTag>
694template <class Solver>
695ReservoirCouplingSlave<typename AdaptiveTimeStepping<TypeTag>::Scalar>&
696AdaptiveTimeStepping<TypeTag>::SubStepper<Solver>::
697reservoirCouplingSlave_()
698{
699 return *(this->solver_.model().simulator().reservoirCouplingSlave());
700}
701
702// Description of the reservoir coupling master and slave substep loop
703// -------------------------------------------------------------------
704// The master and slave processes attempt to reach the end of the report step using a series of substeps
705// (also called timesteps). Each substep has an upper limit roughly determined by a combination of the
706// keywords TUNING (through TSINIT, TSMAXZ), NEXSTEP, WCYCLE, and the start of the next report step
707// (the last substep has to coincide with this time). Note that NEXTSTEP can be updated from an
708// ACTIONX keyword. Although this comment focuses on the maximum substep limit, there is also a lower
709// limit on the substep length, and the substep sizes are adjusted automatically (or retried) based on
710// the convergence behavior of the solver and other criteria.
711//
712// The master chooses a "sync step" — the span between successive master<->slave exchanges — via one
713// of two modes, selected by the `--rescoup-sync-at-report-steps` CLI flag:
714//
715// TSYNC (default, flag=false)
716// Each master time step is a sync step. Every outer iteration of the master substep
717// loop redistributes group-rate targets to the slaves. This gives fine sync granularity and
718// matches the reference simulator's coupling semantics.
719//
720// RSYNC (flag=true)
721// Each chunk between slave-report-step boundaries is one sync step. The master's own adaptive
722// timestepping runs inside the chunk, but slaves only hear back from the master once per chunk.
723// Provided as a developer escape hatch.
724//
725// In both modes the sync step is also limited so as not to overshoot the next slave report date
726// (`maybeChopSubStep`). A second, drift-based limit (GRUPMAST item 4 / RCMASTS) is parsed but not
727// yet honored — tracked as a follow-up PR.
728//
729// Per sync step:
730// - Master receives each activated slave's next report date (`receiveNextReportDateFromSlaves`).
731// - Master picks the sync-step length via `getRcMasterSyncStepLength_()` (mode-dependent) and
732// chops it so as not to overshoot any slave report date.
733// - Master sends the chosen sync-step length to each slave (`sendNextTimeStepToSlaves`).
734//
735// The slaves use the sync-step end as a fixed point — a "mini report step" — that they reach via one
736// or more of their own substeps. If the slave's current report step extends beyond the received
737// sync step, the slave loops waiting for the master to send further sync steps; the loop ends when
738// the received sync step coincides with the end of the slave's current report step.
739
740// Master-side rescoup outer loop. See the block comment above for TSYNC
741// and RSYNC mode definitions.
742//
743// TSYNC (default):
744// Each outer iteration is one master time step. The sync-step
745// length is chosen fresh each iteration by `getRcMasterSyncStepLength_()`
746// from the master's adaptive suggestion (capped at the max time step and
747// at the remaining report-step time), then chopped against slave-report
748// dates. `SubStepIteration::run()` is still used but normally handles
749// only solver-driven chops within a single master time step.
750//
751// RSYNC:
752// The outer loop iterates once per chunk between slave-report boundaries.
753// The sync-step length starts at the full report step and only shrinks
754// via `maybeChopSubStep`; the master's adaptive timestepping runs inside
755// `SubStepIteration` as solver-driven substeps within each chunk.
756template <class TypeTag>
757template <class Solver>
758SimulatorReport
759AdaptiveTimeStepping<TypeTag>::SubStepper<Solver>::
760runStepReservoirCouplingMaster_()
761{
762 int iteration = 0;
763 const double original_time_step = this->simulator_timer_.currentStepLength();
764 double current_time{this->simulator_timer_.simulationTimeElapsed()};
765 double step_end_time = current_time + original_time_step;
766 const double report_step_start_time = current_time;
767 int report_step_substep_offset = 0;
768 // In RSYNC mode this variable persists across outer iterations and
769 // carries the previously-chopped sync span into the next iteration. In
770 // TSYNC mode it is overwritten each iteration by
771 // `getRcMasterSyncStepLength_()`; the initial value is only consumed by
772 // the first helper call in RSYNC mode (as the candidate span for that
773 // iteration).
774 auto current_step_length = original_time_step;
775 auto report_step_idx = this->simulator_timer_.currentStepNum();
776 if (report_step_idx == 0 && iteration == 0) {
777 reservoirCouplingMaster_().initTimeStepping();
778 }
779 SimulatorReport report;
780 // The master needs to know which slaves have activated before it can start the substep loop
781 reservoirCouplingMaster_().maybeReceiveActivationHandshakeFromSlaves(current_time);
782 while (true) {
783 reservoirCouplingMaster_().sendDontTerminateSignalToSlaves(); // Tell the slaves to keep running.
784 reservoirCouplingMaster_().receiveNextReportDateFromSlaves();
785 const bool start_of_report_step = (iteration == 0);
786 if (start_of_report_step) {
787 reservoirCouplingMaster_().initStartOfReportStep(report_step_idx);
788 maybeUpdateTuning_(current_time, suggestedNextTimestep_(), /*substep=*/0);
789 maybeModifySuggestedTimeStepAtBeginningOfReportStep_(original_time_step);
790 }
791 current_step_length = getRcMasterSyncStepLength_(
792 current_step_length, current_time, step_end_time);
793 reservoirCouplingMaster_().sendNextTimeStepToSlaves(current_step_length);
794 AdaptiveSimulatorTimer substep_timer{
795 this->simulator_timer_.startDateTime(),
796 /*stepLength=*/current_step_length,
797 /*elapsedTime=*/current_time,
798 /*timeStepEstimate=*/suggestedNextTimestep_(),
799 /*reportStep=*/this->simulator_timer_.reportStepNum(),
800 maxTimeStep_()
801 };
802 // Make the per-chunk timer log the enclosing report step's span and a
803 // cumulative substep counter (see AdaptiveSimulatorTimer "report step
804 // view"). Without this, log lines would show one sync chunk only.
805 substep_timer.setReportStepStartTime(report_step_start_time);
806 substep_timer.setReportStepTotalTime(step_end_time);
807 substep_timer.setReportStepSubstepOffset(report_step_substep_offset);
808 const bool final_step = ReservoirCoupling::Seconds::compare_gt_or_eq(
809 current_time + current_step_length, step_end_time
810 );
811 // Mark this as the first substep of the "sync" timestep. This flag controls
812 // whether master-slave data exchange should occur in beginTimeStep() in the well model.
813 // It will be cleared after the first runSubStep_() call.
814 reservoirCouplingMaster_().setFirstSubstepOfSyncTimestep(true);
815 // After the first master substep completes, timeStepSucceeded() will
816 // block until slaves finish the sync step and send production data.
817 // This ensures correct summary output for all subsequent substeps.
818 reservoirCouplingMaster_().setNeedsSlaveDataReceive(true);
819 SubStepIteration<Solver> substepIteration{*this, substep_timer, current_step_length, final_step};
820 const auto sub_steps_report = substepIteration.run();
821 report += sub_steps_report;
822 report_step_substep_offset += substep_timer.currentStepNum();
823 current_time += current_step_length;
824 if (final_step) {
825 break;
826 }
827 iteration++;
828 }
829 return report;
830}
831
832template <class TypeTag>
833template <class Solver>
834SimulatorReport
835AdaptiveTimeStepping<TypeTag>::SubStepper<Solver>::
836runStepReservoirCouplingSlave_()
837{
838 checkIfSlaveIsTerminated_();
839 int iteration = 0;
840 const double original_time_step = this->simulator_timer_.currentStepLength();
841 double current_time{this->simulator_timer_.simulationTimeElapsed()};
842 double step_end_time = current_time + original_time_step;
843 const double report_step_start_time = current_time;
844 int report_step_substep_offset = 0;
845 SimulatorReport report;
846 auto report_step_idx = this->simulator_timer_.currentStepNum();
847 if (report_step_idx == 0 && iteration == 0) {
848 reservoirCouplingSlave_().initTimeStepping();
849 }
850 while (true) {
851 bool start_of_report_step = (iteration == 0);
852 if (reservoirCouplingSlave_().maybeReceiveTerminateSignalFromMaster()) {
853 // Call MPI_Comm_disconnect() to terminate the MPI communicator, etc..
854 break;
855 }
856 reservoirCouplingSlave_().sendNextReportDateToMasterProcess();
857 const auto timestep = reservoirCouplingSlave_().receiveNextTimeStepFromMaster();
858 if (start_of_report_step) {
859 maybeUpdateTuning_(current_time, suggestedNextTimestep_(), /*substep=*/0);
860 maybeModifySuggestedTimeStepAtBeginningOfReportStep_(timestep);
861 }
862 AdaptiveSimulatorTimer substep_timer{
863 this->simulator_timer_.startDateTime(),
864 /*step_length=*/timestep,
865 /*elapsed_time=*/current_time,
866 /*time_step_estimate=*/suggestedNextTimestep_(),
867 this->simulator_timer_.reportStepNum(),
868 maxTimeStep_()
869 };
870 // Make the per-chunk timer log the enclosing report step's span and a
871 // cumulative substep counter (see AdaptiveSimulatorTimer "report step
872 // view"). Without this, log lines would show one sync chunk only.
873 substep_timer.setReportStepStartTime(report_step_start_time);
874 substep_timer.setReportStepTotalTime(step_end_time);
875 substep_timer.setReportStepSubstepOffset(report_step_substep_offset);
876 const bool final_step = ReservoirCoupling::Seconds::compare_gt_or_eq(
877 current_time + timestep, step_end_time
878 );
879 // Mark this as the first substep of the "sync" timestep. This flag controls
880 // whether master-slave data exchange should occur in beginTimeStep() in the well model.
881 // It will be cleared after the first runSubStep_() call.
882 reservoirCouplingSlave_().setFirstSubstepOfSyncTimestep(true);
883 SubStepIteration<Solver> substepIteration{*this, substep_timer, timestep, final_step};
884 const auto sub_steps_report = substepIteration.run();
885 report += sub_steps_report;
886 report_step_substep_offset += substep_timer.currentStepNum();
887 current_time += timestep;
888 if (final_step) {
889 break;
890 }
891 iteration++;
892 }
893 return report;
894}
895#endif // RESERVOIR_COUPLING_ENABLED
896
897
898/************************************************
899 * Private class SubStepIteration public methods
900 ************************************************/
901
902template<class TypeTag>
903template<class Solver>
904AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
905SubStepIteration(SubStepper<Solver>& substepper,
906 AdaptiveSimulatorTimer& substep_timer,
907 const double original_time_step,
908 bool final_step)
909 : substepper_{substepper}
910 , substep_timer_{substep_timer}
911 , original_time_step_{original_time_step}
912 , final_step_{final_step}
913 , adaptive_time_stepping_{substepper.getAdaptiveTimerStepper()}
914{
915}
916
917template <class TypeTag>
918template <class Solver>
919SimulatorReport
920AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
921run()
922{
923 auto& simulator = solver_().model().simulator();
924 auto& problem = simulator.problem();
925 const bool truncateTimeStepToFloat = Parameters::Get<Parameters::TruncateTimeStepToFloat>();
926 // counter for solver restarts
927 int restarts = 0;
928 SimulatorReport report;
929
930 // sub step time loop
931 while (!this->substep_timer_.done()) {
932 // if we just chopped the timestep due to convergence i.e. restarts>0
933 // we dont want to update the next timestep based on Tuning
934 if (restarts == 0) {
935 maybeUpdateTuningAndTimeStep_();
936 }
937 const double dt = this->substep_timer_.currentStepLength();
938 if (timeStepVerbose_()) {
939 detail::logTimer(this->substep_timer_);
940 }
941
942 maybeUpdateLastSubstepOfSyncTimestep_(dt); // Needed for reservoir coupling
943 auto substep_report = runSubStep_();
944 markFirstSubStepAsFinished_(); // Needed for reservoir coupling
945
946 if (substep_report.converged || checkContinueOnUnconvergedSolution_(dt)) {
947 Dune::Timer perfTimer;
948 perfTimer.start();
949 // Pass substep to eclwriter for summary output
950 problem.setSubStepReport(substep_report);
951 auto& full_report = adaptive_time_stepping_.report();
952 full_report += substep_report;
953 problem.setSimulationReport(full_report);
954 problem.endTimeStep();
955 substep_report.pre_post_time += perfTimer.stop();
956
957 report += substep_report;
958
959 OPM_TIMEBLOCK(convergenceSucceeded);
960 // When the time step is truncated to float precision, advance the timer
961 // by the truncated step size used by the model and base the next step
962 // size on it. This keeps the elapsed time and the step sizes reproducible
963 // from the step sizes alone, which the timestep replay tests rely on.
964 // The step that ends the report step keeps dt so that the report time
965 // is hit exactly.
966 double dt_taken = dt;
967 if (truncateTimeStepToFloat) {
968 const double model_dt = simulator.timeStepSize();
969 const double remaining = this->substep_timer_.totalTime()
970 - this->substep_timer_.simulationTimeElapsed();
971 if (model_dt > 0.0 && dt < remaining) {
972 this->substep_timer_.setCurrentStepLength(model_dt);
973 dt_taken = model_dt;
974 }
975 }
976 ++this->substep_timer_; // advance by current dt
977
978 const int iterations = getNumIterations_(substep_report);
979 auto dt_estimate = timeStepControlComputeEstimate_(
980 dt_taken, iterations, this->substep_timer_);
981
982 assert(dt_estimate > 0);
983 dt_estimate = maybeRestrictTimeStepGrowth_(dt_taken, dt_estimate, restarts);
984 restarts = 0; // solver converged, reset restarts counter
985
986 maybeReportSubStep_(substep_report);
987 if (this->final_step_ && this->substep_timer_.done()) {
988 // if the time step is done we do not need to write it as this will be done
989 // by the simulator anyway.
990 }
991 else {
992 report.success.output_write_time += writeOutput_();
993 }
994
995 // set new time step length
996 checkTimeStepCanAdvance_(dt_taken, dt_estimate);
997 setTimeStep_(dt_estimate);
998
999 report.success.converged = this->substep_timer_.done();
1000 this->substep_timer_.setLastStepFailed(false);
1001 }
1002 else { // in case of no convergence or time step tolerance test failure
1003 OPM_TIMEBLOCK(convergenceFailed);
1004 report += substep_report;
1005 this->substep_timer_.setLastStepFailed(true);
1006 checkTimeStepMaxRestartLimit_(restarts);
1007
1008 double new_time_step = restartFactor_() * dt;
1009 if (substep_report.time_step_rejected) {
1010 const double tol = Parameters::Get<Parameters::TimeStepControlTolerance>();
1011 const double safetyFactor = Parameters::Get<Parameters::TimeStepControlSafetyFactor>();
1012 const double temp_time_step = std::sqrt(safetyFactor * tol / solver_().model().relativeChange()) * dt;
1013 if (temp_time_step < dt) { // added in case suggested time step is not a reduction
1014 new_time_step = temp_time_step;
1015 }
1016 }
1017 checkTimeStepMinLimit_(new_time_step);
1018 bool wells_shut = false;
1019 if (new_time_step > minTimeStepBeforeClosingWells_()) {
1020 chopTimeStep_(new_time_step);
1021 } else {
1022 wells_shut = chopTimeStepOrCloseFailingWells_(new_time_step);
1023 }
1024 if (wells_shut) {
1025 setTimeStep_(dt); // retry the old timestep
1026 }
1027 else {
1028 restarts++; // only increase if no wells were shut
1029 }
1030 }
1031 problem.setNextTimeStepSize(this->substep_timer_.currentStepLength());
1032 }
1033 updateSuggestedNextStep_();
1034 return report;
1035}
1036
1037
1038/************************************************
1039 * Private class SubStepIteration private methods
1040 ************************************************/
1041
1042
1043template<class TypeTag>
1044template<class Solver>
1045bool
1046AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1047checkContinueOnUnconvergedSolution_(double dt) const
1048{
1049 const bool continue_on_uncoverged_solution = ignoreConvergenceFailure_() && dt <= minTimeStep_();
1050 if (continue_on_uncoverged_solution && solverVerbose_()) {
1051 // NOTE: This method is only called if the solver failed to converge.
1052 const auto msg = fmt::format(
1053 "Solver failed to converge but timestep {} is smaller or equal to {}\n"
1054 "which is the minimum threshold given by option --solver-min-time-step\n",
1055 dt, minTimeStep_()
1056 );
1057 OpmLog::problem(msg);
1058 }
1059 return continue_on_uncoverged_solution;
1060}
1061
1062template<class TypeTag>
1063template<class Solver>
1064void
1065AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1066checkTimeStepMaxRestartLimit_(const int restarts) const
1067{
1068 // If we have restarted (i.e. cut the timestep) too
1069 // many times, we have failed and throw an exception.
1070 if (restarts >= solverRestartMax_()) {
1071 const auto msg = fmt::format(
1072 fmt::runtime("Solver failed to converge after cutting timestep {} times."), restarts
1073 );
1074 if (solverVerbose_()) {
1075 OpmLog::error(msg);
1076 }
1077 // Use throw directly to prevent file and line
1078 throw TimeSteppingBreakdown{msg};
1079 }
1080}
1081
1082template<class TypeTag>
1083template<class Solver>
1084void
1085AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1086checkTimeStepCanAdvance_(const double current_time_step,
1087 const double new_time_step) const
1088{
1089 // Both dt floors - the minimum step size and the restart count - sit on
1090 // the failure path. A run whose substeps all converge, but for which the
1091 // controller keeps proposing ever smaller steps, consults neither: the
1092 // step size shrinks without bound while the elapsed time stands still,
1093 // and the run never ends. Hold the accepted path to the same minimum.
1094 if (new_time_step >= minTimeStep_()) {
1095 return;
1096 }
1097
1098 // A sub-minimum step is valid when the report step is already complete, or
1099 // when timestep control is increasing it. Reject only a shrinking
1100 // sub-minimum step, which is the one that stalls short of the report-step
1101 // boundary.
1102 if (this->substep_timer_.done() || new_time_step >= current_time_step) {
1103 return;
1104 }
1105
1106 const auto msg =
1107 fmt::format("Time step control proposed a step of {:.3E} DAYS, below the "
1108 "minimum of {:.3E} DAYS, while every substep converges. The "
1109 "run is not advancing past {:.6E} DAYS.",
1110 new_time_step / 86400.0,
1111 minTimeStep_() / 86400.0,
1112 this->substep_timer_.simulationTimeElapsed() / 86400.0);
1113
1114 if (solverVerbose_()) {
1115 OpmLog::error(msg);
1116 }
1117
1118 // Use throw directly to prevent file and line
1119 throw TimeSteppingBreakdown{msg};
1120}
1121
1122template<class TypeTag>
1123template<class Solver>
1124void
1125AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1126checkTimeStepMinLimit_(const double new_time_step) const
1127{
1128 using Meas = UnitSystem::measure;
1129 // If we have restarted (i.e. cut the timestep) too
1130 // much, we have failed and throw an exception.
1131 if (new_time_step < minTimeStep_()) {
1132 std::string msg = "Solver failed to converge after cutting timestep to ";
1133 if (Parameters::Get<Parameters::EnableTuning>()) {
1134 const UnitSystem& unit_system = solver_().model().simulator().vanguard().eclState().getDeckUnitSystem();
1135 msg += fmt::format(
1136 "{:.3E} {}\nwhich is the minimum threshold given by the TUNING keyword\n",
1137 unit_system.from_si(Meas::time, minTimeStep_()),
1138 unit_system.name(Meas::time)
1139 );
1140 }
1141 else {
1142 msg += fmt::format(
1143 "{:.3E} DAYS\nwhich is the minimum threshold given by option --solver-min-time-step\n",
1144 minTimeStep_() / 86400.0
1145 );
1146 }
1147 if (solverVerbose_()) {
1148 OpmLog::error(msg);
1149 }
1150 // Use throw directly to prevent file and line
1151 throw TimeSteppingBreakdown{msg};
1152 }
1153}
1154
1155template<class TypeTag>
1156template<class Solver>
1157void
1158AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1159chopTimeStep_(const double new_time_step)
1160{
1161 setTimeStep_(new_time_step);
1162 if (solverVerbose_()) {
1163 const auto msg = fmt::format(fmt::runtime("{}\nTimestep chopped to {} days\n"),
1164 this->cause_of_failure_,
1165 unit::convert::to(this->substep_timer_.currentStepLength(), unit::day));
1166 OpmLog::problem(msg);
1167 }
1168}
1169
1170template<class TypeTag>
1171template<class Solver>
1172bool
1173AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1174chopTimeStepOrCloseFailingWells_(const double new_time_step)
1175{
1176 bool wells_shut = false;
1177 // We are below the threshold, and will check if there are any
1178 // wells that fails repeatedly (that means that it fails in the last three steps)
1179 // we should close rather than chopping again.
1180 // If we already have chopped the timestep two times that is
1181 // new_time_step < minTimeStepBeforeClosingWells_()*restartFactor_()*restartFactor_()
1182 // We also shut wells that fails only on this step.
1183 const bool requireRepeatedFailures =
1184 new_time_step > (minTimeStepBeforeClosingWells_() * restartFactor_() * restartFactor_());
1185 const std::set<std::string> failing_wells =
1186 detail::consistentlyFailingWells(solver_().model().stepReports(), requireRepeatedFailures);
1187
1188 if (failing_wells.empty()) {
1189 // Found no wells to close, chop the timestep
1190 chopTimeStep_(new_time_step);
1191 } else {
1192 // Close all consistently failing wells that are not under group control
1193 std::vector<std::string> shut_wells;
1194 for (const auto& well : failing_wells) {
1195 const bool was_shut =
1196 solver_().model().wellModel().forceShutWellByName(well,
1197 this->substep_timer_.simulationTimeElapsed(),
1198 /*dont_shut_grup_wells =*/ true);
1199 if (was_shut) {
1200 shut_wells.push_back(well);
1201 }
1202 }
1203 // If no wells are closed we also try to shut wells under group control
1204 if (shut_wells.empty()) {
1205 for (const auto& well : failing_wells) {
1206 const bool was_shut =
1207 solver_().model().wellModel().forceShutWellByName(well,
1208 this->substep_timer_.simulationTimeElapsed(),
1209 /*dont_shut_grup_wells =*/ false);
1210 if (was_shut) {
1211 shut_wells.push_back(well);
1212 }
1213 }
1214 }
1215 // If still no wells are closed we must fall back to chopping again
1216 if (shut_wells.empty()) {
1217 chopTimeStep_(new_time_step);
1218 } else {
1219 wells_shut = true;
1220 if (solverVerbose_()) {
1221 const std::string msg =
1222 fmt::format(fmt::runtime("\nProblematic well(s) were shut: {}"
1223 "(retrying timestep)\n"),
1224 fmt::join(shut_wells, " "));
1225 OpmLog::problem(msg);
1226 }
1227 }
1228 }
1229 return wells_shut;
1230}
1231
1232template<class TypeTag>
1233template<class Solver>
1234boost::posix_time::ptime
1235AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1236currentDateTime_() const
1237{
1238 return simulatorTimer_().currentDateTime();
1239}
1240
1241template<class TypeTag>
1242template<class Solver>
1243int
1244AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1245getNumIterations_(const SimulatorReportSingle &substep_report) const
1246{
1247 if (useNewtonIteration_()) {
1248 return substep_report.total_newton_iterations;
1249 }
1250 else {
1251 return substep_report.total_linear_iterations;
1252 }
1253}
1254
1255template<class TypeTag>
1256template<class Solver>
1257double
1258AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1259growthFactor_() const
1260{
1261 return this->adaptive_time_stepping_.growth_factor_;
1262}
1263
1264template<class TypeTag>
1265template<class Solver>
1266bool
1267AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1268ignoreConvergenceFailure_() const
1269{
1270 return adaptive_time_stepping_.ignore_convergence_failure_;
1271}
1272
1273template<class TypeTag>
1274template<class Solver>
1275bool
1276AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1277isReservoirCouplingMaster_() const
1278{
1279 return this->substepper_.isReservoirCouplingMaster_();
1280}
1281
1282template<class TypeTag>
1283template<class Solver>
1284bool
1285AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1286isReservoirCouplingSlave_() const
1287{
1288 return this->substepper_.isReservoirCouplingSlave_();
1289}
1290
1291template<class TypeTag>
1292template<class Solver>
1293void
1294AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1295markFirstSubStepAsFinished_() const
1296{
1297#ifdef RESERVOIR_COUPLING_ENABLED
1298 // Clear the first-substep flag after the first runSubStep_() call.
1299 // This ensures that master-slave synchronization only happens once per sync timestep,
1300 // not on retry attempts after convergence-driven timestep chops.
1301 if (isReservoirCouplingMaster_()) {
1302 reservoirCouplingMaster_().setFirstSubstepOfSyncTimestep(false);
1303 }
1304 else if (isReservoirCouplingSlave_()) {
1305 reservoirCouplingSlave_().setFirstSubstepOfSyncTimestep(false);
1306 }
1307#endif
1308 return;
1309}
1310
1311template<class TypeTag>
1312template<class Solver>
1313double
1314AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1315maxGrowth_() const
1316{
1317 return this->adaptive_time_stepping_.max_growth_;
1318}
1319
1320template<class TypeTag>
1321template<class Solver>
1322void
1323AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1324maybeReportSubStep_(SimulatorReportSingle substep_report) const
1325{
1326 if (timeStepVerbose_()) {
1327 std::ostringstream ss;
1328 substep_report.reportStep(ss);
1329 OpmLog::info(ss.str());
1330 }
1331}
1332
1333template<class TypeTag>
1334template<class Solver>
1335double
1336AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1337maybeRestrictTimeStepGrowth_(const double dt, double dt_estimate, const int restarts) const
1338{
1339 // limit the growth of the timestep size by the growth factor
1340 dt_estimate = std::min(dt_estimate, double(maxGrowth_() * dt));
1341 assert(dt_estimate > 0);
1342 // further restrict time step size growth after convergence problems
1343 if (restarts > 0) {
1344 dt_estimate = std::min(growthFactor_() * dt, dt_estimate);
1345 }
1346
1347 return dt_estimate;
1348}
1349
1350
1351template<class TypeTag>
1352template<class Solver>
1353void
1354AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1355maybeUpdateLastSubstepOfSyncTimestep_([[maybe_unused]] const double dt)
1356{
1357#ifdef RESERVOIR_COUPLING_ENABLED
1358 // For reservoir coupling slaves: predict if this substep will complete
1359 // the sync timestep. If so, timeStepSucceeded() will send production
1360 // data to the master (which is blocking on receive after its first substep).
1361 // This is used for summary data synchronization between slaves and master.
1362 if (isReservoirCouplingSlave_()) {
1364 this->substep_timer_.simulationTimeElapsed() + dt,
1365 this->substep_timer_.totalTime()
1366 );
1367 reservoirCouplingSlave_().setLastSubstepOfSyncTimestep(is_last);
1368 }
1369#endif
1370}
1371
1372// The maybeUpdateTuning_() lambda callback is defined in SimulatorFullyImplicit::runStep()
1373// It has to be called for each substep since TUNING might have been changed for next sub step due
1374// to ACTIONX (via NEXTSTEP) or WCYCLE keywords.
1375template<class TypeTag>
1376template<class Solver>
1377void
1378AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1379maybeUpdateTuningAndTimeStep_()
1380{
1381 // TODO: This function is currently only called if NEXTSTEP is activated from ACTIONX or
1382 // if the WCYCLE keyword needs to modify the current timestep. So this method should rather
1383 // be named maybeUpdateTimeStep_() or similar, since it should not update the tuning. However,
1384 // the current definition of the maybeUpdateTuning_() callback is actually calling
1385 // adaptiveTimeStepping_->updateTUNING(max_next_tstep, tuning) which is updating the tuning
1386 // see SimulatorFullyImplicit::runStep() for more details.
1387 const auto old_value = suggestedNextTimestep_();
1388 if (this->substepper_.maybeUpdateTuning_(this->substep_timer_.simulationTimeElapsed(),
1389 this->substep_timer_.currentStepLength(),
1390 this->substep_timer_.currentStepNum()))
1391 {
1392 // Either NEXTSTEP and WCYCLE wants to change the current time step, but they cannot
1393 // change the current time step directly. Instead, they change the suggested next time step
1394 // by calling updateNEXTSTEP() via the maybeUpdateTuning() callback. We now need to update
1395 // the current time step to the new suggested time step and reset the suggested time step
1396 // to the old value.
1397 setTimeStep_(suggestedNextTimestep_());
1398 setSuggestedNextStep_(old_value);
1399 }
1400}
1401
1402template<class TypeTag>
1403template<class Solver>
1404double
1405AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1406minTimeStepBeforeClosingWells_() const
1407{
1408 return this->adaptive_time_stepping_.min_time_step_before_shutting_problematic_wells_;
1409}
1410
1411template<class TypeTag>
1412template<class Solver>
1413double
1414AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1415minTimeStep_() const
1416{
1417 return this->adaptive_time_stepping_.min_time_step_;
1418}
1419
1420#ifdef RESERVOIR_COUPLING_ENABLED
1421template<class TypeTag>
1422template<class Solver>
1423ReservoirCouplingMaster<typename AdaptiveTimeStepping<TypeTag>::Scalar>&
1424AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1425reservoirCouplingMaster_() const
1426{
1427 return this->substepper_.reservoirCouplingMaster_();
1428}
1429
1430template<class TypeTag>
1431template<class Solver>
1432ReservoirCouplingSlave<typename AdaptiveTimeStepping<TypeTag>::Scalar>&
1433AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1434reservoirCouplingSlave_() const
1435{
1436 return this->substepper_.reservoirCouplingSlave_();
1437}
1438#endif
1439
1440template<class TypeTag>
1441template<class Solver>
1442double
1443AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1444restartFactor_() const
1445{
1446 return this->adaptive_time_stepping_.restart_factor_;
1447}
1448
1449template<class TypeTag>
1450template<class Solver>
1451SimulatorReportSingle
1452AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1453runSubStep_()
1454{
1455 OPM_TIMEFUNCTION();
1456 SimulatorReportSingle substep_report;
1457
1458 auto handleFailure = [this, &substep_report]
1459 (const std::string& failure_reason, const std::exception& e, bool log_exception = true)
1460 {
1461 substep_report = solver_().failureReport();
1462 // Keep the exception text: it carries the actual reason (e.g. which
1463 // equation had a too-large residual), which otherwise only surfaces
1464 // at debug verbosity while the PRT shows just the generic category.
1465 this->cause_of_failure_ = failure_reason;
1466 if (const std::string what = e.what();
1467 !what.empty() && what != failure_reason)
1468 {
1469 this->cause_of_failure_ += " (" + what + ")";
1470 }
1471 if (log_exception && solverVerbose_()) {
1472 OpmLog::debug(std::string("Caught Exception: ") + e.what());
1473 }
1474 };
1475
1476 try {
1477 substep_report = solver_().step(this->substep_timer_, &this->adaptive_time_stepping_.timeStepControl());
1478 if (solverVerbose_()) {
1479 // report number of linear iterations
1480 OpmLog::debug("Overall linear iterations used: "
1481 + std::to_string(substep_report.total_linear_iterations));
1482 }
1483 }
1484 catch (const TooManyIterations& e) {
1485 handleFailure("Solver convergence failure - Iteration limit reached", e);
1486 }
1487 catch (const TimeSteppingBreakdown& e) {
1488 handleFailure(e.what(), e);
1489 }
1490 catch (const ConvergenceMonitorFailure& e) {
1491 handleFailure("Convergence monitor failure", e, /*log_exception=*/false);
1492 }
1493 catch (const LinearSolverProblem& e) {
1494 handleFailure("Linear solver convergence failure", e);
1495 }
1496 catch (const NumericalProblem& e) {
1497 handleFailure("Solver convergence failure - Numerical problem encountered", e);
1498 }
1499 catch (const std::runtime_error& e) {
1500 handleFailure("Runtime error encountered", e);
1501 }
1502 catch (const Dune::ISTLError& e) {
1503 handleFailure("ISTL error - Time step too large", e);
1504 }
1505 catch (const Dune::MatrixBlockError& e) {
1506 handleFailure("Matrix block error", e);
1507 }
1508
1509 return substep_report;
1510}
1511
1512template<class TypeTag>
1513template<class Solver>
1514void
1515AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1516setTimeStep_(double dt_estimate)
1517{
1518 this->substep_timer_.provideTimeStepEstimate(dt_estimate);
1519}
1520
1521template<class TypeTag>
1522template<class Solver>
1523Solver&
1524AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1525solver_() const
1526{
1527 return this->substepper_.solver_;
1528}
1529
1530
1531template<class TypeTag>
1532template<class Solver>
1533int
1534AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1535solverRestartMax_() const
1536{
1537 return this->adaptive_time_stepping_.solver_restart_max_;
1538}
1539
1540template<class TypeTag>
1541template<class Solver>
1542void
1543AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1544setSuggestedNextStep_(double step)
1545{
1546 this->adaptive_time_stepping_.setSuggestedNextStep(step);
1547}
1548
1549template <class TypeTag>
1550template <class Solver>
1551const SimulatorTimer&
1552AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1553simulatorTimer_() const
1554{
1555 return this->substepper_.simulator_timer_;
1556}
1557
1558template <class TypeTag>
1559template <class Solver>
1560bool
1561AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1562solverVerbose_() const
1563{
1564 return this->adaptive_time_stepping_.solver_verbose_;
1565}
1566
1567template<class TypeTag>
1568template<class Solver>
1569boost::posix_time::ptime
1570AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1571startDateTime_() const
1572{
1573 return simulatorTimer_().startDateTime();
1574}
1575
1576template <class TypeTag>
1577template <class Solver>
1578double
1579AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1580suggestedNextTimestep_() const
1581{
1582 return this->adaptive_time_stepping_.suggestedNextStep();
1583}
1584
1585template <class TypeTag>
1586template <class Solver>
1587double
1588AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1589timeStepControlComputeEstimate_(const double dt, const int iterations,
1590 const AdaptiveSimulatorTimer& substepTimer) const
1591{
1592 // create object to compute the time error, simply forwards the call to the model
1593 const SolutionTimeErrorSolverWrapper<Solver> relative_change{solver_()};
1594 return this->adaptive_time_stepping_.time_step_control_->computeTimeStepSize(
1595 dt, iterations, relative_change, substepTimer);
1596}
1597
1598template <class TypeTag>
1599template <class Solver>
1600bool
1601AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1602timeStepVerbose_() const
1603{
1604 return this->adaptive_time_stepping_.timestep_verbose_;
1605}
1606
1607// The suggested time step is the stepsize that will be used as a first try for
1608// the next sub step. It is updated at the end of each substep. It can also be
1609// updated by the TUNING or NEXTSTEP keywords at the beginning of each report step or
1610// at the beginning of each substep by the ACTIONX keyword (via NEXTSTEP), this is
1611// done by the maybeUpdateTuning_() method which is called at the beginning of each substep
1612// (and the begginning of each report step). Note that the WCYCLE keyword can also update the
1613// suggested time step via the maybeUpdateTuning_() method.
1614template <class TypeTag>
1615template <class Solver>
1616void
1617AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1618updateSuggestedNextStep_()
1619{
1620 auto suggested_next_step = this->substep_timer_.currentStepLength();
1621 if (! std::isfinite(suggested_next_step)) { // check for NaN
1622 suggested_next_step = this->original_time_step_;
1623 }
1624 if (timeStepVerbose_()) {
1625 std::ostringstream ss;
1626 this->substep_timer_.report(ss);
1627 ss << "Suggested next step size = "
1628 << unit::convert::to(suggested_next_step, unit::day) << " (days)" << std::endl;
1629 OpmLog::debug(ss.str());
1630 }
1631 setSuggestedNextStep_(suggested_next_step);
1632}
1633
1634template <class TypeTag>
1635template <class Solver>
1636bool
1637AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1638useNewtonIteration_() const
1639{
1640 return this->adaptive_time_stepping_.use_newton_iteration_;
1641}
1642
1643template <class TypeTag>
1644template <class Solver>
1645double
1646AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1647writeOutput_() const
1648{
1649 time::StopWatch perf_timer;
1650 perf_timer.start();
1651 auto& problem = solver_().model().simulator().problem();
1652 problem.writeOutput(true);
1653 return perf_timer.secsSinceStart();
1654}
1655
1656/************************************************
1657 * Private class SolutionTimeErrorSolverWrapper
1658 * **********************************************/
1659
1660template<class TypeTag>
1661template<class Solver>
1662AdaptiveTimeStepping<TypeTag>::
1663SolutionTimeErrorSolverWrapper<Solver>::
1664SolutionTimeErrorSolverWrapper(const Solver& solver)
1665 : solver_{solver}
1666{}
1667
1668template<class TypeTag>
1669template<class Solver>
1670double AdaptiveTimeStepping<TypeTag>::SolutionTimeErrorSolverWrapper<Solver>::relativeChange() const
1671{
1672 // returns: || u^n+1 - u^n || / || u^n+1 ||
1673 return solver_.model().relativeChange();
1674}
1675
1676} // namespace Opm
1677
1678#endif // OPM_ADAPTIVE_TIME_STEPPING_IMPL_HPP
Defines some fundamental parameters for all models.
Adaptive time-stepping coordinator for the black-oil simulator.
Definition: AdaptiveTimeStepping.hpp:93
double max_growth_
factor that limits the maximum growth of a time step
Definition: AdaptiveTimeStepping.hpp:435
double max_time_step_
maximal allowed time step size in days
Definition: AdaptiveTimeStepping.hpp:436
bool solver_verbose_
solver verbosity
Definition: AdaptiveTimeStepping.hpp:440
int solver_restart_max_
how many restart of solver are allowed
Definition: AdaptiveTimeStepping.hpp:439
double timestep_after_event_
suggested size of timestep after an event
Definition: AdaptiveTimeStepping.hpp:444
void init_(const UnitSystem &unitSystem)
Definition: AdaptiveTimeStepping_impl.hpp:430
void setSuggestedNextStep(const double x)
Set the suggested length for the next substep [s].
Definition: AdaptiveTimeStepping_impl.hpp:301
double suggestedNextStep() const
Definition: AdaptiveTimeStepping_impl.hpp:309
bool operator==(const AdaptiveTimeStepping< TypeTag > &rhs) const
Definition: AdaptiveTimeStepping_impl.hpp:141
static AdaptiveTimeStepping< TypeTag > serializationTestObjectSimple()
Definition: AdaptiveTimeStepping_impl.hpp:284
bool ignore_convergence_failure_
continue instead of stop when minimum time step is reached
Definition: AdaptiveTimeStepping.hpp:438
void serializeOp(Serializer &serializer)
Definition: AdaptiveTimeStepping_impl.hpp:213
void updateTUNING(double max_next_tstep, const Tuning &tuning)
Apply TUNING keyword parameters.
Definition: AdaptiveTimeStepping_impl.hpp:338
TimeStepControlType time_step_control_type_
type of time step control object
Definition: AdaptiveTimeStepping.hpp:431
const TimeStepControlInterface & timeStepControl() const
Definition: AdaptiveTimeStepping_impl.hpp:317
std::function< bool(double elapsed, double substep_length, int sub_step_number)> TuningUpdateCallback
Callback invoked at the start of each substep to apply TUNING, NEXTSTEP (via ACTIONX),...
Definition: AdaptiveTimeStepping.hpp:119
bool full_timestep_initially_
beginning with the size of the time step from data file
Definition: AdaptiveTimeStepping.hpp:443
SimulatorReport step(const SimulatorTimer &simulator_timer, Solver &solver, const bool is_event, const TuningUpdateCallback &tuning_updater)
Run one report step by orchestrating adaptive substepping.
Definition: AdaptiveTimeStepping_impl.hpp:198
double growth_factor_
factor to multiply time step when solver recovered from failed convergence
Definition: AdaptiveTimeStepping.hpp:434
double restart_factor_
factor to multiply time step with when solver fails to converge
Definition: AdaptiveTimeStepping.hpp:433
double min_time_step_
minimal allowed time step size before throwing
Definition: AdaptiveTimeStepping.hpp:437
void updateNEXTSTEP(double max_next_tstep)
Set suggested_next_timestep_ to max_next_tstep iff max_next_tstep > 0.
Definition: AdaptiveTimeStepping_impl.hpp:326
static AdaptiveTimeStepping< TypeTag > serializationTestObjectHardcoded()
Definition: AdaptiveTimeStepping_impl.hpp:260
TimeStepController time_step_control_
time step control object
Definition: AdaptiveTimeStepping.hpp:432
static AdaptiveTimeStepping< TypeTag > serializationTestObjectPIDIt()
Definition: AdaptiveTimeStepping_impl.hpp:276
double min_time_step_before_shutting_problematic_wells_
< shut problematic wells when time step size in days are less than this
Definition: AdaptiveTimeStepping.hpp:448
static AdaptiveTimeStepping< TypeTag > serializationTestObject3rdOrder()
Definition: AdaptiveTimeStepping_impl.hpp:292
SimulatorReport & report()
Definition: AdaptiveTimeStepping_impl.hpp:252
static AdaptiveTimeStepping< TypeTag > serializationTestObjectPID()
Definition: AdaptiveTimeStepping_impl.hpp:268
static void registerParameters()
Definition: AdaptiveTimeStepping_impl.hpp:187
bool use_newton_iteration_
use newton iteration count for adaptive time step control
Definition: AdaptiveTimeStepping.hpp:445
Definition: SimulatorTimer.hpp:38
Definition: TimeStepControlInterface.hpp:51
auto Get(bool errorIfNotRegistered=true)
Retrieve a runtime parameter.
Definition: parametersystem.hpp:192
void logTimer(const AdaptiveSimulatorTimer &substep_timer)
void registerAdaptiveParameters()
std::set< std::string > consistentlyFailingWells(const std::vector< StepReport > &sr, bool requireRepeatedFailures)
std::tuple< TimeStepControlType, std::unique_ptr< TimeStepControlInterface >, bool > createController(const UnitSystem &unitSystem)
Definition: blackoilbioeffectsmodules.hh:45
std::string to_string(const ConvergenceReport::ReservoirFailure::Type t)
This file provides the infrastructure to retrieve run-time parameters.
static bool compare_gt_or_eq(double a, double b)
Determines if a is greater than b within the specified tolerance.
Definition: SimulatorReport.hpp:202