SimulatorFullyImplicitBlackoil_impl.hpp
Go to the documentation of this file.
1/*
2 Copyright 2013, 2015, 2020 SINTEF Digital, Mathematics and Cybernetics.
3 Copyright 2015 Andreas Lauser
4 Copyright 2017 IRIS
5
6 This file is part of the Open Porous Media project (OPM).
7
8 OPM is free software: you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation, either version 3 of the License, or
11 (at your option) any later version.
12
13 OPM is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with OPM. If not, see <http://www.gnu.org/licenses/>.
20*/
21
22#ifndef OPM_SIMULATOR_FULLY_IMPLICIT_BLACKOIL_IMPL_HEADER_INCLUDED
23#define OPM_SIMULATOR_FULLY_IMPLICIT_BLACKOIL_IMPL_HEADER_INCLUDED
24
25// Improve IDE experience
26#ifndef OPM_SIMULATOR_FULLY_IMPLICIT_BLACKOIL_HEADER_INCLUDED
27#include <config.h>
29#endif
30
31#include <opm/input/eclipse/Units/UnitSystem.hpp>
32
34
36
37#include <fmt/format.h>
38
39#include <filesystem>
40#include <sstream>
41
42namespace Opm {
43
44template<class TypeTag>
47 : simulator_(simulator)
48 , serializer_(*this,
49 FlowGenericVanguard::comm(),
50 simulator_.vanguard().eclState().getIOConfig(),
51 Parameters::Get<Parameters::SaveStep>(),
52 Parameters::Get<Parameters::LoadStep>(),
53 Parameters::Get<Parameters::SaveFile>(),
54 Parameters::Get<Parameters::LoadFile>())
55{
56 // Only rank 0 does print to std::cout, and only if specifically requested.
57 this->terminalOutput_ = false;
58 if (this->grid().comm().rank() == 0) {
59 this->terminalOutput_ = Parameters::Get<Parameters::EnableTerminalOutput>();
60
62 [compNames = typename Model::ComponentName{}](const int compIdx)
63 { return std::string_view { compNames.name(compIdx) }; }
64 };
65
66 if (!simulator_.vanguard().eclState().getIOConfig().initOnly()) {
68 startThread(this->simulator_.vanguard().eclState(),
69 Parameters::Get<Parameters::OutputExtraConvergenceInfo>(),
70 R"(OutputExtraConvergenceInfo (--output-extra-convergence-info))",
71 getPhaseName);
72 }
73 }
74}
75
76template<class TypeTag>
79{
80 // Safe to call on all ranks, not just the I/O rank.
81 convergence_output_.endThread();
82}
83
84template<class TypeTag>
85void
88{
89 ModelParameters::registerParameters();
90 SolverParameters::registerParameters();
91 TimeStepper::registerParameters();
93
96}
97
98#ifdef RESERVOIR_COUPLING_ENABLED
99template<class TypeTag>
102run(SimulatorTimer& timer, int argc, char** argv)
103{
104 init(timer, argc, argv);
105#else
106template<class TypeTag>
109run(SimulatorTimer& timer)
110{
111 init(timer);
112#endif
113 // Make cache up to date. No need for updating it in elementCtx.
114 // NB! Need to be at the correct step in case of restart
115 simulator_.setEpisodeIndex(timer.currentStepNum());
116 simulator_.model().invalidateAndUpdateIntensiveQuantities(/*timeIdx=*/0);
117 // Main simulation loop.
118 while (!timer.done()) {
119 simulator_.problem().writeReports(timer);
120 bool continue_looping = runStep(timer);
121 if (!continue_looping) break;
122 }
123 simulator_.problem().writeReports(timer);
124
125#ifdef RESERVOIR_COUPLING_ENABLED
126 // Clean up MPI intercommunicators before MPI_Finalize()
127 // Master sends terminate=1 signal; slave receives it and both call MPI_Comm_disconnect()
128 if (this->reservoirCouplingMaster_) {
129 this->reservoirCouplingMaster_->sendTerminateAndDisconnect();
130 }
131 else if (this->reservoirCouplingSlave_ && !this->reservoirCouplingSlave_->terminated()) {
132 // TODO: Implement GECON item 8: stop master process when a slave finishes
133 // Only call if not already terminated via maybeReceiveTerminateSignalFromMaster()
134 // (which happens when master finishes before slave reaches end of its loop)
135 this->reservoirCouplingSlave_->receiveTerminateAndDisconnect();
136 }
137#endif
138
139 return finalize();
140}
141
142#ifdef RESERVOIR_COUPLING_ENABLED
143template<class TypeTag>
144bool
147{
148 for (std::size_t report_step = 0; report_step < this->schedule().size(); ++report_step) {
149 auto rescoup = this->schedule()[report_step].rescoup();
150 auto slave_count = rescoup.slaveCount();
151 auto master_group_count = rescoup.masterGroupCount();
152 // Master mode is enabled when SLAVES keyword is present.
153 // - Prediction mode: SLAVES + GRUPMAST (master allocates rates)
154 // - History mode: SLAVES only (master synchronizes time-stepping)
155 if (slave_count > 0) {
156 return true;
157 }
158 else if (master_group_count > 0) {
159 // GRUPMAST without SLAVES is invalid
160 throw ReservoirCouplingError(
161 "Inconsistent reservoir coupling master schedule: "
162 "Master group count is greater than 0 but slave count is 0"
163 );
164 }
165 }
166 return false;
167}
168#endif
169
170#ifdef RESERVOIR_COUPLING_ENABLED
171template<class TypeTag>
172void
174init(const SimulatorTimer& timer, int argc, char** argv)
175{
176 auto slave_mode = Parameters::Get<Parameters::Slave>();
177 if (slave_mode) {
178 this->reservoirCouplingSlave_ =
179 std::make_unique<ReservoirCouplingSlave<Scalar>>(
181 this->schedule(), timer
182 );
183 this->reservoirCouplingSlave_->sendAndReceiveInitialData();
184 this->simulator_.setReservoirCouplingSlave(this->reservoirCouplingSlave_.get());
185 wellModel_().setReservoirCouplingSlave(this->reservoirCouplingSlave_.get());
186 }
187 else {
188 auto master_mode = checkRunningAsReservoirCouplingMaster();
189 if (master_mode) {
190 this->reservoirCouplingMaster_ =
191 std::make_unique<ReservoirCouplingMaster<Scalar>>(
193 this->schedule(),
194 argc, argv
195 );
196 this->simulator_.setReservoirCouplingMaster(this->reservoirCouplingMaster_.get());
197 wellModel_().setReservoirCouplingMaster(this->reservoirCouplingMaster_.get());
198 }
199 }
200#else
201template<class TypeTag>
202void
204init(const SimulatorTimer& timer)
205{
206#endif
207 simulator_.setEpisodeIndex(-1);
208
209 // Create timers and file for writing timing info.
210 solverTimer_ = std::make_unique<time::StopWatch>();
211 totalTimer_ = std::make_unique<time::StopWatch>();
212 totalTimer_->start();
213
214 // adaptive time stepping
215 bool enableAdaptive = Parameters::Get<Parameters::EnableAdaptiveTimeStepping>();
216 bool enableTUNING = Parameters::Get<Parameters::EnableTuning>();
217 if (enableAdaptive) {
218 const UnitSystem& unitSystem = this->simulator_.vanguard().eclState().getUnits();
219 const auto& sched_state = schedule()[timer.currentStepNum()];
220 auto max_next_tstep = sched_state.max_next_tstep(enableTUNING);
221 if (enableTUNING) {
222 adaptiveTimeStepping_ = std::make_unique<TimeStepper>(max_next_tstep,
223 sched_state.tuning(),
224 unitSystem, report_, terminalOutput_);
225 }
226 else {
227 adaptiveTimeStepping_ = std::make_unique<TimeStepper>(unitSystem, report_, max_next_tstep, terminalOutput_);
228 }
229 if (isRestart()) {
230 // For restarts the simulator may have gotten some information
231 // about the next timestep size from the OPMEXTRA field
232 adaptiveTimeStepping_->setSuggestedNextStep(simulator_.timeStepSize());
233 }
234 }
235}
236
237template<class TypeTag>
238void
240updateTUNING(const Tuning& tuning)
241{
242 modelParam_.tolerance_cnv_ = tuning.TRGCNV;
243 modelParam_.tolerance_cnv_relaxed_ = tuning.XXXCNV;
244 modelParam_.tolerance_mb_ = tuning.TRGMBE;
245 modelParam_.tolerance_mb_relaxed_ = tuning.XXXMBE;
246 modelParam_.newton_max_iter_ = tuning.NEWTMX;
247 modelParam_.newton_min_iter_ = tuning.NEWTMN;
248 if (terminalOutput_) {
249 detail::logTuning(tuning);
250 }
251}
252
253template<class TypeTag>
254void
256updateTUNINGDP(const TuningDp& tuning_dp)
257{
258 // NOTE: If TUNINGDP item is _not_ set it should be 0.0
259 modelParam_.tolerance_max_dp_ = tuning_dp.TRGDDP;
260 modelParam_.tolerance_max_ds_ = tuning_dp.TRGDDS;
261 modelParam_.tolerance_max_drs_ = tuning_dp.TRGDDRS;
262 modelParam_.tolerance_max_drv_ = tuning_dp.TRGDDRV;
263
264 // Terminal warnings
265 if (terminalOutput_) {
266 // Warnings unsupported items
267 if (tuning_dp.TRGLCV_has_value) {
268 OpmLog::warning("TUNINGDP item 1 (TRGLCV) is not supported.");
269 }
270 if (tuning_dp.XXXLCV_has_value) {
271 OpmLog::warning("TUNINGDP item 2 (XXXLCV) is not supported.");
272 }
273 }
274}
275
276template<class TypeTag>
277bool
280{
281 if (schedule().exitStatus().has_value()) {
282 if (terminalOutput_) {
283 OpmLog::info("Stopping simulation since EXIT was triggered by an action keyword.");
284 }
285 report_.success.exit_status = schedule().exitStatus().value();
286 return false;
287 }
288
289 if (serializer_.shouldLoad()) {
290 serializer_.loadTimerInfo(timer);
291 }
292
293 // Report timestep.
294 if (terminalOutput_) {
295 std::ostringstream ss;
296 timer.report(ss);
297 OpmLog::debug(ss.str());
299 }
300
301 // write the inital state at the report stage
302 if (timer.initialStep()) {
303 Dune::Timer perfTimer;
304 perfTimer.start();
305
306 simulator_.setEpisodeIndex(-1);
307 simulator_.setEpisodeLength(0.0);
308 simulator_.setTimeStepSize(0.0);
309 wellModel_().beginReportStep(timer.currentStepNum());
310 simulator_.problem().writeOutput(true);
311
312 report_.success.output_write_time += perfTimer.stop();
313 }
314
315 // Run a multiple steps of the solver depending on the time step control.
316 solverTimer_->start();
317
318 if (!solver_) {
319 solver_ = createSolver(wellModel_());
320 }
321
322 simulator_.startNextEpisode(
323 simulator_.startTime()
324 + schedule().seconds(timer.currentStepNum()),
325 timer.currentStepLength());
326 simulator_.setEpisodeIndex(timer.currentStepNum());
327
328 if (serializer_.shouldLoad()) {
329 wellModel_().prepareDeserialize(serializer_.loadStep() - 1);
330 serializer_.loadState();
331 simulator_.model().invalidateAndUpdateIntensiveQuantities(/*timeIdx=*/0);
332 }
333
334 this->solver_->model().beginReportStep();
335
336 const bool enableTUNING = Parameters::Get<Parameters::EnableTuning>();
337
338 // If sub stepping is enabled allow the solver to sub cycle
339 // in case the report steps are too large for the solver to converge
340 //
341 // \Note: The report steps are met in any case
342 // \Note: The sub stepping will require a copy of the state variables
343 if (adaptiveTimeStepping_) {
344 auto tuningUpdater = [enableTUNING, this,
345 reportStep = timer.currentStepNum()](const double curr_time,
346 double substep_length,
347 const int sub_step_number)
348 {
349 auto& schedule = this->simulator_.vanguard().schedule();
350 auto& events = this->schedule()[reportStep].events();
351
352 bool result = false;
353 if (events.hasEvent(ScheduleEvents::TUNING_CHANGE)) {
354 // Unset the event to not trigger it again on the next sub step
355 schedule.clear_event(ScheduleEvents::TUNING_CHANGE, reportStep);
356 const auto& sched_state = schedule[reportStep];
357 const auto& max_next_tstep = sched_state.max_next_tstep(enableTUNING);
358 const auto& tuning = sched_state.tuning();
359
360 if (enableTUNING) {
361 adaptiveTimeStepping_->updateTUNING(max_next_tstep, tuning);
362 // \Note: Assumes TUNING is only used with adaptive time-stepping
363 // \Note: Need to update both solver (model) and simulator since solver is re-created each report step.
364 solver_->model().updateTUNING(tuning);
365 this->updateTUNING(tuning);
366 substep_length = this->adaptiveTimeStepping_->suggestedNextStep();
367 } else {
368 substep_length = max_next_tstep;
369 this->adaptiveTimeStepping_->updateNEXTSTEP(max_next_tstep);
370 }
371 result = max_next_tstep > 0;
372 }
373
374 if (events.hasEvent(ScheduleEvents::TUNINGDP_CHANGE)) {
375 // Unset the event to not trigger it again on the next sub step
376 schedule.clear_event(ScheduleEvents::TUNINGDP_CHANGE, reportStep);
377
378 // Update TUNINGDP parameters
379 // NOTE: Need to update both solver (model) and simulator since solver is re-created each report
380 // step.
381 const auto& sched_state = schedule[reportStep];
382 const auto& tuning_dp = sched_state.tuning_dp();
383 solver_->model().updateTUNINGDP(tuning_dp);
384 this->updateTUNINGDP(tuning_dp);
385 }
386
387 const auto& wcycle = schedule[reportStep].wcycle.get();
388 if (wcycle.empty()) {
389 return result;
390 }
391
392 const auto& wmatcher = schedule.wellMatcher(reportStep);
393 double wcycle_time_step =
394 wcycle.nextTimeStep(curr_time,
395 substep_length,
396 wmatcher,
397 this->wellModel_().wellOpenTimes(),
398 this->wellModel_().wellCloseTimes(),
399 [sub_step_number,
400 &wg_events = this->wellModel_().reportStepStartEvents()]
401 (const std::string& name)
402 {
403 if (sub_step_number != 0) {
404 return false;
405 }
406 return wg_events.hasEvent(name, ScheduleEvents::REQUEST_OPEN_WELL);
407 });
408
409 wcycle_time_step = this->grid().comm().min(wcycle_time_step);
410 if (substep_length != wcycle_time_step) {
411 this->adaptiveTimeStepping_->updateNEXTSTEP(wcycle_time_step);
412 return true;
413 }
414
415 return result;
416 };
417
418 tuningUpdater(timer.simulationTimeElapsed(),
419 this->adaptiveTimeStepping_->suggestedNextStep(), 0);
420
421#ifdef RESERVOIR_COUPLING_ENABLED
422 if (this->reservoirCouplingMaster_) {
423 this->reservoirCouplingMaster_->maybeSpawnSlaveProcesses(timer.currentStepNum());
424 this->reservoirCouplingMaster_->maybeActivate(timer.currentStepNum());
425 }
426 else if (this->reservoirCouplingSlave_) {
427 this->reservoirCouplingSlave_->maybeActivate(timer.currentStepNum());
428 }
429#endif
430 const auto& events = schedule()[timer.currentStepNum()].events();
431 bool event = events.hasEvent(ScheduleEvents::NEW_WELL) ||
432 events.hasEvent(ScheduleEvents::INJECTION_TYPE_CHANGED) ||
433 events.hasEvent(ScheduleEvents::WELL_SWITCHED_INJECTOR_PRODUCER) ||
434 events.hasEvent(ScheduleEvents::PRODUCTION_UPDATE) ||
435 events.hasEvent(ScheduleEvents::INJECTION_UPDATE) ||
436 events.hasEvent(ScheduleEvents::WELL_STATUS_CHANGE);
437 auto stepReport = adaptiveTimeStepping_->step(timer, *solver_, event, tuningUpdater);
438 report_ += stepReport;
439 } else {
440 // solve for complete report step
441 auto stepReport = solver_->step(timer, nullptr);
442 report_ += stepReport;
443 // Pass simulation report to eclwriter for summary output
444 simulator_.problem().setSubStepReport(stepReport);
445 simulator_.problem().setSimulationReport(report_);
446 simulator_.problem().endTimeStep();
447 if (terminalOutput_) {
448 std::ostringstream ss;
449 stepReport.reportStep(ss);
450 OpmLog::info(ss.str());
451 }
452 }
453
454 // write simulation state at the report stage
455 Dune::Timer perfTimer;
456 perfTimer.start();
457 const double nextstep = adaptiveTimeStepping_ ? adaptiveTimeStepping_->suggestedNextStep() : -1.0;
458 simulator_.problem().setNextTimeStepSize(nextstep);
459 simulator_.problem().writeOutput(true);
460 report_.success.output_write_time += perfTimer.stop();
461
462 solver_->model().endReportStep();
463
464 // take time that was used to solve system for this reportStep
465 solverTimer_->stop();
466
467 // update timing.
468 report_.success.solver_time += solverTimer_->secsSinceStart();
469
470 if (this->grid().comm().rank() == 0) {
471 // Grab the step convergence reports that are new since last we
472 // were here.
473 const auto& reps = this->solver_->model().stepReports();
474 convergence_output_.write(reps);
475 }
476
477 // Increment timer, remember well state.
478 ++timer;
479
480 if (terminalOutput_) {
481 std::string msg =
482 "Time step took " + std::to_string(solverTimer_->secsSinceStart()) + " seconds; "
483 "total solver time " + std::to_string(report_.success.solver_time) + " seconds.";
484 OpmLog::debug(msg);
485 }
486
487 serializer_.save(timer);
488
489 return true;
490}
491
492template<class TypeTag>
495finalize()
496{
497 // make sure all output is written to disk before run is finished
498 {
499 Dune::Timer finalOutputTimer;
500 finalOutputTimer.start();
501
502 simulator_.problem().finalizeOutput();
503 report_.success.output_write_time += finalOutputTimer.stop();
504 }
505
506 // Stop timer and create timing report
507 totalTimer_->stop();
508 report_.success.total_time = totalTimer_->secsSinceStart();
509 report_.success.converged = true;
510
511 return report_;
512}
513
514template<class TypeTag>
515template<class Serializer>
516void
518serializeOp(Serializer& serializer)
519{
520 serializer(simulator_);
521 serializer(report_);
522 serializer(adaptiveTimeStepping_);
523}
524
525template<class TypeTag>
526void
528loadState([[maybe_unused]] HDF5Serializer& serializer,
529 [[maybe_unused]] const std::string& groupName)
530{
531#if HAVE_HDF5
532 serializer.read(*this, groupName, "simulator_data");
533#endif
534}
535
536template<class TypeTag>
537void
539saveState([[maybe_unused]] HDF5Serializer& serializer,
540 [[maybe_unused]] const std::string& groupName) const
541{
542#if HAVE_HDF5
543 serializer.write(*this, groupName, "simulator_data");
544#endif
545}
546
547template<class TypeTag>
548std::array<std::string,5>
550getHeader() const
551{
552 std::ostringstream str;
554 return {"OPM Flow",
557 simulator_.vanguard().caseName(),
558 str.str()};
559}
560
561template<class TypeTag>
562std::unique_ptr<typename SimulatorFullyImplicitBlackoil<TypeTag>::Solver>
564createSolver(WellModel& wellModel)
565{
566 auto model = std::make_unique<Model>(simulator_,
567 modelParam_,
568 wellModel,
569 terminalOutput_);
570
571 if (this->modelParam_.write_partitions_) {
572 const auto& iocfg = this->eclState().cfg().io();
573
574 const auto odir = iocfg.getOutputDir()
575 / std::filesystem::path { "partition" }
576 / iocfg.getBaseName();
577
578 if (this->grid().comm().rank() == 0) {
579 create_directories(odir);
580 }
581
582 this->grid().comm().barrier();
583
584 model->writePartitions(odir);
585
586 this->modelParam_.write_partitions_ = false;
587 }
588
589 return std::make_unique<Solver>(solverParam_, std::move(model));
590}
591
592} // namespace Opm
593
594#endif // OPM_SIMULATOR_FULLY_IMPLICIT_BLACKOIL_IMPL_HEADER_INCLUDED
Class for handling the blackoil well model.
Definition: BlackoilWellModel.hpp:98
std::function< std::string_view(int)> ComponentToPhaseName
Definition: ExtraConvergenceOutputThread.hpp:109
Definition: FlowGenericVanguard.hpp:108
static Parallel::Communication & comm()
Obtain global communicator.
Definition: FlowGenericVanguard.hpp:336
Class for (de-)serializing using HDF5.
Definition: HDF5Serializer.hpp:37
Top-level driver for a fully implicit black-oil simulation.
Definition: SimulatorFullyImplicitBlackoil.hpp:114
GetPropType< TypeTag, Properties::Simulator > Simulator
Definition: SimulatorFullyImplicitBlackoil.hpp:118
void loadState(HDF5Serializer &serializer, const std::string &groupName) override
Load this simulator's data block from an OPMRST file via HDF5.
Definition: SimulatorFullyImplicitBlackoil_impl.hpp:528
void updateTUNINGDP(const TuningDp &tuning_dp)
Apply a TUNINGDP keyword to the cached model parameters.
Definition: SimulatorFullyImplicitBlackoil_impl.hpp:256
void init(const SimulatorTimer &timer)
One-shot setup performed before the first runStep.
Definition: SimulatorFullyImplicitBlackoil_impl.hpp:204
void updateTUNING(const Tuning &tuning)
Apply a TUNING keyword to the cached model parameters.
Definition: SimulatorFullyImplicitBlackoil_impl.hpp:240
SimulatorReport run(SimulatorTimer &timer)
Run the entire simulation to completion.
Definition: SimulatorFullyImplicitBlackoil_impl.hpp:109
~SimulatorFullyImplicitBlackoil() override
Ends the convergence-output thread cleanly on all ranks.
Definition: SimulatorFullyImplicitBlackoil_impl.hpp:78
void serializeOp(Serializer &serializer)
Definition: SimulatorFullyImplicitBlackoil_impl.hpp:518
std::unique_ptr< Solver > createSolver(WellModel &wellModel)
Build the Solver used during the current report step.
Definition: SimulatorFullyImplicitBlackoil_impl.hpp:564
void saveState(HDF5Serializer &serializer, const std::string &groupName) const override
Save this simulator's data block to an OPMRST file via HDF5.
Definition: SimulatorFullyImplicitBlackoil_impl.hpp:539
const Grid & grid() const
Definition: SimulatorFullyImplicitBlackoil.hpp:282
SimulatorFullyImplicitBlackoil(Simulator &simulator)
Construct from the surrounding eWoms Simulator.
Definition: SimulatorFullyImplicitBlackoil_impl.hpp:46
static void registerParameters()
Register all parameters consumed by this class and its major collaborators.
Definition: SimulatorFullyImplicitBlackoil_impl.hpp:87
SimulatorConvergenceOutput convergence_output_
Background thread for INFOSTEP / INFOITER files.
Definition: SimulatorFullyImplicitBlackoil.hpp:356
bool terminalOutput_
Emit high-level progress to std::cout (rank 0 only).
Definition: SimulatorFullyImplicitBlackoil.hpp:341
bool runStep(SimulatorTimer &timer)
Advance the simulation by one report step.
Definition: SimulatorFullyImplicitBlackoil_impl.hpp:279
SimulatorReport finalize()
Stop the timers and emit the final OPMRST output.
Definition: SimulatorFullyImplicitBlackoil_impl.hpp:495
std::array< std::string, 5 > getHeader() const override
Definition: SimulatorFullyImplicitBlackoil_impl.hpp:550
Simulator & simulator_
Surrounding eWoms simulator; observed, not owned.
Definition: SimulatorFullyImplicitBlackoil.hpp:329
Definition: SimulatorTimer.hpp:39
double currentStepLength() const override
bool initialStep() const override
Whether the current step is the first step.
void report(std::ostream &os) const
double simulationTimeElapsed() const override
int currentStepNum() const override
bool done() const override
Return true if op++() has been called numSteps() times.
void printValues(std::ostream &os)
Print values of the run-time parameters.
auto Get(bool errorIfNotRegistered=true)
Retrieve a runtime parameter.
Definition: parametersystem.hpp:187
void logTuning(const Tuning &tuning)
Log tuning parameters.
void registerSimulatorParameters()
void outputReportStep(const SimulatorTimer &timer)
Definition: blackoilbioeffectsmodules.hh:45
std::string compileTimestamp()
std::string moduleVersion()
std::string to_string(const ConvergenceReport::ReservoirFailure::Type t)
Definition: SimulatorReport.hpp:122
SimulatorReportSingle success
Definition: SimulatorReport.hpp:123
double output_write_time
Definition: SimulatorReport.hpp:46
static void registerParameters()