[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