[C++-sig] more examples of automatic type conversion
John Reid
j.reid at mail.cryst.bbk.ac.uk
Thu May 3 13:30:45 CEST 2007
Neal Becker wrote:
> I'm very interested in interfacing various c++ array containers to numpy.
> Do you have any small examples?
I have what works for me although it is not extensively tested. It is
not fancy as it copies data on conversion to/from python but I don't
need any more than that. I've just implemented conversion for
boost::multi_array but other arrays should be straightforward. Also it
is a shameless hack of Roman's tuple example.
No doubt it could be improved considerably but I hope it is useful.
Here is an example of using it to export a property of a simple struct:
struct numpy_test
{
typedef boost::multi_array< double, 2 > double_array;
double_array a;
void print_shape() const { std::cout << a.shape()[ 0 ] << "," <<
a.shape()[ 1 ] << "\n"; }
void print_first() const { std::cout << a[ 0 ][ 0 ] << "\n"; }
};
void
export_model()
{
numpy_multi_array_converter< numpy_test::double_array
>::register_to_and_from_python();
class_< numpy_test >( "numpy_test" )
.def( "print_shape", &numpy_test::print_shape )
.def( "print_first", &numpy_test::print_first )
.add_property(
"array",
make_getter( &numpy_test::a, return_value_policy< return_by_value >() ),
numpy_multi_array_converter< numpy_test::double_array
>::set_member_with_resize< numpy_test, &numpy_test::a > )
;
}
and here is the code to do the conversion:
namespace detail {
template< typename T >
struct get_dtype
{
static const char * name() { throw std::logic_error( "get_dtype not
specialised for this type" ); }
};
#define DECLARE_DTYPE_FOR( type, dtype ) template< > struct get_dtype<
type > { static const char * name() { return dtype; } };
DECLARE_DTYPE_FOR( double, "float64" )
DECLARE_DTYPE_FOR( float, "float32" )
DECLARE_DTYPE_FOR( int, "int32" )
DECLARE_DTYPE_FOR( unsigned, "uint32" )
DECLARE_DTYPE_FOR( long, "int64" )
DECLARE_DTYPE_FOR( unsigned long, "uint64" )
}
// namespace impl
template< typename MultiArrayType >
struct numpy_multi_array_converter
{
typedef MultiArrayType multi_array_t;
typedef std::vector< std::size_t > shape_t;
static void register_to_and_from_python()
{
register_from_python();
register_to_python();
}
static void register_to_python()
{
boost::python::to_python_converter< multi_array_t,
numpy_multi_array_converter< multi_array_t > >();
}
static void register_from_python()
{
boost::python::converter::registry::push_back(
&numpy_multi_array_converter< multi_array_t >::convertible,
&numpy_multi_array_converter< multi_array_t >::construct,
boost::python::type_id< multi_array_t >()
);
}
static
void *
convertible( PyObject * obj )
{
using namespace boost::python;
try
{
shape_t shape;
get_shape( object( handle<>( borrowed( obj ) ) ), shape );
if( multi_array_t::dimensionality != shape.size() ) return 0;
}
catch( ... )
{
return 0;
}
return obj;
}
template<
typename C,
multi_array_t C::* pm
>
static
void
set_member_with_resize( C & c, const multi_array_t & a )
{
std::vector< unsigned > extents;
for( unsigned dim = 0; a.num_dimensions() != dim; ++dim )
extents.push_back( a.shape()[ dim ] );
(c.*pm).resize( extents );
(c.*pm) = a;
}
static
void
construct(
PyObject* obj,
boost::python::converter::rvalue_from_python_stage1_data* data )
{
using namespace boost::python;
//get the storage
typedef converter::rvalue_from_python_storage< multi_array_t >
storage_t;
storage_t * the_storage = reinterpret_cast< storage_t * >( data );
void * memory_chunk = the_storage->storage.bytes;
//new placement
object py_obj( handle<>( borrowed( obj ) ) );
shape_t shape;
get_shape( py_obj, shape );
multi_array_t * a = new (memory_chunk) multi_array_t( shape );
//extract each element from numpy array and put in c array
index i( a->num_dimensions(), 0 );
do
{
using boost::python::list;
using boost::python::tuple;
list numpy_index;
for( unsigned dim = 0; a->num_dimensions() != dim; ++dim ) {
numpy_index.append( i[ dim ] ); }
( *a )( i ) = extract< multi_array_t::element >( py_obj[ tuple(
numpy_index ) ] );
}
while( increment_index( i, *a ) );
data->convertible = memory_chunk;
}
static
PyObject *
convert( const multi_array_t & c_array )
{
using namespace boost::python;
object numpy = object( handle<>( ::PyImport_Import( object( "numpy"
).ptr() ) ) );
if( ! numpy ) throw std::logic_error( "Could not import numpy" );
object array_function = numpy.attr( "empty" );
if( ! array_function ) throw std::logic_error( "Could not find array
function" );
//create a numpy array to put it in
boost::python::list extents;
for( unsigned dim = 0; c_array.num_dimensions() != dim; ++dim )
extents.append( c_array.shape()[ dim ] );
object result(
array_function(
extents,
numpy.attr( "dtype" )( detail::get_dtype< multi_array_t::element
>::name() ) ) );
//copy the elements
index i( c_array.num_dimensions(), 0 );
do
{
boost::python::list numpy_index;
for( unsigned dim = 0; c_array.num_dimensions() != dim; ++dim ) {
numpy_index.append( i[dim] ); }
result[ tuple( numpy_index ) ] = c_array( i );
}
while( increment_index( i, c_array ) );
return incref( result.ptr() );
}
protected:
static
void
get_shape( boost::python::object & obj, shape_t & shape )
{
using namespace boost::python;
shape.clear();
object py_shape = obj.attr( "shape" );
const std::size_t N = len( py_shape );
for( std::size_t i = 0; N != i; ++i ) shape.push_back( extract<
std::size_t >( py_shape[ i ] ) );
}
typedef std::vector< typename multi_array_t::index > index; /**< To
iterate over entries in num_dimensions independent fashion. */
/**< Iterates over entries in num_dimensions independent fashion. */
static
bool
increment_index( index & i, const multi_array_t & c_array )
{
for( unsigned dim = 0; i.size() != dim; ++dim )
{
++i[dim];
if( i[dim] != c_array.shape()[dim] ) return true;
else i[dim] = 0;
}
return false;
}
};
More information about the Cplusplus-sig
mailing list