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 std::string flow_limited_by; // The master group that limits the step (GRUPMAST item 4), if any
657 if (sync_at_report_steps) {
658 current_step_length = prev_step;
659 } else {
660 const double remaining = step_end_time - current_time;
661 // GRUPMAST item 4: the previous sync step's flow changes may restrict this step
662 const double flow_limit = reservoirCouplingMaster_().flowChangeTimeStepLimit();
663 current_step_length = std::min({suggestedNextTimestep_(), maxTimeStep_(), remaining});
664 if (flow_limit < current_step_length) {
665 current_step_length = flow_limit;
666 const auto& [group, change, fraction] = reservoirCouplingMaster_().flowChangeLimitingGroup();
667 flow_limited_by = group;
668 OpmLog::debug(fmt::format(
669 "Sync step limited by GRUPMAST item 4: flow rate of master group {} changed by {:.3g} "
670 "(limit {:.3g}) in the previous sync step",
671 group, change, fraction));
672 // Like AdaptiveSimulatorTimer::provideTimeStepEstimate(), but against the next slave
673 // report date: split the time left in two to avoid a very short step in front of it.
674 // A step that reaches the report date is chopped to it by maybeChopSubStep() below.
675 const double to_slave_report = reservoirCouplingMaster_().maybeChopSubStep(remaining, current_time);
676 if (current_step_length < to_slave_report && 1.5 * current_step_length > to_slave_report) {
677 current_step_length = 0.5 * to_slave_report;
678 }
679 }
680 // NOTE: The substep timer is later constructed (in the caller) with
681 // current_step_length as its span, and after construction,
682 // substep_timer.currentStepLength() should return the same as
683 // current_step_length. The timer's constructor calls
684 // provideTimeStepEstimate() with suggestedNextTimestep_() as the dt_
685 // estimate and current_step_length as the span. Since we took
686 // current_step_length = min(suggestedNextTimestep_(), maxTimeStep_(), remaining)
687 // here (and only shrink it further, via the GRUPMAST item 4 flow limit above
688 // and maybeChopSubStep below), the
689 // estimate is always >= the span, so the timer's snap branch (see
690 // AdaptiveSimulatorTimer::provideTimeStepEstimate) fires and clamps
691 // dt_ to current_step_length.
692 }
693 const double chopped_step_length =
694 reservoirCouplingMaster_().maybeChopSubStep(current_step_length, current_time);
695 if (chopped_step_length < current_step_length) {
696 flow_limited_by.clear(); // a slave report date, not the flow limit, decides the step
697 }
698 current_step_length = chopped_step_length;
699 auto num_active = reservoirCouplingMaster_().numCoupledSlaves();
700 OpmLog::info(fmt::format(
701 "\nChoosing next sync time{} between master and {} active slave {}: {:.2f} days{}",
702 sync_at_report_steps ? " (RSYNC)" : "",
703 num_active, (num_active == 1 ? "process" : "processes"),
704 current_step_length / unit::day,
705 flow_limited_by.empty() ? "" : fmt::format(" (limited by GRUPMAST item 4 for group {})", flow_limited_by)
706 ));
707 return current_step_length;
708}
709
710template <class TypeTag>
711template <class Solver>
712ReservoirCouplingMaster<typename AdaptiveTimeStepping<TypeTag>::Scalar>&
713AdaptiveTimeStepping<TypeTag>::SubStepper<Solver>::
714reservoirCouplingMaster_()
715{
716 return *(this->solver_.model().simulator().reservoirCouplingMaster());
717}
718
719template <class TypeTag>
720template <class Solver>
721ReservoirCouplingSlave<typename AdaptiveTimeStepping<TypeTag>::Scalar>&
722AdaptiveTimeStepping<TypeTag>::SubStepper<Solver>::
723reservoirCouplingSlave_()
724{
725 return *(this->solver_.model().simulator().reservoirCouplingSlave());
726}
727
728// Description of the reservoir coupling master and slave substep loop
729// -------------------------------------------------------------------
730// The master and slave processes attempt to reach the end of the report step using a series of substeps
731// (also called timesteps). Each substep has an upper limit roughly determined by a combination of the
732// keywords TUNING (through TSINIT, TSMAXZ), NEXSTEP, WCYCLE, and the start of the next report step
733// (the last substep has to coincide with this time). Note that NEXTSTEP can be updated from an
734// ACTIONX keyword. Although this comment focuses on the maximum substep limit, there is also a lower
735// limit on the substep length, and the substep sizes are adjusted automatically (or retried) based on
736// the convergence behavior of the solver and other criteria.
737//
738// The master chooses a "sync step" — the span between successive master<->slave exchanges — via one
739// of two modes, selected by the `--rescoup-sync-at-report-steps` CLI flag:
740//
741// TSYNC (default, flag=false)
742// Each master time step is a sync step. Every outer iteration of the master substep
743// loop redistributes group-rate targets to the slaves. This gives fine sync granularity and
744// matches the reference simulator's coupling semantics.
745//
746// RSYNC (flag=true)
747// Each chunk between slave-report-step boundaries is one sync step. The master's own adaptive
748// timestepping runs inside the chunk, but slaves only hear back from the master once per chunk.
749// Provided as a developer escape hatch.
750//
751// In both modes the sync step is also limited so as not to overshoot the next slave report date
752// (`maybeChopSubStep`). A second, drift-based limit (GRUPMAST item 4 / RCMASTS) restricts the next
753// sync step from the previous sync step's group flow changes (TSYNC only). It is retrospective: a step
754// that exceeds the limit is not chopped.
755//
756// Per sync step:
757// - Master receives each activated slave's next report date (`receiveNextReportDateFromSlaves`).
758// - Master picks the sync-step length via `getRcMasterSyncStepLength_()` (mode-dependent) and
759// chops it so as not to overshoot any slave report date.
760// - Master sends the chosen sync-step length to each slave (`sendNextTimeStepToSlaves`).
761//
762// The slaves use the sync-step end as a fixed point — a "mini report step" — that they reach via one
763// or more of their own substeps. If the slave's current report step extends beyond the received
764// sync step, the slave loops waiting for the master to send further sync steps; the loop ends when
765// the received sync step coincides with the end of the slave's current report step.
766
767// Master-side rescoup outer loop. See the block comment above for TSYNC
768// and RSYNC mode definitions.
769//
770// TSYNC (default):
771// Each outer iteration is one master time step. The sync-step
772// length is chosen fresh each iteration by `getRcMasterSyncStepLength_()`
773// from the master's adaptive suggestion (capped at the max time step and
774// at the remaining report-step time), then chopped against slave-report
775// dates. `SubStepIteration::run()` is still used but normally handles
776// only solver-driven chops within a single master time step.
777//
778// RSYNC:
779// The outer loop iterates once per chunk between slave-report boundaries.
780// The sync-step length starts at the full report step and only shrinks
781// via `maybeChopSubStep`; the master's adaptive timestepping runs inside
782// `SubStepIteration` as solver-driven substeps within each chunk.
783template <class TypeTag>
784template <class Solver>
785SimulatorReport
786AdaptiveTimeStepping<TypeTag>::SubStepper<Solver>::
787runStepReservoirCouplingMaster_()
788{
789 int iteration = 0;
790 const double original_time_step = this->simulator_timer_.currentStepLength();
791 double current_time{this->simulator_timer_.simulationTimeElapsed()};
792 double step_end_time = current_time + original_time_step;
793 const double report_step_start_time = current_time;
794 int report_step_substep_offset = 0;
795 // In RSYNC mode this variable persists across outer iterations and
796 // carries the previously-chopped sync span into the next iteration. In
797 // TSYNC mode it is overwritten each iteration by
798 // `getRcMasterSyncStepLength_()`; the initial value is only consumed by
799 // the first helper call in RSYNC mode (as the candidate span for that
800 // iteration).
801 auto current_step_length = original_time_step;
802 auto report_step_idx = this->simulator_timer_.currentStepNum();
803 if (report_step_idx == 0 && iteration == 0) {
804 reservoirCouplingMaster_().initTimeStepping();
805 }
806 SimulatorReport report;
807 // The master needs to know which slaves have activated before it can start the substep loop
808 reservoirCouplingMaster_().maybeReceiveActivationHandshakeFromSlaves(current_time);
809 while (true) {
810 reservoirCouplingMaster_().sendDontTerminateSignalToSlaves(); // Tell the slaves to keep running.
811 reservoirCouplingMaster_().receiveNextReportDateFromSlaves();
812 const bool start_of_report_step = (iteration == 0);
813 if (start_of_report_step) {
814 reservoirCouplingMaster_().initStartOfReportStep(report_step_idx);
815 maybeUpdateTuning_(current_time, suggestedNextTimestep_(), /*substep=*/0);
816 maybeModifySuggestedTimeStepAtBeginningOfReportStep_(original_time_step);
817 }
818 current_step_length = getRcMasterSyncStepLength_(
819 current_step_length, current_time, step_end_time);
820 reservoirCouplingMaster_().sendNextTimeStepToSlaves(current_step_length);
821 AdaptiveSimulatorTimer substep_timer{
822 this->simulator_timer_.startDateTime(),
823 /*stepLength=*/current_step_length,
824 /*elapsedTime=*/current_time,
825 /*timeStepEstimate=*/suggestedNextTimestep_(),
826 /*reportStep=*/this->simulator_timer_.reportStepNum(),
827 maxTimeStep_()
828 };
829 // Make the per-chunk timer log the enclosing report step's span and a
830 // cumulative substep counter (see AdaptiveSimulatorTimer "report step
831 // view"). Without this, log lines would show one sync chunk only.
832 substep_timer.setReportStepStartTime(report_step_start_time);
833 substep_timer.setReportStepTotalTime(step_end_time);
834 substep_timer.setReportStepSubstepOffset(report_step_substep_offset);
835 const bool final_step = ReservoirCoupling::Seconds::compare_gt_or_eq(
836 current_time + current_step_length, step_end_time
837 );
838 // Mark this as the first substep of the "sync" timestep. This flag controls
839 // whether master-slave data exchange should occur in beginTimeStep() in the well model.
840 // It will be cleared after the first runSubStep_() call.
841 reservoirCouplingMaster_().setFirstSubstepOfSyncTimestep(true);
842 // After the first master substep completes, timeStepSucceeded() will
843 // block until slaves finish the sync step and send production data.
844 // This ensures correct summary output for all subsequent substeps.
845 reservoirCouplingMaster_().setNeedsSlaveDataReceive(true);
846 SubStepIteration<Solver> substepIteration{*this, substep_timer, current_step_length, final_step};
847 const auto sub_steps_report = substepIteration.run();
848 report += sub_steps_report;
849 report_step_substep_offset += substep_timer.currentStepNum();
850 current_time += current_step_length;
851 if (final_step) {
852 break;
853 }
854 iteration++;
855 }
856 return report;
857}
858
859template <class TypeTag>
860template <class Solver>
861SimulatorReport
862AdaptiveTimeStepping<TypeTag>::SubStepper<Solver>::
863runStepReservoirCouplingSlave_()
864{
865 checkIfSlaveIsTerminated_();
866 int iteration = 0;
867 const double original_time_step = this->simulator_timer_.currentStepLength();
868 double current_time{this->simulator_timer_.simulationTimeElapsed()};
869 double step_end_time = current_time + original_time_step;
870 const double report_step_start_time = current_time;
871 int report_step_substep_offset = 0;
872 SimulatorReport report;
873 auto report_step_idx = this->simulator_timer_.currentStepNum();
874 if (report_step_idx == 0 && iteration == 0) {
875 reservoirCouplingSlave_().initTimeStepping();
876 }
877 while (true) {
878 bool start_of_report_step = (iteration == 0);
879 if (reservoirCouplingSlave_().maybeReceiveTerminateSignalFromMaster()) {
880 // Call MPI_Comm_disconnect() to terminate the MPI communicator, etc..
881 break;
882 }
883 reservoirCouplingSlave_().sendNextReportDateToMasterProcess();
884 const auto timestep = reservoirCouplingSlave_().receiveNextTimeStepFromMaster();
885 if (start_of_report_step) {
886 maybeUpdateTuning_(current_time, suggestedNextTimestep_(), /*substep=*/0);
887 maybeModifySuggestedTimeStepAtBeginningOfReportStep_(timestep);
888 }
889 AdaptiveSimulatorTimer substep_timer{
890 this->simulator_timer_.startDateTime(),
891 /*step_length=*/timestep,
892 /*elapsed_time=*/current_time,
893 /*time_step_estimate=*/suggestedNextTimestep_(),
894 this->simulator_timer_.reportStepNum(),
895 maxTimeStep_()
896 };
897 // Make the per-chunk timer log the enclosing report step's span and a
898 // cumulative substep counter (see AdaptiveSimulatorTimer "report step
899 // view"). Without this, log lines would show one sync chunk only.
900 substep_timer.setReportStepStartTime(report_step_start_time);
901 substep_timer.setReportStepTotalTime(step_end_time);
902 substep_timer.setReportStepSubstepOffset(report_step_substep_offset);
903 const bool final_step = ReservoirCoupling::Seconds::compare_gt_or_eq(
904 current_time + timestep, step_end_time
905 );
906 // Mark this as the first substep of the "sync" timestep. This flag controls
907 // whether master-slave data exchange should occur in beginTimeStep() in the well model.
908 // It will be cleared after the first runSubStep_() call.
909 reservoirCouplingSlave_().setFirstSubstepOfSyncTimestep(true);
910 SubStepIteration<Solver> substepIteration{*this, substep_timer, timestep, final_step};
911 const auto sub_steps_report = substepIteration.run();
912 report += sub_steps_report;
913 report_step_substep_offset += substep_timer.currentStepNum();
914 current_time += timestep;
915 if (final_step) {
916 break;
917 }
918 iteration++;
919 }
920 return report;
921}
922#endif // RESERVOIR_COUPLING_ENABLED
923
924
925/************************************************
926 * Private class SubStepIteration public methods
927 ************************************************/
928
929template<class TypeTag>
930template<class Solver>
931AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
932SubStepIteration(SubStepper<Solver>& substepper,
933 AdaptiveSimulatorTimer& substep_timer,
934 const double original_time_step,
935 bool final_step)
936 : substepper_{substepper}
937 , substep_timer_{substep_timer}
938 , original_time_step_{original_time_step}
939 , final_step_{final_step}
940 , adaptive_time_stepping_{substepper.getAdaptiveTimerStepper()}
941{
942}
943
944template <class TypeTag>
945template <class Solver>
946SimulatorReport
947AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
948run()
949{
950 auto& simulator = solver_().model().simulator();
951 auto& problem = simulator.problem();
952 const bool truncateTimeStepToFloat = Parameters::Get<Parameters::TruncateTimeStepToFloat>();
953 // counter for solver restarts
954 int restarts = 0;
955 SimulatorReport report;
956
957 // sub step time loop
958 while (!this->substep_timer_.done()) {
959 // if we just chopped the timestep due to convergence i.e. restarts>0
960 // we dont want to update the next timestep based on Tuning
961 if (restarts == 0) {
962 maybeUpdateTuningAndTimeStep_();
963 }
964 const double dt = this->substep_timer_.currentStepLength();
965 if (timeStepVerbose_()) {
966 detail::logTimer(this->substep_timer_);
967 }
968
969 maybeUpdateLastSubstepOfSyncTimestep_(dt); // Needed for reservoir coupling
970 auto substep_report = runSubStep_();
971 markFirstSubStepAsFinished_(); // Needed for reservoir coupling
972
973 if (substep_report.converged || checkContinueOnUnconvergedSolution_(dt)) {
974 Dune::Timer perfTimer;
975 perfTimer.start();
976 // Pass substep to eclwriter for summary output
977 problem.setSubStepReport(substep_report);
978 auto& full_report = adaptive_time_stepping_.report();
979 full_report += substep_report;
980 problem.setSimulationReport(full_report);
981 problem.endTimeStep();
982 substep_report.pre_post_time += perfTimer.stop();
983
984 report += substep_report;
985
986 OPM_TIMEBLOCK(convergenceSucceeded);
987 // When the time step is truncated to float precision, advance the timer
988 // by the truncated step size used by the model and base the next step
989 // size on it. This keeps the elapsed time and the step sizes reproducible
990 // from the step sizes alone, which the timestep replay tests rely on.
991 // The step that ends the report step keeps dt so that the report time
992 // is hit exactly.
993 double dt_taken = dt;
994 if (truncateTimeStepToFloat) {
995 const double model_dt = simulator.timeStepSize();
996 const double remaining = this->substep_timer_.totalTime()
997 - this->substep_timer_.simulationTimeElapsed();
998 if (model_dt > 0.0 && dt < remaining) {
999 this->substep_timer_.setCurrentStepLength(model_dt);
1000 dt_taken = model_dt;
1001 }
1002 }
1003 ++this->substep_timer_; // advance by current dt
1004
1005 const int iterations = getNumIterations_(substep_report);
1006 auto dt_estimate = timeStepControlComputeEstimate_(
1007 dt_taken, iterations, this->substep_timer_);
1008
1009 assert(dt_estimate > 0);
1010 dt_estimate = maybeRestrictTimeStepGrowth_(dt_taken, dt_estimate, restarts);
1011 restarts = 0; // solver converged, reset restarts counter
1012
1013 maybeReportSubStep_(substep_report);
1014 if (this->final_step_ && this->substep_timer_.done()) {
1015 // if the time step is done we do not need to write it as this will be done
1016 // by the simulator anyway.
1017 }
1018 else {
1019 report.success.output_write_time += writeOutput_();
1020 }
1021
1022 // set new time step length
1023 checkTimeStepCanAdvance_(dt_taken, dt_estimate);
1024 setTimeStep_(dt_estimate);
1025
1026 report.success.converged = this->substep_timer_.done();
1027 this->substep_timer_.setLastStepFailed(false);
1028 }
1029 else { // in case of no convergence or time step tolerance test failure
1030 OPM_TIMEBLOCK(convergenceFailed);
1031 report += substep_report;
1032 this->substep_timer_.setLastStepFailed(true);
1033 checkTimeStepMaxRestartLimit_(restarts);
1034
1035 double new_time_step = restartFactor_() * dt;
1036 if (substep_report.time_step_rejected) {
1037 const double tol = Parameters::Get<Parameters::TimeStepControlTolerance>();
1038 const double safetyFactor = Parameters::Get<Parameters::TimeStepControlSafetyFactor>();
1039 const double temp_time_step = std::sqrt(safetyFactor * tol / solver_().model().relativeChange()) * dt;
1040 if (temp_time_step < dt) { // added in case suggested time step is not a reduction
1041 new_time_step = temp_time_step;
1042 }
1043 }
1044 checkTimeStepMinLimit_(new_time_step);
1045 bool wells_shut = false;
1046 if (new_time_step > minTimeStepBeforeClosingWells_()) {
1047 chopTimeStep_(new_time_step);
1048 } else {
1049 wells_shut = chopTimeStepOrCloseFailingWells_(new_time_step);
1050 }
1051 if (wells_shut) {
1052 setTimeStep_(dt); // retry the old timestep
1053 }
1054 else {
1055 restarts++; // only increase if no wells were shut
1056 }
1057 }
1058 problem.setNextTimeStepSize(this->substep_timer_.currentStepLength());
1059 }
1060 updateSuggestedNextStep_();
1061 return report;
1062}
1063
1064
1065/************************************************
1066 * Private class SubStepIteration private methods
1067 ************************************************/
1068
1069
1070template<class TypeTag>
1071template<class Solver>
1072bool
1073AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1074checkContinueOnUnconvergedSolution_(double dt) const
1075{
1076 const bool continue_on_uncoverged_solution = ignoreConvergenceFailure_() && dt <= minTimeStep_();
1077 if (continue_on_uncoverged_solution && solverVerbose_()) {
1078 // NOTE: This method is only called if the solver failed to converge.
1079 const auto msg = fmt::format(
1080 "Solver failed to converge but timestep {} is smaller or equal to {}\n"
1081 "which is the minimum threshold given by option --solver-min-time-step\n",
1082 dt, minTimeStep_()
1083 );
1084 OpmLog::problem(msg);
1085 }
1086 return continue_on_uncoverged_solution;
1087}
1088
1089template<class TypeTag>
1090template<class Solver>
1091void
1092AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1093checkTimeStepMaxRestartLimit_(const int restarts) const
1094{
1095 // If we have restarted (i.e. cut the timestep) too
1096 // many times, we have failed and throw an exception.
1097 if (restarts >= solverRestartMax_()) {
1098 const auto msg = fmt::format(
1099 fmt::runtime("Solver failed to converge after cutting timestep {} times."), restarts
1100 );
1101 if (solverVerbose_()) {
1102 OpmLog::error(msg);
1103 }
1104 // Use throw directly to prevent file and line
1105 throw TimeSteppingBreakdown{msg};
1106 }
1107}
1108
1109template<class TypeTag>
1110template<class Solver>
1111void
1112AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1113checkTimeStepCanAdvance_(const double current_time_step,
1114 const double new_time_step) const
1115{
1116 // Both dt floors - the minimum step size and the restart count - sit on
1117 // the failure path. A run whose substeps all converge, but for which the
1118 // controller keeps proposing ever smaller steps, consults neither: the
1119 // step size shrinks without bound while the elapsed time stands still,
1120 // and the run never ends. Hold the accepted path to the same minimum.
1121 if (new_time_step >= minTimeStep_()) {
1122 return;
1123 }
1124
1125 // A sub-minimum step is valid when the report step is already complete, or
1126 // when timestep control is increasing it. Reject only a shrinking
1127 // sub-minimum step, which is the one that stalls short of the report-step
1128 // boundary.
1129 if (this->substep_timer_.done() || new_time_step >= current_time_step) {
1130 return;
1131 }
1132
1133 const auto msg =
1134 fmt::format("Time step control proposed a step of {:.3E} DAYS, below the "
1135 "minimum of {:.3E} DAYS, while every substep converges. The "
1136 "run is not advancing past {:.6E} DAYS.",
1137 new_time_step / 86400.0,
1138 minTimeStep_() / 86400.0,
1139 this->substep_timer_.simulationTimeElapsed() / 86400.0);
1140
1141 if (solverVerbose_()) {
1142 OpmLog::error(msg);
1143 }
1144
1145 // Use throw directly to prevent file and line
1146 throw TimeSteppingBreakdown{msg};
1147}
1148
1149template<class TypeTag>
1150template<class Solver>
1151void
1152AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1153checkTimeStepMinLimit_(const double new_time_step) const
1154{
1155 using Meas = UnitSystem::measure;
1156 // If we have restarted (i.e. cut the timestep) too
1157 // much, we have failed and throw an exception.
1158 if (new_time_step < minTimeStep_()) {
1159 std::string msg = "Solver failed to converge after cutting timestep to ";
1160 if (Parameters::Get<Parameters::EnableTuning>()) {
1161 const UnitSystem& unit_system = solver_().model().simulator().vanguard().eclState().getDeckUnitSystem();
1162 msg += fmt::format(
1163 "{:.3E} {}\nwhich is the minimum threshold given by the TUNING keyword\n",
1164 unit_system.from_si(Meas::time, minTimeStep_()),
1165 unit_system.name(Meas::time)
1166 );
1167 }
1168 else {
1169 msg += fmt::format(
1170 "{:.3E} DAYS\nwhich is the minimum threshold given by option --solver-min-time-step\n",
1171 minTimeStep_() / 86400.0
1172 );
1173 }
1174 if (solverVerbose_()) {
1175 OpmLog::error(msg);
1176 }
1177 // Use throw directly to prevent file and line
1178 throw TimeSteppingBreakdown{msg};
1179 }
1180}
1181
1182template<class TypeTag>
1183template<class Solver>
1184void
1185AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1186chopTimeStep_(const double new_time_step)
1187{
1188 setTimeStep_(new_time_step);
1189 if (solverVerbose_()) {
1190 const auto msg = fmt::format(fmt::runtime("{}\nTimestep chopped to {} days\n"),
1191 this->cause_of_failure_,
1192 unit::convert::to(this->substep_timer_.currentStepLength(), unit::day));
1193 OpmLog::problem(msg);
1194 }
1195}
1196
1197template<class TypeTag>
1198template<class Solver>
1199bool
1200AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1201chopTimeStepOrCloseFailingWells_(const double new_time_step)
1202{
1203 bool wells_shut = false;
1204 // We are below the threshold, and will check if there are any
1205 // wells that fails repeatedly (that means that it fails in the last three steps)
1206 // we should close rather than chopping again.
1207 // If we already have chopped the timestep two times that is
1208 // new_time_step < minTimeStepBeforeClosingWells_()*restartFactor_()*restartFactor_()
1209 // We also shut wells that fails only on this step.
1210 const bool requireRepeatedFailures =
1211 new_time_step > (minTimeStepBeforeClosingWells_() * restartFactor_() * restartFactor_());
1212 const std::set<std::string> failing_wells =
1213 detail::consistentlyFailingWells(solver_().model().stepReports(), requireRepeatedFailures);
1214
1215 if (failing_wells.empty()) {
1216 // Found no wells to close, chop the timestep
1217 chopTimeStep_(new_time_step);
1218 } else {
1219 // Close all consistently failing wells that are not under group control
1220 std::vector<std::string> shut_wells;
1221 for (const auto& well : failing_wells) {
1222 const bool was_shut =
1223 solver_().model().wellModel().forceShutWellByName(well,
1224 this->substep_timer_.simulationTimeElapsed(),
1225 /*dont_shut_grup_wells =*/ true);
1226 if (was_shut) {
1227 shut_wells.push_back(well);
1228 }
1229 }
1230 // If no wells are closed we also try to shut wells under group control
1231 if (shut_wells.empty()) {
1232 for (const auto& well : failing_wells) {
1233 const bool was_shut =
1234 solver_().model().wellModel().forceShutWellByName(well,
1235 this->substep_timer_.simulationTimeElapsed(),
1236 /*dont_shut_grup_wells =*/ false);
1237 if (was_shut) {
1238 shut_wells.push_back(well);
1239 }
1240 }
1241 }
1242 // If still no wells are closed we must fall back to chopping again
1243 if (shut_wells.empty()) {
1244 chopTimeStep_(new_time_step);
1245 } else {
1246 wells_shut = true;
1247 if (solverVerbose_()) {
1248 const std::string msg =
1249 fmt::format(fmt::runtime("\nProblematic well(s) were shut: {}"
1250 "(retrying timestep)\n"),
1251 fmt::join(shut_wells, " "));
1252 OpmLog::problem(msg);
1253 }
1254 }
1255 }
1256 return wells_shut;
1257}
1258
1259template<class TypeTag>
1260template<class Solver>
1261boost::posix_time::ptime
1262AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1263currentDateTime_() const
1264{
1265 return simulatorTimer_().currentDateTime();
1266}
1267
1268template<class TypeTag>
1269template<class Solver>
1270int
1271AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1272getNumIterations_(const SimulatorReportSingle &substep_report) const
1273{
1274 if (useNewtonIteration_()) {
1275 return substep_report.total_newton_iterations;
1276 }
1277 else {
1278 return substep_report.total_linear_iterations;
1279 }
1280}
1281
1282template<class TypeTag>
1283template<class Solver>
1284double
1285AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1286growthFactor_() const
1287{
1288 return this->adaptive_time_stepping_.growth_factor_;
1289}
1290
1291template<class TypeTag>
1292template<class Solver>
1293bool
1294AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1295ignoreConvergenceFailure_() const
1296{
1297 return adaptive_time_stepping_.ignore_convergence_failure_;
1298}
1299
1300template<class TypeTag>
1301template<class Solver>
1302bool
1303AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1304isReservoirCouplingMaster_() const
1305{
1306 return this->substepper_.isReservoirCouplingMaster_();
1307}
1308
1309template<class TypeTag>
1310template<class Solver>
1311bool
1312AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1313isReservoirCouplingSlave_() const
1314{
1315 return this->substepper_.isReservoirCouplingSlave_();
1316}
1317
1318template<class TypeTag>
1319template<class Solver>
1320void
1321AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1322markFirstSubStepAsFinished_() const
1323{
1324#ifdef RESERVOIR_COUPLING_ENABLED
1325 // Clear the first-substep flag after the first runSubStep_() call.
1326 // This ensures that master-slave synchronization only happens once per sync timestep,
1327 // not on retry attempts after convergence-driven timestep chops.
1328 if (isReservoirCouplingMaster_()) {
1329 reservoirCouplingMaster_().setFirstSubstepOfSyncTimestep(false);
1330 }
1331 else if (isReservoirCouplingSlave_()) {
1332 reservoirCouplingSlave_().setFirstSubstepOfSyncTimestep(false);
1333 }
1334#endif
1335 return;
1336}
1337
1338template<class TypeTag>
1339template<class Solver>
1340double
1341AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1342maxGrowth_() const
1343{
1344 return this->adaptive_time_stepping_.max_growth_;
1345}
1346
1347template<class TypeTag>
1348template<class Solver>
1349void
1350AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1351maybeReportSubStep_(SimulatorReportSingle substep_report) const
1352{
1353 if (timeStepVerbose_()) {
1354 std::ostringstream ss;
1355 substep_report.reportStep(ss);
1356 OpmLog::info(ss.str());
1357 }
1358}
1359
1360template<class TypeTag>
1361template<class Solver>
1362double
1363AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1364maybeRestrictTimeStepGrowth_(const double dt, double dt_estimate, const int restarts) const
1365{
1366 // limit the growth of the timestep size by the growth factor
1367 dt_estimate = std::min(dt_estimate, double(maxGrowth_() * dt));
1368 assert(dt_estimate > 0);
1369 // further restrict time step size growth after convergence problems
1370 if (restarts > 0) {
1371 dt_estimate = std::min(growthFactor_() * dt, dt_estimate);
1372 }
1373
1374 return dt_estimate;
1375}
1376
1377
1378template<class TypeTag>
1379template<class Solver>
1380void
1381AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1382maybeUpdateLastSubstepOfSyncTimestep_([[maybe_unused]] const double dt)
1383{
1384#ifdef RESERVOIR_COUPLING_ENABLED
1385 // For reservoir coupling slaves: predict if this substep will complete
1386 // the sync timestep. If so, timeStepSucceeded() will send production
1387 // data to the master (which is blocking on receive after its first substep).
1388 // This is used for summary data synchronization between slaves and master.
1389 if (isReservoirCouplingSlave_()) {
1391 this->substep_timer_.simulationTimeElapsed() + dt,
1392 this->substep_timer_.totalTime()
1393 );
1394 reservoirCouplingSlave_().setLastSubstepOfSyncTimestep(is_last);
1395 }
1396#endif
1397}
1398
1399// The maybeUpdateTuning_() lambda callback is defined in SimulatorFullyImplicit::runStep()
1400// It has to be called for each substep since TUNING might have been changed for next sub step due
1401// to ACTIONX (via NEXTSTEP) or WCYCLE keywords.
1402template<class TypeTag>
1403template<class Solver>
1404void
1405AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1406maybeUpdateTuningAndTimeStep_()
1407{
1408 // TODO: This function is currently only called if NEXTSTEP is activated from ACTIONX or
1409 // if the WCYCLE keyword needs to modify the current timestep. So this method should rather
1410 // be named maybeUpdateTimeStep_() or similar, since it should not update the tuning. However,
1411 // the current definition of the maybeUpdateTuning_() callback is actually calling
1412 // adaptiveTimeStepping_->updateTUNING(max_next_tstep, tuning) which is updating the tuning
1413 // see SimulatorFullyImplicit::runStep() for more details.
1414 const auto old_value = suggestedNextTimestep_();
1415 if (this->substepper_.maybeUpdateTuning_(this->substep_timer_.simulationTimeElapsed(),
1416 this->substep_timer_.currentStepLength(),
1417 this->substep_timer_.currentStepNum()))
1418 {
1419 // Either NEXTSTEP and WCYCLE wants to change the current time step, but they cannot
1420 // change the current time step directly. Instead, they change the suggested next time step
1421 // by calling updateNEXTSTEP() via the maybeUpdateTuning() callback. We now need to update
1422 // the current time step to the new suggested time step and reset the suggested time step
1423 // to the old value.
1424 setTimeStep_(suggestedNextTimestep_());
1425 setSuggestedNextStep_(old_value);
1426 }
1427}
1428
1429template<class TypeTag>
1430template<class Solver>
1431double
1432AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1433minTimeStepBeforeClosingWells_() const
1434{
1435 return this->adaptive_time_stepping_.min_time_step_before_shutting_problematic_wells_;
1436}
1437
1438template<class TypeTag>
1439template<class Solver>
1440double
1441AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1442minTimeStep_() const
1443{
1444 return this->adaptive_time_stepping_.min_time_step_;
1445}
1446
1447#ifdef RESERVOIR_COUPLING_ENABLED
1448template<class TypeTag>
1449template<class Solver>
1450ReservoirCouplingMaster<typename AdaptiveTimeStepping<TypeTag>::Scalar>&
1451AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1452reservoirCouplingMaster_() const
1453{
1454 return this->substepper_.reservoirCouplingMaster_();
1455}
1456
1457template<class TypeTag>
1458template<class Solver>
1459ReservoirCouplingSlave<typename AdaptiveTimeStepping<TypeTag>::Scalar>&
1460AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1461reservoirCouplingSlave_() const
1462{
1463 return this->substepper_.reservoirCouplingSlave_();
1464}
1465#endif
1466
1467template<class TypeTag>
1468template<class Solver>
1469double
1470AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1471restartFactor_() const
1472{
1473 return this->adaptive_time_stepping_.restart_factor_;
1474}
1475
1476template<class TypeTag>
1477template<class Solver>
1478SimulatorReportSingle
1479AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1480runSubStep_()
1481{
1482 OPM_TIMEFUNCTION();
1483 SimulatorReportSingle substep_report;
1484
1485 auto handleFailure = [this, &substep_report]
1486 (const std::string& failure_reason, const std::exception& e, bool log_exception = true)
1487 {
1488 substep_report = solver_().failureReport();
1489 // Keep the exception text: it carries the actual reason (e.g. which
1490 // equation had a too-large residual), which otherwise only surfaces
1491 // at debug verbosity while the PRT shows just the generic category.
1492 this->cause_of_failure_ = failure_reason;
1493 if (const std::string what = e.what();
1494 !what.empty() && what != failure_reason)
1495 {
1496 this->cause_of_failure_ += " (" + what + ")";
1497 }
1498 if (log_exception && solverVerbose_()) {
1499 OpmLog::debug(std::string("Caught Exception: ") + e.what());
1500 }
1501 };
1502
1503 try {
1504 substep_report = solver_().step(this->substep_timer_, &this->adaptive_time_stepping_.timeStepControl());
1505 if (solverVerbose_()) {
1506 // report number of linear iterations
1507 OpmLog::debug("Overall linear iterations used: "
1508 + std::to_string(substep_report.total_linear_iterations));
1509 }
1510 }
1511 catch (const TooManyIterations& e) {
1512 handleFailure("Solver convergence failure - Iteration limit reached", e);
1513 }
1514 catch (const TimeSteppingBreakdown& e) {
1515 handleFailure(e.what(), e);
1516 }
1517 catch (const ConvergenceMonitorFailure& e) {
1518 handleFailure("Convergence monitor failure", e, /*log_exception=*/false);
1519 }
1520 catch (const LinearSolverProblem& e) {
1521 handleFailure("Linear solver convergence failure", e);
1522 }
1523 catch (const NumericalProblem& e) {
1524 handleFailure("Solver convergence failure - Numerical problem encountered", e);
1525 }
1526 catch (const std::runtime_error& e) {
1527 handleFailure("Runtime error encountered", e);
1528 }
1529 catch (const Dune::ISTLError& e) {
1530 handleFailure("ISTL error - Time step too large", e);
1531 }
1532 catch (const Dune::MatrixBlockError& e) {
1533 handleFailure("Matrix block error", e);
1534 }
1535
1536 return substep_report;
1537}
1538
1539template<class TypeTag>
1540template<class Solver>
1541void
1542AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1543setTimeStep_(double dt_estimate)
1544{
1545 this->substep_timer_.provideTimeStepEstimate(dt_estimate);
1546}
1547
1548template<class TypeTag>
1549template<class Solver>
1550Solver&
1551AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1552solver_() const
1553{
1554 return this->substepper_.solver_;
1555}
1556
1557
1558template<class TypeTag>
1559template<class Solver>
1560int
1561AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1562solverRestartMax_() const
1563{
1564 return this->adaptive_time_stepping_.solver_restart_max_;
1565}
1566
1567template<class TypeTag>
1568template<class Solver>
1569void
1570AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1571setSuggestedNextStep_(double step)
1572{
1573 this->adaptive_time_stepping_.setSuggestedNextStep(step);
1574}
1575
1576template <class TypeTag>
1577template <class Solver>
1578const SimulatorTimer&
1579AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1580simulatorTimer_() const
1581{
1582 return this->substepper_.simulator_timer_;
1583}
1584
1585template <class TypeTag>
1586template <class Solver>
1587bool
1588AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1589solverVerbose_() const
1590{
1591 return this->adaptive_time_stepping_.solver_verbose_;
1592}
1593
1594template<class TypeTag>
1595template<class Solver>
1596boost::posix_time::ptime
1597AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1598startDateTime_() const
1599{
1600 return simulatorTimer_().startDateTime();
1601}
1602
1603template <class TypeTag>
1604template <class Solver>
1605double
1606AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1607suggestedNextTimestep_() const
1608{
1609 return this->adaptive_time_stepping_.suggestedNextStep();
1610}
1611
1612template <class TypeTag>
1613template <class Solver>
1614double
1615AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1616timeStepControlComputeEstimate_(const double dt, const int iterations,
1617 const AdaptiveSimulatorTimer& substepTimer) const
1618{
1619 // create object to compute the time error, simply forwards the call to the model
1620 const SolutionTimeErrorSolverWrapper<Solver> relative_change{solver_()};
1621 return this->adaptive_time_stepping_.time_step_control_->computeTimeStepSize(
1622 dt, iterations, relative_change, substepTimer);
1623}
1624
1625template <class TypeTag>
1626template <class Solver>
1627bool
1628AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1629timeStepVerbose_() const
1630{
1631 return this->adaptive_time_stepping_.timestep_verbose_;
1632}
1633
1634// The suggested time step is the stepsize that will be used as a first try for
1635// the next sub step. It is updated at the end of each substep. It can also be
1636// updated by the TUNING or NEXTSTEP keywords at the beginning of each report step or
1637// at the beginning of each substep by the ACTIONX keyword (via NEXTSTEP), this is
1638// done by the maybeUpdateTuning_() method which is called at the beginning of each substep
1639// (and the begginning of each report step). Note that the WCYCLE keyword can also update the
1640// suggested time step via the maybeUpdateTuning_() method.
1641template <class TypeTag>
1642template <class Solver>
1643void
1644AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1645updateSuggestedNextStep_()
1646{
1647 auto suggested_next_step = this->substep_timer_.currentStepLength();
1648 if (! std::isfinite(suggested_next_step)) { // check for NaN
1649 suggested_next_step = this->original_time_step_;
1650 }
1651 if (timeStepVerbose_()) {
1652 std::ostringstream ss;
1653 this->substep_timer_.report(ss);
1654 ss << "Suggested next step size = "
1655 << unit::convert::to(suggested_next_step, unit::day) << " (days)" << std::endl;
1656 OpmLog::debug(ss.str());
1657 }
1658 setSuggestedNextStep_(suggested_next_step);
1659}
1660
1661template <class TypeTag>
1662template <class Solver>
1663bool
1664AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1665useNewtonIteration_() const
1666{
1667 return this->adaptive_time_stepping_.use_newton_iteration_;
1668}
1669
1670template <class TypeTag>
1671template <class Solver>
1672double
1673AdaptiveTimeStepping<TypeTag>::SubStepIteration<Solver>::
1674writeOutput_() const
1675{
1676 time::StopWatch perf_timer;
1677 perf_timer.start();
1678 auto& problem = solver_().model().simulator().problem();
1679 problem.writeOutput(true);
1680 return perf_timer.secsSinceStart();
1681}
1682
1683/************************************************
1684 * Private class SolutionTimeErrorSolverWrapper
1685 * **********************************************/
1686
1687template<class TypeTag>
1688template<class Solver>
1689AdaptiveTimeStepping<TypeTag>::
1690SolutionTimeErrorSolverWrapper<Solver>::
1691SolutionTimeErrorSolverWrapper(const Solver& solver)
1692 : solver_{solver}
1693{}
1694
1695template<class TypeTag>
1696template<class Solver>
1697double AdaptiveTimeStepping<TypeTag>::SolutionTimeErrorSolverWrapper<Solver>::relativeChange() const
1698{
1699 // returns: || u^n+1 - u^n || / || u^n+1 ||
1700 return solver_.model().relativeChange();
1701}
1702
1703} // namespace Opm
1704
1705#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