diff --git a/opm/io/eclipse/EclUtil.hpp b/opm/io/eclipse/EclUtil.hpp index c9cab8ef3..a0c99b7d9 100644 --- a/opm/io/eclipse/EclUtil.hpp +++ b/opm/io/eclipse/EclUtil.hpp @@ -37,6 +37,17 @@ namespace Opm { namespace EclIO { bool isFormatted(const std::string& filename); bool is_number(const std::string& numstr); + /// Compute the linearly combined summary vector ID number from two + /// constituents. + /// + /// Constituents are *typically* one-based region IDs, but at least one + /// of the two could be a component ID too. + int combineSummaryNumbers(const int n1, const int n2); + + /// Split a combined summary vector ID ('NUMS' entry) into its original + /// two constituent IDs. + std::tuple splitSummaryNumber(const int n); + std::tuple block_size_data_binary(eclArrType arrType); std::tuple block_size_data_formatted(eclArrType arrType); diff --git a/src/opm/input/eclipse/EclipseState/Grid/FieldProps.hpp b/src/opm/input/eclipse/EclipseState/Grid/FieldProps.hpp index e93b1af8c..94934c8cd 100644 --- a/src/opm/input/eclipse/EclipseState/Grid/FieldProps.hpp +++ b/src/opm/input/eclipse/EclipseState/Grid/FieldProps.hpp @@ -344,7 +344,7 @@ public: case FieldProps::GetStatus::MISSING_KEYWORD: throw std::out_of_range("No such keyword in deck: " + keyword); case FieldProps::GetStatus::NOT_SUPPPORTED_KEYWORD: - throw std::logic_error("The kewyord " + keyword + " is not supported"); + throw std::logic_error("The keyword " + keyword + " is not supported"); } } diff --git a/src/opm/input/eclipse/EclipseState/SummaryConfig/SummaryConfig.cpp b/src/opm/input/eclipse/EclipseState/SummaryConfig/SummaryConfig.cpp index 47f7899c7..ea6d4554c 100644 --- a/src/opm/input/eclipse/EclipseState/SummaryConfig/SummaryConfig.cpp +++ b/src/opm/input/eclipse/EclipseState/SummaryConfig/SummaryConfig.cpp @@ -17,6 +17,34 @@ along with OPM. If not, see . */ +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + #include #include #include @@ -25,37 +53,14 @@ #include #include #include +#include #include #include +#include #include #include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - - namespace Opm { namespace { @@ -218,6 +223,11 @@ struct SummaryConfigContext { bool is_rate(const std::string& keyword) { static const keyword_set ratekw { "OPR", "GPR", "WPR", "GLIR", "LPR", "NPR", "CPR", "VPR", "TPR", "TPC", + + "OFR", "OFR+", "OFR-", + "GFR", "GFR+", "GFR-", + "WFR", "WFR+", "WFR-", + "OPGR", "GPGR", "WPGR", "VPGR", "OPRH", "GPRH", "WPRH", "LPRH", "OVPR", "GVPR", "WVPR", @@ -233,7 +243,10 @@ struct SummaryConfigContext { "AQR", "AQRG", "NQR", }; - return is_in_set(ratekw, keyword.substr(1)) || (keyword.length() > 4 && is_in_set({"TPR","TPC","TIR","TIC"}, keyword.substr(1,3)));; + return is_in_set(ratekw, keyword.substr(1)) + || ((keyword.length() > 4) && + is_in_set({ "TPR", "TPC", "TIR", "TIC" }, + keyword.substr(1, 3))); } bool is_ratio(const std::string& keyword) { @@ -252,13 +265,20 @@ struct SummaryConfigContext { "WPTH", "OPTH", "GPTH", "LPTH", "GPTS", "OPTS", "GPTF", "OPTF", + "OFT", "OFT+", "OFT-", "OFTL", "OFTG", + "GFT", "GFT+", "GFT-", "GFTL", "GFTG", + "WFT", "WFT+", "WFT-", + "WIT", "OIT", "GIT", "LIT", "NIT", "CIT", "VIT", "TIT", "WITH", "OITH", "GITH", "WVIT", "OVIT", "GVIT", "AQT", "AQTG", "NQT", }; - return is_in_set(totalkw, keyword.substr(1)) || (keyword.length() > 4 && is_in_set({"TPT","TIT"}, keyword.substr(1,3))); + return is_in_set(totalkw, keyword.substr(1)) + || ((keyword.length() > 4) && + is_in_set({ "TPT", "TIT" }, + keyword.substr(1, 3))); } bool is_count(const std::string& keyword) { @@ -293,15 +313,27 @@ struct SummaryConfigContext { return keyword == "WPIL"; } - bool is_region_to_region(const std::string& keyword) + bool is_supported_region_to_region(const std::string& keyword) { - static const auto rate = std::regex { R"(R[OGWEK]F[RT][-+GL]?)" }; - static const auto ngl = std::regex { R"(RNLF[RT][-+]?)" }; + static const auto supported_kw = std::regex { R"(R[OGW]F[RT][-+GL]?)" }; // R[OGW]F[RT][-+GL]? (e.g., "ROFTG", "RGFR+", or "RWFT") - // RNLF[RT].? (e.g., "RNLFR-" or "RNLFT") - return std::regex_match(keyword, rate) - || std::regex_match(keyword, ngl); + return std::regex_match(keyword, supported_kw); + } + + bool is_unsupported_region_to_region(const std::string& keyword) + { + static const auto unsupported_kw = std::regex { R"(R([EK]|NL)F[RT][-+]?)" }; + + // R[EK]F[RT][-+]? (e.g., "REFT" or "RKFR+") + // RNLF[RT][-+]? (e.g., "RNLFR-" or "RNLFT") + return std::regex_match(keyword, unsupported_kw); + } + + bool is_region_to_region(const std::string& keyword) + { + return is_supported_region_to_region (keyword) + || is_unsupported_region_to_region(keyword); } bool is_aquifer(const std::string& keyword) @@ -799,52 +831,129 @@ inline void keywordB( SummaryConfig::keyword_list& list, } } -inline void keywordR2R( SummaryConfig::keyword_list& /* list */, - const ParseContext& parseContext, - ErrorGuard& errors, - const DeckKeyword& keyword) +inline std::optional +establishRegionContext(const DeckKeyword& keyword, + const FieldPropsManager& field_props, + const ParseContext& parseContext, + ErrorGuard& errors, + SummaryConfigContext& context) { - const auto& location = keyword.location(); - std::string msg_fmt = "Region to region summary keyword {keyword} is ignored\n" - "In {file} line {line}"; - parseContext.handleError(ParseContext::SUMMARY_UNHANDLED_KEYWORD, msg_fmt, location, errors); + auto region_name = std::string { "FIPNUM" }; + + if (keyword.name().size() > 5) { + region_name = "FIP" + keyword.name().substr(5, 3); + + if (! field_props.has_int(region_name)) { + const auto msg_fmt = + fmt::format("Problem with summary keyword {{keyword}}\n" + "In {{file}} line {{line}}\n" + "FIP region {} not defined in " + "REGIONS section - {{keyword}} ignored", region_name); + + parseContext.handleError(ParseContext::SUMMARY_INVALID_FIPNUM, + msg_fmt, keyword.location(), errors); + return std::nullopt; + } + } + + if (context.regions.count(region_name) == 0) { + const auto& fipnum = field_props.get_int(region_name); + context.regions.emplace(std::piecewise_construct, + std::forward_as_tuple(region_name), + std::forward_as_tuple(fipnum.begin(), fipnum.end())); + } + + return { region_name }; +} + +inline void keywordR2R_unsupported(const DeckKeyword& keyword, + const ParseContext& parseContext, + ErrorGuard& errors) +{ + const auto msg_fmt = std::string { + "Region to region summary keyword {keyword} is ignored\n" + "In {file} line {line}" + }; + + parseContext.handleError(ParseContext::SUMMARY_UNHANDLED_KEYWORD, + msg_fmt, keyword.location(), errors); +} + +inline void keywordR2R(const DeckKeyword& keyword, + const FieldPropsManager& field_props, + const ParseContext& parseContext, + ErrorGuard& errors, + SummaryConfigContext& context, + SummaryConfig::keyword_list& list) +{ + if (is_unsupported_region_to_region(keyword.name())) { + keywordR2R_unsupported(keyword, parseContext, errors); + } + + if (is_udq(keyword.name())) { + throw std::invalid_argument { + "Inter-Region quantity '" + + keyword.name() + "' " + + "cannot be a user-defined quantity" + }; + } + + const auto region_name = establishRegionContext(keyword, field_props, + parseContext, errors, + context); + + if (! region_name.has_value()) { + return; + } + + auto param = SummaryConfigNode { + keyword.name(), SummaryConfigNode::Category::Region, keyword.location() + } + .parameterType(parseKeywordType(keyword.name())) + .fip_region(region_name.value()) + .isUserDefined(false); + + // Expected format: + // + // ROFT + // 1 2 / + // 1 4 / + // / + for (const auto& record : keyword) { + // We *intentionally* record/use one-based region IDs here. + const auto r1 = record.getItem("REGION1").get(0); + const auto r2 = record.getItem("REGION2").get(0); + + list.push_back(param.number(EclIO::combineSummaryNumbers(r1, r2))); + } } -inline void keywordR( SummaryConfig::keyword_list& list, - SummaryConfigContext& context, - const DeckKeyword& deck_keyword, - const Schedule& schedule, - const FieldPropsManager& field_props, - const ParseContext& parseContext, - ErrorGuard& errors ) { - - auto keyword = deck_keyword.name(); - if( is_region_to_region(keyword) ) { - keywordR2R( list, parseContext, errors, deck_keyword ); +inline void keywordR(SummaryConfig::keyword_list& list, + SummaryConfigContext& context, + const DeckKeyword& deck_keyword, + const Schedule& schedule, + const FieldPropsManager& field_props, + const ParseContext& parseContext, + ErrorGuard& errors) +{ + const auto keyword = deck_keyword.name(); + if (is_region_to_region(keyword)) { + keywordR2R(deck_keyword, field_props, parseContext, errors, context, list); return; } - std::string region_name = "FIPNUM"; - if (keyword.size() > 5) { - region_name = "FIP" + keyword.substr(5,3); - if (!field_props.has_int(region_name)) { - std::string msg_fmt = fmt::format("Problem with summary keyword {{keyword}}\n" - "In {{file}} line {{line}}\n" - "FIP region {} not defined in REGIONS section - {{keyword}} ignored", region_name); - parseContext.handleError(ParseContext::SUMMARY_INVALID_FIPNUM, msg_fmt, deck_keyword.location(), errors); - return; - } - } - if (context.regions.count(region_name) == 0) { - const auto& fipnum = field_props.get_int(region_name); - context.regions.emplace( region_name, std::set{ fipnum.begin(), fipnum.end()}); + + const auto region_name = establishRegionContext(deck_keyword, field_props, + parseContext, errors, + context); + + if (! region_name.has_value()) { + return; } const auto& item = deck_keyword.getDataRecord().getDataItem(); std::vector regions; - - /* Assume that the FIPNUM array contains the values {1,2,4}; i.e. the maximum value is 4 and the value 3 is missing. Values which are too large, i.e. > @@ -863,12 +972,12 @@ inline void keywordR( SummaryConfig::keyword_list& list, if (item.data_size() > 0) { for (const auto& region_id : item.getData()) { - const auto& region_set = context.regions.at(region_name); + const auto& region_set = context.regions.at(region_name.value()); auto max_iter = region_set.rbegin(); if (region_id > *max_iter) { std::string msg_fmt = fmt::format("Problem with summary keyword {{keyword}}\n" "In {{file}} line {{line}}\n" - "FIP region {} not present in {} - ignored", region_id, region_name); + "FIP region {} not present in {} - ignored", region_id, region_name.value()); parseContext.handleError(ParseContext::SUMMARY_REGION_TOO_LARGE, msg_fmt, deck_keyword.location(), errors); continue; } @@ -876,14 +985,15 @@ inline void keywordR( SummaryConfig::keyword_list& list, if (region_set.count(region_id) == 0) { std::string msg_fmt = fmt::format("Problem with summary keyword {{keyword}}\n" "In {{file}} line {{line}}\n" - "FIP region {} not present in {} - will use 0", region_id, region_name); + "FIP region {} not present in {} - will use 0", region_id, region_name.value()); parseContext.handleError(ParseContext::SUMMARY_EMPTY_REGION, msg_fmt, deck_keyword.location(), errors); } regions.push_back( region_id ); } - } else { - for (const auto& region_id : context.regions.at(region_name)) + } + else { + for (const auto& region_id : context.regions.at(region_name.value())) regions.push_back( region_id ); } @@ -899,17 +1009,16 @@ inline void keywordR( SummaryConfig::keyword_list& list, list.push_back( copt_node ); } } - } - auto param = SummaryConfigNode { keyword, SummaryConfigNode::Category::Region, deck_keyword.location() } - .fip_region( region_name ) + .parameterType(parseKeywordType(keyword)) + .fip_region( region_name.value() ) .isUserDefined( is_udq(keyword) ); - for( const auto& region : regions ) + for (const auto& region : regions) list.push_back( param.number( region ) ); } diff --git a/src/opm/input/eclipse/Schedule/SummaryState.cpp b/src/opm/input/eclipse/Schedule/SummaryState.cpp index a3faf84cb..2a4632ff6 100644 --- a/src/opm/input/eclipse/Schedule/SummaryState.cpp +++ b/src/opm/input/eclipse/Schedule/SummaryState.cpp @@ -31,10 +31,15 @@ namespace Opm{ namespace { bool is_total(const std::string& key) { - static const std::vector totals = {"OPT" , "GPT" , "WPT" , "GIT", "WIT", "OPTF" , "OPTS" , "OIT" , "OVPT" , "OVIT" , "MWT" , - "WVPT" , "WVIT" , "GMT" , "GPTF" , "SGT" , "GST" , "FGT" , "GCT" , "GIMT" , - "WGPT" , "WGIT" , "EGT" , "EXGT" , "GVPT" , "GVIT" , "LPT" , "VPT" , "VIT" , "NPT" , "NIT", - "TPT", "TIT", "CPT", "CIT", "SPT", "SIT", "EPT", "EIT", "TPTHEA", "TITHEA"}; + static const std::vector totals = { + "OPT" , "GPT" , "WPT" , "GIT", "WIT", "OPTF" , "OPTS" , "OIT" , "OVPT" , "OVIT" , "MWT" , + "WVPT" , "WVIT" , "GMT" , "GPTF" , "SGT" , "GST" , "FGT" , "GCT" , "GIMT" , + "WGPT" , "WGIT" , "EGT" , "EXGT" , "GVPT" , "GVIT" , "LPT" , "VPT" , "VIT" , "NPT" , "NIT", + "TPT", "TIT", "CPT", "CIT", "SPT", "SIT", "EPT", "EIT", "TPTHEA", "TITHEA", + "OFT", "OFT+", "OFT-", "OFTG", "OFTL", + "GFT", "GFT+", "GFT-", "GFTG", "GFTL", + "WFT", "WFT+", "WFT-", + }; auto sep_pos = key.find(':'); diff --git a/src/opm/io/eclipse/ESmry.cpp b/src/opm/io/eclipse/ESmry.cpp index 417317ab8..a469f69b6 100644 --- a/src/opm/io/eclipse/ESmry.cpp +++ b/src/opm/io/eclipse/ESmry.cpp @@ -1220,8 +1220,7 @@ std::string ESmry::makeKeyString(const std::string& keywordArg, const std::strin if ((str34 == "FR") || (str34 == "FT") || (str45 == "FR") || (str45 == "FT")) { // NUMS = R1 + 32768*(R2 + 10) - const auto r1 = num % (1UL << 15); - const auto r2 = (num / (1UL << 15)) - 10; + const auto& [r1, r2] = splitSummaryNumber(num); return fmt::format("{}:{}-{}", keywordArg, r1, r2); } diff --git a/src/opm/io/eclipse/EclUtil.cpp b/src/opm/io/eclipse/EclUtil.cpp index fcba130a0..0251ec0fc 100644 --- a/src/opm/io/eclipse/EclUtil.cpp +++ b/src/opm/io/eclipse/EclUtil.cpp @@ -103,6 +103,18 @@ bool Opm::EclIO::isEOF(std::fstream* fileH) } } +int Opm::EclIO::combineSummaryNumbers(const int n1, const int n2) +{ + return n1 + (1 << 15)*(n2 + 10); +} + +std::tuple Opm::EclIO::splitSummaryNumber(const int n) +{ + const auto n1 = n % (1 << 15); + const auto n2 = (n / (1 << 15)) - 10; + + return { n1, n2 }; +} std::tuple Opm::EclIO::block_size_data_binary(eclArrType arrType) { diff --git a/tests/parser/SummaryConfigTests.cpp b/tests/parser/SummaryConfigTests.cpp index 8c2cb00bf..cb7a334fe 100644 --- a/tests/parser/SummaryConfigTests.cpp +++ b/tests/parser/SummaryConfigTests.cpp @@ -388,6 +388,7 @@ ROFTL ROFTG 1 2/ 3 4/ +1 3/ / RGFT 5 6/ @@ -441,17 +442,118 @@ RWFR- / RWIP / +)" }; + + ParseContext parseContext; + + const auto summary = createSummary(input, parseContext); + + { + const auto expect_kw = std::vector { + "ROFT", "ROFT+", "ROFT-", "ROFR", "ROFR+", "ROFR-", "ROFTL", "ROFTG", + "RGFT", "RGFT+", "RGFT-", "RGFR", "RGFR+", "RGFR-", "RGFTL", "RGFTG", + "RGFT", "RGFT+", "RGFT-", "RGFR", "RGFR+", "RGFR-", + }; + + for (const auto& kw : expect_kw) { + BOOST_CHECK_MESSAGE(summary.hasKeyword(kw), + "SummaryConfig MUST have keyword '" << kw << "'"); + } + } + + { + const auto kw = summary.keywords("ROFT"); + BOOST_CHECK_EQUAL(kw.size(), 2); + + BOOST_CHECK_MESSAGE(kw[1].namedEntity().empty(), + "ROFT vector must NOT have an associated named entity"); + + BOOST_CHECK_MESSAGE(kw[0].type() == SummaryConfigNode::Type::Total, + "ROFT must be a Cumulative Total"); + + BOOST_CHECK_MESSAGE(kw[1].category() == SummaryConfigNode::Category::Region, + "ROFT must be a Region vector"); + + const auto expect_number = std::vector { + 393'217, // 1 2 + 458'755, // 3 4 + }; + + const auto n1 = kw[0].number(); + const auto n2 = kw[1].number(); + + BOOST_CHECK_MESSAGE(((n1 == expect_number[0]) && (n2 == expect_number[1])) || + ((n2 == expect_number[0]) && (n1 == expect_number[1])), + R"(ROFT 'NUMS' must match expected set)"); + } + + { + const auto kw = summary.keywords("RGFR-"); + BOOST_CHECK_EQUAL(kw.size(), 2); + + BOOST_CHECK_MESSAGE(kw[1].namedEntity().empty(), + "RGFR- vector must NOT have an associated named entity"); + + BOOST_CHECK_MESSAGE(kw[0].type() == SummaryConfigNode::Type::Rate, + "RGFR- must be a Rate"); + + BOOST_CHECK_MESSAGE(kw[1].category() == SummaryConfigNode::Category::Region, + "RGFR- must be a Region vector"); + + const auto expect_number = std::vector { + 524'293, // 5 6 + 589'831, // 7 8 + }; + + const auto n1 = kw[0].number(); + const auto n2 = kw[1].number(); + + BOOST_CHECK_MESSAGE(((n1 == expect_number[0]) && (n2 == expect_number[1])) || + ((n2 == expect_number[0]) && (n1 == expect_number[1])), + R"(RGFR- 'NUMS' must match expected set)"); + } + + { + const auto kw = summary.keywords("ROFTG"); + BOOST_CHECK_EQUAL(kw.size(), 3); + + BOOST_CHECK_MESSAGE(kw[1].namedEntity().empty(), + "ROFTG vector must NOT have an associated named entity"); + + BOOST_CHECK_MESSAGE(kw[0].type() == SummaryConfigNode::Type::Total, + "ROFTG must be a Cumulative Total"); + + BOOST_CHECK_MESSAGE(kw[1].category() == SummaryConfigNode::Category::Region, + "ROFTG must be a Region vector"); + + const auto expect_number = std::vector { + 393'217, // 1 2 + 458'755, // 3 4 + 425'985, // 1 3 + }; + + const auto actual = std::vector { + kw[0].number(), + kw[1].number(), + kw[2].number(), + }; + + BOOST_CHECK_MESSAGE(std::is_permutation(actual .begin(), actual .end(), + expect_number.begin(), expect_number.end()), + R"(ROFTG 'NUMS' must match expected set)"); + } +} + +BOOST_AUTO_TEST_CASE(region2region_unsupported) { + const auto input = std::string { R"(REFR- +2 3 / +/ +RKFT +2 3 / +/ )" }; ParseContext parseContext; - parseContext.update(ParseContext::SUMMARY_UNHANDLED_KEYWORD, InputError::IGNORE); - - const auto summary = createSummary( input, parseContext ); - const auto keywords = { "RWIP", "RWIP", "RWIP" }; - const auto names = sorted_keywords( summary ); - - BOOST_CHECK_EQUAL_COLLECTIONS(keywords.begin(), keywords.end(), - names.begin(), names.end() ); parseContext.update(ParseContext::SUMMARY_UNHANDLED_KEYWORD, InputError::THROW_EXCEPTION); BOOST_CHECK_THROW( createSummary(input, parseContext), OpmInputError); diff --git a/tests/test_EclIO.cpp b/tests/test_EclIO.cpp index 822b51fe5..7e9c5c19e 100644 --- a/tests/test_EclIO.cpp +++ b/tests/test_EclIO.cpp @@ -530,3 +530,20 @@ BOOST_AUTO_TEST_CASE(TestEcl_Write_CHAR) { BOOST_CHECK(std::get<1>(arrayList[0]) == Opm::EclIO::C0NN); } } + +BOOST_AUTO_TEST_CASE(CombinedVectorID) { + BOOST_CHECK_EQUAL(combineSummaryNumbers(1, 2), 393'217); + BOOST_CHECK_EQUAL(combineSummaryNumbers(10, 1), 360'458); + + { + const auto [n1, n2] = splitSummaryNumber(393'217); + BOOST_CHECK_EQUAL(n1, 1); + BOOST_CHECK_EQUAL(n2, 2); + } + + { + const auto [n1, n2] = splitSummaryNumber(360'458); + BOOST_CHECK_EQUAL(n1, 10); + BOOST_CHECK_EQUAL(n2, 1); + } +}