From a91378d6ef0df5d8a6aa2cd654e7bd2070ebe2a4 Mon Sep 17 00:00:00 2001 From: Markus Blatt Date: Fri, 2 Jan 2026 16:59:02 +0100 Subject: [PATCH] Also output RUNTIMEI to SMSPEC file. This is needed by some external tools. RUNTIMEI is an integer vector of size 50 and we store the following - item 1: 2 if simulation is finshed, 1 otherwise - item 3: The index of the first report step of the simulation (one-based) - item 4: The index of the current report step of the simulation (one-based) - item 5-9: When the simulation was started (YYYY, MM, DD, HH, MM, SS). Note that this a rough approximation as we use the time that initialize output. - item 10-14: The simulation time when the last report step was written (YYYY, MM, DD, HH, MM, SS) - item 35: The value used for BASIC of RPTRST or 0 if that was omitted. All other items are zero. --- opm/io/eclipse/OutputStream.cpp | 56 ++++++++++-- opm/io/eclipse/OutputStream.hpp | 15 +++- opm/output/eclipse/Summary.cpp | 22 +++-- tests/test_OutputStream.cpp | 147 +++++++++++++++++++++++++++++--- 4 files changed, 212 insertions(+), 28 deletions(-) diff --git a/opm/io/eclipse/OutputStream.cpp b/opm/io/eclipse/OutputStream.cpp index 10549341b4a..98cac2f3119 100644 --- a/opm/io/eclipse/OutputStream.cpp +++ b/opm/io/eclipse/OutputStream.cpp @@ -1,5 +1,6 @@ /* Copyright (c) 2019 Equinor ASA + Copyright (c) 2026 OPM-OP AS This file is part of the Open Porous Media project (OPM). @@ -683,6 +684,28 @@ namespace { }; } + std::vector + makeRuntimeiDate(const SummarySpecification::StartTime start) + { + const auto timepoint = std::chrono::system_clock::to_time_t(start); + const auto tm = *std::gmtime(&timepoint); + + // { Day, Month, Year, Hour, Minute, Seconds } + + return { + tm.tm_year + 1900, + + // 1..12 1..32 + tm.tm_mon + 1, tm.tm_mday, + + // 0..23 0..59 + tm.tm_hour, tm.tm_min, + + // 0..59 + std::min(tm.tm_sec, 59) + }; + } + std::vector makeDimens(const int nparam, const std::array& cartDims, @@ -713,12 +736,14 @@ SummarySpecification(const ResultSet& rset, const UnitConvention uconv, const std::array& cartDims, const RestartSpecification& restart, - const StartTime start) - : unit_ (unitConvention(uconv)) - , restartStep_(makeRestartStep(restart)) - , cartDims_ (cartDims) - , startDate_ (start) - , restart_ (restartRoot(restart)) + const StartTime start, + const StartTime computeStart) + : unit_ (unitConvention(uconv)) + , restartStep_ (makeRestartStep(restart)) + , cartDims_ (cartDims) + , startDate_ (start) + , computeStart_ (computeStart) + , restart_ (restartRoot(restart)) { const auto fname = outputFileName(rset, FileExtension::smspec(fmt.set)); @@ -755,7 +780,9 @@ operator=(SummarySpecification&& rhs) void Opm::EclIO::OutputStream:: -SummarySpecification::write(const Parameters& params) +SummarySpecification::write(const Parameters& params, + const bool simulationFinished, const int currentStep, + const int basic) { this->rewindStream(); @@ -778,6 +805,21 @@ SummarySpecification::write(const Parameters& params) smspec.write("STARTDAT", makeStartDate(this->startDate_)); + // Create and write RUNTIMEI + std::vector runtimei(50, 0); + runtimei[0] = simulationFinished ? 2 : 1; + runtimei[1] = (this->restartStep_ == -1)? 1 : this->restartStep_+1; + runtimei[2] = currentStep + 1; + const auto computeStart = makeRuntimeiDate(this->computeStart_); + std::copy(computeStart.begin(), computeStart.end(), runtimei.begin()+3); + + using std::chrono::system_clock; + const auto now = makeRuntimeiDate(Opm::TimeService::now()); + std::copy(now.begin(), now.end(), runtimei.begin()+9); + runtimei[34] = basic; + + smspec.write("RUNTIMEI", runtimei); + this->flushStream(); } diff --git a/opm/io/eclipse/OutputStream.hpp b/opm/io/eclipse/OutputStream.hpp index deb83f7a217..c995e6f62f7 100644 --- a/opm/io/eclipse/OutputStream.hpp +++ b/opm/io/eclipse/OutputStream.hpp @@ -1,5 +1,6 @@ /* Copyright (c) 2019 Equinor ASA + Copyroght (c) 2026 OPM-OP AS This file is part of the Open Porous Media project (OPM). @@ -414,7 +415,8 @@ namespace Opm { namespace EclIO { namespace OutputStream { const UnitConvention uconv, const std::array& cartDims, const RestartSpecification& restart, - const StartTime start); + const StartTime start, + const StartTime computeStart); ~SummarySpecification(); @@ -424,13 +426,22 @@ namespace Opm { namespace EclIO { namespace OutputStream { SummarySpecification& operator=(const SummarySpecification& rhs) = delete; SummarySpecification& operator=(SummarySpecification&& rhs); - void write(const Parameters& params); + /// \brief wirte SMSPEC file + /// + /// \param simulationFinished whether the simulation has finished (i.e. + /// this is the last written for it) + /// \param currentStep Index of the current report step + /// \param basic The value assigned to BASIC in RPRTRST + void write(const Parameters& params, const bool simulationFinished, + const int currentStep, const int basic); private: int unit_; int restartStep_; std::array cartDims_; StartTime startDate_; + /// \brief When the simulation started + StartTime computeStart_; std::vector> restart_; /// Summary specification (SMSPEC) file output stream. diff --git a/opm/output/eclipse/Summary.cpp b/opm/output/eclipse/Summary.cpp index 4ae7d55b84a..604455527ff 100644 --- a/opm/output/eclipse/Summary.cpp +++ b/opm/output/eclipse/Summary.cpp @@ -1,7 +1,6 @@ /* - Copyright 2021 Equinor ASA. - Copyright 2019 Equinor ASA. - Copyright 2016 Statoil ASA. + Copyright 2016-2021 Equinor ASA. + Copyright 2026 OPM-OP AS. This file is part of the Open Porous Media project (OPM). @@ -4950,13 +4949,16 @@ class SMSpecStreamDeferredCreation { return std::make_unique(rset, fmt, this->uconv(), this->cartDims_, this->restart_, - this->start_); + this->start_, + this->computeStart_); } private: Opm::UnitSystem::UnitType utype_; std::array cartDims_; Spec::StartTime start_; + /// \brief Time when the simulation started. + Spec::StartTime computeStart_; Spec::RestartSpecification restart_{}; Spec::UnitConvention uconv() const; @@ -4970,6 +4972,8 @@ SMSpecStreamDeferredCreation(const Opm::InitConfig& initcfg, : utype_ (utype) , cartDims_(grid.getNXYZ()) , start_ (Opm::TimeService::from_time_t(start)) + // This is not exactly when the simulation started, but should make the tools happy enough. + , computeStart_(Opm::TimeService::now()) { if (initcfg.restartRequested()) { this->restart_.root = initcfg.getRestartRootNameInput(); @@ -5278,10 +5282,16 @@ void Opm::out::Summary::SummaryImplementation::write(const bool is_final_summary return; } + const auto& last = this->lastUnwritten(); + this->createSMSpecIfNecessary(); - if (this->prevReportStepID_ < this->lastUnwritten().seq) { - this->smspec_->write(this->outputParameters_.summarySpecification()); + if (this->prevReportStepID_ < last.seq) { + this->smspec_->write(this->outputParameters_.summarySpecification(), + // pass additional parameters for RUNTIMEI keyword + is_final_summary, last.seq, + sched_.get()[last.seq].get().get() + .basic.value_or(0)); } for (auto i = 0*this->numUnwritten_; i < this->numUnwritten_; ++i) { diff --git a/tests/test_OutputStream.cpp b/tests/test_OutputStream.cpp index 5bf2266595b..2bbed0ab528 100644 --- a/tests/test_OutputStream.cpp +++ b/tests/test_OutputStream.cpp @@ -1,5 +1,6 @@ /* Copyright 2019 Equinor + Copyright 2026 OPM-OP AS This file is part of the Open Porous Media project (OPM). @@ -1840,7 +1841,8 @@ BOOST_AUTO_TEST_CASE(Unformatted_Base) const auto uconv = static_cast(1729); BOOST_CHECK_THROW(SMSpec(rset, fmt, uconv, cartDims, noRestart(), - start(2019, 10, 1, 12, 34, 56)), + start(2019, 10, 1, 12, 34, 56), + Opm::TimeService::now()), std::invalid_argument); } @@ -1850,10 +1852,12 @@ BOOST_AUTO_TEST_CASE(Unformatted_Base) auto smspec = SMSpec { rset, fmt, uconv, cartDims, noRestart(), - start(2019, 10, 1, 12, 34, 56) + start(2019, 10, 1, 12, 34, 56), + start(2026, 1, 2, 15, 2, 51) }; - smspec.write(summaryParameters()); + smspec.write(summaryParameters(), /* simulationFinished = */ false, + /* currentStep = */ 0, /*rprtrst_basic = */ 0); } { @@ -1875,6 +1879,7 @@ BOOST_AUTO_TEST_CASE(Unformatted_Base) Opm::EclIO::EclFile::EclEntry{"NUMS", Opm::EclIO::eclArrType::INTE, 4}, Opm::EclIO::EclFile::EclEntry{"UNITS", Opm::EclIO::eclArrType::CHAR, 4}, Opm::EclIO::EclFile::EclEntry{"STARTDAT", Opm::EclIO::eclArrType::INTE, 6}, + Opm::EclIO::EclFile::EclEntry{"RUNTIMEI", Opm::EclIO::eclArrType::INTE, 50}, }; BOOST_CHECK_EQUAL_COLLECTIONS(vectors.begin(), vectors.end(), @@ -1950,6 +1955,40 @@ BOOST_AUTO_TEST_CASE(Unformatted_Base) BOOST_CHECK_EQUAL_COLLECTIONS(S.begin(), S.end(), expect.begin(), expect.end()); } + + { + const auto& R = smspec.get("RUNTIMEI"); + auto expect = std::vector(10, 0); + expect[0] = 1; // simulation finished + expect[1] = 1; // first report step index + expect[2] = 1; // current report step index + expect[3] = 2026; // Year simulation started + expect[4] = 1; // Month simulation started + expect[5] = 2; // Day simulation started + expect[6] = 15; // Hour simulation started + expect[7] = 2; // Minute simulation started + expect[8] = 51; // Second simulation started + // We neglect the current simulation time in items 10-15 as we cannot really determine it + // 10th entry is BASIC of RPTRST + expect[9] = 0; + + for(std::size_t i = 0; i< 9; ++i) + { + BOOST_CHECK_EQUAL(R[i], expect[i]); + } + + for(std::size_t i = 15; i < 34; ++i) + { + BOOST_CHECK_EQUAL(R[i], 0); + } + + BOOST_CHECK_EQUAL(R[34], expect[9]); + + for(std::size_t i = 35; i < 50; ++i) + { + BOOST_CHECK_EQUAL(R[i], 0); + } + } } // ========================= FIELD ======================= @@ -1958,10 +1997,12 @@ BOOST_AUTO_TEST_CASE(Unformatted_Base) auto smspec = SMSpec { rset, fmt, uconv, cartDims, noRestart(), - start(1970, 1, 1, 0, 0, 0) + start(1970, 1, 1, 0, 0, 0), + start(2026, 1, 2, 15, 2, 51) }; - smspec.write(summaryParameters()); + smspec.write(summaryParameters(), /* simulationFinished = */ true, + /* currentStep = */ 3, /*rprtrst_basic = */ 6); } { @@ -1981,6 +2022,7 @@ BOOST_AUTO_TEST_CASE(Unformatted_Base) Opm::EclIO::EclFile::EclEntry{"NUMS", Opm::EclIO::eclArrType::INTE, 4}, Opm::EclIO::EclFile::EclEntry{"UNITS", Opm::EclIO::eclArrType::CHAR, 4}, Opm::EclIO::EclFile::EclEntry{"STARTDAT", Opm::EclIO::eclArrType::INTE, 6}, + Opm::EclIO::EclFile::EclEntry{"RUNTIMEI", Opm::EclIO::eclArrType::INTE, 50}, }; BOOST_CHECK_EQUAL_COLLECTIONS(vectors.begin(), vectors.end(), @@ -2056,6 +2098,40 @@ BOOST_AUTO_TEST_CASE(Unformatted_Base) BOOST_CHECK_EQUAL_COLLECTIONS(S.begin(), S.end(), expect.begin(), expect.end()); } + + { + const auto& R = smspec.get("RUNTIMEI"); + auto expect = std::vector(10, 0); + expect[0] = 2; // simulation finished + expect[1] = 1; // first report step index + expect[2] = 4; // current report step index + expect[3] = 2026; // Year simulation started + expect[4] = 1; // Month simulation started + expect[5] = 2; // Day simulation started + expect[6] = 15; // Hour simulation started + expect[7] = 2; // Minute simulation started + expect[8] = 51; // Second simulation started + // We neglect the current simulation time in items 10-15 as we cannot really determine it + // 10th entry is BASIC of RPTRST + expect[9] = 6; + + for(std::size_t i = 0; i< 9; ++i) + { + BOOST_CHECK_EQUAL(R[i], expect[i]); + } + + for(std::size_t i = 15; i < 34; ++i) + { + BOOST_CHECK_EQUAL(R[i], 0); + } + + BOOST_CHECK_EQUAL(R[34], expect[9]); + + for(std::size_t i = 35; i < 50; ++i) + { + BOOST_CHECK_EQUAL(R[i], 0); + } + } } // ========================= LAB ======================= @@ -2064,10 +2140,12 @@ BOOST_AUTO_TEST_CASE(Unformatted_Base) auto smspec = SMSpec { rset, fmt, uconv, cartDims, noRestart(), - start(2018, 12, 24, 17, 0, 0) + start(2018, 12, 24, 17, 0, 0), + Opm::TimeService::now() }; - smspec.write(summaryParameters()); + smspec.write(summaryParameters(), /* simulationFinished = */ false, + /* currentStep = */ 0, /*rprtrst_basic = */ 0); } { @@ -2087,6 +2165,7 @@ BOOST_AUTO_TEST_CASE(Unformatted_Base) Opm::EclIO::EclFile::EclEntry{"NUMS", Opm::EclIO::eclArrType::INTE, 4}, Opm::EclIO::EclFile::EclEntry{"UNITS", Opm::EclIO::eclArrType::CHAR, 4}, Opm::EclIO::EclFile::EclEntry{"STARTDAT", Opm::EclIO::eclArrType::INTE, 6}, + Opm::EclIO::EclFile::EclEntry{"RUNTIMEI", Opm::EclIO::eclArrType::INTE, 50}, }; BOOST_CHECK_EQUAL_COLLECTIONS(vectors.begin(), vectors.end(), @@ -2170,10 +2249,12 @@ BOOST_AUTO_TEST_CASE(Unformatted_Base) auto smspec = SMSpec { rset, fmt, uconv, cartDims, noRestart(), - start(1983, 1, 1, 1, 2, 3) + start(1983, 1, 1, 1, 2, 3), + Opm::TimeService::now() }; - smspec.write(summaryParameters()); + smspec.write(summaryParameters(), /* simulationFinished = */ false, + /* currentStep = */ 0, /*rprtrst_basic = */ 0); } { @@ -2193,6 +2274,7 @@ BOOST_AUTO_TEST_CASE(Unformatted_Base) Opm::EclIO::EclFile::EclEntry{"NUMS", Opm::EclIO::eclArrType::INTE, 4}, Opm::EclIO::EclFile::EclEntry{"UNITS", Opm::EclIO::eclArrType::CHAR, 4}, Opm::EclIO::EclFile::EclEntry{"STARTDAT", Opm::EclIO::eclArrType::INTE, 6}, + Opm::EclIO::EclFile::EclEntry{"RUNTIMEI", Opm::EclIO::eclArrType::INTE, 50}, }; BOOST_CHECK_EQUAL_COLLECTIONS(vectors.begin(), vectors.end(), @@ -2286,11 +2368,13 @@ BOOST_AUTO_TEST_CASE(Formatted_Restarted) auto smspec = SMSpec { rset, fmt, UConv::Pvt_M, cartDims, restartedSimulationTooLongBasename(), - start(2019, 10, 1, 12, 34, 56) + start(2019, 10, 1, 12, 34, 56), + Opm::TimeService::now() }; // Should *NOT* write RESTART vector (name too long). - smspec.write(summaryParameters()); + smspec.write(summaryParameters(), /* simulationFinished = */ false, + /* currentStep = */ 0, /*rprtrst_basic = */ 0); } { @@ -2312,10 +2396,12 @@ BOOST_AUTO_TEST_CASE(Formatted_Restarted) auto smspec = SMSpec { rset, fmt, uconv, cartDims, restartedSimulation(), - start(2019, 10, 1, 12, 34, 56) + start(2019, 10, 1, 12, 34, 56), + start(2026, 1, 2, 15, 2, 51) }; - smspec.write(summaryParameters()); + smspec.write(summaryParameters(), /* simulationFinished = */ false, + /* currentStep = */ 127, /*rprtrst_basic = */ 0); } { @@ -2335,6 +2421,7 @@ BOOST_AUTO_TEST_CASE(Formatted_Restarted) Opm::EclIO::EclFile::EclEntry{"NUMS", Opm::EclIO::eclArrType::INTE, 4}, Opm::EclIO::EclFile::EclEntry{"UNITS", Opm::EclIO::eclArrType::CHAR, 4}, Opm::EclIO::EclFile::EclEntry{"STARTDAT", Opm::EclIO::eclArrType::INTE, 6}, + Opm::EclIO::EclFile::EclEntry{"RUNTIMEI", Opm::EclIO::eclArrType::INTE, 50}, }; BOOST_CHECK_EQUAL_COLLECTIONS(vectors.begin(), vectors.end(), @@ -2422,6 +2509,40 @@ BOOST_AUTO_TEST_CASE(Formatted_Restarted) BOOST_CHECK_EQUAL_COLLECTIONS(S.begin(), S.end(), expect.begin(), expect.end()); } + + { + const auto& R = smspec.get("RUNTIMEI"); + auto expect = std::vector(10, 0); + expect[0] = 1; // simulation finished + expect[1] = 124; // first report step index + expect[2] = 128; // current report step index + expect[3] = 2026; // Year simulation started + expect[4] = 1; // Month simulation started + expect[5] = 2; // Day simulation started + expect[6] = 15; // Hour simulation started + expect[7] = 2; // Minute simulation started + expect[8] = 51; // Second simulation started + // We neglect the current simulation time in items 10-15 as we cannot really determine it + // 10th entry is BASIC of RPTRST + expect[9] = 0; + + for(std::size_t i = 0; i< 9; ++i) + { + BOOST_CHECK_EQUAL(R[i], expect[i]); + } + + for(std::size_t i = 15; i < 34; ++i) + { + BOOST_CHECK_EQUAL(R[i], 0); + } + + BOOST_CHECK_EQUAL(R[34], expect[9]); + + for(std::size_t i = 35; i < 50; ++i) + { + BOOST_CHECK_EQUAL(R[i], 0); + } + } } }