Split out converters in separate file.

This commit is contained in:
Jørgen Kvalsvik
2016-12-17 12:46:25 +01:00
parent d3618225f0
commit c1bfade15f
2 changed files with 127 additions and 60 deletions

View File

@@ -0,0 +1,113 @@
#ifndef SUNBEAM_CONVERTERS_HPP
#define SUNBEAM_CONVERTERS_HPP
#include <exception>
#include <vector>
#include <boost/python.hpp>
#include <datetime.h>
namespace py = boost::python;
using namespace Opm;
/*
* 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, y) */
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;
for( const auto& x : v ) l.append( x );
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() );
}
#endif //SUNBEAM_CONVERTERS_HPP

View File

@@ -1,49 +1,33 @@
#include <boost/python.hpp>
#include <boost/python/suite/indexing/vector_indexing_suite.hpp>
#include <datetime.h>
#include <opm/parser/eclipse/EclipseState/EclipseState.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/Schedule.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/Well.hpp>
#include <opm/parser/eclipse/Parser/Parser.hpp>
#include "converters.hpp"
namespace py = boost::python;
using namespace Opm;
namespace {
/* converters */
py::list group_wellnames( const Group& g, size_t timestep ) {
return iterable_to_pylist( g.getWells( timestep ) );
}
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() ) );
std::string well_status( const Well* w, size_t timestep ) {
return WellCommon::Status2String( w->getStatus( timestep ) );
}
std::string well_prefphase( const Well* w ) {
switch( w->getPreferredPhase() ) {
case Phase::OIL: return "OIL";
case Phase::GAS: return "GAS";
case Phase::WATER: return "WATER";
default: throw std::logic_error( "Unhandled enum value" );
}
};
class key_error : public std::exception {
public:
static void translate( const key_error& e ) {
PyErr_SetString( PyExc_KeyError, e.what() );
}
key_error( const std::string& m ) : msg( m ) {}
const char* what() const throw() { return this->msg.c_str(); }
private:
std::string msg;
};
template< typename T >
py::list iterable_to_pylist( const T& v ) {
py::list l;
for( const auto& x : v ) l.append( x );
return l;
}
std::vector< Well > get_wells( const Schedule& sch ) {
@@ -60,36 +44,6 @@ const Well& get_well( const Schedule& sch, const std::string& name ) try {
throw key_error( name );
}
py::list group_wellnames( const Group& g, size_t timestep ) {
return iterable_to_pylist( g.getWells( timestep ) );
}
/* alias some of boost's long names and operations */
using ref = py::return_internal_reference<>;
using copy = py::return_value_policy< py::copy_const_reference >;
template< typename F >
auto mkref( F f ) -> decltype( py::make_function( f, ref() ) ) {
return py::make_function( f, ref() );
}
template< typename F >
auto mkcopy( F f ) -> decltype( py::make_function( f, copy() ) ) {
return py::make_function( f, copy() );
}
std::string well_status( const Well* w, size_t timestep ) {
return WellCommon::Status2String( w->getStatus( timestep ) );
}
std::string well_prefphase( const Well* w ) {
switch( w->getPreferredPhase() ) {
case Phase::OIL: return "OIL";
case Phase::GAS: return "GAS";
case Phase::WATER: return "WATER";
default: throw std::logic_error( "Unhandled enum value" );
}
}
boost::posix_time::ptime get_start_time( const Schedule* s ) {
return boost::posix_time::from_time_t( s->posixStartTime() );