diff --git a/compareECLFiles.cmake b/compareECLFiles.cmake index b529c0ab7ed..5a9508a135f 100644 --- a/compareECLFiles.cmake +++ b/compareECLFiles.cmake @@ -60,7 +60,7 @@ endfunction() # - This test class compares output from a simulation to reference files. function(add_test_compareECLFiles) set(oneValueArgs CASENAME FILENAME SIMULATOR ABS_TOL REL_TOL DIR DIR_PREFIX PREFIX RESTART_STEP RESTART_SCHED) - set(multiValueArgs TEST_ARGS) + set(multiValueArgs TEST_ARGS TEST_ARGS_REPLAY) cmake_parse_arguments(PARAM "$" "${oneValueArgs}" "${multiValueArgs}" ${ARGN} ) if(NOT PARAM_DIR) set(PARAM_DIR ${PARAM_CASENAME}) @@ -84,6 +84,9 @@ function(add_test_compareECLFiles) if(PARAM_RESTART_SCHED STREQUAL "false" OR PARAM_RESTART_SCHED STREQUAL "true") list(APPEND DRIVER_ARGS -h ${PARAM_RESTART_SCHED}) endif() + foreach(arg IN LISTS PARAM_TEST_ARGS_REPLAY) + list(APPEND DRIVER_ARGS -y ${arg}) + endforeach() opm_add_test(${PARAM_PREFIX}_${PARAM_SIMULATOR}+${PARAM_FILENAME} NO_COMPILE EXE_NAME ${PARAM_SIMULATOR} DRIVER_ARGS ${DRIVER_ARGS} diff --git a/flowexperimental/comp/wells/CompWellModel.hpp b/flowexperimental/comp/wells/CompWellModel.hpp index 52daa76fb5f..dabec5a4866 100644 --- a/flowexperimental/comp/wells/CompWellModel.hpp +++ b/flowexperimental/comp/wells/CompWellModel.hpp @@ -96,6 +96,11 @@ class CompWellModel : WellConnectionAuxiliaryModulemodel().numTotalDof(), false); } + void resetPrimaryVariableSwitches() + { + numPriVarsSwitched_ = 0; + std::fill(wasSwitched_.begin(), wasSwitched_.end(), false); + } + /*! * \brief Register all run-time parameters for the blackoil newton method. */ diff --git a/opm/models/utils/simulator.hh b/opm/models/utils/simulator.hh index 4876bb00adc..dd6ba01bb78 100644 --- a/opm/models/utils/simulator.hh +++ b/opm/models/utils/simulator.hh @@ -395,7 +395,9 @@ public: * \param value The new value for the time step size \f$\mathrm{[s]}\f$ */ void setTimeStepSize(Scalar value) - { timeStepSize_ = value; } + { + timeStepSize_ = float(value); + } /*! * \brief Set the current time step index to a given value. diff --git a/opm/simulators/flow/BlackoilModel_impl.hpp b/opm/simulators/flow/BlackoilModel_impl.hpp index bf0945a09a0..50073042101 100644 --- a/opm/simulators/flow/BlackoilModel_impl.hpp +++ b/opm/simulators/flow/BlackoilModel_impl.hpp @@ -122,10 +122,11 @@ prepareStep(const SimulatorTimerInterface& timer) "- the previous step succeeded on some ranks but failed on others."); } if (lastStepFailed) { - simulator_.model().updateFailed(); + simulator_.problem().updateFailed(); + simulator_.model().newtonMethod().eraseMatrix(); } else { - simulator_.model().advanceTimeLevel(); + simulator_.problem().advanceTimeLevel(); } // Set the timestep size and episode index for the model explicitly. @@ -142,6 +143,7 @@ prepareStep(const SimulatorTimerInterface& timer) unsigned numDof = simulator_.model().numGridDof(); wasSwitched_.resize(numDof); std::fill(wasSwitched_.begin(), wasSwitched_.end(), false); + simulator_.model().newtonMethod().resetPrimaryVariableSwitches(); if (param_.update_equations_scaling_) { OpmLog::error("Equation scaling not supported"); diff --git a/opm/simulators/flow/FlowProblem.hpp b/opm/simulators/flow/FlowProblem.hpp index 73ff334ffd9..2cae05cc2db 100644 --- a/opm/simulators/flow/FlowProblem.hpp +++ b/opm/simulators/flow/FlowProblem.hpp @@ -367,6 +367,8 @@ class FlowProblem : public GetPropType const int episodeIdx = this->episodeIndex(); const int timeStepSize = this->simulator().timeStepSize(); + this->captureBeginTimeStepState_(); + this->beginTimeStep_(enableExperiments, episodeIdx, this->simulator().timeStepIndex(), @@ -392,6 +394,19 @@ class FlowProblem : public GetPropType } + void updateFailed() + { + this->restoreBeginTimeStepState_(); + wellModel_.updateFailed(); + this->model().updateFailed(); + } + + void advanceTimeLevel() + { + this->model().advanceTimeLevel(); + wellModel_.advanceTimeLevel(); + } + /*! * \brief Called by the simulator before each Newton-Raphson iteration. */ @@ -1271,6 +1286,26 @@ class FlowProblem : public GetPropType { return *static_cast(this); } protected: + virtual void captureBeginTimeStepState_() + { + prev_timestep_first_step_ = first_step_; + prev_timestep_max_polymer_adsorption_ = this->polymer_.maxAdsorption; + prev_timestep_max_oil_saturation_ = this->maxOilSaturation_; + prev_timestep_max_water_saturation_ = this->maxWaterSaturation_; + prev_timestep_min_ref_pressure_ = this->minRefPressure_; + prev_timestep_rock_comp_trans_mult_val_ = this->rockCompTransMultVal_; + } + + virtual void restoreBeginTimeStepState_() + { + first_step_ = prev_timestep_first_step_; + this->polymer_.maxAdsorption = prev_timestep_max_polymer_adsorption_; + this->maxOilSaturation_ = prev_timestep_max_oil_saturation_; + this->maxWaterSaturation_ = prev_timestep_max_water_saturation_; + this->minRefPressure_ = prev_timestep_min_ref_pressure_; + this->rockCompTransMultVal_ = prev_timestep_rock_comp_trans_mult_val_; + } + template void updateProperty_(const std::string& failureMsg, UpdateFunc func) @@ -1857,6 +1892,13 @@ class FlowProblem : public GetPropType BCData bcindex_; bool nonTrivialBoundaryConditions_ = false; bool first_step_ = true; + bool prev_timestep_first_step_ = true; + + std::vector prev_timestep_max_polymer_adsorption_; + std::vector prev_timestep_max_oil_saturation_; + std::vector prev_timestep_max_water_saturation_; + std::vector prev_timestep_min_ref_pressure_; + std::vector prev_timestep_rock_comp_trans_mult_val_; /// Whether or not the current episode will end at the end of the /// current time step. diff --git a/opm/simulators/flow/FlowProblemBlackoil.hpp b/opm/simulators/flow/FlowProblemBlackoil.hpp index e240505fbcc..8d583547432 100644 --- a/opm/simulators/flow/FlowProblemBlackoil.hpp +++ b/opm/simulators/flow/FlowProblemBlackoil.hpp @@ -188,6 +188,7 @@ class FlowProblemBlackoil : public FlowProblem : FlowProblemType(simulator) , thresholdPressures_(simulator) , mixControls_(simulator.vanguard().schedule()) + , prev_timestep_mixControls_(simulator.vanguard().schedule()) , actionHandler_(simulator.vanguard().eclState(), simulator.vanguard().schedule(), simulator.vanguard().actionState(), @@ -1173,6 +1174,18 @@ class FlowProblemBlackoil : public FlowProblem } protected: + void captureBeginTimeStepState_() override + { + FlowProblemType::captureBeginTimeStepState_(); + prev_timestep_mixControls_ = mixControls_; + } + + void restoreBeginTimeStepState_() override + { + FlowProblemType::restoreBeginTimeStepState_(); + mixControls_ = prev_timestep_mixControls_; + } + void updateExplicitQuantities_(int episodeIdx, int timeStepSize, const bool first_step_after_restart) override { this->updateExplicitQuantities_(first_step_after_restart); @@ -1734,6 +1747,7 @@ class FlowProblemBlackoil : public FlowProblem std::unique_ptr damarisWriter_; #endif MixingRateControls mixControls_; + MixingRateControls prev_timestep_mixControls_; ActionHandler actionHandler_; diff --git a/opm/simulators/linalg/ISTLSolver.hpp b/opm/simulators/linalg/ISTLSolver.hpp index 8ecb0e4be1f..b9eac998108 100644 --- a/opm/simulators/linalg/ISTLSolver.hpp +++ b/opm/simulators/linalg/ISTLSolver.hpp @@ -313,9 +313,18 @@ std::unique_ptr blockJacobiAdjacency(const Grid& grid, element_chunks_ = std::make_unique(simulator_.vanguard().gridView(), Dune::Partitions::all, ThreadManager::maxThreads()); } - // nothing to clean here void eraseMatrix() override { + matrix_ = nullptr; + rhs_ = nullptr; + iterations_ = 0; + solveCount_ = 0; + + for (auto& solverInfo : flexibleSolver_) { + solverInfo.pre_ = nullptr; + solverInfo.solver_.reset(); + solverInfo.op_.reset(); + } } void setActiveSolver(const int num) override diff --git a/opm/simulators/linalg/ISTLSolverTPSA.hpp b/opm/simulators/linalg/ISTLSolverTPSA.hpp index e3e86094a0b..531781168a8 100644 --- a/opm/simulators/linalg/ISTLSolverTPSA.hpp +++ b/opm/simulators/linalg/ISTLSolverTPSA.hpp @@ -274,7 +274,15 @@ class ISTLSolverTPSA : public AbstractISTLSolveradaptive_time_stepping_.maybeModifySuggestedTimeStepAtBeginningOfReportStep_( original_time_step, this->is_event_ ); + + if (this->adaptive_time_stepping_.time_step_control_type_ != TimeStepControlType::HardCodedTimeStep) { + return; + } + + struct ZeroRelativeChange final : RelativeChangeInterface { + double relativeChange() const override { return 0.0; } + } zero_relative_change; + + const auto* hardcoded_control = static_cast( + this->adaptive_time_stepping_.time_step_control_.get()); + AdaptiveSimulatorTimer report_step_timer{ + this->simulator_timer_.startDateTime(), + original_time_step, + this->simulator_timer_.simulationTimeElapsed(), + original_time_step, + this->simulator_timer_.reportStepNum(), + maxTimeStep_() + }; + + const double hardcoded_initial_step = hardcoded_control->computeTimeStepSize( + original_time_step, + 0, + zero_relative_change, + report_step_timer); + if (std::isfinite(hardcoded_initial_step) && hardcoded_initial_step > 0.0) { + this->adaptive_time_stepping_.setSuggestedNextStep(hardcoded_initial_step); + } } // The maybeUpdateTuning_() lambda callback is defined in SimulatorFullyImplicitBlackoil::runStep() diff --git a/opm/simulators/timestepping/SimulatorReport.cpp b/opm/simulators/timestepping/SimulatorReport.cpp index 64ce8667b52..4dce35e4fd9 100644 --- a/opm/simulators/timestepping/SimulatorReport.cpp +++ b/opm/simulators/timestepping/SimulatorReport.cpp @@ -395,7 +395,7 @@ namespace Opm os << " Time(day) TStep(day) Assembly LSetup LSolve LocSol Update Output WellIt Lins NewtIt LinIt Conv\n"; for (std::size_t i = 0; i < this->stepreports.size(); ++i) { const SimulatorReportSingle& sr = this->stepreports[i]; - os.precision(10); + os.precision(20); os << std::defaultfloat; os << std::setw(11) << unit::convert::to(sr.global_time, unit::day) << " "; os << std::setw(11) << unit::convert::to(sr.timestep_length, unit::day) << " "; diff --git a/opm/simulators/timestepping/TimeStepControl.cpp b/opm/simulators/timestepping/TimeStepControl.cpp index c5dbf4fdf10..ce507a0b60a 100644 --- a/opm/simulators/timestepping/TimeStepControl.cpp +++ b/opm/simulators/timestepping/TimeStepControl.cpp @@ -41,6 +41,14 @@ namespace Opm { + namespace { + double hardcodedTimeTolerance(const double a, const double b, const double c = 0.0) + { + const double scale = std::max({1.0, std::abs(a), std::abs(b), std::abs(c)}); + return 64.0 * std::numeric_limits::epsilon() * scale; + } + } + //////////////////////////////////////////////////////// // // InterationCountTimeStepControl Implementation @@ -128,12 +136,14 @@ namespace Opm std::string::size_type sz; std::string line; while ( std::getline(infile, line)) { - if( line[0] != '-') { // ignore lines starting with '-' + if (!line.empty() && line[0] != '-') { // ignore lines starting with '-' const double time = std::stod(line,&sz); // read the first number i.e. the actual substep time subStepTime_.push_back( time * unit::day ); } } + + std::sort(subStepTime_.begin(), subStepTime_.end()); } HardcodedTimeStepControl HardcodedTimeStepControl::serializationTestObject() @@ -145,14 +155,25 @@ namespace Opm } double - HardcodedTimeStepControl::computeTimeStepSize(const double /*dt */, + HardcodedTimeStepControl::computeTimeStepSize(const double dt, const int /*iterations */, const RelativeChangeInterface& /* relativeChange */, const AdaptiveSimulatorTimer& substepTimer) const { - auto nextTime - = std::upper_bound(subStepTime_.begin(), subStepTime_.end(), substepTimer.simulationTimeElapsed()); - return (*nextTime - substepTimer.simulationTimeElapsed()); + const double currentTime = substepTimer.simulationTimeElapsed(); + const double remaining = substepTimer.totalTime() - currentTime; + const double tol = hardcodedTimeTolerance(currentTime, substepTimer.totalTime(), dt); + + auto nextTime = std::upper_bound(subStepTime_.begin(), subStepTime_.end(), currentTime + tol); + while (nextTime != subStepTime_.end() && (*nextTime - currentTime) <= tol) { + ++nextTime; + } + + if (nextTime == subStepTime_.end()) { + return remaining > tol ? remaining : std::max(dt, tol); + } + + return std::max(*nextTime - currentTime, tol); } bool HardcodedTimeStepControl::operator==(const HardcodedTimeStepControl& ctrl) const diff --git a/opm/simulators/wells/BlackoilWellModel.hpp b/opm/simulators/wells/BlackoilWellModel.hpp index f4792403ef7..481f9b4ec51 100644 --- a/opm/simulators/wells/BlackoilWellModel.hpp +++ b/opm/simulators/wells/BlackoilWellModel.hpp @@ -154,6 +154,10 @@ template class WellContributions; void beginTimeStep(); + void updateFailed(); + + void advanceTimeLevel(); + void beginIteration() { OPM_TIMEBLOCK(beginIteration); diff --git a/opm/simulators/wells/BlackoilWellModelGeneric.cpp b/opm/simulators/wells/BlackoilWellModelGeneric.cpp index e4969aad544..d65f5a5f163 100644 --- a/opm/simulators/wells/BlackoilWellModelGeneric.cpp +++ b/opm/simulators/wells/BlackoilWellModelGeneric.cpp @@ -108,9 +108,12 @@ BlackoilWellModelGeneric(Schedule& schedule, , terminal_output_(comm_.rank() == 0 && Parameters::Get()) , guideRate_(schedule) + , prev_timestep_guideRate_(schedule) , active_wgstate_(pu) , last_valid_wgstate_(pu) + , prev_timestep_wgstate_(pu) , nupcol_wgstate_(pu) + , prev_timestep_nupcol_wgstate_(pu) , group_state_helper_(this->wellState(), this->groupState(), this->schedule(), @@ -1964,10 +1967,12 @@ operator==(const BlackoilWellModelGeneric& rhs) const && this->last_run_wellpi_ == rhs.last_run_wellpi_ && this->local_shut_wells_ == rhs.local_shut_wells_ && this->closed_this_step_ == rhs.closed_this_step_ + && this->prev_timestep_closed_this_step_ == rhs.prev_timestep_closed_this_step_ && this->genNetwork_ == rhs.genNetwork_ && this->prev_inj_multipliers_ == rhs.prev_inj_multipliers_ && this->active_wgstate_ == rhs.active_wgstate_ && this->last_valid_wgstate_ == rhs.last_valid_wgstate_ + && this->prev_timestep_wgstate_ == rhs.prev_timestep_wgstate_ && this->nupcol_wgstate_ == rhs.nupcol_wgstate_ && this->switched_prod_groups_ == rhs.switched_prod_groups_ && this->switched_inj_groups_ == rhs.switched_inj_groups_ diff --git a/opm/simulators/wells/BlackoilWellModelGeneric.hpp b/opm/simulators/wells/BlackoilWellModelGeneric.hpp index b8a0e08c195..c37b24bfbe1 100644 --- a/opm/simulators/wells/BlackoilWellModelGeneric.hpp +++ b/opm/simulators/wells/BlackoilWellModelGeneric.hpp @@ -212,6 +212,31 @@ class BlackoilWellModelGeneric data::GroupAndNetworkValues groupAndNetworkData(const int reportStepIdx) const; + // Snapshot dynamic state at start of timestep for failure recovery. + void advanceTimeLevel() + { + this->prev_timestep_wgstate_ = this->active_wgstate_; + this->prev_timestep_nupcol_wgstate_ = this->nupcol_wgstate_; + this->prev_timestep_closed_this_step_ = this->closed_this_step_; + this->prev_timestep_guideRate_ = this->guideRate_; + this->prev_timestep_well_open_times_ = this->well_open_times_; + this->prev_timestep_well_close_times_ = this->well_close_times_; + this->genNetwork_.commitState(); + } + + // Restore dynamic state captured at the start of the failing timestep. + void updateFailed() + { + this->active_wgstate_ = this->prev_timestep_wgstate_; + this->nupcol_wgstate_ = this->prev_timestep_nupcol_wgstate_; + this->closed_this_step_ = this->prev_timestep_closed_this_step_; + this->guideRate_ = this->prev_timestep_guideRate_; + this->well_open_times_ = this->prev_timestep_well_open_times_; + this->well_close_times_ = this->prev_timestep_well_close_times_; + this->genNetwork_.resetState(); + this->group_state_helper_.updateState(this->wellState(), this->groupState()); + } + /// Shut down any single well /// Returns true if the well was actually found and shut. bool forceShutWellByName(const std::string& wellname, @@ -254,11 +279,13 @@ class BlackoilWellModelGeneric serializer(last_run_wellpi_); serializer(local_shut_wells_); serializer(closed_this_step_); + serializer(prev_timestep_closed_this_step_); serializer(guideRate_); serializer(genNetwork_); serializer(prev_inj_multipliers_); serializer(active_wgstate_); serializer(last_valid_wgstate_); + serializer(prev_timestep_wgstate_); serializer(nupcol_wgstate_); serializer(switched_prod_groups_); serializer(switched_inj_groups_); @@ -530,9 +557,11 @@ class BlackoilWellModelGeneric // Times at which wells were opened (for WCYCLE) std::map well_open_times_; + std::map prev_timestep_well_open_times_; // Times at which wells were shut (for WCYCLE) std::map well_close_times_; + std::map prev_timestep_well_close_times_; std::vector conn_idx_map_{}; std::function not_on_process_{}; @@ -550,8 +579,10 @@ class BlackoilWellModelGeneric std::vector pvt_region_idx_; mutable std::unordered_set closed_this_step_; + std::unordered_set prev_timestep_closed_this_step_; GuideRate guideRate_; + GuideRate prev_timestep_guideRate_; std::unique_ptr> vfp_properties_{}; // previous injection multiplier, it is used in the injection multiplier calculation for WINJMULT keyword @@ -568,7 +599,9 @@ class BlackoilWellModelGeneric */ WGState active_wgstate_; WGState last_valid_wgstate_; + WGState prev_timestep_wgstate_; WGState nupcol_wgstate_; + WGState prev_timestep_nupcol_wgstate_; GroupStateHelperType group_state_helper_; WellGroupEvents report_step_start_events_; //!< Well group events at start of report step diff --git a/opm/simulators/wells/BlackoilWellModel_impl.hpp b/opm/simulators/wells/BlackoilWellModel_impl.hpp index 46dfdd352cf..bfc77f9cc78 100644 --- a/opm/simulators/wells/BlackoilWellModel_impl.hpp +++ b/opm/simulators/wells/BlackoilWellModel_impl.hpp @@ -569,6 +569,22 @@ namespace Opm { } + template + void + BlackoilWellModel:: + updateFailed() + { + this->BlackoilWellModelGeneric::updateFailed(); + } + + template + void + BlackoilWellModel:: + advanceTimeLevel() + { + this->BlackoilWellModelGeneric::advanceTimeLevel(); + } + #ifdef RESERVOIR_COUPLING_ENABLED // Automatically manages the lifecycle of the DeferredLogger pointer // in the reservoir coupling logger. Ensures the logger is properly diff --git a/regressionTests.cmake b/regressionTests.cmake index 19df609ca39..99a2d652db8 100644 --- a/regressionTests.cmake +++ b/regressionTests.cmake @@ -5,6 +5,8 @@ opm_set_test_driver(${PROJECT_SOURCE_DIR}/tests/run-regressionTest.sh "") set(abs_tol 2e-2) set(rel_tol 1e-5) set(coarse_rel_tol 1e-2) +set(timestep_replay_abs_tol 2e-14) +set(timestep_replay_rel_tol 2e-14) add_test_compareECLFiles(CASENAME spe1flowexp FILENAME SPE1CASE2 @@ -52,6 +54,161 @@ add_multiple_tests( DIR spe1 ) +opm_set_test_driver(${PROJECT_SOURCE_DIR}/tests/run-timestep-replay-regressionTest.sh "") + +add_test_compareECLFiles(CASENAME spe1_timestep_replay + FILENAME SPE1CASE1 + SIMULATOR flow_blackoil + PREFIX compareTimestepReplay + ABS_TOL ${timestep_replay_abs_tol} + REL_TOL ${timestep_replay_rel_tol} + DIR spe1 + TEST_ARGS_REPLAY --initial-time-step-in-days=11111111) + +add_test_compareECLFiles(CASENAME spe9_timestep_replay + FILENAME SPE9_CP_SHORT + SIMULATOR flow_blackoil + PREFIX compareTimestepReplay + ABS_TOL ${timestep_replay_abs_tol} + REL_TOL ${timestep_replay_rel_tol} + DIR spe9 + TEST_ARGS_REPLAY --initial-time-step-in-days=11111111) + +# add_test_compareECLFiles(CASENAME spe9_timestep_replay_all +# FILENAME SPE9 +# SIMULATOR flow_blackoil +# PREFIX compareTimestepReplay +# ABS_TOL ${timestep_replay_abs_tol} +# REL_TOL ${timestep_replay_rel_tol} +# DIR spe9 +# TEST_ARGS_REPLAY --initial-time-step-in-days=11111111) + +add_test_compareECLFiles(CASENAME base_model_small_timestep_replay + FILENAME 0_BASE_MODEL6 + SIMULATOR flow_oilwater + PREFIX compareTimestepReplay + ABS_TOL ${timestep_replay_abs_tol} + REL_TOL ${timestep_replay_rel_tol} + DIR model6 + TEST_ARGS --full-time-step-initially=false --solver-max-time-step-in-days=10 + TEST_ARGS_REPLAY --initial-time-step-in-days=11111111) + +add_test_compareECLFiles(CASENAME base_model_large_timestep_replay + FILENAME 0_BASE_MODEL6 + SIMULATOR flow_oilwater + PREFIX compareTimestepReplayFail + ABS_TOL ${timestep_replay_abs_tol} + REL_TOL ${timestep_replay_rel_tol} + DIR model6 + TEST_ARGS --full-time-step-initially=true --cpr-reuse-setup=0 + TEST_ARGS_REPLAY --initial-time-step-in-days=11111111) + +add_test_compareECLFiles(CASENAME msw_model6_large_timestep_replay + FILENAME 1_MSW_MODEL6 + SIMULATOR flow_oilwater + PREFIX compareTimestepReplayFail + ABS_TOL ${timestep_replay_abs_tol} + REL_TOL ${timestep_replay_rel_tol} + DIR model6 + TEST_ARGS --full-time-step-initially=true --cpr-reuse-setup=0 --newton-max-iterations=8 + TEST_ARGS_REPLAY --initial-time-step-in-days=11111111) + +add_test_compareECLFiles(CASENAME aq_model6_large_timestep_replay + FILENAME 0A_AQUCT_MODEL6 + SIMULATOR flow_oilwater + PREFIX compareTimestepReplayFail + ABS_TOL ${timestep_replay_abs_tol} + REL_TOL ${timestep_replay_rel_tol} + DIR model6 + TEST_ARGS --full-time-step-initially=true --cpr-reuse-setup=0 --newton-max-iterations=8 + TEST_ARGS_REPLAY --initial-time-step-in-days=11111111) + +add_test_compareECLFiles(CASENAME model5_large_timestep_replay + #FILENAME 0_BASE_MODEL5 + #FILENAME 5_NETWORK_MODEL5_MSW + FILENAME 4_GLIFT_MODEL5 + SIMULATOR flow_blackoil + PREFIX compareTimestepReplayFail + ABS_TOL ${timestep_replay_abs_tol} + REL_TOL ${timestep_replay_rel_tol} + DIR model5 + TEST_ARGS --full-time-step-initially=true --cpr-reuse-setup=0 --newton-max-iterations=8 + TEST_ARGS_REPLAY --initial-time-step-in-days=11111111) + +add_test_compareECLFiles(CASENAME grupcntl_large_timestep_replay + FILENAME GRUPCNTL-36 + SIMULATOR flow_blackoil + PREFIX compareTimestepReplayFail + ABS_TOL ${timestep_replay_abs_tol} + REL_TOL ${timestep_replay_rel_tol} + DIR grupcntl + TEST_ARGS --full-time-step-initially=true --cpr-reuse-setup=0 --newton-max-iterations=8 --tolerance-cnv-relaxed=1e-3 + TEST_ARGS_REPLAY --initial-time-step-in-days=11111111) + +add_test_compareECLFiles(CASENAME model2_fail_large_timestep_replay + FILENAME 9_2A_DEPL_GCONPROD_1L_MSW + SIMULATOR flow_blackoil + PREFIX compareTimestepReplayFail + ABS_TOL ${timestep_replay_abs_tol} + REL_TOL ${timestep_replay_rel_tol} + DIR model2 + TEST_ARGS --full-time-step-initially=true --cpr-reuse-setup=0 --newton-max-iterations=8 --tolerance-cnv-relaxed=1e-3 + TEST_ARGS_REPLAY --initial-time-step-in-days=11111111) + +add_test_compareECLFiles(CASENAME model2_fail_large_timestep_replay + FILENAME 9_2B_DEPL_GCONPROD_2L_MSW + SIMULATOR flow_blackoil + PREFIX compareTimestepReplayFail + ABS_TOL ${timestep_replay_abs_tol} + REL_TOL 2e-7 + DIR model2 + TEST_ARGS --full-time-step-initially=true --cpr-reuse-setup=0 --newton-max-iterations=8 --tolerance-cnv-relaxed=1e-3 + TEST_ARGS_REPLAY --initial-time-step-in-days=11111111) + +add_test_compareECLFiles(CASENAME model2_large_timestep_replay + FILENAME 0_BASE_MODEL2 + SIMULATOR flow_blackoil + PREFIX compareTimestepReplayFail + ABS_TOL ${timestep_replay_abs_tol} + REL_TOL ${timestep_replay_rel_tol} + DIR model2 + TEST_ARGS --full-time-step-initially=true --cpr-reuse-setup=0 --newton-max-iterations=8 --tolerance-cnv-relaxed=1e-3 + TEST_ARGS_REPLAY --initial-time-step-in-days=11111111) + +add_test_compareECLFiles(CASENAME model2_large_timestep_replay + #FILENAME 7_HYSTERESIS_MODEL2 #ok + FILENAME 9_4E_WINJ_GINJ_GUIDERATE_MSW #ok + SIMULATOR flow_blackoil + PREFIX compareTimestepReplayFail + ABS_TOL ${timestep_replay_abs_tol} + REL_TOL ${timestep_replay_rel_tol} + DIR model2 + TEST_ARGS --full-time-step-initially=true --cpr-reuse-setup=0 --newton-max-iterations=8 --tolerance-cnv-relaxed=1e-3 + TEST_ARGS_REPLAY --initial-time-step-in-days=11111111) + +add_test_compareECLFiles(CASENAME model2_large_timestep_replay + FILENAME 0_BASE_MODEL2_LET + SIMULATOR flow_blackoil + PREFIX compareTimestepReplayFail + ABS_TOL ${timestep_replay_abs_tol} + REL_TOL ${timestep_replay_rel_tol} + DIR model2 + TEST_ARGS --full-time-step-initially=true --cpr-reuse-setup=0 --newton-max-iterations=8 --tolerance-cnv-relaxed=1e-3 + TEST_ARGS_REPLAY --initial-time-step-in-days=11111111) + +add_test_compareECLFiles(CASENAME model2_large_timestep_replay + FILENAME 0A4_GRCTRL_LRAT_LRAT_GGR_BASE_MODEL2_MSW + SIMULATOR flow_blackoil + PREFIX compareTimestepReplayFail + ABS_TOL ${timestep_replay_abs_tol} + REL_TOL ${timestep_replay_rel_tol} + DIR model2 + TEST_ARGS --full-time-step-initially=true --cpr-reuse-setup=0 --newton-max-iterations=8 --tolerance-cnv-relaxed=1e-3 + TEST_ARGS_REPLAY --initial-time-step-in-days=11111111) + + +opm_set_test_driver(${PROJECT_SOURCE_DIR}/tests/run-regressionTest.sh "") + set(_spe1_coarse_tests SPE1CASE2_GASWATER SPE1CASE2_GASWATER_MSW diff --git a/tests/run-timestep-replay-regressionTest.sh b/tests/run-timestep-replay-regressionTest.sh new file mode 100755 index 00000000000..350b2ad24f4 --- /dev/null +++ b/tests/run-timestep-replay-regressionTest.sh @@ -0,0 +1,169 @@ +#!/bin/bash + +# This runs a simulator, extracts the accepted substep end times from the +# generated INFOSTEP file, reruns the same case using the hardcoded timestep +# controller, and compares the two outputs. + +set -u + +if test $# -eq 0 +then + echo -e "Usage:\t$0 -- [additional simulator options]" + echo -e "\tMandatory options:" + echo -e "\t\t -i Path to read deck from" + echo -e "\t\t -r Path to store results in" + echo -e "\t\t -b Path to simulator binary" + echo -e "\t\t -f Deck file name" + echo -e "\t\t -a Absolute tolerance in comparison" + echo -e "\t\t -t Relative tolerance in comparison" + echo -e "\t\t -c Path to comparison tool" + echo -e "\t\t -e Simulator binary to use" + echo -e "\tOptional options:" + echo -e "\t\t -d Unused, accepted for compatibility with other drivers" + exit 1 +fi + +OPTIND=1 +declare -a TEST_ARGS_REPLAY=() +while getopts "i:r:b:f:a:t:c:d:e:y:" OPT +do + case "${OPT}" in + i) INPUT_DATA_PATH=${OPTARG} ;; + r) RESULT_PATH=${OPTARG} ;; + b) BINPATH=${OPTARG} ;; + f) FILENAME=${OPTARG} ;; + a) ABS_TOL=${OPTARG} ;; + t) REL_TOL=${OPTARG} ;; + c) COMPARE_ECL_COMMAND=${OPTARG} ;; + d) : ;; + e) EXE_NAME=${OPTARG} ;; + y) TEST_ARGS_REPLAY+=("${OPTARG}") ;; + esac +done +shift $(($OPTIND-1)) +declare -a TEST_ARGS=("$@") + +BASELINE_PATH=${RESULT_PATH}/baseline +REPLAY_PATH=${RESULT_PATH}/replay +TIMESTEP_FILE=${RESULT_PATH}/${FILENAME}.timesteps +BASELINE_LOG=${RESULT_PATH}/${FILENAME}.baseline.log +REPLAY_LOG=${RESULT_PATH}/${FILENAME}.replay.log +BASELINE_INFOSTEP=${BASELINE_PATH}/${FILENAME}.INFOSTEP + +resolve_simulator_binary() { + local candidate="${BINPATH}/${EXE_NAME}" + if [ -x "${candidate}" ]; then + printf '%s\n' "${candidate}" + return 0 + fi + + if [ "${EXE_NAME}" = "flow" ] && [ -x "${BINPATH}/flow_blackoil" ]; then + printf '%s\n' "${BINPATH}/flow_blackoil" + return 0 + fi + + echo "ERROR: Simulator binary not found: ${candidate}" >&2 + return 1 +} + +run_simulation() { + local output_path=$1 + local log_path=$2 + shift 2 + local simulator_binary + + simulator_binary=$(resolve_simulator_binary) || return 1 + + mkdir -p "${output_path}" + "${simulator_binary}" "$@" --output-dir="${output_path}" "${INPUT_DATA_PATH}/${FILENAME}" > "${log_path}" 2>&1 + local status=$? + if [ ${status} -ne 0 ]; then + cat "${log_path}" + return ${status} + fi + + return 0 +} + +extract_timesteps() { + local infostep_path=$1 + local log_path=$2 + local output_file=$3 + + python3 - "$infostep_path" "$log_path" "$output_file" <<'PY' +from pathlib import Path +import re +import sys + +infostep = Path(sys.argv[1]) +log_path = Path(sys.argv[2]) +output = Path(sys.argv[3]) + +if not infostep.exists(): + raise FileNotFoundError(f"INFOSTEP file not found: {infostep}") +if not log_path.exists(): + raise FileNotFoundError(f"Simulation log not found: {log_path}") + +lines = [line.strip() for line in infostep.read_text().splitlines() if line.strip()] +header_idx = next((i for i, line in enumerate(lines) if "Time(day)" in line and "Conv" in line), None) +if header_idx is None: + raise ValueError(f"Unable to locate INFOSTEP header in {infostep}") + +header = lines[header_idx].split() +time_idx = header.index("Time(day)") +conv_idx = header.index("Conv") + +accepted_times = [] +for row in lines[header_idx + 1:]: + cols = row.split() + if len(cols) <= max(time_idx, conv_idx): + continue + if cols[conv_idx] not in {"1", "1.0", "true", "True"}: + continue + accepted_times.append(cols[time_idx]) + +if not accepted_times: + raise ValueError(f"No accepted timesteps found in {infostep}") + +# INFOSTEP currently omits the last accepted endpoint of the full simulation, +# so append the total simulation time from the baseline log if needed. +final_time = None +for line in reversed(log_path.read_text().splitlines()): + match = re.search(r"day\s+[^/]+/(\S+)", line) + if match: + final_time = match.group(1).rstrip(",;") + break + +if final_time is None: + raise ValueError(f"Unable to determine final simulation time from {log_path}") + +if accepted_times[-1] != final_time: + accepted_times.append(final_time) + +output.write_text("\n".join(accepted_times) + "\n") +PY +} + +rm -rf "${RESULT_PATH}" +mkdir -p "${RESULT_PATH}" + +# Generate precise accepted substep end times from INFOSTEP. +run_simulation "${BASELINE_PATH}" "${BASELINE_LOG}" ${TEST_ARGS[@]+"${TEST_ARGS[@]}"} --output-extra-convergence-info=steps +test $? -eq 0 || exit 1 + +extract_timesteps "${BASELINE_INFOSTEP}" "${BASELINE_LOG}" "${TIMESTEP_FILE}" +test $? -eq 0 || exit 1 + +run_simulation "${REPLAY_PATH}" "${REPLAY_LOG}" ${TEST_ARGS[@]+"${TEST_ARGS[@]}"} ${TEST_ARGS_REPLAY[@]+"${TEST_ARGS_REPLAY[@]}"} --output-extra-convergence-info=steps --time-step-control=hardcoded --time-step-control-file-name="${TIMESTEP_FILE}" +test $? -eq 0 || exit 1 + +ecode=0 +echo "=== Executing comparison for EGRID, INIT, UNRST, UNSMRY and RFT files ===" +"${COMPARE_ECL_COMMAND}" "${BASELINE_PATH}/${FILENAME}" "${REPLAY_PATH}/${FILENAME}" "${ABS_TOL}" "${REL_TOL}" +if [ $? -ne 0 ] +then + ecode=1 + "${COMPARE_ECL_COMMAND}" -a "${BASELINE_PATH}/${FILENAME}" "${REPLAY_PATH}/${FILENAME}" "${ABS_TOL}" "${REL_TOL}" +fi + +exit ${ecode} \ No newline at end of file