Add support for dynamic parser extensions

Sunbeam can now be used to parse eclipse files to deck representations.
Added support for dynamic parser extensions which can be used to handle
unsupported eclipse keywords when parsing to deck representations.

Fixed missing function declarations
This commit is contained in:
Jens Gåsemyr Magnus
2017-08-31 13:49:09 +02:00
parent 9acb937361
commit 11fe32df4f
22 changed files with 343 additions and 74 deletions

View File

@@ -2,6 +2,7 @@ set(PYTHON_SOURCES
__init__.py
sunbeam.py
config.py
parser.py
properties.py
schedule.py)

View File

@@ -1,7 +1,7 @@
from .schedule import Well
from .libsunbeam import action
from .properties import parse
from .config import EclipseConfig
from .parser import parse_deck, parse
__version__ = '0.0.1'
__license__ = 'GNU General Public License version 3'

58
python/sunbeam/parser.py Normal file
View File

@@ -0,0 +1,58 @@
from os.path import isfile
import json
import libsunbeam as lib
from .properties import EclipseState
def _parse_context(actions):
ctx = lib.ParseContext()
if actions is None:
return ctx
# this might be a single tuple, in which case we unpack it and repack it
# into a list. If it's not a tuple we assume it's an iterable and just
# carry on
try:
key, action = actions
except ValueError:
pass
else:
actions = [(key, action)]
for key, action in actions:
ctx.update(key, action)
return ctx
def parse(deck, actions = None):
"""deck may be a deck string, or a file path"""
if isfile(deck):
return EclipseState(lib.parse(deck, _parse_context(actions)))
return EclipseState(lib.parse_data(deck, _parse_context(actions)))
def parse_deck(deck, **kwargs):
args = [deck]
if 'keywords' in kwargs:
keywords = kwargs['keywords']
# this might be a single keyword dictionary, in which case we pack it
# into a list. If it's not a dict we assume it's an iterable and just
# carry on
if isinstance(keywords, dict):
keywords = [keywords]
json_keywords = map(json.dumps, keywords)
args.append(json_keywords);
else:
args.append([])
args.append(isfile(deck)) # If the deck is a file, the deck is read from
# that file. Otherwise it is assumed to be a
# string representation of the the deck.
if 'actions' in kwargs:
args.append(_parse_context(kwargs['actions']))
return lib.parse_deck(*args)

View File

@@ -101,30 +101,3 @@ class EclipseGrid(object):
if na != g:
cnt += ', active = %s' % na
return 'EclipseGrid(%s)' % cnt
def _parse_context(actions):
ctx = lib.ParseContext()
if actions is None:
return ctx
# this might be a single tuple, in which case we unpack it and repack it
# into a list. If it's not a tuple we assume it's an iterable and just
# carry on
try:
key, action = actions
except ValueError:
pass
else:
actions = [(key, action)]
for key, action in actions:
ctx.update(key, action)
return ctx
def parse(deck, actions = None):
"""deck may be a deck string, or a file path"""
if isfile(deck):
return EclipseState(lib.parse(deck, _parse_context(actions)))
return EclipseState(lib.parseData(deck, _parse_context(actions)))

View File

@@ -5,15 +5,17 @@ include_directories(SYSTEM ${PYTHON_INCLUDE_DIRS}
${Boost_INCLUDE_DIR}
${opm-parser_INCLUDE_DIRS})
add_library( sunbeam SHARED eclipse_state.cpp
add_library( sunbeam SHARED sunbeam.cpp
completion.cpp
deck.cpp
deck_keyword.cpp
eclipse_3d_properties.cpp
eclipse_config.cpp
eclipse_grid.cpp
eclipse_state.cpp
group.cpp
parser.cpp
schedule.cpp
sunbeam.cpp
table_manager.cpp
well.cpp )
set_target_properties( sunbeam PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/python/sunbeam )

View File

@@ -11,7 +11,6 @@
namespace py = boost::python;
/*
* boost.python lacks converters for a lot of types we use, or we need to
* fine-tune the behaviour a little bit. They're often for one specific

58
sunbeam/deck.cpp Normal file
View File

@@ -0,0 +1,58 @@
#include <opm/parser/eclipse/Deck/DeckKeyword.hpp>
#include "deck.hpp"
namespace py = boost::python;
using namespace Opm;
using ref = py::return_internal_reference<>;
using copy = py::return_value_policy< py::copy_const_reference >;
namespace deck {
/*
* Deck methods inherited from DeckView
*/
size_t size( const Deck& deck ) {
return deck.size();
}
size_t count( const Deck& deck, const std::string& kw ) {
return deck.count(kw);
}
bool hasKeyword( const Deck& deck, const std::string& kw ) {
return deck.hasKeyword(kw);
}
const DeckKeyword& getKeyword0( const Deck& deck, py::tuple i ) {
const std::string kw = py::extract<const std::string>(py::str(i[0]));
const size_t index = py::extract<size_t>(i[1]);
return deck.getKeyword(kw, index);
}
const DeckKeyword& getKeyword1( const Deck& deck, const std::string& kw ) {
return deck.getKeyword(kw);
}
const DeckKeyword& getKeyword2( const Deck& deck, size_t index ) {
return deck.getKeyword(index);
}
const DeckView::const_iterator begin( const Deck& deck ) { return deck.begin(); }
const DeckView::const_iterator end( const Deck& deck ) { return deck.end(); }
void export_Deck() {
py::class_< Deck >( "Deck", py::no_init )
.def( "__len__", &size )
.def( "__contains__", &hasKeyword )
.def( "__iter__", py::range< ref >( &begin, &end ) )
.def( "__getitem__", &getKeyword0, ref() )
.def( "__getitem__", &getKeyword1, ref() )
.def( "__getitem__", &getKeyword2, ref() )
.def( "count", &count )
;
}
}

25
sunbeam/deck.hpp Normal file
View File

@@ -0,0 +1,25 @@
#include <boost/python.hpp>
#include <opm/parser/eclipse/Deck/Deck.hpp>
namespace Opm {
class Deck;
class DeckKeyword;
}
namespace deck {
using namespace Opm;
size_t size( const Deck& deck );
size_t count( const Deck& deck, const std::string& kw );
bool hasKeyword( const Deck& deck, const std::string& kw );
const DeckKeyword& getKeyword0( const Deck& deck, boost::python::tuple i );
const DeckKeyword& getKeyword1( const Deck& deck, const std::string& kw );
const DeckKeyword& getKeyword2( const Deck& deck, size_t index );
const DeckView::const_iterator begin( const Deck& deck );
const DeckView::const_iterator end( const Deck& deck );
void export_Deck();
}

25
sunbeam/deck_keyword.cpp Normal file
View File

@@ -0,0 +1,25 @@
#include <boost/python.hpp>
#include <opm/parser/eclipse/Deck/DeckKeyword.hpp>
#include <opm/parser/eclipse/Deck/DeckRecord.hpp>
#include "deck_keyword.hpp"
namespace py = boost::python;
using namespace Opm;
// using ref = py::return_internal_reference<>;
using copy = py::return_value_policy< py::copy_const_reference >;
namespace deck_keyword {
void export_DeckKeyword() {
py::class_< DeckKeyword >( "DeckKeyword", py::no_init )
.def( "__repr__", &DeckKeyword::name, copy() )
;
}
}

7
sunbeam/deck_keyword.hpp Normal file
View File

@@ -0,0 +1,7 @@
namespace deck_keyword {
void export_DeckKeyword();
}

View File

@@ -38,7 +38,6 @@ namespace eclipse_3d_properties {
void export_Eclipse3DProperties() {
py::class_< Eclipse3DProperties >( "Eclipse3DProperties", py::no_init )
.def( "getRegions", &regions )
.def( "__contains__", &contains )

View File

@@ -14,6 +14,8 @@ namespace eclipse_3d_properties {
py::list getitem( const Eclipse3DProperties& p, const std::string& kw);
bool contains( const Eclipse3DProperties& p, const std::string& kw);
py::list regions( const Eclipse3DProperties& p, const std::string& kw);
void export_Eclipse3DProperties();
}

View File

@@ -1,8 +1,10 @@
#include <boost/python.hpp>
#include <opm/parser/eclipse/EclipseState/EclipseConfig.hpp>
#include <opm/parser/eclipse/EclipseState/IOConfig/RestartConfig.hpp>
#include <opm/parser/eclipse/EclipseState/InitConfig/InitConfig.hpp>
#include <opm/parser/eclipse/EclipseState/SummaryConfig/SummaryConfig.hpp>
#include <opm/parser/eclipse/EclipseState/SimulationConfig/SimulationConfig.hpp>
#include <opm/parser/eclipse/EclipseState/InitConfig/InitConfig.hpp>
#include <opm/parser/eclipse/EclipseState/IOConfig/RestartConfig.hpp>
#include "eclipse_config.hpp"

View File

@@ -1,15 +1,8 @@
#ifndef SUNBEAM_ECLIPSE_CONFIG_HPP
#define SUNBEAM_ECLIPSE_CONFIG_HPP
#include <boost/python.hpp>
namespace Opm {
}
namespace eclipse_config {
namespace py = boost::python;
using namespace Opm;
void export_EclipseConfig();

View File

@@ -13,6 +13,13 @@ namespace eclipse_grid {
using namespace Opm;
py::tuple getXYZ( const EclipseGrid& grid );
int getNumActive( const EclipseGrid& grid );
int getCartesianSize( const EclipseGrid& grid );
int getGlobalIndex( const EclipseGrid& grid, int i, int j, int k );
py::tuple getIJK( const EclipseGrid& grid, int g );
double cellVolume1G( const EclipseGrid& grid, size_t glob_idx);
double cellVolume3( const EclipseGrid& grid, size_t i_idx, size_t j_idx, size_t k_idx);
void export_EclipseGrid();
}

View File

@@ -1,6 +1,5 @@
#include <opm/parser/eclipse/EclipseState/EclipseState.hpp>
#include <opm/parser/eclipse/EclipseState/Grid/FaultCollection.hpp>
#include <opm/parser/eclipse/Parser/Parser.hpp>
#include "eclipse_state.hpp"
@@ -88,16 +87,8 @@ namespace eclipse_state {
return l;
}
EclipseState (*parse)( const std::string&, const ParseContext& ) = &Parser::parse;
EclipseState (*parseData) (const std::string &data, const ParseContext& context) = &Parser::parseData;
void (ParseContext::*ctx_update)(const std::string&, InputError::Action) = &ParseContext::update;
void export_EclipseState() {
py::def( "parse", parse );
py::def( "parseData", parseData );
py::class_< EclipseState >( "EclipseState", py::no_init )
.add_property( "title", &EclipseState::getTitle )
.def( "_schedule", &EclipseState::getSchedule, ref() )
@@ -112,21 +103,5 @@ namespace eclipse_state {
.def( "jfunc", &jfunc )
;
/*
* Temporarily
*/
py::class_< ParseContext >( "ParseContext" )
.def( "update", ctx_update )
;
py::enum_< InputError::Action >( "action" )
.value( "throw", InputError::Action::THROW_EXCEPTION )
.value( "warn", InputError::Action::WARN )
.value( "ignore", InputError::Action::IGNORE )
;
}
}

View File

@@ -2,8 +2,8 @@
#define ECLIPSE_STATE_HPP
#include <boost/python.hpp>
#include <opm/parser/eclipse/EclipseState/Grid/FaceDir.hpp>
#include <opm/parser/eclipse/EclipseState/Grid/FaceDir.hpp>
namespace Opm {
class EclipseState;

53
sunbeam/parser.cpp Normal file
View File

@@ -0,0 +1,53 @@
#include <opm/json/JsonObject.hpp>
#include <opm/parser/eclipse/Parser/Parser.hpp>
#include <opm/parser/eclipse/Deck/Deck.hpp>
#include "parser.hpp"
namespace py = boost::python;
using namespace Opm;
namespace parser {
Deck parseDeck( const std::string& deckStr,
const boost::python::list& keywords,
bool isFile,
const ParseContext& pc ) {
Parser p;
size_t len = py::extract<size_t>(keywords.attr("__len__")());
for (size_t i = 0; i < len; i++) {
const std::string kw = py::extract<const std::string>(py::str(keywords[i]));
const Json::JsonObject jkw(kw);
p.addParserKeyword(jkw);
}
if (isFile)
return p.parseFile(deckStr, pc);
return p.parseString(deckStr, pc);
}
EclipseState (*parse)( const std::string&, const ParseContext& ) = &Parser::parse;
EclipseState (*parseData) (const std::string &data, const ParseContext& context) = &Parser::parseData;
void (ParseContext::*ctx_update)(const std::string&, InputError::Action) = &ParseContext::update;
void export_Parser() {
py::def( "parse", parse );
py::def( "parse_data", parseData );
py::def( "parse_deck", &parseDeck );
py::class_< ParseContext >( "ParseContext" )
.def( "update", ctx_update )
;
py::enum_< InputError::Action >( "action" )
.value( "throw", InputError::Action::THROW_EXCEPTION )
.value( "warn", InputError::Action::WARN )
.value( "ignore", InputError::Action::IGNORE )
;
}
}

19
sunbeam/parser.hpp Normal file
View File

@@ -0,0 +1,19 @@
#include <boost/python.hpp>
#include <opm/parser/eclipse/Parser/ParseContext.hpp>
namespace Opm {
class Deck;
class ParseContext;
}
namespace parser {
using namespace Opm;
Deck parseDeck( const std::string& deckStr,
const boost::python::list& keywords,
bool isFile,
const ParseContext& = ParseContext() );
void export_Parser();
}

View File

@@ -1,11 +1,15 @@
#include "converters.hpp"
#include "completion.hpp"
#include "converters.hpp"
#include "deck.hpp"
#include "deck_keyword.hpp"
#include "eclipse_3d_properties.hpp"
#include "eclipse_config.hpp"
#include "eclipse_grid.hpp"
#include "eclipse_state.hpp"
#include "group.hpp"
#include "parser.hpp"
#include "schedule.hpp"
#include "table_manager.hpp"
#include "well.hpp"
@@ -25,18 +29,18 @@ BOOST_PYTHON_MODULE(libsunbeam) {
py::register_exception_translator< key_error >( &key_error::translate );
eclipse_state::export_EclipseState();
eclipse_config::export_EclipseConfig();
completion::export_Completion();
deck::export_Deck();
deck_keyword::export_DeckKeyword();
eclipse_3d_properties::export_Eclipse3DProperties();
eclipse_config::export_EclipseConfig();
eclipse_grid::export_EclipseGrid();
eclipse_state::export_EclipseState();
group::export_Group();
parser::export_Parser();
schedule::export_Schedule();
table_manager::export_TableManager();
well::export_Well();
// parser::export_Parser();
// deck::export_Deck();
// deck_keyword::export_DeckKeyword();
}

View File

@@ -2,6 +2,6 @@ configure_file(spe3/SPE3CASE1.DATA spe3/SPE3CASE1.DATA COPYONLY)
configure_file(data/CORNERPOINT_ACTNUM.DATA data/CORNERPOINT_ACTNUM.DATA COPYONLY)
configure_file(data/JFUNC.DATA data/JFUNC.DATA COPYONLY)
foreach(prog parse state props schedule wells)
foreach(prog parse_deck parse state props schedule wells)
add_python_test(${prog} ${prog}.py)
endforeach()

67
tests/parse_deck.py Normal file
View File

@@ -0,0 +1,67 @@
import unittest
import sunbeam
import os.path
class TestParse(unittest.TestCase):
DECK_ADDITIONAL_KEYWORDS = """
START -- 0
10 MAI 2007 /
RUNSPEC
TESTKEY0
1 2 3 4 5 6 7 8 /
TESTKEY1
TESTKEY2
/
DIMENS
2 2 1 /
GRID
DX
4*0.25 /
DY
4*0.25 /
DZ
4*0.25 /
TOPS
4*0.25 /
REGIONS
OPERNUM
3 3 1 2 /
FIPNUM
1 1 2 3 /
"""
KEYWORDS = [{
"name" : "TESTKEY0",
"sections" : [ "RUNSPEC" ],
"data" : {"value_type" : "INT" }
}, {
"name" : "TESTKEY1",
"sections" : [ "RUNSPEC" ],
"size":0
}, {
"name" : "TESTKEY2",
"sections" : [ "RUNSPEC" ],
"size":1,
"items":[
{ "name":"TEST", "value_type":"STRING", "default":"DEFAULT" }
]
}, ]
def setUp(self):
self.spe3fn = 'spe3/SPE3CASE1.DATA'
self.norne_fname = os.path.abspath('../../examples/data/norne/NORNE_ATW2013.DATA')
def testParerFailWithoutExtension(self):
action = ("PARSE_RANDOM_SLASH", sunbeam.action.ignore)
with self.assertRaises(ValueError):
sunbeam.parse_deck(self.DECK_ADDITIONAL_KEYWORDS,
actions=action )
def testParerExtension(self):
action = ("PARSE_RANDOM_SLASH", sunbeam.action.ignore)
deck = sunbeam.parse_deck(self.DECK_ADDITIONAL_KEYWORDS,
keywords=self.KEYWORDS, actions=action )
self.assertTrue( 'TESTKEY0' in deck )
self.assertTrue( 'TESTKEY1' in deck )
self.assertTrue( 'TESTKEY2' in deck )