Output Summary for all Numeric Aquifers When ID Defaulted
This commit extends the 'SummaryConfig' aquifer processing to recognize numeric aquifer keywords without an explicit list of aquifer IDs, i.e., summary keywords of the form ANQR / We use the new functions '{analytic,numeric}AquiferIDs()' to form lists of pertinent IDs once and then ensure that we create nodes only for proper subsets of these ID sets. Add unit tests to both the analytic and numeric configurations to ensure that we generate these output sets or input errors when the input file refers to IDs of incorrect type or out of range.
This commit is contained in:
parent
79b1f97640
commit
51cf551eac
@ -196,7 +196,9 @@ struct SummaryConfigContext {
|
||||
|
||||
bool is_pressure(const std::string& keyword) {
|
||||
static const keyword_set presskw {
|
||||
"BHP", "BHPH", "THP", "THPH", "PR", "PRD", "PRDH", "PRDF", "PRDA",
|
||||
"BHP", "BHPH", "THP", "THPH", "PR",
|
||||
"PRD", "PRDH", "PRDF", "PRDA",
|
||||
"AQP", "NQP",
|
||||
};
|
||||
|
||||
return is_in_set(presskw, keyword.substr(1));
|
||||
@ -216,6 +218,8 @@ struct SummaryConfigContext {
|
||||
"OVIR", "GVIR", "WVIR",
|
||||
|
||||
"OPI", "OPP", "GPI", "GPP", "WPI", "WPP",
|
||||
|
||||
"AQR", "AQRG", "NQR",
|
||||
};
|
||||
|
||||
return is_in_set(ratekw, keyword.substr(1));
|
||||
@ -239,6 +243,8 @@ struct SummaryConfigContext {
|
||||
|
||||
"WIT", "OIT", "GIT", "LIT", "NIT", "CIT", "VIT",
|
||||
"WITH", "OITH", "GITH", "WVIT", "OVIT", "GVIT",
|
||||
|
||||
"AQT", "AQTG", "NQT",
|
||||
};
|
||||
|
||||
return is_in_set(totalkw, keyword.substr(1));
|
||||
@ -288,7 +294,22 @@ struct SummaryConfigContext {
|
||||
|
||||
bool is_aquifer(const std::string& keyword)
|
||||
{
|
||||
return (keyword[0] == 'A') && (keyword != "ALL");
|
||||
static const auto aqukw = keyword_set {
|
||||
"AQP", "AQR", "AQRG", "AQT", "AQTG",
|
||||
"LQR", "LQT", "LQRG", "LQTG",
|
||||
"NQP", "NQR", "NQT",
|
||||
"AQTD", "AQPD",
|
||||
};
|
||||
|
||||
return (keyword.size() >= std::string::size_type{4})
|
||||
&& (keyword[0] == 'A')
|
||||
&& is_in_set(aqukw, keyword.substr(1));
|
||||
}
|
||||
|
||||
bool is_numeric_aquifer(const std::string& keyword)
|
||||
{
|
||||
// ANQP, ANQR, ANQT
|
||||
return is_aquifer(keyword) && (keyword[1] == 'N');
|
||||
}
|
||||
|
||||
bool is_connection_completion(const std::string& keyword)
|
||||
@ -398,9 +419,15 @@ void handleMissingNode( const ParseContext& parseContext, ErrorGuard& errors, co
|
||||
parseContext.handleError( ParseContext::SUMMARY_UNKNOWN_NODE, msg_fmt, location, errors );
|
||||
}
|
||||
|
||||
void handleMissingAquifer( const ParseContext& parseContext, ErrorGuard& errors, const KeywordLocation& location, const int id) {
|
||||
std::string msg_fmt = fmt::format("Request for missing aquifer {} in {{keyword}}\n"
|
||||
"In {{file}} line {{line}}", id);
|
||||
void handleMissingAquifer( const ParseContext& parseContext,
|
||||
ErrorGuard& errors,
|
||||
const KeywordLocation& location,
|
||||
const int id,
|
||||
const bool is_numeric)
|
||||
{
|
||||
std::string msg_fmt = fmt::format("Request for missing {} aquifer {} in {{keyword}}\n"
|
||||
"In {{file}} line {{line}}",
|
||||
is_numeric ? "numeric" : "anlytic", id);
|
||||
parseContext.handleError(ParseContext::SUMMARY_UNKNOWN_AQUIFER, msg_fmt, location, errors);
|
||||
}
|
||||
|
||||
@ -412,30 +439,28 @@ inline void keywordW( SummaryConfig::keyword_list& list,
|
||||
}
|
||||
|
||||
inline void keywordAquifer( SummaryConfig::keyword_list& list,
|
||||
const AquiferConfig& aquiferConfig,
|
||||
SummaryConfigNode baseAquiferParam) {
|
||||
if ( !aquiferConfig.active() ) return;
|
||||
|
||||
for (const auto& aq : aquiferConfig.ct()) {
|
||||
list.push_back(baseAquiferParam.number(aq.aquiferID));
|
||||
}
|
||||
for (const auto& aq : aquiferConfig.fetp()) {
|
||||
list.push_back(baseAquiferParam.number(aq.aquiferID));
|
||||
const std::vector<int>& aquiferIDs,
|
||||
SummaryConfigNode baseAquiferParam)
|
||||
{
|
||||
for (const auto& id : aquiferIDs) {
|
||||
list.push_back(baseAquiferParam.number(id));
|
||||
}
|
||||
}
|
||||
|
||||
// later check whether parseContext and errors are required
|
||||
// maybe loc will be needed
|
||||
inline void keywordAquifer( SummaryConfig::keyword_list& list,
|
||||
const AquiferConfig& aquiferConfig,
|
||||
const ParseContext& parseContext,
|
||||
ErrorGuard& errors,
|
||||
const DeckKeyword& keyword) {
|
||||
|
||||
void keywordAquifer( SummaryConfig::keyword_list& list,
|
||||
const std::vector<int>& analyticAquiferIDs,
|
||||
const std::vector<int>& numericAquiferIDs,
|
||||
const ParseContext& parseContext,
|
||||
ErrorGuard& errors,
|
||||
const DeckKeyword& keyword)
|
||||
{
|
||||
/*
|
||||
The keywords starting with AL take as arguments a list of Aquiferlists -
|
||||
this is not supported at all.
|
||||
*/
|
||||
if (keyword.name().rfind("AL", 0) == 0) {
|
||||
if (keyword.name().find("AL") == std::string::size_type{0}) {
|
||||
Opm::OpmLog::warning(Opm::OpmInputError::format("Unhandled summary keyword {keyword}\n"
|
||||
"In {file} line {line}", keyword.location()));
|
||||
return;
|
||||
@ -447,16 +472,33 @@ inline void keywordAquifer( SummaryConfig::keyword_list& list,
|
||||
.parameterType( parseKeywordType(keyword.name()) )
|
||||
.isUserDefined( is_udq(keyword.name()) );
|
||||
|
||||
if (!keyword.empty() && keyword.getDataRecord().getDataItem().hasValue(0)) {
|
||||
for( const int id: keyword.getIntData()) {
|
||||
if (aquiferConfig.hasAquifer(id)) {
|
||||
list.push_back(param.number(id));
|
||||
} else {
|
||||
handleMissingAquifer(parseContext, errors, keyword.location(), id);
|
||||
const auto is_numeric = is_numeric_aquifer(keyword.name());
|
||||
const auto& pertinentIDs = is_numeric
|
||||
? numericAquiferIDs : analyticAquiferIDs;
|
||||
|
||||
if (keyword.empty() ||
|
||||
! keyword.getDataRecord().getDataItem().hasValue(0))
|
||||
{
|
||||
keywordAquifer(list, pertinentIDs, param);
|
||||
}
|
||||
else {
|
||||
auto ids = std::vector<int>{};
|
||||
auto end = pertinentIDs.end();
|
||||
|
||||
for (const int id : keyword.getIntData()) {
|
||||
// Note: std::find() could be std::lower_bound() here, but we
|
||||
// typically expect the number of pertinent aquifer IDs to be
|
||||
// small (< 10) so there's no big gain from a log(N) algorithm
|
||||
// in the common case.
|
||||
if (std::find(pertinentIDs.begin(), end, id) == end) {
|
||||
handleMissingAquifer(parseContext, errors, keyword.location(), id, is_numeric);
|
||||
continue;
|
||||
}
|
||||
|
||||
ids.push_back(id);
|
||||
}
|
||||
} else {
|
||||
keywordAquifer(list, aquiferConfig, param);
|
||||
|
||||
keywordAquifer(list, ids, param);
|
||||
}
|
||||
}
|
||||
|
||||
@ -700,15 +742,21 @@ inline void keywordF( SummaryConfig::keyword_list& list,
|
||||
|
||||
inline void keywordAquifer( SummaryConfig::keyword_list& list,
|
||||
const std::string& keyword,
|
||||
const AquiferConfig& aquiferConfig,
|
||||
KeywordLocation loc) {
|
||||
const std::vector<int>& analyticAquiferIDs,
|
||||
const std::vector<int>& numericAquiferIDs,
|
||||
KeywordLocation loc)
|
||||
{
|
||||
auto param = SummaryConfigNode {
|
||||
keyword, SummaryConfigNode::Category::Aquifer, std::move(loc)
|
||||
keyword, SummaryConfigNode::Category::Aquifer, std::move(loc)
|
||||
}
|
||||
.parameterType( parseKeywordType(keyword) )
|
||||
.isUserDefined( is_udq(keyword) );
|
||||
.parameterType( parseKeywordType(keyword) )
|
||||
.isUserDefined( is_udq(keyword) );
|
||||
|
||||
keywordAquifer(list, aquiferConfig, param);
|
||||
const auto& pertinentIDs = is_numeric_aquifer(keyword)
|
||||
? numericAquiferIDs
|
||||
: analyticAquiferIDs;
|
||||
|
||||
keywordAquifer(list, pertinentIDs, param);
|
||||
}
|
||||
|
||||
inline void keywordF( SummaryConfig::keyword_list& list,
|
||||
@ -1127,10 +1175,11 @@ inline void keywordMISC( SummaryConfig::keyword_list& list,
|
||||
inline void handleKW( SummaryConfig::keyword_list& list,
|
||||
SummaryConfigContext& context,
|
||||
const std::vector<std::string>& node_names,
|
||||
const std::vector<int>& analyticAquiferIDs,
|
||||
const std::vector<int>& numericAquiferIDs,
|
||||
const DeckKeyword& keyword,
|
||||
const Schedule& schedule,
|
||||
const FieldPropsManager& field_props,
|
||||
const AquiferConfig& aquiferConfig,
|
||||
const ParseContext& parseContext,
|
||||
ErrorGuard& errors,
|
||||
const GridDims& dims) {
|
||||
@ -1149,7 +1198,7 @@ inline void keywordMISC( SummaryConfig::keyword_list& list,
|
||||
case Cat::Connection: return keywordC( list, parseContext, errors, keyword, schedule, dims);
|
||||
case Cat::Segment: return keywordS( list, parseContext, errors, keyword, schedule );
|
||||
case Cat::Node: return keyword_node( list, node_names, parseContext, errors, keyword );
|
||||
case Cat::Aquifer: return keywordAquifer(list, aquiferConfig, parseContext, errors, keyword);
|
||||
case Cat::Aquifer: return keywordAquifer(list, analyticAquiferIDs, numericAquiferIDs, parseContext, errors, keyword);
|
||||
case Cat::Miscellaneous: return keywordMISC( list, keyword );
|
||||
|
||||
default:
|
||||
@ -1162,9 +1211,10 @@ inline void keywordMISC( SummaryConfig::keyword_list& list,
|
||||
|
||||
inline void handleKW( SummaryConfig::keyword_list& list,
|
||||
const std::string& keyword,
|
||||
const std::vector<int>& analyticAquiferIDs,
|
||||
const std::vector<int>& numericAquiferIDs,
|
||||
const KeywordLocation& location,
|
||||
const Schedule& schedule,
|
||||
const AquiferConfig& aquiferConfig,
|
||||
const ParseContext& /* parseContext */,
|
||||
ErrorGuard& /* errors */) {
|
||||
|
||||
@ -1179,7 +1229,7 @@ inline void handleKW( SummaryConfig::keyword_list& list,
|
||||
case Cat::Well: return keywordW( list, keyword, location, schedule );
|
||||
case Cat::Group: return keywordG( list, keyword, location, schedule );
|
||||
case Cat::Field: return keywordF( list, keyword, location );
|
||||
case Cat::Aquifer: return keywordAquifer( list, keyword, aquiferConfig, location );
|
||||
case Cat::Aquifer: return keywordAquifer( list, keyword, analyticAquiferIDs, numericAquiferIDs, location );
|
||||
case Cat::Miscellaneous: return keywordMISC( list, keyword, location);
|
||||
|
||||
default:
|
||||
@ -1430,13 +1480,21 @@ SummaryConfig::SummaryConfig( const Deck& deck,
|
||||
try {
|
||||
SUMMARYSection section( deck );
|
||||
SummaryConfigContext context;
|
||||
const auto node_names = need_node_names(section) ? collect_node_names(schedule) : std::vector<std::string> {};
|
||||
|
||||
const auto node_names = need_node_names(section)
|
||||
? collect_node_names(schedule)
|
||||
: std::vector<std::string> {};
|
||||
|
||||
const auto analyticAquifers = analyticAquiferIDs(aquiferConfig);
|
||||
const auto numericAquifers = numericAquiferIDs(aquiferConfig);
|
||||
|
||||
for (const auto& kw : section) {
|
||||
if (is_processing_instruction(kw.name())) {
|
||||
handleProcessingInstruction(kw.name());
|
||||
} else {
|
||||
handleKW(this->m_keywords, context, node_names, kw, schedule, field_props, aquiferConfig, parseContext, errors, dims);
|
||||
handleKW(this->m_keywords, context,
|
||||
node_names, analyticAquifers, numericAquifers,
|
||||
kw, schedule, field_props, parseContext, errors, dims);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1448,7 +1506,9 @@ SummaryConfig::SummaryConfig( const Deck& deck,
|
||||
KeywordLocation location = deck_keyword.location();
|
||||
location.keyword = fmt::format("{}/{}", meta_pair.first, kw);
|
||||
|
||||
handleKW(this->m_keywords, kw, location, schedule, aquiferConfig, parseContext, errors);
|
||||
handleKW(this->m_keywords, kw,
|
||||
analyticAquifers, numericAquifers,
|
||||
location, schedule, parseContext, errors);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -83,7 +83,7 @@ DIMENS
|
||||
REGDIMS
|
||||
3/
|
||||
AQUDIMS
|
||||
1* 1* 1* 1* 3 200 1* 1* /
|
||||
4 4 1* 1* 3 200 1* 1* /
|
||||
GRID
|
||||
DXV
|
||||
10*400 /
|
||||
@ -101,6 +101,19 @@ COPY
|
||||
/
|
||||
PORO
|
||||
1000*0.15 /
|
||||
AQUNUM
|
||||
4 1 1 1 15000 5000 0.3 30 2700 / aq cell
|
||||
5 2 1 1 150000 9000 0.3 30 2700 / aq cell
|
||||
6 3 1 1 150000 9000 0.3 30 2700 / aq cell
|
||||
7 4 1 1 150000 9000 0.3 30 2700 / aq cell
|
||||
/
|
||||
AQUCON
|
||||
-- # I1 I2 J1 J2 K1 K2 Face
|
||||
4 1 1 16 18 19 20 'I-' / connecting cells
|
||||
5 2 2 16 18 19 20 'I-' / connecting cells
|
||||
6 3 3 16 18 19 20 'I-' / connecting cells
|
||||
7 4 4 16 18 19 20 'I-' / connecting cells
|
||||
/
|
||||
REGIONS
|
||||
FIPNUM
|
||||
200*1 300*2 500*3 /
|
||||
@ -175,7 +188,7 @@ static std::vector< std::string > sorted_key_names( const SummaryConfig& summary
|
||||
return ret;
|
||||
}
|
||||
|
||||
static SummaryConfig createSummary( std::string input , const ParseContext& parseContext = ParseContext()) {
|
||||
static SummaryConfig createSummary(const std::string& input , const ParseContext& parseContext = ParseContext()) {
|
||||
ErrorGuard errors;
|
||||
auto deck = createDeck( input );
|
||||
auto python = std::make_shared<Python>();
|
||||
@ -563,22 +576,44 @@ BOOST_AUTO_TEST_CASE( REMOVE_DUPLICATED_ENTRIES ) {
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE( ANALYTICAL_AQUIFERS ) {
|
||||
{
|
||||
const auto faulty_input = std::string {R"(
|
||||
AAQT
|
||||
-- Neither of these are analytic aquifers => input error
|
||||
4 5 6 7 /
|
||||
)" };
|
||||
|
||||
BOOST_CHECK_THROW(const auto summary = createSummary(faulty_input),
|
||||
OpmInputError);
|
||||
}
|
||||
|
||||
{
|
||||
const auto faulty_input = std::string {R"(
|
||||
AAQP
|
||||
-- Aquifer ID out of range => input error
|
||||
1729 /
|
||||
)" };
|
||||
|
||||
BOOST_CHECK_THROW(const auto summary = createSummary(faulty_input),
|
||||
OpmInputError);
|
||||
}
|
||||
|
||||
const std::string input = R"(
|
||||
AAQR
|
||||
1 2 /
|
||||
AAQP
|
||||
2 1 /
|
||||
AAQT
|
||||
/
|
||||
AAQRG
|
||||
/
|
||||
AAQTG
|
||||
/
|
||||
AAQTD
|
||||
/
|
||||
AAQPD
|
||||
/
|
||||
)";
|
||||
AAQR
|
||||
1 2 /
|
||||
AAQP
|
||||
2 1 /
|
||||
AAQT
|
||||
/
|
||||
AAQRG
|
||||
/
|
||||
AAQTG
|
||||
/
|
||||
AAQTD
|
||||
/
|
||||
AAQPD
|
||||
/
|
||||
)";
|
||||
const auto summary = createSummary( input );
|
||||
|
||||
const auto keywords = { "AAQP", "AAQP", "AAQPD", "AAQPD", "AAQPD",
|
||||
@ -593,16 +628,46 @@ BOOST_AUTO_TEST_CASE( ANALYTICAL_AQUIFERS ) {
|
||||
names.begin(), names.end() );
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE( NUMERICAL_AQUIFERS ) {
|
||||
BOOST_AUTO_TEST_CASE( NUMERICAL_AQUIFERS )
|
||||
{
|
||||
{
|
||||
const auto faulty_input = std::string {R"(
|
||||
ANQR
|
||||
-- Neither of these are numeric aquifers => input error
|
||||
1 2 3 /
|
||||
)" };
|
||||
|
||||
BOOST_CHECK_THROW(const auto summary = createSummary(faulty_input),
|
||||
OpmInputError);
|
||||
}
|
||||
|
||||
{
|
||||
const auto faulty_input = std::string {R"(
|
||||
ANQP
|
||||
-- Aquifer ID out of range => input error
|
||||
42 /
|
||||
)" };
|
||||
|
||||
BOOST_CHECK_THROW(const auto summary = createSummary(faulty_input),
|
||||
OpmInputError);
|
||||
}
|
||||
|
||||
const std::string input = R"(
|
||||
ANQR
|
||||
1 2 /
|
||||
ANQP
|
||||
2 1 /
|
||||
ANQT
|
||||
/
|
||||
)";
|
||||
ANQR
|
||||
5 /
|
||||
ANQP
|
||||
4 7 /
|
||||
ANQT
|
||||
/
|
||||
)";
|
||||
const auto summary = createSummary( input );
|
||||
|
||||
const auto keywords = { "ANQP", "ANQP", "ANQR", "ANQT", "ANQT", "ANQT", "ANQT" };
|
||||
const auto names = sorted_keywords( summary );
|
||||
|
||||
BOOST_CHECK_EQUAL_COLLECTIONS(
|
||||
keywords.begin(), keywords.end(),
|
||||
names.begin(), names.end() );
|
||||
}
|
||||
|
||||
static const auto GMWSET_keywords = {
|
||||
|
Loading…
Reference in New Issue
Block a user