add functionality to the parser for parsing selected sections of a data deck

- made available in python bindings
 - added unit tests both for c++ and python
This commit is contained in:
Torbjørn Skille 2021-06-20 21:26:42 +02:00
parent 8b625b6590
commit eded5fdb0c
12 changed files with 1276 additions and 13 deletions

View File

@ -471,6 +471,8 @@ if(ENABLE_ECL_OUTPUT)
tests/SPE1CASE1.DATA
tests/SPE1CASE1.SMSPEC
tests/SPE1CASE1A.SMSPEC
tests/SPE1CASE1B.DATA
tests/props_spe1case1b.inc
tests/SPE9_CP_PACKED.DATA
tests/SOFR_TEST.DATA
tests/UDQ_BASE.DATA

View File

@ -41,6 +41,13 @@ namespace Json {
namespace Opm {
namespace Ecl {
enum SectionType {
GRID, PROPS, REGIONS, SOLUTION, SUMMARY, SCHEDULE
};
};
class Deck;
class ParseContext;
class ErrorGuard;
@ -59,11 +66,17 @@ namespace Opm {
/// The starting point of the parsing process. The supplied file is parsed, and the resulting Deck is returned.
Deck parseFile(const std::string &dataFile,
const ParseContext&,
ErrorGuard& errors) const;
ErrorGuard& errors,
const std::vector<Opm::Ecl::SectionType>& sections = {}) const;
Deck parseFile(const std::string&,
const ParseContext&) const;
Deck parseFile(const std::string&,
const ParseContext&,
const std::vector<Opm::Ecl::SectionType>& sections
) const;
Deck parseFile(const std::string& datafile) const;
Deck parseString(const std::string &data,

View File

@ -51,11 +51,20 @@ void python::common::export_Parser(py::module& module) {
py::class_<ParserKeyword>(module, "ParserKeyword")
.def_property_readonly("name", &ParserKeyword::getName);
py::enum_<Opm::Ecl::SectionType>(module, "eclSectionType", py::arithmetic())
.value("GRID", Opm::Ecl::GRID)
.value("PROPS", Opm::Ecl::PROPS)
.value("REGIONS", Opm::Ecl::REGIONS)
.value("SOLUTION", Opm::Ecl::SOLUTION)
.value("SUMMARY", Opm::Ecl::SUMMARY)
.value("SCHEDULE", Opm::Ecl::SCHEDULE)
.export_values();
py::class_<Parser>(module, "Parser")
.def(py::init<bool>(), py::arg("add_default") = true)
.def("parse", py::overload_cast<const std::string&>(&Parser::parseFile, py::const_))
.def("parse" , py::overload_cast<const std::string&, const ParseContext&>(&Parser::parseFile, py::const_))
.def("parse" , py::overload_cast<const std::string&, const ParseContext&, const std::vector<Opm::Ecl::SectionType>&>(&Parser::parseFile, py::const_))
.def("parse_string", py::overload_cast<const std::string&>(&Parser::parseString, py::const_))
.def("parse_string", py::overload_cast<const std::string&, const ParseContext&>(&Parser::parseString, py::const_))
.def("add_keyword", py::overload_cast<ParserKeyword>(&Parser::addParserKeyword))

View File

@ -11,7 +11,7 @@
from __future__ import absolute_import
from .libopmcommon_python import action
from .libopmcommon_python import Parser, ParseContext, Builtin
from .libopmcommon_python import Parser, ParseContext, Builtin, eclSectionType
from .libopmcommon_python import DeckKeyword
from .libopmcommon_python import DeckItem

View File

@ -2,3 +2,4 @@ from opm._common import action
from opm._common import Parser
from opm._common import ParseContext
from opm._common import Builtin
from opm._common import eclSectionType

View File

@ -0,0 +1,351 @@
-- This reservoir simulation deck is made available under the Open Database
-- License: http://opendatacommons.org/licenses/odbl/1.0/. Any rights in
-- individual contents of the database are licensed under the Database Contents
-- License: http://opendatacommons.org/licenses/dbcl/1.0/
-- Copyright (C) 2015 Statoil
-- This simulation is based on the data given in
-- 'Comparison of Solutions to a Three-Dimensional
-- Black-Oil Reservoir Simulation Problem' by Aziz S. Odeh,
-- Journal of Petroleum Technology, January 1981
---------------------------------------------------------------------------
------------------------ SPE1 - CASE 1 ------------------------------------
---------------------------------------------------------------------------
RUNSPEC
-- -------------------------------------------------------------------------
TITLE
SPE1 - CASE 1
DIMENS
10 10 3 /
-- The number of equilibration regions is inferred from the EQLDIMS
-- keyword.
EQLDIMS
/
-- The number of PVTW tables is inferred from the TABDIMS keyword;
-- when no data is included in the keyword the default values are used.
TABDIMS
/
OIL
GAS
WATER
DISGAS
-- As seen from figure 4 in Odeh, GOR is increasing with time,
-- which means that dissolved gas is present
FIELD
START
1 'JAN' 2015 /
WELLDIMS
-- Item 1: maximum number of wells in the model
-- - there are two wells in the problem; injector and producer
-- Item 2: maximum number of grid blocks connected to any one well
-- - must be one as the wells are located at specific grid blocks
-- Item 3: maximum number of groups in the model
-- - we are dealing with only one 'group'
-- Item 4: maximum number of wells in any one group
-- - there must be two wells in a group as there are two wells in total
4 1 1 4 /
UNIFOUT
GRID
-- The INIT keyword is used to request an .INIT file. The .INIT file
-- is written before the simulation actually starts, and contains grid
-- properties and saturation tables as inferred from the input
-- deck. There are no other keywords which can be used to configure
-- exactly what is written to the .INIT file.
INIT
-- -------------------------------------------------------------------------
NOECHO
DX
-- There are in total 300 cells with length 1000ft in x-direction
300*1000 /
DY
-- There are in total 300 cells with length 1000ft in y-direction
300*1000 /
DZ
-- The layers are 20, 30 and 50 ft thick, in each layer there are 100 cells
100*20 100*30 100*50 /
TOPS
-- The depth of the top of each grid block
100*8325 /
PORO
-- Constant porosity of 0.3 throughout all 300 grid cells
300*0.3 /
PERMX
-- The layers have perm. 500mD, 50mD and 200mD, respectively.
100*500 100*50 100*200 /
PERMY
-- Equal to PERMX
100*500 100*50 100*200 /
PERMZ
-- Cannot find perm. in z-direction in Odeh's paper
-- For the time being, we will assume PERMZ equal to PERMX and PERMY:
100*500 100*50 100*200 /
ECHO
-- PROPS section keyword "hidden" in include file
-- not able to parse individual sections
INCLUDE
'props_spe1case1b.inc' /
REGIONS
FIPNUM
300*1 /
SOLUTION
-- -------------------------------------------------------------------------
EQUIL
-- Item 1: datum depth (ft)
-- Item 2: pressure at datum depth (psia)
-- - Odeh's table 1 says that initial reservoir pressure is
-- 4800 psi at 8400ft, which explains choice of item 1 and 2
-- Item 3: depth of water-oil contact (ft)
-- - chosen to be directly under the reservoir
-- Item 4: oil-water capillary pressure at the water oil contact (psi)
-- - given to be 0 in Odeh's paper
-- Item 5: depth of gas-oil contact (ft)
-- - chosen to be directly above the reservoir
-- Item 6: gas-oil capillary pressure at gas-oil contact (psi)
-- - given to be 0 in Odeh's paper
-- Item 7: RSVD-table
-- Item 8: RVVD-table
-- Item 9: Set to 0 as this is the only value supported by OPM
-- Item #: 1 2 3 4 5 6 7 8 9
8400 4800 8450 0 8300 0 1 0 0 /
RSVD
-- Dissolved GOR is initially constant with depth through the reservoir.
-- The reason is that the initial reservoir pressure given is higher
---than the bubble point presssure of 4014.7psia, meaning that there is no
-- free gas initially present.
8300 1.270
8450 1.270 /
SUMMARY
-- -------------------------------------------------------------------------
DATE
RPR__NUM
/
RUNSUM
FMWPA
FMWIA
-- 1a) Oil rate vs time
FOPR
-- Field Oil Production Rate
-- 1b) GOR vs time
WGOR
-- Well Gas-Oil Ratio
'PROD'
/
-- Using FGOR instead of WGOR:PROD results in the same graph
FGOR
-- 2a) Pressures of the cell where the injector and producer are located
BPR
1 1 1 /
10 10 3 /
/
-- 2b) Gas saturation at grid points given in Odeh's paper
BGSAT
1 1 1 /
1 1 2 /
1 1 3 /
10 1 1 /
10 1 2 /
10 1 3 /
10 10 1 /
10 10 2 /
10 10 3 /
/
-- In order to compare Eclipse with Flow:
WBHP
'INJ'
'PROD'
/
WGIR
'INJ'
'PROD'
/
WGIT
'INJ'
'PROD'
/
WGPR
'INJ'
'PROD'
/
WGPT
'INJ'
'PROD'
/
WOIR
'INJ'
'PROD'
/
WOIT
'INJ'
'PROD'
/
WOPR
'INJ'
'PROD'
/
WOPT
'INJ'
'PROD'
/
WWIR
'INJ'
'PROD'
/
WWIT
'INJ'
'PROD'
/
WWPR
'INJ'
'PROD'
/
WWPT
'INJ'
'PROD'
/
SCHEDULE
-- -------------------------------------------------------------------------
RPTSCHED
'PRES' 'SGAS' 'RS' 'WELLS' 'WELSPECS' /
RPTRST
'BASIC=1' /
-- If no resolution (i.e. case 1), the two following lines must be added:
DRSDT
0 /
-- if DRSDT is set to 0, GOR cannot rise and free gas does not
-- dissolve in undersaturated oil -> constant bubble point pressure
WELSPECS
-- Item #: 1 2 3 4 5 6
'PROD' 'G1' 10 10 8400 'OIL' /
'INJ' 'G1' 1 1 8335 'GAS' /
'RFTP' 'G1' 10 10 8400 'OIL' /
'RFTI' 'G1' 9 9 8400 'WATER' /
/
-- Coordinates in item 3-4 are retrieved from Odeh's figure 1 and 2
-- Note that the depth at the midpoint of the well grid blocks
-- has been used as reference depth for bottom hole pressure in item 5
COMPDAT
-- Item #: 1 2 3 4 5 6 7 8 9
'PROD' 10 10 3 3 'OPEN' 1* 1* 0.5 /
'RFTP' 10 10 3 3 'OPEN' 1* 1* 0.5 /
'RFTI' 9 9 3 3 'OPEN' 1* 1* 0.5 /
'INJ' 1 1 1 1 'OPEN' 1* 1* 0.5 /
/
-- Coordinates in item 2-5 are retreived from Odeh's figure 1 and 2
-- Item 9 is the well bore internal diameter,
-- the radius is given to be 0.25ft in Odeh's paper
WCONPROD
-- Item #:1 2 3 4 5 9
'PROD' 'OPEN' 'ORAT' 20000 4* 1000 /
'RFTP' 'SHUT' 'ORAT' 20000 4* 1000 /
/
-- It is stated in Odeh's paper that the maximum oil prod. rate
-- is 20 000stb per day which explains the choice of value in item 4.
-- The items > 4 are defaulted with the exception of item 9,
-- the BHP lower limit, which is given to be 1000psia in Odeh's paper
WCONINJE
-- Item #:1 2 3 4 5 6 7
'INJ' 'GAS' 'OPEN' 'RATE' 100000 1* 9014 /
'RFTI' 'GAS' 'SHUT' 'RATE' 0 /
/
-- Stated in Odeh that gas inj. rate (item 5) is 100MMscf per day
-- BHP upper limit (item 7) should not be exceeding the highest
-- pressure in the PVT table=9014.7psia (default is 100 000psia)
TSTEP
--Advance the simulater once a month for TEN years:
31 28 31 30 31 30 31 31 30 31 30 31 /
WELOPEN
'RFTP' OPEN /
'RFTI' OPEN /
/
WCONHIST
'RFTP' 'OPEN' 'RESV' 0 /
/
WCONINJE
'RFTI' 'GAS' 'OPEN' 'RATE' 0 /
/
TSTEP
31 /
WELOPEN
'RFTP' 'SHUT' /
'RFTI' 'STOP' /
/
TSTEP
28 31 30 31 30 31 31 30 31 30 31
31 28 31 30 31 30 31 31 30 31 30 31
31 28 31 30 31 30 31 31 30 31 30 31
31 /
DATES
28 'FEB' 2019 /
/
TSTEP
31 30 31 30 31 31 30 31 30 31
31 28 31 30 31 30 31 31 30 31 30 31
31 28 31 30 31 30 31 31 30 31 30 31
31 28 31 30 31 30 31 31 30 31 30 31
31 28 31 30 31 30 31 31 30 31 30 31
31 28 31 30 31 30 31 31 30 31 30 31 /
--Advance the simulator once a year for TEN years:
--10*365 /
END

View File

@ -0,0 +1,156 @@
-- This reservoir simulation deck is made available under the Open Database
-- License: http://opendatacommons.org/licenses/odbl/1.0/. Any rights in
-- individual contents of the database are licensed under the Database Contents
-- License: http://opendatacommons.org/licenses/dbcl/1.0/
-- Copyright (C) 2015 Statoil
-- This simulation is based on the data given in
-- 'Comparison of Solutions to a Three-Dimensional
-- Black-Oil Reservoir Simulation Problem' by Aziz S. Odeh,
-- Journal of Petroleum Technology, January 1981
PROPS
-- -------------------------------------------------------------------------
PVTW
-- Item 1: pressure reference (psia)
-- Item 2: water FVF (rb per bbl or rb per stb)
-- Item 3: water compressibility (psi^{-1})
-- Item 4: water viscosity (cp)
-- Item 5: water 'viscosibility' (psi^{-1})
-- Using values from Norne:
-- In METRIC units:
-- 277.0 1.038 4.67E-5 0.318 0.0 /
-- In FIELD units:
4017.55 1.038 3.22E-6 0.318 0.0 /
ROCK
-- Item 1: reference pressure (psia)
-- Item 2: rock compressibility (psi^{-1})
-- Using values from table 1 in Odeh:
14.7 3E-6 /
SWOF
-- Column 1: water saturation
-- - this has been set to (almost) equally spaced values from 0.12 to 1
-- Column 2: water relative permeability
-- - generated from the Corey-type approx. formula
-- the coeffisient is set to 10e-5, S_{orw}=0 and S_{wi}=0.12
-- Column 3: oil relative permeability when only oil and water are present
-- - we will use the same values as in column 3 in SGOF.
-- This is not really correct, but since only the first
-- two values are of importance, this does not really matter
-- Column 4: water-oil capillary pressure (psi)
0.12 0 1 0
0.18 4.64876033057851E-008 1 0
0.24 0.000000186 0.997 0
0.3 4.18388429752066E-007 0.98 0
0.36 7.43801652892562E-007 0.7 0
0.42 1.16219008264463E-006 0.35 0
0.48 1.67355371900826E-006 0.2 0
0.54 2.27789256198347E-006 0.09 0
0.6 2.97520661157025E-006 0.021 0
0.66 3.7654958677686E-006 0.01 0
0.72 4.64876033057851E-006 0.001 0
0.78 0.000005625 0.0001 0
0.84 6.69421487603306E-006 0 0
0.91 8.05914256198347E-006 0 0
1 0.00001 0 0 /
SGOF
-- Column 1: gas saturation
-- Column 2: gas relative permeability
-- Column 3: oil relative permeability when oil, gas and connate water are present
-- Column 4: oil-gas capillary pressure (psi)
-- - stated to be zero in Odeh's paper
-- Values in column 1-3 are taken from table 3 in Odeh's paper:
0 0 1 0
0.001 0 1 0
0.02 0 0.997 0
0.05 0.005 0.980 0
0.12 0.025 0.700 0
0.2 0.075 0.350 0
0.25 0.125 0.200 0
0.3 0.190 0.090 0
0.4 0.410 0.021 0
0.45 0.60 0.010 0
0.5 0.72 0.001 0
0.6 0.87 0.0001 0
0.7 0.94 0.000 0
0.85 0.98 0.000 0
0.88 0.984 0.000 0 /
--1.00 1.0 0.000 0 /
-- Warning from Eclipse: first sat. value in SWOF + last sat. value in SGOF
-- must not be greater than 1, but Eclipse still runs
-- Flow needs the sum to be excactly 1 so I added a row with gas sat. = 0.88
-- The corresponding krg value was estimated by assuming linear rel. between
-- gas sat. and krw. between gas sat. 0.85 and 1.00 (the last two values given)
DENSITY
-- Density (lb per ft³) at surface cond. of
-- oil, water and gas, respectively (in that order)
-- Using values from Norne:
-- In METRIC units:
-- 859.5 1033.0 0.854 /
-- In FIELD units:
53.66 64.49 0.0533 /
PVDG
-- Column 1: gas phase pressure (psia)
-- Column 2: gas formation volume factor (rb per Mscf)
-- - in Odeh's paper the units are said to be given in rb per bbl,
-- but this is assumed to be a mistake: FVF-values in Odeh's paper
-- are given in rb per scf, not rb per bbl. This will be in
-- agreement with conventions
-- Column 3: gas viscosity (cP)
-- Using values from lower right table in Odeh's table 2:
14.700 166.666 0.008000
264.70 12.0930 0.009600
514.70 6.27400 0.011200
1014.7 3.19700 0.014000
2014.7 1.61400 0.018900
2514.7 1.29400 0.020800
3014.7 1.08000 0.022800
4014.7 0.81100 0.026800
5014.7 0.64900 0.030900
9014.7 0.38600 0.047000 /
PVTO
-- Column 1: dissolved gas-oil ratio (Mscf per stb)
-- Column 2: bubble point pressure (psia)
-- Column 3: oil FVF for saturated oil (rb per stb)
-- Column 4: oil viscosity for saturated oil (cP)
-- Use values from top left table in Odeh's table 2:
0.0010 14.7 1.0620 1.0400 /
0.0905 264.7 1.1500 0.9750 /
0.1800 514.7 1.2070 0.9100 /
0.3710 1014.7 1.2950 0.8300 /
0.6360 2014.7 1.4350 0.6950 /
0.7750 2514.7 1.5000 0.6410 /
0.9300 3014.7 1.5650 0.5940 /
1.2700 4014.7 1.6950 0.5100
9014.7 1.5790 0.7400 /
1.6180 5014.7 1.8270 0.4490
9014.7 1.7370 0.6310 /
-- It is required to enter data for undersaturated oil for the highest GOR
-- (i.e. the last row) in the PVTO table.
-- In order to fulfill this requirement, values for oil FVF and viscosity
-- at 9014.7psia and GOR=1.618 for undersaturated oil have been approximated:
-- It has been assumed that there is a linear relation between the GOR
-- and the FVF when keeping the pressure constant at 9014.7psia.
-- From Odeh we know that (at 9014.7psia) the FVF is 2.357 at GOR=2.984
-- for saturated oil and that the FVF is 1.579 at GOR=1.27 for undersaturated oil,
-- so it is possible to use the assumption described above.
-- An equivalent approximation for the viscosity has been used.
/

View File

@ -10,9 +10,10 @@ try:
except ImportError:
from utils import test_path
from opm.io.parser import Parser, ParseContext
from opm.io.parser import Parser, ParseContext, eclSectionType
from opm.io.deck import DeckKeyword
class TestParse(unittest.TestCase):
DECK_ADDITIONAL_KEYWORDS = """
@ -149,6 +150,59 @@ FIPNUM
self.assertEqual(eqlnum.dtype, "int32")
def test_parser_section_deckItems(self):
all_spe1case1 = ["RUNSPEC", "TITLE", "DIMENS", "EQLDIMS", "TABDIMS", "REGDIMS", "OIL", "GAS",
"WATER", "DISGAS", "FIELD", "START", "WELLDIMS", "UNIFOUT", "UDQDIMS", "UDADIMS",
"GRID", "INIT", "NOECHO", "DX", "DY", "DZ", "TOPS", "PORO", "PERMX", "PERMY",
"PERMZ", "ECHO", "PROPS", "PVTW", "ROCK", "SWOF", "SGOF", "DENSITY", "PVDG",
"PVTO", "REGIONS", "EQLNUM", "FIPNUM", "SOLUTION", "EQUIL", "RSVD", "SUMMARY",
"FOPR", "WGOR", "FGOR", "BPR", "BGSAT", "WBHP", "WGIR", "WGIT", "WGPR", "WGPT",
"WOIR", "WOIT", "WOPR", "WOPT", "WWIR", "WWIT", "WWPR", "WWPT", "WUOPRL", "SCHEDULE",
"UDQ", "RPTSCHED", "RPTRST", "DRSDT", "WELSPECS", "COMPDAT", "WCONPROD", "WCONINJE", "TSTEP"]
# notice that RUNSPEC keywords will always be parsed since these properties from these keyword
# are needed to parse following sections.
props_spe1case1 = ["RUNSPEC", "TITLE", "DIMENS", "EQLDIMS", "TABDIMS", "REGDIMS", "OIL", "GAS",
"WATER", "DISGAS", "FIELD", "START", "WELLDIMS", "UNIFOUT", "UDQDIMS", "UDADIMS", "GRID",
"PVTW", "ROCK", "SWOF", "SGOF", "DENSITY", "PVDG",
"PVTO"]
parser = Parser()
error_recovery = [("PARSE_RANDOM_SLASH", opm.io.action.ignore),
("PARSE_EXTRA_RECORDS", opm.io.action.ignore)]
context = ParseContext(error_recovery)
deck1 = parser.parse(test_path("data/SPE1CASE1.DATA"), context)
self.assertEqual( len(deck1), len(all_spe1case1))
test_1 = [dkw.name for dkw in deck1]
for test, ref in zip(test_1, all_spe1case1):
self.assertEqual( test, ref)
section_list = [eclSectionType.PROPS]
deck2 = parser.parse(test_path("data/SPE1CASE1.DATA"), context, section_list)
self.assertEqual( len(deck2), len(props_spe1case1))
test_2 = [dkw.name for dkw in deck2]
for test, ref in zip(test_2, props_spe1case1):
self.assertEqual( test, ref)
# props section keyword located in include file for this deck (SPE1CASE1B.DATA)
# not possible to parse individual sections
with self.assertRaises(RuntimeError):
parser.parse(test_path("data/SPE1CASE1B.DATA"), context, section_list)
if __name__ == "__main__":
unittest.main()

View File

@ -26,6 +26,7 @@
#include <string>
#include <utility>
#include <vector>
#include <algorithm>
#include <fmt/format.h>
@ -360,8 +361,13 @@ void InputStack::push( std::string&& input, Opm::filesystem::path p ) {
class ParserState {
public:
ParserState( const std::vector<std::pair<std::string,std::string>>&, const ParseContext&, ErrorGuard& );
ParserState( const std::vector<std::pair<std::string,std::string>>&, const ParseContext&, ErrorGuard&, Opm::filesystem::path );
ParserState( const std::vector<std::pair<std::string,std::string>>&,
const ParseContext&, ErrorGuard&,
const std::set<Opm::Ecl::SectionType>& ignore = {});
ParserState( const std::vector<std::pair<std::string,std::string>>&,
const ParseContext&, ErrorGuard&,
Opm::filesystem::path, const std::set<Opm::Ecl::SectionType>& ignore = {});
void loadString( const std::string& );
void loadFile( const Opm::filesystem::path& );
@ -379,10 +385,14 @@ class ParserState {
void ungetline(const std::string_view& ln);
void closeFile();
const std::set<Opm::Ecl::SectionType>& get_ignore() {return ignore_sections; };
bool check_section_keywords();
private:
const std::vector<std::pair<std::string, std::string>> code_keywords;
InputStack input_stack;
std::set<Opm::Ecl::SectionType> ignore_sections;
std::map< std::string, std::string > pathMap;
public:
@ -443,26 +453,63 @@ void ParserState::closeFile() {
ParserState::ParserState(const std::vector<std::pair<std::string, std::string>>& code_keywords_arg,
const ParseContext& __parseContext,
ErrorGuard& errors_arg) :
ErrorGuard& errors_arg,
const std::set<Opm::Ecl::SectionType>& ignore) :
code_keywords(code_keywords_arg),
python( std::make_unique<Python>() ),
parseContext( __parseContext ),
errors( errors_arg )
errors( errors_arg ),
ignore_sections(ignore)
{}
ParserState::ParserState( const std::vector<std::pair<std::string, std::string>>& code_keywords_arg,
const ParseContext& context,
ErrorGuard& errors_arg,
Opm::filesystem::path p ) :
Opm::filesystem::path p,
const std::set<Opm::Ecl::SectionType>& ignore ) :
code_keywords(code_keywords_arg),
rootPath( Opm::filesystem::canonical( p ).parent_path() ),
python( std::make_unique<Python>() ),
parseContext( context ),
errors( errors_arg )
errors( errors_arg ),
ignore_sections(ignore)
{
openRootFile( p );
}
bool ParserState::check_section_keywords() {
std::string_view root_file_str = this->input_stack.top().input;
int n = 0;
int p0 = root_file_str.find_first_not_of(" \t\n");
while (p0 != std::string::npos){
int p1 = root_file_str.find_first_of(" \t\n", p0 + 1);
if (root_file_str.substr(p0, p1-p0) == "GRID")
n++;
else if (root_file_str.substr(p0, p1-p0) == "PROPS")
n++;
else if (root_file_str.substr(p0, p1-p0) == "REGIONS")
n++;
else if (root_file_str.substr(p0, p1-p0) == "SOLUTION")
n++;
else if (root_file_str.substr(p0, p1-p0) == "SUMMARY")
n++;
else if (root_file_str.substr(p0, p1-p0) == "SCHEDULE")
n++;
p0 = root_file_str.find_first_not_of(" \t\n", p1);
}
if (n < 6)
return false;
else
return true;
}
void ParserState::loadString(const std::string& input) {
this->input_stack.push( str::clean( this->code_keywords, input + "\n" ) );
}
@ -542,6 +589,7 @@ void ParserState::handleRandomText(const std::string_view& keywordString ) const
void ParserState::openRootFile( const Opm::filesystem::path& inputFile) {
this->loadFile( inputFile );
this->deck.setDataFile( inputFile.string() );
const Opm::filesystem::path& inputFileCanonical = Opm::filesystem::canonical(inputFile);
@ -885,15 +933,59 @@ std::unique_ptr<RawKeyword> tryParseKeyword( ParserState& parserState, const Par
return rawKeyword;
}
std::string_view advance_parser_state( ParserState& parserState, const std::string& to_keyw )
{
auto line = parserState.getline();
while (line != to_keyw) {
line = parserState.getline();
}
return line;
}
bool parseState( ParserState& parserState, const Parser& parser ) {
std::string filename = parserState.current_path().string();
auto ignore = parserState.get_ignore();
if (ignore.size() > 0)
if (!parserState.check_section_keywords())
throw std::runtime_error("Parsing individual sections not posible when section keywords in root input file");
bool ignore_grid = ignore.find(Opm::Ecl::GRID) !=ignore.end() ? true : false;
bool ignore_props = ignore.find(Opm::Ecl::PROPS) !=ignore.end() ? true : false;
bool ignore_regions = ignore.find(Opm::Ecl::REGIONS) !=ignore.end() ? true : false;
bool ignore_solution = ignore.find(Opm::Ecl::SOLUTION) !=ignore.end() ? true : false;
bool ignore_summary = ignore.find(Opm::Ecl::SUMMARY) !=ignore.end() ? true : false;
bool ignore_schedule = ignore.find(Opm::Ecl::SCHEDULE) !=ignore.end() ? true : false;
while( !parserState.done() ) {
auto rawKeyword = tryParseKeyword( parserState, parser);
if( !rawKeyword )
continue;
std::string_view keyw = rawKeyword->getKeywordName();
if ((ignore_grid) && (keyw=="GRID"))
keyw = advance_parser_state( parserState, "PROPS" );
if ((ignore_props) && (keyw=="PROPS"))
keyw = advance_parser_state( parserState, "REGIONS" );
if ((ignore_regions) && (keyw=="REGIONS"))
keyw = advance_parser_state( parserState, "SOLUTION" );
if ((ignore_solution) && (keyw=="SOLUTION"))
keyw = advance_parser_state( parserState, "SUMMARY" );
if ((ignore_summary) && (keyw=="SUMMARY"))
keyw = advance_parser_state( parserState, "SCHEDULE" );
if ((ignore_schedule) && (keyw=="SCHEDULE"))
return true;
if (rawKeyword->getKeywordName() == Opm::RawConsts::end)
return true;
@ -1002,6 +1094,7 @@ bool parseState( ParserState& parserState, const Parser& parser ) {
// The addDefaultKeywords() method is implemented in a source file
// ${PROJECT_BINARY_DIR}/ParserInit.cpp which is generated by the build
// system.
if (addDefault)
this->addDefaultKeywords();
}
@ -1059,17 +1152,41 @@ bool parseState( ParserState& parserState, const Parser& parser ) {
return parse(deck, context).getInputGrid();
}
Deck Parser::parseFile(const std::string &dataFileName, const ParseContext& parseContext, ErrorGuard& errors) const {
ParserState parserState( this->codeKeywords(), parseContext, errors, dataFileName );
parseState( parserState, *this );
Deck Parser::parseFile(const std::string &dataFileName, const ParseContext& parseContext,
ErrorGuard& errors, const std::vector<Opm::Ecl::SectionType>& sections) const {
std::set<Opm::Ecl::SectionType> ignore_sections;
if (sections.size() > 0) {
std::set<Opm::Ecl::SectionType> all_sections;
all_sections = {Opm::Ecl::GRID, Opm::Ecl::PROPS, Opm::Ecl::REGIONS, Opm::Ecl::SOLUTION, Opm::Ecl::SUMMARY, Opm::Ecl::SCHEDULE};
std::set<Opm::Ecl::SectionType> read_sections;
for (auto sec : sections)
read_sections.insert(sec);
std::set_difference(all_sections.begin(), all_sections.end(), read_sections.begin(), read_sections.end(),
std::inserter(ignore_sections, ignore_sections.end()));
}
ParserState parserState( this->codeKeywords(), parseContext, errors, dataFileName, ignore_sections);
parseState( parserState, *this );
return std::move( parserState.deck );
}
Deck Parser::parseFile(const std::string& dataFileName,
const ParseContext& parseContext) const {
ErrorGuard errors;
return this->parseFile(dataFileName, parseContext, errors);
return this->parseFile(dataFileName, parseContext, errors, {});
}
Deck Parser::parseFile(const std::string& dataFileName,
const ParseContext& parseContext,
const std::vector<Opm::Ecl::SectionType>& sections) const {
ErrorGuard errors;
return this->parseFile(dataFileName, parseContext, errors, sections);
}
Deck Parser::parseFile(const std::string& dataFileName) const {

351
tests/SPE1CASE1B.DATA Normal file
View File

@ -0,0 +1,351 @@
-- This reservoir simulation deck is made available under the Open Database
-- License: http://opendatacommons.org/licenses/odbl/1.0/. Any rights in
-- individual contents of the database are licensed under the Database Contents
-- License: http://opendatacommons.org/licenses/dbcl/1.0/
-- Copyright (C) 2015 Statoil
-- This simulation is based on the data given in
-- 'Comparison of Solutions to a Three-Dimensional
-- Black-Oil Reservoir Simulation Problem' by Aziz S. Odeh,
-- Journal of Petroleum Technology, January 1981
---------------------------------------------------------------------------
------------------------ SPE1 - CASE 1 ------------------------------------
---------------------------------------------------------------------------
RUNSPEC
-- -------------------------------------------------------------------------
TITLE
SPE1 - CASE 1
DIMENS
10 10 3 /
-- The number of equilibration regions is inferred from the EQLDIMS
-- keyword.
EQLDIMS
/
-- The number of PVTW tables is inferred from the TABDIMS keyword;
-- when no data is included in the keyword the default values are used.
TABDIMS
/
OIL
GAS
WATER
DISGAS
-- As seen from figure 4 in Odeh, GOR is increasing with time,
-- which means that dissolved gas is present
FIELD
START
1 'JAN' 2015 /
WELLDIMS
-- Item 1: maximum number of wells in the model
-- - there are two wells in the problem; injector and producer
-- Item 2: maximum number of grid blocks connected to any one well
-- - must be one as the wells are located at specific grid blocks
-- Item 3: maximum number of groups in the model
-- - we are dealing with only one 'group'
-- Item 4: maximum number of wells in any one group
-- - there must be two wells in a group as there are two wells in total
4 1 1 4 /
UNIFOUT
GRID
-- The INIT keyword is used to request an .INIT file. The .INIT file
-- is written before the simulation actually starts, and contains grid
-- properties and saturation tables as inferred from the input
-- deck. There are no other keywords which can be used to configure
-- exactly what is written to the .INIT file.
INIT
-- -------------------------------------------------------------------------
NOECHO
DX
-- There are in total 300 cells with length 1000ft in x-direction
300*1000 /
DY
-- There are in total 300 cells with length 1000ft in y-direction
300*1000 /
DZ
-- The layers are 20, 30 and 50 ft thick, in each layer there are 100 cells
100*20 100*30 100*50 /
TOPS
-- The depth of the top of each grid block
100*8325 /
PORO
-- Constant porosity of 0.3 throughout all 300 grid cells
300*0.3 /
PERMX
-- The layers have perm. 500mD, 50mD and 200mD, respectively.
100*500 100*50 100*200 /
PERMY
-- Equal to PERMX
100*500 100*50 100*200 /
PERMZ
-- Cannot find perm. in z-direction in Odeh's paper
-- For the time being, we will assume PERMZ equal to PERMX and PERMY:
100*500 100*50 100*200 /
ECHO
-- PROPS section keyword "hidden" in include file
-- not able to parse individual sections
INCLUDE
'props_spe1case1b.inc' /
REGIONS
FIPNUM
300*1 /
SOLUTION
-- -------------------------------------------------------------------------
EQUIL
-- Item 1: datum depth (ft)
-- Item 2: pressure at datum depth (psia)
-- - Odeh's table 1 says that initial reservoir pressure is
-- 4800 psi at 8400ft, which explains choice of item 1 and 2
-- Item 3: depth of water-oil contact (ft)
-- - chosen to be directly under the reservoir
-- Item 4: oil-water capillary pressure at the water oil contact (psi)
-- - given to be 0 in Odeh's paper
-- Item 5: depth of gas-oil contact (ft)
-- - chosen to be directly above the reservoir
-- Item 6: gas-oil capillary pressure at gas-oil contact (psi)
-- - given to be 0 in Odeh's paper
-- Item 7: RSVD-table
-- Item 8: RVVD-table
-- Item 9: Set to 0 as this is the only value supported by OPM
-- Item #: 1 2 3 4 5 6 7 8 9
8400 4800 8450 0 8300 0 1 0 0 /
RSVD
-- Dissolved GOR is initially constant with depth through the reservoir.
-- The reason is that the initial reservoir pressure given is higher
---than the bubble point presssure of 4014.7psia, meaning that there is no
-- free gas initially present.
8300 1.270
8450 1.270 /
SUMMARY
-- -------------------------------------------------------------------------
DATE
RPR__NUM
/
RUNSUM
FMWPA
FMWIA
-- 1a) Oil rate vs time
FOPR
-- Field Oil Production Rate
-- 1b) GOR vs time
WGOR
-- Well Gas-Oil Ratio
'PROD'
/
-- Using FGOR instead of WGOR:PROD results in the same graph
FGOR
-- 2a) Pressures of the cell where the injector and producer are located
BPR
1 1 1 /
10 10 3 /
/
-- 2b) Gas saturation at grid points given in Odeh's paper
BGSAT
1 1 1 /
1 1 2 /
1 1 3 /
10 1 1 /
10 1 2 /
10 1 3 /
10 10 1 /
10 10 2 /
10 10 3 /
/
-- In order to compare Eclipse with Flow:
WBHP
'INJ'
'PROD'
/
WGIR
'INJ'
'PROD'
/
WGIT
'INJ'
'PROD'
/
WGPR
'INJ'
'PROD'
/
WGPT
'INJ'
'PROD'
/
WOIR
'INJ'
'PROD'
/
WOIT
'INJ'
'PROD'
/
WOPR
'INJ'
'PROD'
/
WOPT
'INJ'
'PROD'
/
WWIR
'INJ'
'PROD'
/
WWIT
'INJ'
'PROD'
/
WWPR
'INJ'
'PROD'
/
WWPT
'INJ'
'PROD'
/
SCHEDULE
-- -------------------------------------------------------------------------
RPTSCHED
'PRES' 'SGAS' 'RS' 'WELLS' 'WELSPECS' /
RPTRST
'BASIC=1' /
-- If no resolution (i.e. case 1), the two following lines must be added:
DRSDT
0 /
-- if DRSDT is set to 0, GOR cannot rise and free gas does not
-- dissolve in undersaturated oil -> constant bubble point pressure
WELSPECS
-- Item #: 1 2 3 4 5 6
'PROD' 'G1' 10 10 8400 'OIL' /
'INJ' 'G1' 1 1 8335 'GAS' /
'RFTP' 'G1' 10 10 8400 'OIL' /
'RFTI' 'G1' 9 9 8400 'WATER' /
/
-- Coordinates in item 3-4 are retrieved from Odeh's figure 1 and 2
-- Note that the depth at the midpoint of the well grid blocks
-- has been used as reference depth for bottom hole pressure in item 5
COMPDAT
-- Item #: 1 2 3 4 5 6 7 8 9
'PROD' 10 10 3 3 'OPEN' 1* 1* 0.5 /
'RFTP' 10 10 3 3 'OPEN' 1* 1* 0.5 /
'RFTI' 9 9 3 3 'OPEN' 1* 1* 0.5 /
'INJ' 1 1 1 1 'OPEN' 1* 1* 0.5 /
/
-- Coordinates in item 2-5 are retreived from Odeh's figure 1 and 2
-- Item 9 is the well bore internal diameter,
-- the radius is given to be 0.25ft in Odeh's paper
WCONPROD
-- Item #:1 2 3 4 5 9
'PROD' 'OPEN' 'ORAT' 20000 4* 1000 /
'RFTP' 'SHUT' 'ORAT' 20000 4* 1000 /
/
-- It is stated in Odeh's paper that the maximum oil prod. rate
-- is 20 000stb per day which explains the choice of value in item 4.
-- The items > 4 are defaulted with the exception of item 9,
-- the BHP lower limit, which is given to be 1000psia in Odeh's paper
WCONINJE
-- Item #:1 2 3 4 5 6 7
'INJ' 'GAS' 'OPEN' 'RATE' 100000 1* 9014 /
'RFTI' 'GAS' 'SHUT' 'RATE' 0 /
/
-- Stated in Odeh that gas inj. rate (item 5) is 100MMscf per day
-- BHP upper limit (item 7) should not be exceeding the highest
-- pressure in the PVT table=9014.7psia (default is 100 000psia)
TSTEP
--Advance the simulater once a month for TEN years:
31 28 31 30 31 30 31 31 30 31 30 31 /
WELOPEN
'RFTP' OPEN /
'RFTI' OPEN /
/
WCONHIST
'RFTP' 'OPEN' 'RESV' 0 /
/
WCONINJE
'RFTI' 'GAS' 'OPEN' 'RATE' 0 /
/
TSTEP
31 /
WELOPEN
'RFTP' 'SHUT' /
'RFTI' 'STOP' /
/
TSTEP
28 31 30 31 30 31 31 30 31 30 31
31 28 31 30 31 30 31 31 30 31 30 31
31 28 31 30 31 30 31 31 30 31 30 31
31 /
DATES
28 'FEB' 2019 /
/
TSTEP
31 30 31 30 31 31 30 31 30 31
31 28 31 30 31 30 31 31 30 31 30 31
31 28 31 30 31 30 31 31 30 31 30 31
31 28 31 30 31 30 31 31 30 31 30 31
31 28 31 30 31 30 31 31 30 31 30 31
31 28 31 30 31 30 31 31 30 31 30 31 /
--Advance the simulator once a year for TEN years:
--10*365 /
END

View File

@ -2375,3 +2375,56 @@ GUIDERAT
auto deck = parser.parseString(deck_string);
BOOST_CHECK( deck.hasKeyword("GUIDERAT") );
}
BOOST_AUTO_TEST_CASE(parseSections) {
Opm::Parser parser;
Opm::ParseContext parseContext;
parseContext.update(Opm::ParseContext::PARSE_EXTRA_DATA , Opm::InputError::IGNORE );
parseContext.update(Opm::ParseContext::PARSE_EXTRA_RECORDS , Opm::InputError::IGNORE );
parseContext.update(Opm::ParseContext::PARSE_RANDOM_SLASH , Opm::InputError::IGNORE );
const auto deck_all = parser.parseFile("./tests/SPE1CASE1.DATA", parseContext);
BOOST_CHECK_EQUAL( deck_all.size(), 79 );
std::vector<Opm::Ecl::SectionType> grid_section = {Opm::Ecl::GRID};
std::vector<Opm::Ecl::SectionType> props_section = {Opm::Ecl::PROPS};
std::vector<Opm::Ecl::SectionType> regions_section = {Opm::Ecl::REGIONS};
std::vector<Opm::Ecl::SectionType> solution_section = {Opm::Ecl::SOLUTION};
std::vector<Opm::Ecl::SectionType> summary_section = {Opm::Ecl::SUMMARY};
std::vector<Opm::Ecl::SectionType> schedule_section = {Opm::Ecl::SCHEDULE};
std::vector<Opm::Ecl::SectionType> test1 = {Opm::Ecl::PROPS, Opm::Ecl::SOLUTION};
const auto deck_grid = parser.parseFile("./tests/SPE1CASE1.DATA", parseContext, grid_section);
const auto deck_props = parser.parseFile("./tests/SPE1CASE1.DATA", parseContext, props_section);
const auto deck_regions = parser.parseFile("./tests/SPE1CASE1.DATA", parseContext, regions_section);
const auto deck_solution = parser.parseFile("./tests/SPE1CASE1.DATA", parseContext, solution_section);
const auto deck_summary = parser.parseFile("./tests/SPE1CASE1.DATA", parseContext, summary_section);
const auto deck_schecule = parser.parseFile("./tests/SPE1CASE1.DATA", parseContext, schedule_section);
const auto deck_test1 = parser.parseFile("./tests/SPE1CASE1.DATA", parseContext, test1);
BOOST_CHECK_EQUAL( deck_grid.size(), 25 );
BOOST_CHECK_EQUAL( deck_props.size(), 21 );
BOOST_CHECK_EQUAL( deck_regions.size(), 15 );
BOOST_CHECK_EQUAL( deck_solution.size(), 16 );
BOOST_CHECK_EQUAL( deck_summary.size(), 37 );
BOOST_CHECK_EQUAL( deck_schecule.size(), 30 );
BOOST_CHECK_EQUAL( deck_test1.size(), 24 );
const auto deck1b_all = parser.parseFile("./tests/SPE1CASE1B.DATA", parseContext);
BOOST_CHECK_EQUAL( deck1b_all.size(), 79 );
BOOST_CHECK_THROW(parser.parseFile("./tests/SPE1CASE1B.DATA", parseContext, grid_section), std::runtime_error);
}

156
tests/props_spe1case1b.inc Normal file
View File

@ -0,0 +1,156 @@
-- This reservoir simulation deck is made available under the Open Database
-- License: http://opendatacommons.org/licenses/odbl/1.0/. Any rights in
-- individual contents of the database are licensed under the Database Contents
-- License: http://opendatacommons.org/licenses/dbcl/1.0/
-- Copyright (C) 2015 Statoil
-- This simulation is based on the data given in
-- 'Comparison of Solutions to a Three-Dimensional
-- Black-Oil Reservoir Simulation Problem' by Aziz S. Odeh,
-- Journal of Petroleum Technology, January 1981
PROPS
-- -------------------------------------------------------------------------
PVTW
-- Item 1: pressure reference (psia)
-- Item 2: water FVF (rb per bbl or rb per stb)
-- Item 3: water compressibility (psi^{-1})
-- Item 4: water viscosity (cp)
-- Item 5: water 'viscosibility' (psi^{-1})
-- Using values from Norne:
-- In METRIC units:
-- 277.0 1.038 4.67E-5 0.318 0.0 /
-- In FIELD units:
4017.55 1.038 3.22E-6 0.318 0.0 /
ROCK
-- Item 1: reference pressure (psia)
-- Item 2: rock compressibility (psi^{-1})
-- Using values from table 1 in Odeh:
14.7 3E-6 /
SWOF
-- Column 1: water saturation
-- - this has been set to (almost) equally spaced values from 0.12 to 1
-- Column 2: water relative permeability
-- - generated from the Corey-type approx. formula
-- the coeffisient is set to 10e-5, S_{orw}=0 and S_{wi}=0.12
-- Column 3: oil relative permeability when only oil and water are present
-- - we will use the same values as in column 3 in SGOF.
-- This is not really correct, but since only the first
-- two values are of importance, this does not really matter
-- Column 4: water-oil capillary pressure (psi)
0.12 0 1 0
0.18 4.64876033057851E-008 1 0
0.24 0.000000186 0.997 0
0.3 4.18388429752066E-007 0.98 0
0.36 7.43801652892562E-007 0.7 0
0.42 1.16219008264463E-006 0.35 0
0.48 1.67355371900826E-006 0.2 0
0.54 2.27789256198347E-006 0.09 0
0.6 2.97520661157025E-006 0.021 0
0.66 3.7654958677686E-006 0.01 0
0.72 4.64876033057851E-006 0.001 0
0.78 0.000005625 0.0001 0
0.84 6.69421487603306E-006 0 0
0.91 8.05914256198347E-006 0 0
1 0.00001 0 0 /
SGOF
-- Column 1: gas saturation
-- Column 2: gas relative permeability
-- Column 3: oil relative permeability when oil, gas and connate water are present
-- Column 4: oil-gas capillary pressure (psi)
-- - stated to be zero in Odeh's paper
-- Values in column 1-3 are taken from table 3 in Odeh's paper:
0 0 1 0
0.001 0 1 0
0.02 0 0.997 0
0.05 0.005 0.980 0
0.12 0.025 0.700 0
0.2 0.075 0.350 0
0.25 0.125 0.200 0
0.3 0.190 0.090 0
0.4 0.410 0.021 0
0.45 0.60 0.010 0
0.5 0.72 0.001 0
0.6 0.87 0.0001 0
0.7 0.94 0.000 0
0.85 0.98 0.000 0
0.88 0.984 0.000 0 /
--1.00 1.0 0.000 0 /
-- Warning from Eclipse: first sat. value in SWOF + last sat. value in SGOF
-- must not be greater than 1, but Eclipse still runs
-- Flow needs the sum to be excactly 1 so I added a row with gas sat. = 0.88
-- The corresponding krg value was estimated by assuming linear rel. between
-- gas sat. and krw. between gas sat. 0.85 and 1.00 (the last two values given)
DENSITY
-- Density (lb per ft³) at surface cond. of
-- oil, water and gas, respectively (in that order)
-- Using values from Norne:
-- In METRIC units:
-- 859.5 1033.0 0.854 /
-- In FIELD units:
53.66 64.49 0.0533 /
PVDG
-- Column 1: gas phase pressure (psia)
-- Column 2: gas formation volume factor (rb per Mscf)
-- - in Odeh's paper the units are said to be given in rb per bbl,
-- but this is assumed to be a mistake: FVF-values in Odeh's paper
-- are given in rb per scf, not rb per bbl. This will be in
-- agreement with conventions
-- Column 3: gas viscosity (cP)
-- Using values from lower right table in Odeh's table 2:
14.700 166.666 0.008000
264.70 12.0930 0.009600
514.70 6.27400 0.011200
1014.7 3.19700 0.014000
2014.7 1.61400 0.018900
2514.7 1.29400 0.020800
3014.7 1.08000 0.022800
4014.7 0.81100 0.026800
5014.7 0.64900 0.030900
9014.7 0.38600 0.047000 /
PVTO
-- Column 1: dissolved gas-oil ratio (Mscf per stb)
-- Column 2: bubble point pressure (psia)
-- Column 3: oil FVF for saturated oil (rb per stb)
-- Column 4: oil viscosity for saturated oil (cP)
-- Use values from top left table in Odeh's table 2:
0.0010 14.7 1.0620 1.0400 /
0.0905 264.7 1.1500 0.9750 /
0.1800 514.7 1.2070 0.9100 /
0.3710 1014.7 1.2950 0.8300 /
0.6360 2014.7 1.4350 0.6950 /
0.7750 2514.7 1.5000 0.6410 /
0.9300 3014.7 1.5650 0.5940 /
1.2700 4014.7 1.6950 0.5100
9014.7 1.5790 0.7400 /
1.6180 5014.7 1.8270 0.4490
9014.7 1.7370 0.6310 /
-- It is required to enter data for undersaturated oil for the highest GOR
-- (i.e. the last row) in the PVTO table.
-- In order to fulfill this requirement, values for oil FVF and viscosity
-- at 9014.7psia and GOR=1.618 for undersaturated oil have been approximated:
-- It has been assumed that there is a linear relation between the GOR
-- and the FVF when keeping the pressure constant at 9014.7psia.
-- From Odeh we know that (at 9014.7psia) the FVF is 2.357 at GOR=2.984
-- for saturated oil and that the FVF is 1.579 at GOR=1.27 for undersaturated oil,
-- so it is possible to use the assumption described above.
-- An equivalent approximation for the viscosity has been used.
/