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()