diff --git a/CMakeLists_files.cmake b/CMakeLists_files.cmake index 886d7686c..690a8c52e 100644 --- a/CMakeLists_files.cmake +++ b/CMakeLists_files.cmake @@ -963,6 +963,7 @@ if(ENABLE_ECL_OUTPUT) opm/output/eclipse/Summary.hpp opm/output/eclipse/Tables.hpp opm/output/eclipse/UDQDims.hpp + opm/output/eclipse/WStat.hpp opm/output/eclipse/WindowedArray.hpp opm/output/eclipse/WriteInit.hpp opm/output/eclipse/WriteRFT.hpp diff --git a/opm/io/eclipse/SummaryNode.hpp b/opm/io/eclipse/SummaryNode.hpp index 05dce39f5..8bcc213b4 100644 --- a/opm/io/eclipse/SummaryNode.hpp +++ b/opm/io/eclipse/SummaryNode.hpp @@ -77,7 +77,11 @@ struct SummaryNode { bool is_user_defined() const; - static Category category_from_keyword(const std::string&, const std::unordered_set &miscellaneous_keywords = {}); + static Category category_from_keyword(const std::string&); + + // Return true for keywords which should be Miscellaneous, although the + // naive first-character-based classification suggests something else. + static bool miscellaneous_exception(const std::string& keyword); std::optional display_name() const; std::optional display_number() const; diff --git a/opm/output/eclipse/WStat.hpp b/opm/output/eclipse/WStat.hpp new file mode 100644 index 000000000..05e16b1d8 --- /dev/null +++ b/opm/output/eclipse/WStat.hpp @@ -0,0 +1,58 @@ +/* + Copyright 2021 Equinor ASA. + + This file is part of the Open Porous Media project (OPM). + + OPM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + OPM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with OPM. If not, see . +*/ +#ifndef WSTAT_HPP +#define WSTAT_HPP + +#include + +namespace Opm { + +namespace WStat { +namespace numeric { +constexpr int UNKNOWN = 0; +constexpr int PROD = 1; +constexpr int INJ = 2; +constexpr int SHUT = 3; +constexpr int STOP = 4; +constexpr int PSHUT = 5; +constexpr int PSTOP = 6; +} + +namespace symbolic { +const std::string UNKNOWN = "UNKNOWN"; +const std::string PROD = "PROD"; +const std::string INJ = "INJ"; +const std::string SHUT = "SHUT"; +const std::string STOP = "STOP"; +const std::string PSHUT = "PSHUT"; +const std::string PSTOP = "PSTOP"; +} + + + + + +} +} + + + + +#endif + diff --git a/src/opm/io/eclipse/ERsm.cpp b/src/opm/io/eclipse/ERsm.cpp index 045f69fd5..833dd1ae8 100644 --- a/src/opm/io/eclipse/ERsm.cpp +++ b/src/opm/io/eclipse/ERsm.cpp @@ -31,6 +31,7 @@ #include #include #include +#include namespace Opm { namespace EclIO { @@ -140,6 +141,20 @@ std::vector make_multiplier(std::deque& lines) { return multiplier; } + +double convert_wstat(const std::string& symbolic_wstat) { + static const std::unordered_map wstat_map = { + {Opm::WStat::symbolic::UNKNOWN, Opm::WStat::numeric::UNKNOWN}, + {Opm::WStat::symbolic::PROD, Opm::WStat::numeric::PROD}, + {Opm::WStat::symbolic::INJ, Opm::WStat::numeric::INJ}, + {Opm::WStat::symbolic::SHUT, Opm::WStat::numeric::SHUT}, + {Opm::WStat::symbolic::STOP, Opm::WStat::numeric::STOP}, + {Opm::WStat::symbolic::PSHUT, Opm::WStat::numeric::PSHUT}, + {Opm::WStat::symbolic::PSTOP, Opm::WStat::numeric::PSTOP}, + }; + return static_cast(wstat_map.at(symbolic_wstat)); +} + } void ERsm::load_block(std::deque& lines, std::size_t& vector_length) { @@ -194,7 +209,12 @@ void ERsm::load_block(std::deque& lines, std::size_t& vector_length auto data_row = split_line(pop_return(lines)); for (std::size_t data_index = 0; data_index < num_rows; data_index++) { - double value = std::stod(data_row[data_index + 1]) * mult_list[data_index + 1]; + const auto& keyword = kw_list[data_index + 1]; + double value; + if (keyword == "WSTAT") + value = convert_wstat(data_row[data_index + 1]); + else + value = std::stod(data_row[data_index + 1]) * mult_list[data_index + 1]; block_data[data_index].data.push_back(value); } diff --git a/src/opm/io/eclipse/ESmry.cpp b/src/opm/io/eclipse/ESmry.cpp index 1c7d67623..4621f2a57 100644 --- a/src/opm/io/eclipse/ESmry.cpp +++ b/src/opm/io/eclipse/ESmry.cpp @@ -125,12 +125,6 @@ ESmry::ESmry(const std::string &filename, bool loadBaseRunData) : std::set keywList; std::vector> smryArray; - const std::unordered_set segmentExceptions { - "SEPARATE", - "STEPTYPE", - "SUMTHIN", - } ; - std::vector smspecList; std::vector vectList = {"DIMENS", "RESTART", "KEYWORDS", "NUMS", "UNITS"}; @@ -194,7 +188,7 @@ ESmry::ESmry(const std::string &filename, bool loadBaseRunData) : if (! keyString.empty()) { summaryNodes.push_back( { keywords[i], - SummaryNode::category_from_keyword(keywords[i], segmentExceptions), + SummaryNode::category_from_keyword(keywords[i]), SummaryNode::Type::Undefined, wgnames[i], nums[i], @@ -215,7 +209,7 @@ ESmry::ESmry(const std::string &filename, bool loadBaseRunData) : if (! keyString.empty()) { summaryNodes.push_back( { keywords[i], - SummaryNode::category_from_keyword(keywords[i], segmentExceptions), + SummaryNode::category_from_keyword(keywords[i]), SummaryNode::Type::Undefined, wgnames[i], nums[i], @@ -326,7 +320,7 @@ ESmry::ESmry(const std::string &filename, bool loadBaseRunData) : if (! keyString.empty()) { summaryNodes.push_back( { keywords[i], - SummaryNode::category_from_keyword(keywords[i], segmentExceptions), + SummaryNode::category_from_keyword(keywords[i]), SummaryNode::Type::Undefined, wgnames[i], nums[i], @@ -349,7 +343,7 @@ ESmry::ESmry(const std::string &filename, bool loadBaseRunData) : if (! keyString.empty()) { summaryNodes.push_back( { keywords[i], - SummaryNode::category_from_keyword(keywords[i], segmentExceptions), + SummaryNode::category_from_keyword(keywords[i]), SummaryNode::Type::Undefined, wgnames[i], nums[i], @@ -1218,10 +1212,7 @@ std::string ESmry::makeKeyString(const std::string& keywordArg, const std::strin } if (first == 'S') { - const std::vector segmExcep= {"STEPTYPE", "SEPARATE", "SUMTHIN"}; - - auto it = std::find(segmExcep.begin(), segmExcep.end(), keywordArg); - if (it != segmExcep.end()) { + if (SummaryNode::miscellaneous_exception(keywordArg)) { return keywordArg; } diff --git a/src/opm/io/eclipse/ESmry_write_rsm.cpp b/src/opm/io/eclipse/ESmry_write_rsm.cpp index 94a4bb6b5..e1338913a 100644 --- a/src/opm/io/eclipse/ESmry_write_rsm.cpp +++ b/src/opm/io/eclipse/ESmry_write_rsm.cpp @@ -31,9 +31,12 @@ #include #include +#include + #include #include #include +#include #include "project-version.h" @@ -111,12 +114,33 @@ namespace { os << '\n'; } - void write_data_row(std::ostream& os, const std::vector& time_column, const std::vector, int>>& data, std::size_t index, char prefix = ' ') { + std::string convert_wstat(double numeric_wstat) { + static const std::unordered_map wstat_map = { + {Opm::WStat::numeric::UNKNOWN, Opm::WStat::symbolic::UNKNOWN}, + {Opm::WStat::numeric::PROD, Opm::WStat::symbolic::PROD}, + {Opm::WStat::numeric::INJ, Opm::WStat::symbolic::INJ}, + {Opm::WStat::numeric::SHUT, Opm::WStat::symbolic::SHUT}, + {Opm::WStat::numeric::STOP, Opm::WStat::symbolic::STOP}, + {Opm::WStat::numeric::PSHUT, Opm::WStat::symbolic::PSHUT}, + {Opm::WStat::numeric::PSTOP, Opm::WStat::symbolic::PSTOP}, + }; + return wstat_map.at(static_cast(numeric_wstat)); + } + + + + void write_data_row(std::ostream& os, const std::vector& time_column, const std::vector& summary_nodes, const std::vector, int>>& data, std::size_t time_index, char prefix = ' ') { os << prefix; - print_time_element( os, time_column[index] ); - for (const auto& vector : data) { - print_float_element(os, vector.first[index] * std::pow(10.0, -vector.second)); + print_time_element( os, time_column[time_index] ); + for (std::size_t row_index = 0; row_index < data.size(); row_index++) { + const auto& [time_series, scale_factor] = data[row_index]; + const auto& summary_node = summary_nodes[row_index]; + + if (summary_node.keyword == "WSTAT") + print_text_element(os, convert_wstat(time_series[time_index])); + else + print_float_element(os, time_series[time_index] * std::pow(10.0, -scale_factor)); } write_padding(os, data.size()); @@ -127,8 +151,8 @@ namespace { os << prefix; print_text_element(os, ""); - for (const auto& vector : data) { - const auto scale_factor { vector.second } ; + for (const auto& [_ , scale_factor] : data) { + (void)_; if (scale_factor) { print_text_element(os, "*10**" + std::to_string(scale_factor)); } else { @@ -155,7 +179,7 @@ void ESmry::write_block(std::ostream& os, bool write_dates, const std::vectorget(vector) } ; auto max = *std::max_element(vector_data.begin(), vector_data.end()); int scale_factor { std::max(0, 3 * static_cast(std::floor(( std::log10(max) - 4 ) / 3 ))) } ; @@ -186,7 +210,7 @@ void ESmry::write_block(std::ostream& os, bool write_dates, const std::vector miscellaneous_keywords = {"SEPARATE", "STEPTYPE", "SUMTHIN"}; + return miscellaneous_keywords.count(keyword) == 1; +} + + Opm::EclIO::SummaryNode::Category Opm::EclIO::SummaryNode::category_from_keyword( - const std::string& keyword, - const std::unordered_set& miscellaneous_keywords + const std::string& keyword ) { + static const std::unordered_set miscellaneous_keywords = {"SEPARATE", "STEPTYPE", "SUMTHIN"}; if (keyword.length() == 0) { return Category::Miscellaneous; } - if (miscellaneous_keywords.find(keyword) != miscellaneous_keywords.end()) { + if (Opm::EclIO::SummaryNode::miscellaneous_exception(keyword)) return Category::Miscellaneous; - } switch (keyword[0]) { case 'A': return Category::Aquifer; diff --git a/src/opm/output/eclipse/Summary.cpp b/src/opm/output/eclipse/Summary.cpp index 8ef3feba1..41d68eb9a 100644 --- a/src/opm/output/eclipse/Summary.cpp +++ b/src/opm/output/eclipse/Summary.cpp @@ -20,6 +20,7 @@ */ #include +#include #include #include @@ -1064,6 +1065,25 @@ inline quantity segpress ( const fn_args& args ) return { segment->second.pressures[ix], measure::pressure }; } +inline quantity wstat( const fn_args& args ) { + const quantity zero = { Opm::WStat::numeric::UNKNOWN, measure::identity}; + if (args.schedule_wells.empty()) + return zero; + const auto& sched_well = args.schedule_wells.front(); + const auto& arg_well = args.wells.find(sched_well->name()); + + if (arg_well == args.wells.end() || arg_well->second.dynamicStatus == Opm::Well::Status::SHUT) + return {Opm::WStat::numeric::SHUT, measure::identity}; + + if (arg_well->second.dynamicStatus == Opm::Well::Status::STOP) + return {Opm::WStat::numeric::STOP, measure::identity}; + + if (sched_well->isInjector()) + return {Opm::WStat::numeric::INJ, measure::identity}; + + return {Opm::WStat::numeric::PROD, measure::identity}; +} + inline quantity bhp( const fn_args& args ) { const quantity zero = { 0, measure::pressure }; if (args.schedule_wells.empty()) @@ -1676,6 +1696,7 @@ static const std::unordered_map< std::string, ofun > funs = { { "WGLR", div( rate< rt::gas, producer >, sum( rate< rt::wat, producer >, rate< rt::oil, producer > ) ) }, + { "WSTAT", wstat }, { "WBHP", bhp }, { "WTHP", thp }, { "WTPCHEA", temperature< producer >}, diff --git a/tests/parser/SummaryConfigTests.cpp b/tests/parser/SummaryConfigTests.cpp index 56843e978..70c70c883 100644 --- a/tests/parser/SummaryConfigTests.cpp +++ b/tests/parser/SummaryConfigTests.cpp @@ -20,7 +20,7 @@ #define BOOST_TEST_MODULE SummaryConfigTests #include - +#include #include #include #include @@ -209,6 +209,16 @@ BOOST_AUTO_TEST_CASE(wells_all) { names.begin(), names.end() ); } +BOOST_AUTO_TEST_CASE(WSTATE) { + const auto input = "WSTAT\n/\n"; + const auto summary = createSummary( input ); + + const auto wells = { "PRODUCER", "WX2", "W_1", "W_3" }; + for (const auto& well : wells) + BOOST_CHECK(summary.hasSummaryKey(fmt::format("WSTAT:{}", well))); +} + + BOOST_AUTO_TEST_CASE(EMPTY) { auto deck = createDeck_no_wells( "" ); auto python = std::make_shared(); diff --git a/tests/summary_deck.DATA b/tests/summary_deck.DATA index 5ff3db332..dfcd6f4e5 100644 --- a/tests/summary_deck.DATA +++ b/tests/summary_deck.DATA @@ -748,6 +748,9 @@ COPRL W_2 / / +WSTAT +/ + -- Water injection per connection CWIR diff --git a/tests/test_Summary.cpp b/tests/test_Summary.cpp index abb35cbc9..969538541 100644 --- a/tests/test_Summary.cpp +++ b/tests/test_Summary.cpp @@ -29,12 +29,14 @@ #include #include #include +#include #include #include #include #include +#include #include #include @@ -53,6 +55,7 @@ #include #include +#include #include @@ -791,6 +794,11 @@ BOOST_AUTO_TEST_CASE(well_keywords) { BOOST_CHECK_CLOSE( 0.2, ecl_sum_get_well_var( resp, 1, "W_1", "WTHPH" ), 1e-5 ); BOOST_CHECK_CLOSE( 1.2, ecl_sum_get_well_var( resp, 1, "W_2", "WTHPH" ), 1e-5 ); BOOST_CHECK_CLOSE( 2.2, ecl_sum_get_well_var( resp, 1, "W_3", "WTHPH" ), 1e-5 ); + + /* State */ + BOOST_CHECK_CLOSE( WStat::numeric::PROD, ecl_sum_get_well_var(resp, 1,"W_1", "WSTAT"), 1e-5 ); + BOOST_CHECK_CLOSE( WStat::numeric::PROD, ecl_sum_get_well_var(resp, 1,"W_2", "WSTAT"), 1e-5 ); + BOOST_CHECK_CLOSE( WStat::numeric::INJ, ecl_sum_get_well_var(resp, 1,"W_3", "WSTAT"), 1e-5 ); } BOOST_AUTO_TEST_CASE(well_keywords_dynamic_close) { @@ -819,6 +827,10 @@ BOOST_AUTO_TEST_CASE(well_keywords_dynamic_close) { auto res = readsum( cfg.name ); const auto* resp = res.get(); + /* State */ + BOOST_CHECK_CLOSE( WStat::numeric::SHUT, ecl_sum_get_well_var(resp, 1,"W_2", "WSTAT"), 1e-5 ); + BOOST_CHECK_CLOSE( WStat::numeric::PROD, ecl_sum_get_well_var(resp, 2,"W_2", "WSTAT"), 1e-5 ); + /* Production rates */ BOOST_CHECK_CLOSE( 0.0, ecl_sum_get_well_var( resp, 1, "W_2", "WWPR" ), 1e-5 ); BOOST_CHECK_CLOSE( 0.0, ecl_sum_get_well_var( resp, 1, "W_2", "WTPRSEA" ), 1e-5 ); @@ -917,6 +929,16 @@ BOOST_AUTO_TEST_CASE(well_keywords_dynamic_close) { BOOST_CHECK_CLOSE( 1.2, ecl_sum_get_well_var( resp, 0, "W_2", "WTHPH" ), 1e-5 ); BOOST_CHECK_CLOSE( 1.2, ecl_sum_get_well_var( resp, 1, "W_2", "WTHPH" ), 1e-5 ); BOOST_CHECK_CLOSE( 1.2, ecl_sum_get_well_var( resp, 2, "W_2", "WTHPH" ), 1e-5 ); + + // Dump summary object as RSM file, load the new RSM file and compare. + { + std::string rsm_file = "TEST.RSM"; + std::filesystem::path rsm_path{rsm_file}; + resp->write_rsm_file(rsm_path); + + Opm::EclIO::ERsm rsm(rsm_file); + BOOST_CHECK(cmp(*resp, rsm)); + } } BOOST_AUTO_TEST_CASE(udq_keywords) {