From 6cc65ada5942f8489d256e99a18684b06e461640 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A5rd=20Skaflestad?= Date: Tue, 29 Jan 2019 14:20:13 +0100 Subject: [PATCH] Add Tool for Checking Array Dimensions This commit introduces a new function, Opm::checkConsistentArrayDimensions() that inspects various dynamic array/table sizes and compares those to the maximum dimensions requested in the relevant *DIMS keywords of the RUNSPEC section. At present we check only items one through four of WELLDIMS since those are directly relevant for the ECLIPSE-compatible output code. We leverage the ErrorGuard to integrate into the existing mechanism for terminating a simulation run if there's a parse failure. --- CMakeLists_files.cmake | 4 + .../EclipseState/Schedule/ArrayDimChecker.hpp | 42 +++ .../EclipseState/Schedule/ArrayDimChecker.cpp | 189 ++++++++++++ tests/expect-wdims.err.out | 8 + tests/test_ArrayDimChecker.cpp | 280 ++++++++++++++++++ 5 files changed, 523 insertions(+) create mode 100644 opm/parser/eclipse/EclipseState/Schedule/ArrayDimChecker.hpp create mode 100644 src/opm/parser/eclipse/EclipseState/Schedule/ArrayDimChecker.cpp create mode 100644 tests/expect-wdims.err.out create mode 100644 tests/test_ArrayDimChecker.cpp diff --git a/CMakeLists_files.cmake b/CMakeLists_files.cmake index 38b8493b4..dbac7de9b 100644 --- a/CMakeLists_files.cmake +++ b/CMakeLists_files.cmake @@ -82,6 +82,7 @@ if(ENABLE_ECL_INPUT) src/opm/parser/eclipse/EclipseState/Schedule/Action/ActionParser.cpp src/opm/parser/eclipse/EclipseState/Schedule/Action/ActionValue.cpp src/opm/parser/eclipse/EclipseState/Schedule/Action/ASTNode.cpp + src/opm/parser/eclipse/EclipseState/Schedule/ArrayDimChecker.cpp src/opm/parser/eclipse/EclipseState/Schedule/Connection.cpp src/opm/parser/eclipse/EclipseState/Schedule/WellConnections.cpp src/opm/parser/eclipse/EclipseState/Schedule/Events.cpp @@ -256,6 +257,7 @@ if(ENABLE_ECL_OUTPUT) tests/test_AggregateWellData.cpp #The unit tests are not finished yet, will be added in a separate pullrequest soon #tests/test_AggregateMSWData.cpp + tests/test_ArrayDimChecker.cpp tests/test_CharArrayNullTerm.cpp tests/test_EclipseIO.cpp tests/test_DoubHEAD.cpp @@ -281,6 +283,7 @@ list (APPEND TEST_DATA_FILES ) if(ENABLE_ECL_OUTPUT) list (APPEND TEST_DATA_FILES + tests/expect-wdims.err.out tests/FIRST_SIM.DATA tests/FIRST_SIM_THPRES.DATA tests/summary_deck.DATA @@ -465,6 +468,7 @@ if(ENABLE_ECL_INPUT) opm/parser/eclipse/EclipseState/Schedule/Action/ActionContext.hpp opm/parser/eclipse/EclipseState/Schedule/Action/Actions.hpp opm/parser/eclipse/EclipseState/Schedule/Action/ActionX.hpp + opm/parser/eclipse/EclipseState/Schedule/ArrayDimChecker.hpp opm/parser/eclipse/EclipseState/Schedule/TimeMap.hpp opm/parser/eclipse/EclipseState/Schedule/VFPInjTable.hpp opm/parser/eclipse/EclipseState/Schedule/VFPProdTable.hpp diff --git a/opm/parser/eclipse/EclipseState/Schedule/ArrayDimChecker.hpp b/opm/parser/eclipse/EclipseState/Schedule/ArrayDimChecker.hpp new file mode 100644 index 000000000..8cb5681ae --- /dev/null +++ b/opm/parser/eclipse/EclipseState/Schedule/ArrayDimChecker.hpp @@ -0,0 +1,42 @@ +/* + Copyright (c) 2019 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 OPM_ARRAYDIM_CHECKER_HPP +#define OPM_ARRAYDIM_CHECKER_HPP + +#include + +namespace Opm { + class ErrorGuard; + class EclipseState; + class ParseContext; + class Schedule; +} // Opm + +namespace Opm { + int maxGroupSize(const Schedule& sched, + const std::size_t step); + + void checkConsistentArrayDimensions(const EclipseState& es, + const Schedule& sched, + const ParseContext& ctxt, + ErrorGuard& guard); +} // Opm + +#endif // OPM_ARRAYDIM_CHECKER_HPP diff --git a/src/opm/parser/eclipse/EclipseState/Schedule/ArrayDimChecker.cpp b/src/opm/parser/eclipse/EclipseState/Schedule/ArrayDimChecker.cpp new file mode 100644 index 000000000..d47214616 --- /dev/null +++ b/src/opm/parser/eclipse/EclipseState/Schedule/ArrayDimChecker.cpp @@ -0,0 +1,189 @@ +/* + Copyright (c) 2019 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 . +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +namespace { + namespace WellDims { + void checkNumWells(const Opm::Welldims& wdims, + const Opm::Schedule& sched, + const Opm::ParseContext& ctxt, + Opm::ErrorGuard& guard) + { + const auto nWells = sched.numWells(); + + if (nWells > static_cast(wdims.maxWellsInField())) + { + std::ostringstream os; + os << "Run uses " << nWells << " wells, but allocates at " + << "most " << wdims.maxWellsInField() << " in RUNSPEC " + << "section. Increase item 1 of WELLDIMS accordingly."; + + ctxt.handleError(Opm::ParseContext::RUNSPEC_NUMWELLS_TOO_LARGE, + os.str(), guard); + } + } + + void checkConnPerWell(const Opm::Welldims& wdims, + const Opm::Schedule& sched, + const Opm::ParseContext& ctxt, + Opm::ErrorGuard& guard) + { + auto nconn = std::size_t{0}; + for (const auto* well : sched.getWells()) { + nconn = std::max(nconn, well->getConnections().size()); + } + + if (nconn > static_cast(wdims.maxConnPerWell())) + { + std::ostringstream os; + os << "Run has well with " << nconn << " reservoir connections, " + << "but allocates at most " << wdims.maxConnPerWell() + << " connections per well in RUNSPEC section. Increase item " + << "2 of WELLDIMS accordingly."; + + ctxt.handleError(Opm::ParseContext::RUNSPEC_CONNS_PER_WELL_TOO_LARGE, + os.str(), guard); + } + } + + void checkNumGroups(const Opm::Welldims& wdims, + const Opm::Schedule& sched, + const Opm::ParseContext& ctxt, + Opm::ErrorGuard& guard) + { + const auto nGroups = sched.numGroups(); + + // Note: "1 +" to account for FIELD group being in 'sched.numGroups()' + // but excluded from WELLDIMS(3). + if (nGroups > 1 + static_cast(wdims.maxGroupsInField())) + { + std::ostringstream os; + os << "Run uses " << (nGroups - 1) << " non-FIELD groups, but " + << "allocates at most " << wdims.maxGroupsInField() + << " in RUNSPEC section. Increase item 3 of WELLDIMS " + << "accordingly."; + + ctxt.handleError(Opm::ParseContext::RUNSPEC_NUMGROUPS_TOO_LARGE, + os.str(), guard); + } + } + + void checkGroupSize(const Opm::Welldims& wdims, + const Opm::Schedule& sched, + const Opm::ParseContext& ctxt, + Opm::ErrorGuard& guard) + { + const auto numSteps = sched.getTimeMap().numTimesteps(); + + auto size = std::size_t{0}; + for (auto step = 0*numSteps; step < numSteps; ++step) { + const auto nwgmax = maxGroupSize(sched, step); + + size = std::max(size, static_cast(nwgmax)); + } + + if (size >= static_cast(wdims.maxWellsPerGroup())) + { + std::ostringstream os; + os << "Run uses maximum group size of " << size << ", but " + << "allocates at most " << wdims.maxWellsPerGroup() + << " in RUNSPEC section. Increase item 4 of WELLDIMS " + << "accordingly."; + + ctxt.handleError(Opm::ParseContext::RUNSPEC_GROUPSIZE_TOO_LARGE, + os.str(), guard); + } + } + } // WellDims + + void consistentWellDims(const Opm::Welldims& wdims, + const Opm::Schedule& sched, + const Opm::ParseContext& ctxt, + Opm::ErrorGuard& guard) + { + WellDims::checkNumWells (wdims, sched, ctxt, guard); + WellDims::checkConnPerWell(wdims, sched, ctxt, guard); + WellDims::checkNumGroups (wdims, sched, ctxt, guard); + WellDims::checkGroupSize (wdims, sched, ctxt, guard); + } +} // Anonymous + +void +Opm::checkConsistentArrayDimensions(const EclipseState& es, + const Schedule& sched, + const ParseContext& ctxt, + ErrorGuard& guard) +{ + consistentWellDims(es.runspec().wellDimensions(), sched, ctxt, guard); +} + +int +Opm::maxGroupSize(const Opm::Schedule& sched, + const std::size_t step) +{ + const auto& gt = sched.getGroupTree(step); + + auto groups = std::vector{"FIELD"}; + auto nwgmax = std::size_t{0}; + + auto update_size = [&nwgmax](const std::size_t n) { + nwgmax = std::max(nwgmax, n); + }; + + // Note: We update 'groups' in the body of the loop, so + // groups.size() is potentially increasing as we + // traverse down the group tree to its leaf nodes. + for (auto i = 0*groups.size(); i < groups.size(); ++i) { + const auto& grp = groups[i]; + auto children = gt.children(grp); + + if (children.empty()) { + // Well group. Size is number of wells. + update_size(sched.getGroup(grp).numWells(step)); + } + else { + // Node group. Size is number of child nodes. + update_size(children.size()); + + // Enqueue those children to get their sizes. + groups.insert(groups.end(), + std::make_move_iterator(children.begin()), + std::make_move_iterator(children.end())); + } + } + + return nwgmax; +} diff --git a/tests/expect-wdims.err.out b/tests/expect-wdims.err.out new file mode 100644 index 000000000..84cfa2821 --- /dev/null +++ b/tests/expect-wdims.err.out @@ -0,0 +1,8 @@ + + +Errors: + RUNSPEC_NUMWELLS_TOO_LARGE : Run uses 12 wells, but allocates at most 0 in RUNSPEC section. Increase item 1 of WELLDIMS accordingly. + RUNSPEC_CONNS_PER_WELL_TOO_LARGE: Run has well with 15 reservoir connections, but allocates at most 0 connections per well in RUNSPEC section. Increase item 2 of WELLDIMS accordingly. + RUNSPEC_NUMGROUPS_TOO_LARGE : Run uses 11 non-FIELD groups, but allocates at most 0 in RUNSPEC section. Increase item 3 of WELLDIMS accordingly. + RUNSPEC_GROUPSIZE_TOO_LARGE : Run uses maximum group size of 10, but allocates at most 0 in RUNSPEC section. Increase item 4 of WELLDIMS accordingly. + diff --git a/tests/test_ArrayDimChecker.cpp b/tests/test_ArrayDimChecker.cpp new file mode 100644 index 000000000..431c1e056 --- /dev/null +++ b/tests/test_ArrayDimChecker.cpp @@ -0,0 +1,280 @@ +/* + Copyright 2019 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 . +*/ + +#define BOOST_TEST_MODULE Array_Dimension_Checker + +#include +#include + +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include + +namespace { + Opm::Deck simCaseWellDims() + { + const auto input = std::string{ R"(RUNSPEC +TITLE + 'Check Well Dimensions' / + +DIMENS + 20 20 15 / + +OIL +WATER + +METRIC + +EQLDIMS +-- Defaulted +/ + +TABDIMS +-- Defaulted +/ + +WELLDIMS +-- Defaulted +/ + +-- ==================================================================== +GRID + +SPECGRID + 20 20 15 1 F / + +DXV + 20*100.0 / + +DYV + 20*100.0 / + +DZV + 15*0.1 / + +DEPTHZ + 441*2000 / + +PORO + 6000*0.3 / + +PERMX + 6000*100.0 / + +COPY + 'PERMX' 'PERMY' / + 'PERMX' 'PERMZ' / +/ + +MULTIPLY + 'PERMZ' 0.1 / +/ + +-- ==================================================================== +PROPS + +SWOF + 0 0 1 0 + 1 1 0 0 / + +PVDO + 1 1.0 0.5 + 800 0.99 0.51 / + +PVTW + 300 0.99 1.0e-6 0.25 0 / + +DENSITY + 850.0 1014.0 1.05 / + +-- ==================================================================== +SOLUTION + +EQUIL + 2000 300 2010 0.0 2000 10 / + +-- ==================================================================== +SUMMARY +ALL + +-- ==================================================================== +SCHEDULE + +RPTRST + BASIC=5 FREQ=6 / + +GRUPTREE + 'G1' 'FIELD' / + 'PLAT1' 'G1' / + 'PLAT2' 'G1' / + 'I-NORTH' 'PLAT1' / + 'P-NORTH' 'PLAT1' / + 'O-WEST' 'PLAT2' / + 'I-SOUTH' 'PLAT2' / + 'P-EAST' 'PLAT2' / + 'G2' 'FIELD' / + 'PLAT3' 'G2' / + 'I-2' 'PLAT3' / +/ + +WELSPECS + 'I-N-1' 'I-NORTH' 1 1 2000.15 'WATER' / + 'I-N-2' 'I-NORTH' 5 1 2001.05 'WATER' / + 'P-N-0' 'P-NORTH' 1 10 2000.15 'OIL' / + 'P-N-1' 'P-NORTH' 10 15 2000.15 'OIL' / + 'P-N-2' 'P-NORTH' 1 20 2000.15 'OIL' / + 'P-N-3' 'P-NORTH' 19 20 2000.15 'OIL' / + 'P-N-4' 'P-NORTH' 15 10 2000.15 'OIL' / + 'P-N-5' 'P-NORTH' 10 10 2000.15 'OIL' / + 'P-N-6' 'P-NORTH' 10 20 2000.15 'OIL' / + 'P-N-7' 'P-NORTH' 7 15 2000.15 'OIL' / + 'P-N-8' 'P-NORTH' 2 20 2000.15 'OIL' / + 'P-N-9' 'P-NORTH' 20 1 2000.05 'OIL' / +/ + +COMPDAT + 'I-N-1' 0 0 2 10 'OPEN' 1* 1* 1.0 / + 'I-N-2' 0 0 10 15 'OPEN' 1* 1* 1.0 / + 'P-N-0' 0 0 2 3 'OPEN' 1* 1* 1.0 / + 'P-N-1' 0 0 2 4 'OPEN' 1* 1* 1.0 / + 'P-N-2' 0 0 2 5 'OPEN' 1* 1* 1.0 / + 'P-N-3' 0 0 2 6 'OPEN' 1* 1* 1.0 / + 'P-N-4' 0 0 2 7 'OPEN' 1* 1* 1.0 / + 'P-N-5' 0 0 2 8 'OPEN' 1* 1* 1.0 / + 'P-N-6' 0 0 2 9 'OPEN' 1* 1* 1.0 / + 'P-N-7' 0 0 2 10 'OPEN' 1* 1* 1.0 / + 'P-N-8' 0 0 2 5 'OPEN' 1* 1* 1.0 / + 'P-N-9' 0 0 1 15 'OPEN' 1* 1* 1.0 / +/ + +WCONPROD +-- Well O/S Mode ORAT WRAT GRAT LRAT RESV BHP + 'P-N-*' 'OPEN' 'LRAT' 1* 1* 1* 5E3 1* 100 / +/ + +WCONINJE +-- Well Type O/S Mode RATE RESV BHP + 'I-N-*' 'WATER' 'OPEN' 'RATE' 25E3 1* 500 / +/ + +TSTEP +100*30 / + +END +)" }; + + return Opm::Parser{}.parseString(input); + } +} + +struct CaseObjects +{ + explicit CaseObjects(const Opm::Deck& deck); + ~CaseObjects(); + + CaseObjects(const CaseObjects& rhs) = default; + CaseObjects(CaseObjects&& rhs) = default; + + CaseObjects& operator=(const CaseObjects& rhs) = default; + CaseObjects& operator=(CaseObjects&& rhs) = default; + + Opm::ErrorGuard guard; + Opm::ParseContext ctxt; + Opm::EclipseState es; + Opm::Schedule sched; +}; + +CaseObjects::CaseObjects(const Opm::Deck& deck) + : guard{} + , ctxt {} + , es (deck, ctxt, guard) + , sched(deck, es, ctxt, guard) +{} + +CaseObjects::~CaseObjects() +{ + this->guard.clear(); +} + +class RedirectCERR +{ +public: + explicit RedirectCERR(std::streambuf* buf); + ~RedirectCERR(); + +private: + std::streambuf* orig_; +}; + +RedirectCERR::RedirectCERR(std::streambuf* buf) + : orig_{ std::cerr.rdbuf(buf) } +{ +} + +RedirectCERR::~RedirectCERR() +{ + std::cerr.rdbuf(this->orig_); +} + +// ==================================================================== + +BOOST_AUTO_TEST_SUITE(WellDimensions) + +BOOST_AUTO_TEST_CASE(MaxGroupSize) +{ + auto cse = CaseObjects{ simCaseWellDims() }; + + // Verify at most ten wells in a single group. + BOOST_CHECK_EQUAL(Opm::maxGroupSize(cse.sched, 1), 10); +} + +BOOST_AUTO_TEST_CASE(WellDims) +{ + auto cse = CaseObjects{ simCaseWellDims() }; + + // There should be no failures in basic input layer + BOOST_CHECK(!cse.guard); + + Opm::checkConsistentArrayDimensions(cse.es , cse.sched, + cse.ctxt, cse.guard); + + // There *should* be errors from dimension checking + BOOST_CHECK(cse.guard); + + // Verify that we get expected output from ErrorGuard::dump() + boost::test_tools::output_test_stream output{"expect-wdims.err.out", true}; + { + RedirectCERR stream(output.rdbuf()); + + cse.guard.dump(); + + BOOST_CHECK(output.match_pattern()); + } +} + +BOOST_AUTO_TEST_SUITE_END()