diff --git a/opm/parser/eclipse/EclipseState/Schedule/TimeMap.hpp b/opm/parser/eclipse/EclipseState/Schedule/TimeMap.hpp index 640f34a2c..55035060e 100644 --- a/opm/parser/eclipse/EclipseState/Schedule/TimeMap.hpp +++ b/opm/parser/eclipse/EclipseState/Schedule/TimeMap.hpp @@ -26,6 +26,7 @@ #include #include #include +#include #include @@ -38,7 +39,7 @@ namespace Opm { class TimeMap { public: TimeMap() = default; - explicit TimeMap(const Deck& deck); + explicit TimeMap(const Deck& deck, const std::pair& restart = std::make_pair(std::time_t{0}, std::size_t{0})); explicit TimeMap(const std::vector& time_points); size_t size() const; @@ -83,17 +84,17 @@ namespace Opm { } }; - std::vector m_timeList; - bool isTimestepInFreqSequence (size_t timestep, size_t start_timestep, size_t frequency, bool years) const; size_t closest(const std::vector & vec, size_t value) const; void addTStep(int64_t step); void addTime(std::time_t newTime); - void addFromDATESKeyword( const DeckKeyword& DATESKeyword ); void addFromTSTEPKeyword( const DeckKeyword& TSTEPKeyword ); + void init_start(std::time_t start_time); + std::vector m_timeList; std::vector m_first_timestep_years; // A list of the first timestep of every year std::vector m_first_timestep_months; // A list of the first timestep of every month + std::size_t restart_offset = 0; }; } diff --git a/src/opm/parser/eclipse/EclipseState/Schedule/TimeMap.cpp b/src/opm/parser/eclipse/EclipseState/Schedule/TimeMap.cpp index 982fc399c..b9dc2e521 100644 --- a/src/opm/parser/eclipse/EclipseState/Schedule/TimeMap.cpp +++ b/src/opm/parser/eclipse/EclipseState/Schedule/TimeMap.cpp @@ -22,12 +22,17 @@ #include +#include + #include #include #include #include #include + +constexpr const std::time_t invalid_time = -1; + namespace Opm { namespace { @@ -49,52 +54,108 @@ namespace { {"DES", 12}}; } + void TimeMap::init_start(std::time_t start_time) { + auto timestamp = TimeStampUTC{start_time}; + + this->m_timeList.push_back(start_time); + this->m_first_timestep_months.push_back({0, timestamp}); + this->m_first_timestep_years.push_back({0, timestamp}); + } + + TimeMap::TimeMap(const std::vector& time_points) { if (time_points.empty()) throw std::invalid_argument("Can not initialize with empty list of time points"); - this->m_timeList.push_back(time_points[0]); - m_first_timestep_months.push_back({0, TimeStampUTC{time_points[0]}}); - m_first_timestep_years.push_back({0, TimeStampUTC{time_points[0]}}); - for (std::size_t ti = 1; ti < time_points.size(); ti++) - this->addTime( time_points[ti] ); + this->init_start(time_points[0]); + for (std::size_t ti = 1; ti < time_points.size(); ti++) { + if (time_points[ti] == invalid_time) { + this->m_timeList.push_back(invalid_time); + this->restart_offset += 1; + } + else + this->addTime( time_points[ti] ); + } + if (this->restart_offset > 0) + this->restart_offset += 1; } - TimeMap::TimeMap( const Deck& deck) { - - std::time_t time; - if (deck.hasKeyword("START")) { - // Use the 'START' keyword to find out the start date (if the - // keyword was specified) - const auto& keyword = deck.getKeyword("START"); - time = timeFromEclipse(keyword.getRecord(0)); - } else { - // The default start date is not specified in the Eclipse - // reference manual. We hence just assume it is same as for - // the START keyword for Eclipse R100, i.e., January 1st, - // 1983... - time = mkdate(1983, 1, 1); + TimeMap::TimeMap( const Deck& deck, const std::pair& restart) { + bool skiprest = deck.hasKeyword(); + { + std::time_t start_time; + if (deck.hasKeyword("START")) { + // Use the 'START' keyword to find out the start date (if the + // keyword was specified) + const auto& keyword = deck.getKeyword("START"); + start_time = timeFromEclipse(keyword.getRecord(0)); + } else { + // The default start date is not specified in the Eclipse + // reference manual. We hence just assume it is same as for + // the START keyword for Eclipse R100, i.e., January 1st, + // 1983... + start_time = mkdate(1983, 1, 1); + } + this->init_start(start_time); + } + + auto restart_time = restart.first; + this->restart_offset = restart.second; + bool skip = false; + + for (std::size_t it = 1; it < this->restart_offset; it++) + this->m_timeList.push_back(invalid_time); + + if (this->restart_offset > 0) { + if (skiprest) + skip = true; + else { + this->m_timeList.push_back(restart_time); + skip = false; + } } - m_timeList.push_back(time); - auto timestamp = TimeStampUTC{time}; - m_first_timestep_months.push_back({0, timestamp}); - m_first_timestep_years.push_back({0, timestamp}); - // find all "TSTEP" and "DATES" keywords in the deck and deal - // with them one after another for( const auto& keyword : deck ) { // We're only interested in "TSTEP" and "DATES" keywords, // so we ignore everything else here... - if (keyword.name() != "TSTEP" && - keyword.name() != "DATES") - { + if (keyword.name() != "TSTEP" && keyword.name() != "DATES") + continue; + + if (keyword.name() == "DATES") { + for (size_t recordIndex = 0; recordIndex < keyword.size(); recordIndex++) { + const auto &record = keyword.getRecord(recordIndex); + const std::time_t nextTime = TimeMap::timeFromEclipse(record); + if (nextTime == restart_time) + skip = false; + + if (!skip) + addTime(nextTime); + } + continue; } - if (keyword.name() == "TSTEP") - addFromTSTEPKeyword(keyword); - else if (keyword.name() == "DATES") - addFromDATESKeyword(keyword); + if (skip) + continue; + + addFromTSTEPKeyword(keyword); + } + + /* + There is a coupling between the presence of the SKIPREST keyword and + the restart argument: The restart argument indicates whether this is + deck should be parsed as restarted deck. If restart_offset == 0 we do + not interpret this as restart situation and the presence of SKIPREST + is ignored. In the opposite case we verify - post loading - that we + have actually located the restart date - otherwise "something is + broken". + */ + if (this->restart_offset != 0) { + if (skiprest) { + const auto iter = std::find(this->m_timeList.begin(), this->m_timeList.end(), restart_time); + if (iter == this->m_timeList.end()) + throw std::invalid_argument("Could not find restart date"); + } } } @@ -105,7 +166,7 @@ namespace { std::time_t TimeMap::getStartTime(size_t tStepIdx) const { - return this->operator[]( tStepIdx ); + return this->operator[](tStepIdx); } std::time_t TimeMap::getEndTime() const { @@ -189,16 +250,6 @@ namespace { return date; } - void TimeMap::addFromDATESKeyword(const DeckKeyword &DATESKeyword) { - if (DATESKeyword.name() != "DATES") - throw std::invalid_argument("Method requires DATES keyword input."); - - for (size_t recordIndex = 0; recordIndex < DATESKeyword.size(); recordIndex++) { - const auto &record = DATESKeyword.getRecord(recordIndex); - const std::time_t nextTime = TimeMap::timeFromEclipse(record); - addTime(nextTime); - } - } void TimeMap::addFromTSTEPKeyword(const DeckKeyword &TSTEPKeyword) { if (TSTEPKeyword.name() != "TSTEP") @@ -239,7 +290,8 @@ namespace { { return this->m_timeList == data.m_timeList && this->m_first_timestep_months == data.m_first_timestep_months && - this->m_first_timestep_years == data.m_first_timestep_years; + this->m_first_timestep_years == data.m_first_timestep_years && + this->restart_offset == data.restart_offset; } bool TimeMap::isTimestepInFirstOfMonthsYearsSequence(size_t timestep, bool years, size_t start_timestep, size_t frequency) const { @@ -329,12 +381,14 @@ namespace { std::time_t TimeMap::operator[] (size_t index) const { - if (index < m_timeList.size()) { - return m_timeList[index]; - } else + if (index >= m_timeList.size()) throw std::invalid_argument("Index out of range"); - } + if (index > 0 && index < this->restart_offset) + throw std::invalid_argument("Tried to get time information from the base case in restarted run"); + + return m_timeList[index]; + } std::time_t TimeMap::mkdate(int in_year, int in_month, int in_day) { return mkdatetime(in_year , in_month , in_day, 0,0,0); diff --git a/tests/parser/TimeMapTest.cpp b/tests/parser/TimeMapTest.cpp index e572d63f9..13c3e114d 100644 --- a/tests/parser/TimeMapTest.cpp +++ b/tests/parser/TimeMapTest.cpp @@ -594,3 +594,108 @@ BOOST_AUTO_TEST_CASE(TimeServiceOperatorPlus) { } +BOOST_AUTO_TEST_CASE(RESTART) { + std::string deck_string1 = R"( +START + 1 JAN 2000 / + +RESTART + 'CASE' 5 / + +SCHEDULE + +SKIPREST + +DATES + 1 JAN 2001 / + 1 JAN 2002 / + 1 JAN 2003 / + 1 JAN 2004 / +/ + +DATES + 1 JAN 2005 / +/ + +DATES + 1 JAN 2006 / + 1 JAN 2007 / + 1 JAN 2008 / + 1 JAN 2009 / + 1 JAN 2010 / +/ +)"; + + std::string deck_string2 = R"( +START + 1 JAN 2000 / + +RESTART + 'CASE' 5 / + +SCHEDULE + +-- The period before the restart dates has been removed - the restart date +-- should still be picked up as report step 5. +--DATES +-- 1 JAN 2001 / +-- 1 JAN 2002 / +-- 1 JAN 2003 / +-- 1 JAN 2004 / +--/ + +DATES + 1 JUL 2005 / +/ + +DATES + 1 JAN 2006 / + 1 JAN 2007 / + 1 JAN 2008 / + 1 JAN 2009 / + 1 JAN 2010 / +/ +)"; + + std::string deck_string3 = R"( +START + 1 JAN 2000 / + +RESTART + 'CASE' 5 / + +SCHEDULE + +-- This test does not have SKIPREST + +TSTEP + 1 1 1 / +)"; + + Opm::Parser parser; + const auto deck1 = parser.parseString(deck_string1); + const auto deck2 = parser.parseString(deck_string2); + const auto deck3 = parser.parseString(deck_string3); + + // The date 2005-01-02 is not present as a DATES in the deck; invalid input. + auto invalid_restart = std::make_pair(Opm::asTimeT(Opm::TimeStampUTC({2005, 1, 2})), 5); + auto valid_restart = std::make_pair(Opm::asTimeT(Opm::TimeStampUTC({2005, 1, 1})), 5); + + BOOST_CHECK_THROW( Opm::TimeMap(deck1, invalid_restart) , std::invalid_argument); + Opm::TimeMap tm1(deck1, valid_restart); + BOOST_CHECK_THROW( tm1[1], std::invalid_argument ); + BOOST_CHECK_THROW( tm1[4], std::invalid_argument ); + auto start = tm1[0]; + BOOST_CHECK_EQUAL(start , Opm::asTimeT(Opm::TimeStampUTC({2000,1,1}))); + BOOST_CHECK_EQUAL(tm1[5] , Opm::asTimeT(Opm::TimeStampUTC({2005,1,1}))); + + Opm::TimeMap tm2(deck2, valid_restart); + BOOST_CHECK_EQUAL(tm2[5], Opm::asTimeT(Opm::TimeStampUTC({2005,1,1}))); + BOOST_CHECK_EQUAL(tm2[6], Opm::asTimeT(Opm::TimeStampUTC({2005,7,1}))); + + Opm::TimeMap tm3(deck3, valid_restart); + BOOST_CHECK_EQUAL(tm3[5], Opm::asTimeT(Opm::TimeStampUTC({2005,1,1}))); + BOOST_CHECK_EQUAL(tm3[6], Opm::asTimeT(Opm::TimeStampUTC({2005,1,2}))); + BOOST_CHECK_EQUAL(tm3[7], Opm::asTimeT(Opm::TimeStampUTC({2005,1,3}))); + BOOST_CHECK_EQUAL(tm3[8], Opm::asTimeT(Opm::TimeStampUTC({2005,1,4}))); +}