From 67b30fe715993edc927d6c94ff75eb83916f0cbb Mon Sep 17 00:00:00 2001 From: Joakim Hove Date: Tue, 30 Jan 2018 07:46:03 +0100 Subject: [PATCH] Use pybind11 as binding framework This commit changes the api for the Schedule class, the various time related methods now return datetime.datetime instances instead of datetime.data. --- CMakeLists.txt | 12 ++-- python/CMakeLists.txt | 4 -- python/sunbeam/schedule.py | 6 ++ sunbeam/CMakeLists.txt | 42 +++++------- sunbeam/completion.cpp | 34 +++++----- sunbeam/converters.hpp | 102 +----------------------------- sunbeam/deck.cpp | 39 +++++++----- sunbeam/deck_keyword.cpp | 25 +++++--- sunbeam/eclipse_3d_properties.cpp | 24 ++++--- sunbeam/eclipse_config.cpp | 30 ++++----- sunbeam/eclipse_grid.cpp | 12 +++- sunbeam/eclipse_state.cpp | 22 ++++--- sunbeam/group.cpp | 15 ++--- sunbeam/group_tree.cpp | 5 +- sunbeam/parser.cpp | 35 +++++----- sunbeam/schedule.cpp | 87 +++++++++++++++++-------- sunbeam/sunbeam.cpp | 46 +++++--------- sunbeam/sunbeam.hpp | 37 ++++++----- sunbeam/table_manager.cpp | 11 ++-- sunbeam/well.cpp | 26 +++----- 20 files changed, 268 insertions(+), 346 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2bbdcaa49..5bd4f34c8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,9 @@ enable_language( CXX ) include( CheckCXXSourceCompiles ) include( CTest ) -# assuming gcc-compatible options +find_package(opm-parser REQUIRED) +find_package(PythonInterp 2.7 REQUIRED) + set( warnings "-Wall -Wextra -pedantic -Wpointer-arith" ) set( warnings "${warnings} -Wformat-nonliteral -Wcast-align" ) set( warnings "${warnings} -Wmissing-declarations -Wcast-qual" ) @@ -12,10 +14,6 @@ set( warnings "${warnings} -Wwrite-strings -Wchar-subscripts " ) set( warnings "${warnings} -Wredundant-decls" ) set( CMAKE_CXX_FLAGS "-std=c++11 ${warnings} ${CMAKE_CXX_FLAGS}" ) -find_package( opm-parser REQUIRED ) -find_package( PythonLibs 2.7 REQUIRED ) -find_package( PythonInterp 2.7 REQUIRED ) -find_package( Boost COMPONENTS python REQUIRED ) list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/pycmake/cmake/Modules) @@ -25,6 +23,8 @@ if (USE_RPATH) set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) endif () -add_subdirectory( sunbeam ) +add_subdirectory( pybind11 ) add_subdirectory( python ) +add_subdirectory( sunbeam ) add_subdirectory( tests ) + diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 90c5d5afc..8bc55ce89 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -1,11 +1,7 @@ include( FindPythonModule ) include( PythonPackage ) -#python_module( ctypes ) -python_module( six ) -#python_module( numpy REQUIRED 1.7 ) python_module( inspect OPTIONAL ) - if (DEFINED PY_inspect) message(STATUS "We have inspect.") else() diff --git a/python/sunbeam/schedule.py b/python/sunbeam/schedule.py index 04806fc77..25ba3c9e4 100644 --- a/python/sunbeam/schedule.py +++ b/python/sunbeam/schedule.py @@ -19,6 +19,9 @@ class Schedule(object): def groups(self, timestep=0): return [Group(x, self, timestep) for x in self._groups if x.name != 'FIELD'] + def __getitem__(self,well): + return Well(self._getwell(well)) + @delegate(lib.Well) class Well(object): @@ -34,6 +37,9 @@ class Well(object): def completions(self, timestep): return map(Completion, self._completions(timestep)) + def __eq__(self,other): + return self._sun.__equal__(other._sun) + @staticmethod def defined(timestep): def fn(well): return well.isdefined(timestep) diff --git a/sunbeam/CMakeLists.txt b/sunbeam/CMakeLists.txt index 26616199c..7c60b31cf 100644 --- a/sunbeam/CMakeLists.txt +++ b/sunbeam/CMakeLists.txt @@ -1,26 +1,18 @@ -include( FindPythonModule ) -include( PythonPackage ) +pybind11_add_module(libsunbeam sunbeam.cpp + eclipse_state.cpp + deck_keyword.cpp + deck.cpp + well.cpp + schedule.cpp + completion.cpp + eclipse_config.cpp + table_manager.cpp + eclipse_grid.cpp + group.cpp + group_tree.cpp + eclipse_3d_properties.cpp + parser.cpp) -include_directories(SYSTEM ${PYTHON_INCLUDE_DIRS} - ${Boost_INCLUDE_DIR} - ${opm-parser_INCLUDE_DIRS}) - -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 - table_manager.cpp - well.cpp - group_tree.cpp ) - -set_target_properties( sunbeam PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/python/sunbeam ) -target_link_libraries( sunbeam ${Boost_LIBRARIES} ${opm-parser_LIBRARIES} ) - -install(TARGETS sunbeam DESTINATION ${PYTHON_INSTALL_PREFIX}/sunbeam) +set_target_properties( libsunbeam PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/python/sunbeam ) +target_link_libraries( libsunbeam PRIVATE ${opm-parser_LIBRARIES} ) +install(TARGETS libsunbeam DESTINATION ${PYTHON_INSTALL_PREFIX}/sunbeam) diff --git a/sunbeam/completion.cpp b/sunbeam/completion.cpp index 584ea582c..a4807c5b6 100644 --- a/sunbeam/completion.cpp +++ b/sunbeam/completion.cpp @@ -15,23 +15,21 @@ std::string direction( const Completion& c ) { } -void sunbeam::export_Completion() { - - py::class_< Completion >( "Completion", py::no_init ) - .add_property( "direction", &direction ) - .add_property( "state", &state ) - .def_readonly( "I", &Completion::getI ) - .def_readonly( "J", &Completion::getJ ) - .def_readonly( "K", &Completion::getK ) - .def_readonly( "attached_to_segment", &Completion::attachedToSegment ) - .def_readonly( "center_depth", &Completion::getCenterDepth ) - .def_readonly( "diameter", &Completion::getDiameter ) - .def_readonly( "number", &Completion::complnum) - .def_readonly( "sat_table_id", &Completion::getSatTableId ) - .def_readonly( "segment_number", &Completion::getSegmentNumber ) - .def_readonly( "skin_factor", &Completion::getSkinFactor ) - .def_readonly( "transmissibility", &Completion::getConnectionTransmissibilityFactor ) - .def_readonly( "well_pi", &Completion::getWellPi ) - ; +void sunbeam::export_Completion(py::module& module) { + py::class_< Completion >( module, "Completion") + .def_property_readonly("direction", &direction ) + .def_property_readonly("state", &state ) + .def_property_readonly( "I", &Completion::getI ) + .def_property_readonly( "J", &Completion::getJ ) + .def_property_readonly( "K", &Completion::getK ) + .def_property_readonly( "attached_to_segment", &Completion::attachedToSegment ) + .def_property_readonly( "center_depth", &Completion::getCenterDepth ) + .def_property_readonly( "diameter", &Completion::getDiameter ) + .def_property_readonly( "number", &Completion::complnum) + .def_property_readonly( "sat_table_id", &Completion::getSatTableId ) + .def_property_readonly( "segment_number", &Completion::getSegmentNumber ) + .def_property_readonly( "skin_factor", &Completion::getSkinFactor ) + .def_property_readonly( "transmissibility", &Completion::getConnectionTransmissibilityFactor ) + .def_property_readonly( "well_pi", &Completion::getWellPi ); } diff --git a/sunbeam/converters.hpp b/sunbeam/converters.hpp index 574050d42..4416ca009 100644 --- a/sunbeam/converters.hpp +++ b/sunbeam/converters.hpp @@ -1,75 +1,11 @@ #ifndef SUNBEAM_CONVERTERS_HPP #define SUNBEAM_CONVERTERS_HPP -#include -#include #include +#include -#include -#include +namespace py = pybind11; -#include - -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 - * instantiation or function, but they have in common that they're not really - * reflected much in C++ code for other things than conversion registries or - * single instantiations. - * - * a converter is a struct with the static method - * `PyObject* convert( const type& )` - * which returns a Python C-api object either by invoking the C api directly or - * going via boost types. It is the job of the conversion function to make sure - * resources and ownership is handled and converted properly. - */ - - -/* boost.posix_time(y, m, d) -> datetime.date(y, m, d) */ -struct ptime_to_python_datetime { - static PyObject* convert( const boost::posix_time::ptime& pt ) { - const auto& date = pt.date(); - return PyDate_FromDate( int( date.year() ), - int( date.month() ), - int( date.day() ) ); - - } -}; - -/* - * Plenty of sunbeam's types are essentially dictionaries, and we want to map - * directly from some C++ function to __getitem__. However, C++ associative - * containers throw std::out_of_range if the key is not present, and in python - * you'd expect a KeyError for this. Boost.python is not able to map between - * these two types so we register our own exception with boost. When this - * exception is thrown it will raise a corresponding KeyError in python. - */ -struct key_error : public std::out_of_range { - static void translate( const key_error& e ) { - PyErr_SetString( PyExc_KeyError, e.what() ); - } - - using std::out_of_range::out_of_range; -}; - -/* - * opm returns plenty of container or container-like types. In python, this - * rigid structure isn't as interesting and we often want a quick way of obtain - * a first-class python list of our values. This can either be achieved by - * registering it with vector_indexing_suite, which is perfect when the value - * is already a C++ by-value or by-ref vector of some type with value - * semantics. However, sometimes C++ returns a set where we in python would - * probably rather use a list, or C++ returns a type that doesnt't play as nice - * with boost.python (see functions that return a vector of non-owning pointers - * for instance). In these cases we directly construct a python list with what - * is essentially a C++-backed list comprehension. It works on anything that - * supports C++' for-each syntax. - * - * Requires that whatever's being mapped is registered with a C++ -> python - * converter. - */ template< typename T > py::list iterable_to_pylist( const T& v ) { py::list l; @@ -77,40 +13,6 @@ py::list iterable_to_pylist( const T& v ) { return l; } -/* - * Return value modifiers in boost python are verbose and clunky, so they're - * aliased for some comfort. - */ -using ref = py::return_internal_reference<>; -using copy = py::return_value_policy< py::copy_const_reference >; - -/* - * boost isn't that good at figuring out to when to return properties by - * reference, and unlike .def()'s can't take the return value modifier as its - * third argument because it possibly expects a setter there. This is easily - * fixed by manually constructing the py::function, but the syntax is a - * handful. mkref takes any C++ function and creates a boost.python function - * that returns by (internal) reference and can be easily used with - * .add_property. - */ -template< typename F > -auto mkref( F f ) -> decltype( py::make_function( f, ref() ) ) { - return py::make_function( f, ref() ); -} - -/* - * boost doesn't really like functions that returns strings by reference, - * becuase it's somewhat difficult to map onto python's owning, immutable - * strings. Since string data (and similar) typically are trivial and - * uninteresting to preserve references, mkcopy() takes any function that - * returns by const ref and makes sure the returned python object copies it and - * owns the new instance. - */ -template< typename F > -auto mkcopy( F f ) -> decltype( py::make_function( f, copy() ) ) { - return py::make_function( f, copy() ); -} - template< typename T > std::string str( const T& t ) { std::stringstream stream; diff --git a/sunbeam/deck.cpp b/sunbeam/deck.cpp index 4995b6f73..b1271ace6 100644 --- a/sunbeam/deck.cpp +++ b/sunbeam/deck.cpp @@ -1,5 +1,7 @@ #include +#include +#include "converters.hpp" #include "sunbeam.hpp" @@ -8,41 +10,46 @@ namespace { 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(py::str(i[0])); - const size_t index = py::extract(i[1]); + const DeckKeyword& getKeyword_tuple( const Deck& deck, py::tuple kw_index ) { + const std::string kw = py::cast(kw_index[0]); + const size_t index = py::cast(kw_index[1]); return deck.getKeyword(kw, index); } - const DeckKeyword& getKeyword1( const Deck& deck, const std::string& kw ) { + + const DeckKeyword& getKeyword_string( const Deck& deck, const std::string& kw ) { return deck.getKeyword(kw); } - const DeckKeyword& getKeyword2( const Deck& deck, size_t index ) { + + const DeckKeyword& getKeyword_int( 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 sunbeam::export_Deck() { +void sunbeam::export_Deck(py::module &module) { - py::class_< Deck >( "Deck", py::no_init ) + py::class_< Deck >(module, "Deck") .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( "__str__", &str ) + .def("__iter__", + [] (const Deck &deck) { return py::make_iterator(deck.begin(), deck.end()); }, py::keep_alive<0, 1>()) + .def( "__getitem__", &getKeyword_int, ref_internal) + .def( "__getitem__", &getKeyword_string, ref_internal) + .def( "__getitem__", &getKeyword_tuple, ref_internal) + .def( "__str__", &str) .def( "count", &count ) - ; - + ; } + + diff --git a/sunbeam/deck_keyword.cpp b/sunbeam/deck_keyword.cpp index 4dd55cd8c..443001df3 100644 --- a/sunbeam/deck_keyword.cpp +++ b/sunbeam/deck_keyword.cpp @@ -14,6 +14,8 @@ namespace { /* DeckKeyword */ const DeckRecord& (DeckKeyword::*getRecord)(size_t index) const = &DeckKeyword::getRecord; + + py::list item_to_pylist( const DeckItem& item ) { switch (item.getType()) @@ -39,12 +41,14 @@ struct DeckRecordIterator this->record = record; this->it = this->record->begin(); } + const DeckRecord* record; DeckRecord::const_iterator it; + py::list next() { if (it == record->end()) { PyErr_SetString(PyExc_StopIteration, "At end."); - py::throw_error_already_set(); + throw py::error_already_set(); } return item_to_pylist(*(it++)); } @@ -52,17 +56,18 @@ struct DeckRecordIterator } -void sunbeam::export_DeckKeyword() { - py::class_< DeckKeyword >( "DeckKeyword", py::no_init ) - .def( "__repr__", &DeckKeyword::name, copy() ) +void sunbeam::export_DeckKeyword(py::module& module) { + py::class_< DeckKeyword >( module, "DeckKeyword") + .def( "__repr__", &DeckKeyword::name ) .def( "__str__", &str ) - .def( "__iter__", py::range< ref >( &DeckKeyword::begin, &DeckKeyword::end )) - .def( "__getitem__", getRecord, ref() ) + .def("__iter__", [] (const DeckKeyword &keyword) { return py::make_iterator(keyword.begin(), keyword.end()); }, py::keep_alive<0,1>()) + .def( "__getitem__", getRecord, ref_internal) .def( "__len__", &DeckKeyword::size ) - .add_property("name", py::make_function( &DeckKeyword::name, copy() )) + .def_property_readonly("name", &DeckKeyword::name ) ; - py::class_< DeckRecord >( "DeckRecord", py::no_init ) + + py::class_< DeckRecord >( module, "DeckRecord") .def( "__repr__", &str ) .def( "__iter__", +[](const DeckRecord& record){ return DeckRecordIterator(&record); @@ -76,8 +81,10 @@ void sunbeam::export_DeckKeyword() { .def( "__len__", &DeckRecord::size ) ; - py::class_< DeckRecordIterator >( "DeckRecordIterator", py::no_init ) + + py::class_< DeckRecordIterator >( module, "DeckRecordIterator") .def( "__next__", &DeckRecordIterator::next ) .def( "next", &DeckRecordIterator::next ) ; + } diff --git a/sunbeam/eclipse_3d_properties.cpp b/sunbeam/eclipse_3d_properties.cpp index 1f8f1b9b2..3c2efd67a 100644 --- a/sunbeam/eclipse_3d_properties.cpp +++ b/sunbeam/eclipse_3d_properties.cpp @@ -1,7 +1,8 @@ #include +#include #include "sunbeam.hpp" - +#include "converters.hpp" namespace { @@ -10,10 +11,12 @@ namespace { if (ip.supportsKeyword(kw) && ip.hasKeyword(kw)) return iterable_to_pylist(p.getIntGridProperty(kw).getData()); + const auto& dp = p.getDoubleProperties(); if (dp.supportsKeyword(kw) && dp.hasKeyword(kw)) return iterable_to_pylist(p.getDoubleGridProperty(kw).getData()); - throw key_error( "no such grid property " + kw ); + + throw py::key_error( "no such grid property " + kw ); } bool contains( const Eclipse3DProperties& p, const std::string& kw) { @@ -25,18 +28,19 @@ namespace { p.getDoubleProperties().hasKeyword(kw)) ; } - py::list regions( const Eclipse3DProperties& p, const std::string& kw) { - return iterable_to_pylist( p.getRegions(kw) ); + + std::vector regions( const Eclipse3DProperties& p, const std::string& kw) { + return p.getRegions(kw); } } -void sunbeam::export_Eclipse3DProperties() { +void sunbeam::export_Eclipse3DProperties(py::module& module) { - py::class_< Eclipse3DProperties >( "Eclipse3DProperties", py::no_init ) - .def( "getRegions", ®ions ) - .def( "__contains__", &contains ) - .def( "__getitem__", &getitem ) - ; + py::class_< Eclipse3DProperties >( module, "Eclipse3DProperties") + .def( "getRegions", ®ions ) + .def( "__contains__", &contains ) + .def( "__getitem__", &getitem ) + ; } diff --git a/sunbeam/eclipse_config.cpp b/sunbeam/eclipse_config.cpp index 59dd65f42..fac550b58 100644 --- a/sunbeam/eclipse_config.cpp +++ b/sunbeam/eclipse_config.cpp @@ -8,34 +8,28 @@ #include "sunbeam.hpp" -void sunbeam::export_EclipseConfig() +void sunbeam::export_EclipseConfig(py::module& module) { + py::class_< EclipseConfig >( module, "EclipseConfig" ) + .def( "init", &EclipseConfig::init, ref_internal) + .def( "restart", &EclipseConfig::restart, ref_internal); - py::class_< EclipseConfig >( "EclipseConfig", py::no_init ) - .def( "init", &EclipseConfig::init, ref()) - .def( "restart", &EclipseConfig::restart, ref()) - ; + py::class_< SummaryConfig >( module, "SummaryConfig") + .def( "__contains__", &SummaryConfig::hasKeyword ); - py::class_< SummaryConfig >( "SummaryConfig", py::no_init ) - .def( "__contains__", &SummaryConfig::hasKeyword ) - ; - - py::class_< InitConfig >( "InitConfig", py::no_init ) + py::class_< InitConfig >( module, "InitConfig") .def( "hasEquil", &InitConfig::hasEquil ) .def( "restartRequested", &InitConfig::restartRequested ) - .def( "getRestartStep" , &InitConfig::getRestartStep ) - ; + .def( "getRestartStep" , &InitConfig::getRestartStep ); - py::class_< RestartConfig >( "RestartConfig", py::no_init ) + py::class_< RestartConfig >( module, "RestartConfig") .def( "getKeyword", &RestartConfig::getKeyword ) .def( "getFirstRestartStep", &RestartConfig::getFirstRestartStep ) - .def( "getWriteRestartFile", &RestartConfig::getWriteRestartFile ) - ; + .def( "getWriteRestartFile", &RestartConfig::getWriteRestartFile ); - py::class_< SimulationConfig >( "SimulationConfig", py::no_init ) + py::class_< SimulationConfig >( module, "SimulationConfig") .def("hasThresholdPressure", &SimulationConfig::hasThresholdPressure ) .def("useCPR", &SimulationConfig::useCPR ) .def("hasDISGAS", &SimulationConfig::hasDISGAS ) - .def("hasVAPOIL", &SimulationConfig::hasVAPOIL ) - ; + .def("hasVAPOIL", &SimulationConfig::hasVAPOIL ); } diff --git a/sunbeam/eclipse_grid.cpp b/sunbeam/eclipse_grid.cpp index 74446a75f..3e9c8f68b 100644 --- a/sunbeam/eclipse_grid.cpp +++ b/sunbeam/eclipse_grid.cpp @@ -13,31 +13,37 @@ namespace { grid.getNY(), grid.getNZ()); } + int getNumActive( const EclipseGrid& grid ) { return grid.getNumActive(); } + int getCartesianSize( const EclipseGrid& grid ) { return grid.getCartesianSize(); } + int getGlobalIndex( const EclipseGrid& grid, int i, int j, int k ) { return grid.getGlobalIndex(i, j, k); } + py::tuple getIJK( const EclipseGrid& grid, int g ) { const auto& ijk = grid.getIJK(g); return py::make_tuple(ijk[0], ijk[1], ijk[2]); } + double cellVolume1G( const EclipseGrid& grid, size_t glob_idx) { return grid.getCellVolume(glob_idx); } + double cellVolume3( const EclipseGrid& grid, size_t i_idx, size_t j_idx, size_t k_idx) { return grid.getCellVolume(i_idx, j_idx, k_idx); } } -void sunbeam::export_EclipseGrid() { +void sunbeam::export_EclipseGrid(py::module& module) { - py::class_< EclipseGrid >( "EclipseGrid", py::no_init ) + py::class_< EclipseGrid >( module, "EclipseGrid") .def( "_getXYZ", &getXYZ ) .def( "nactive", &getNumActive ) .def( "cartesianSize", &getCartesianSize ) @@ -45,6 +51,6 @@ void sunbeam::export_EclipseGrid() { .def( "getIJK", &getIJK ) .def( "_cellVolume1G", &cellVolume1G) .def( "_cellVolume3", &cellVolume3) - ; + ; } diff --git a/sunbeam/eclipse_state.cpp b/sunbeam/eclipse_state.cpp index ed58b1e96..771196d89 100644 --- a/sunbeam/eclipse_state.cpp +++ b/sunbeam/eclipse_state.cpp @@ -12,6 +12,7 @@ namespace { l.append( py::make_tuple( x.cell1, x.cell2, x.trans ) ); return l; } + py::list faultNames( const EclipseState& state ) { py::list l; const auto& fc = state.getFaults(); @@ -21,6 +22,7 @@ namespace { } return l; } + py::dict jfunc( const EclipseState& s) { const auto& tm = s.getTableManager(); if (!tm.useJFunc()) @@ -83,18 +85,18 @@ namespace { } -void sunbeam::export_EclipseState() { +void sunbeam::export_EclipseState(py::module& module) { - py::class_< EclipseState >( "EclipseState", py::no_init ) - .add_property( "title", &EclipseState::getTitle ) - .def( "_schedule", &EclipseState::getSchedule, ref() ) - .def( "_props", &EclipseState::get3DProperties, ref() ) - .def( "_grid", &EclipseState::getInputGrid, ref() ) - .def( "_cfg", &EclipseState::cfg, ref() ) - .def( "_tables", &EclipseState::getTableManager, ref() ) + py::class_< EclipseState >( module, "EclipseState" ) + .def_property_readonly( "title", &EclipseState::getTitle ) + .def( "_schedule", &EclipseState::getSchedule, ref_internal) + .def( "_props", &EclipseState::get3DProperties, ref_internal) + .def( "_grid", &EclipseState::getInputGrid, ref_internal) + .def( "_cfg", &EclipseState::cfg, ref_internal) + .def( "_tables", &EclipseState::getTableManager, ref_internal) .def( "has_input_nnc", &EclipseState::hasInputNNC ) - .def( "simulation", &EclipseState::getSimulationConfig, ref()) - .def( "summary", &EclipseState::getSummaryConfig , ref()) + .def( "simulation", &EclipseState::getSimulationConfig, ref_internal) + .def( "summary", &EclipseState::getSummaryConfig, ref_internal) .def( "input_nnc", &getNNC ) .def( "faultNames", &faultNames ) .def( "faultFaces", &faultFaces ) diff --git a/sunbeam/group.cpp b/sunbeam/group.cpp index bbca94d76..44bd5b0d7 100644 --- a/sunbeam/group.cpp +++ b/sunbeam/group.cpp @@ -1,20 +1,19 @@ #include - +#include #include "sunbeam.hpp" namespace { - py::list wellnames( const Group& g, size_t timestep ) { - return iterable_to_pylist( g.getWells( timestep ) ); + const std::set wellnames( const Group& g, size_t timestep ) { + return g.getWells( timestep ); } } -void sunbeam::export_Group() { +void sunbeam::export_Group(py::module& module) { - py::class_< Group >( "Group", py::no_init ) - .add_property( "name", mkcopy( &Group::name ) ) - .def( "_wellnames", &wellnames ) - ; + py::class_< Group >( module, "Group") + .def_property_readonly( "name", &Group::name) + .def( "_wellnames", &wellnames ); } diff --git a/sunbeam/group_tree.cpp b/sunbeam/group_tree.cpp index d8a17bab2..b60e91233 100644 --- a/sunbeam/group_tree.cpp +++ b/sunbeam/group_tree.cpp @@ -2,6 +2,7 @@ #include #include "sunbeam.hpp" +#include "converters.hpp" namespace { @@ -14,9 +15,9 @@ namespace { } } -void sunbeam::export_GroupTree() { +void sunbeam::export_GroupTree(py::module& module) { - py::class_< GroupTree >( "GroupTree", py::no_init) + py::class_< GroupTree >(module, "GroupTree") .def( "_parent", &parent, "parent function returning parent of a group") .def( "_children", &children, "children function returning python" diff --git a/sunbeam/parser.cpp b/sunbeam/parser.cpp index de5e49de2..99aba39d0 100644 --- a/sunbeam/parser.cpp +++ b/sunbeam/parser.cpp @@ -2,20 +2,20 @@ #include #include +#include + #include "sunbeam.hpp" namespace { Deck parseDeck( const std::string& deckStr, - const boost::python::list& keywords, + const std::vector& keywords, bool isFile, const ParseContext& pc ) { Parser p; - size_t len = py::extract(keywords.attr("__len__")()); - for (size_t i = 0; i < len; i++) { - const std::string kw = py::extract(py::str(keywords[i])); - const Json::JsonObject jkw(kw); + for (const auto& keyword : keywords) { + const Json::JsonObject jkw(keyword); p.addParserKeyword(jkw); } return isFile ? p.parseFile(deckStr, pc) : p.parseString(deckStr, pc); @@ -34,23 +34,20 @@ namespace { } void (ParseContext::*ctx_update)(const std::string&, InputError::Action) = &ParseContext::update; - } -void sunbeam::export_Parser() { +void sunbeam::export_Parser(py::module& module) { - py::def( "parse", parse ); - py::def( "parse_data", parseData ); - py::def( "parse_deck", &parseDeck ); + module.def( "parse", parse ); + module.def( "parse_data", parseData ); + module.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 ) - ; + py::class_< ParseContext >(module, "ParseContext" ) + .def(py::init<>()) + .def( "update", ctx_update ); + py::enum_< InputError::Action >( module, "action" ) + .value( "throw", InputError::Action::THROW_EXCEPTION ) + .value( "warn", InputError::Action::WARN ) + .value( "ignore", InputError::Action::IGNORE ); } diff --git a/sunbeam/schedule.cpp b/sunbeam/schedule.cpp index 1f961c2f5..e828ce4cf 100644 --- a/sunbeam/schedule.cpp +++ b/sunbeam/schedule.cpp @@ -1,11 +1,48 @@ +#include +#include #include -#include "converters.hpp" +#include +#include #include "sunbeam.hpp" namespace { + using system_clock = std::chrono::system_clock; + + + /* + timezones - the stuff that make you wonder why didn't do social science in + university. The situation here is as follows: + + 1. In the C++ code Eclipse style string literals like "20. NOV 2017" are + converted to time_t values using the utc based function timegm() which + does not take timezones into account. + + 2. Here we use the function gmtime( ) to convert back from a time_t value + to a broken down struct tm representation. + + 3. The broken down representation is then converted to a time_t value + using the timezone aware function mktime(). + + 4. The time_t value is converted to a std::chrono::system_clock value. + + Finally std::chrono::system_clock value is automatically converted to a + python datetime object as part of the pybind11 process. This latter + conversion *is* timezone aware, that is the reason we must go through + these hoops. + */ + system_clock::time_point datetime( std::time_t utc_time) { + struct tm utc_tm; + time_t local_time; + + gmtime_r(&utc_time, &utc_tm); + local_time = mktime(&utc_tm); + + return system_clock::from_time_t(local_time); + } + std::vector< Well > get_wells( const Schedule& sch ) { std::vector< Well > wells; for( const auto& w : sch.getWells() ) @@ -17,55 +54,51 @@ namespace { const Well& get_well( const Schedule& sch, const std::string& name ) try { return *sch.getWell( name ); } catch( const std::invalid_argument& e ) { - throw key_error( name ); + throw py::key_error( name ); } const GroupTree& get_grouptree ( const Schedule& sch, const size_t& timestep) { return sch.getGroupTree(timestep); } - - boost::posix_time::ptime get_start_time( const Schedule& s ) { - return boost::posix_time::from_time_t( s.posixStartTime() ); + system_clock::time_point get_start_time( const Schedule& s ) { + return datetime(s.posixStartTime()); } - boost::posix_time::ptime get_end_time( const Schedule& s ) { - return boost::posix_time::from_time_t( s.posixEndTime() ); + system_clock::time_point get_end_time( const Schedule& s ) { + return datetime(s.posixEndTime()); } - py::list get_timesteps( const Schedule& s ) { - namespace time = boost::posix_time; + std::vector get_timesteps( const Schedule& s ) { const auto& tm = s.getTimeMap(); - std::vector< time::ptime > v; + std::vector< system_clock::time_point > v; v.reserve( tm.size() ); for( size_t i = 0; i < tm.size(); ++i ) - v.push_back( time::from_time_t(tm[ i ]) ); + v.push_back( datetime( tm[ i ] )); - return iterable_to_pylist( v ); + return v; } - py::list get_groups( const Schedule& sch ) { + std::vector get_groups( const Schedule& sch ) { std::vector< Group > groups; for( const auto& g : sch.getGroups() ) groups.push_back( *g ); - - return iterable_to_pylist( groups ); + return groups; } } -void sunbeam::export_Schedule() { +void sunbeam::export_Schedule(py::module& module) { - py::class_< Schedule >( "Schedule", py::no_init ) - .add_property( "_wells", &get_wells ) - .add_property( "_groups", &get_groups ) - .add_property( "start", &get_start_time ) - .add_property( "end", &get_end_time ) - .add_property( "timesteps", &get_timesteps ) - .def( "__contains__", &Schedule::hasWell ) - .def( "__getitem__", &get_well, ref() ) - .def( "_group", &Schedule::getGroup, ref() ) - .def( "_group_tree", &get_grouptree, ref() ) - ; + py::class_< Schedule >( module, "Schedule") + .def_property_readonly( "_wells", &get_wells ) + .def_property_readonly( "_groups", &get_groups ) + .def_property_readonly( "start", &get_start_time ) + .def_property_readonly( "end", &get_end_time ) + .def_property_readonly( "timesteps", &get_timesteps ) + .def("_getwell", &get_well) + .def( "__contains__", &Schedule::hasWell ) + .def( "_group", &Schedule::getGroup, ref_internal) + .def( "_group_tree", &get_grouptree, ref_internal); } diff --git a/sunbeam/sunbeam.cpp b/sunbeam/sunbeam.cpp index 4f45b4960..fbd49f57b 100644 --- a/sunbeam/sunbeam.cpp +++ b/sunbeam/sunbeam.cpp @@ -1,34 +1,20 @@ -#include - -#include "converters.hpp" +#include #include "sunbeam.hpp" -BOOST_PYTHON_MODULE(libsunbeam) { - - /* - * Python C-API requires this macro to be invoked before anything from - * datetime.h is used. - */ - PyDateTime_IMPORT; - - /* register all converters */ - py::to_python_converter< const boost::posix_time::ptime, - ptime_to_python_datetime >(); - - py::register_exception_translator< key_error >( &key_error::translate ); - - sunbeam::export_Completion(); - sunbeam::export_Deck(); - sunbeam::export_DeckKeyword(); - sunbeam::export_Eclipse3DProperties(); - sunbeam::export_EclipseConfig(); - sunbeam::export_EclipseGrid(); - sunbeam::export_EclipseState(); - sunbeam::export_Group(); - sunbeam::export_Parser(); - sunbeam::export_Schedule(); - sunbeam::export_TableManager(); - sunbeam::export_Well(); - sunbeam::export_GroupTree(); +PYBIND11_MODULE(libsunbeam, module) { + sunbeam::export_Parser(module); + sunbeam::export_Deck(module); + sunbeam::export_DeckKeyword(module); + sunbeam::export_Schedule(module); + sunbeam::export_Well(module); + sunbeam::export_Group(module); + sunbeam::export_GroupTree(module); + sunbeam::export_Completion(module); + sunbeam::export_EclipseConfig(module); + sunbeam::export_Eclipse3DProperties(module); + sunbeam::export_EclipseState(module); + sunbeam::export_TableManager(module); + sunbeam::export_EclipseGrid(module); } + diff --git a/sunbeam/sunbeam.hpp b/sunbeam/sunbeam.hpp index eb1ab24e0..586e1b93b 100644 --- a/sunbeam/sunbeam.hpp +++ b/sunbeam/sunbeam.hpp @@ -1,32 +1,31 @@ #ifndef SUNBEAM_HPP #define SUNBEAM_HPP -#include - -#include "converters.hpp" +#include namespace Opm { } -namespace py = boost::python; +namespace py = pybind11; using namespace Opm; -using ref = py::return_internal_reference<>; -using copy = py::return_value_policy< py::copy_const_reference >; +const py::return_value_policy ref_internal = py::return_value_policy::reference_internal; +const py::return_value_policy python_owner = py::return_value_policy::take_ownership; +const py::return_value_policy move = py::return_value_policy::move; namespace sunbeam { -void export_Completion(); -void export_Deck(); -void export_DeckKeyword(); -void export_Eclipse3DProperties(); -void export_EclipseConfig(); -void export_EclipseGrid(); -void export_EclipseState(); -void export_Group(); -void export_Parser(); -void export_Schedule(); -void export_TableManager(); -void export_Well(); -void export_GroupTree(); + void export_Completion(py::module& module); + void export_Deck(py::module& module); + void export_DeckKeyword(py::module& module); + void export_Eclipse3DProperties(py::module& module); + void export_EclipseConfig(py::module& module); + void export_EclipseGrid(py::module& module); + void export_EclipseState(py::module& module); + void export_Group(py::module& module); + void export_Parser(py::module& module); + void export_Schedule(py::module& module); + void export_TableManager(py::module& module); + void export_Well(py::module& module); + void export_GroupTree(py::module& module); } diff --git a/sunbeam/table_manager.cpp b/sunbeam/table_manager.cpp index 62b356de5..5bcd41764 100644 --- a/sunbeam/table_manager.cpp +++ b/sunbeam/table_manager.cpp @@ -11,16 +11,15 @@ namespace { std::string col_name, double x ) try { return tab[tab_name].getTable(tab_idx).evaluate(col_name, x); } catch( std::invalid_argument& e ) { - throw key_error( e.what() ); + throw py::key_error( e.what() ); } } -void sunbeam::export_TableManager() { +void sunbeam::export_TableManager(py::module& module) { - py::class_< TableManager >( "Tables", py::no_init ) - .def( "__contains__", &TableManager::hasTables ) - .def("_evaluate", &evaluate ) - ; + py::class_< TableManager >( module, "Tables") + .def( "__contains__", &TableManager::hasTables ) + .def("_evaluate", &evaluate ); } diff --git a/sunbeam/well.cpp b/sunbeam/well.cpp index 8c4b49a5d..bd73bcc96 100644 --- a/sunbeam/well.cpp +++ b/sunbeam/well.cpp @@ -1,14 +1,13 @@ -#include - #include - +#include #include "sunbeam.hpp" namespace { - py::list completions( const Well& w, size_t timestep ) { - return iterable_to_pylist( w.getCompletions( timestep ) ); + std::vector completions( const Well& w, size_t timestep ) { + const auto& well_completions = w.getCompletions( timestep ); + return std::vector(well_completions.begin(), well_completions.end()); } std::string status( const Well& w, size_t timestep ) { @@ -34,11 +33,11 @@ namespace { } -void sunbeam::export_Well() { +void sunbeam::export_Well(py::module& module) { - py::class_< Well >( "Well", py::no_init ) - .add_property( "name", mkcopy( &Well::name ) ) - .add_property( "preferred_phase", &preferred_phase ) + py::class_< Well >( module, "Well") + .def_property_readonly( "name", &Well::name ) + .def_property_readonly( "preferred_phase", &preferred_phase ) .def( "I", headI ) .def( "I", headI_at ) .def( "J", headJ ) @@ -52,12 +51,7 @@ void sunbeam::export_Well() { .def( "group", &Well::getGroupName ) .def( "guide_rate", &Well::getGuideRate ) .def( "available_gctrl", &Well::isAvailableForGroupControl ) - .def( "__eq__", &Well::operator== ) - .def( "_completions", &completions ) - ; - - py::class_< std::vector< Well > >( "WellList", py::no_init ) - .def( py::vector_indexing_suite< std::vector< Well > >() ) - ; + .def( "__equal__", &Well::operator== ) + .def( "_completions", &completions ); }