diff options
Diffstat (limited to 'boost/python/numpy')
-rw-r--r-- | boost/python/numpy/dtype.hpp | 117 | ||||
-rw-r--r-- | boost/python/numpy/internal.hpp | 35 | ||||
-rw-r--r-- | boost/python/numpy/invoke_matching.hpp | 186 | ||||
-rw-r--r-- | boost/python/numpy/matrix.hpp | 82 | ||||
-rw-r--r-- | boost/python/numpy/ndarray.hpp | 296 | ||||
-rw-r--r-- | boost/python/numpy/numpy_object_mgr_traits.hpp | 36 | ||||
-rw-r--r-- | boost/python/numpy/scalars.hpp | 58 | ||||
-rw-r--r-- | boost/python/numpy/ufunc.hpp | 205 |
8 files changed, 1015 insertions, 0 deletions
diff --git a/boost/python/numpy/dtype.hpp b/boost/python/numpy/dtype.hpp new file mode 100644 index 0000000000..1284f9e5d8 --- /dev/null +++ b/boost/python/numpy/dtype.hpp @@ -0,0 +1,117 @@ +// Copyright Jim Bosch 2010-2012. +// Copyright Stefan Seefeld 2016. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef boost_python_numpy_dtype_hpp_ +#define boost_python_numpy_dtype_hpp_ + +/** + * @file boost/python/numpy/dtype.hpp + * @brief Object manager for Python's numpy.dtype class. + */ + +#include <boost/python.hpp> +#include <boost/python/numpy/numpy_object_mgr_traits.hpp> + +#include <boost/mpl/for_each.hpp> +#include <boost/type_traits/add_pointer.hpp> + +namespace boost { namespace python { namespace numpy { + +/** + * @brief A boost.python "object manager" (subclass of object) for numpy.dtype. + * + * @todo This could have a lot more interesting accessors. + */ +class dtype : public object { + static python::detail::new_reference convert(object::object_cref arg, bool align); +public: + + /// @brief Convert an arbitrary Python object to a data-type descriptor object. + template <typename T> + explicit dtype(T arg, bool align=false) : object(convert(arg, align)) {} + + /** + * @brief Get the built-in numpy dtype associated with the given scalar template type. + * + * This is perhaps the most useful part of the numpy API: it returns the dtype object + * corresponding to a built-in C++ type. This should work for any integer or floating point + * type supported by numpy, and will also work for std::complex if + * sizeof(std::complex<T>) == 2*sizeof(T). + * + * It can also be useful for users to add explicit specializations for POD structs + * that return field-based dtypes. + */ + template <typename T> static dtype get_builtin(); + + /// @brief Return the size of the data type in bytes. + int get_itemsize() const; + + /** + * @brief Compare two dtypes for equivalence. + * + * This is more permissive than equality tests. For instance, if long and int are the same + * size, the dtypes corresponding to each will be equivalent, but not equal. + */ + friend bool equivalent(dtype const & a, dtype const & b); + + /** + * @brief Register from-Python converters for NumPy's built-in array scalar types. + * + * This is usually called automatically by initialize(), and shouldn't be called twice + * (doing so just adds unused converters to the Boost.Python registry). + */ + static void register_scalar_converters(); + + BOOST_PYTHON_FORWARD_OBJECT_CONSTRUCTORS(dtype, object); + +}; + +bool equivalent(dtype const & a, dtype const & b); + +namespace detail +{ + +template <int bits, bool isUnsigned> dtype get_int_dtype(); + +template <int bits> dtype get_float_dtype(); + +template <int bits> dtype get_complex_dtype(); + +template <typename T, bool isInt=boost::is_integral<T>::value> +struct builtin_dtype; + +template <typename T> +struct builtin_dtype<T,true> { + static dtype get() { return get_int_dtype< 8*sizeof(T), boost::is_unsigned<T>::value >(); } +}; + +template <> +struct builtin_dtype<bool,true> { + static dtype get(); +}; + +template <typename T> +struct builtin_dtype<T,false> { + static dtype get() { return get_float_dtype< 8*sizeof(T) >(); } +}; + +template <typename T> +struct builtin_dtype< std::complex<T>, false > { + static dtype get() { return get_complex_dtype< 16*sizeof(T) >(); } +}; + +} // namespace detail + +template <typename T> +inline dtype dtype::get_builtin() { return detail::builtin_dtype<T>::get(); } + +} // namespace boost::python::numpy + +namespace converter { +NUMPY_OBJECT_MANAGER_TRAITS(numpy::dtype); +}}} // namespace boost::python::converter + +#endif diff --git a/boost/python/numpy/internal.hpp b/boost/python/numpy/internal.hpp new file mode 100644 index 0000000000..fed31cbb08 --- /dev/null +++ b/boost/python/numpy/internal.hpp @@ -0,0 +1,35 @@ +// Copyright Jim Bosch 2010-2012. +// Copyright Stefan Seefeld 2016. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef boost_python_numpy_internal_hpp_ +#define boost_python_numpy_internal_hpp_ + +/** + * @file boost/python/numpy/internal.hpp + * @brief Internal header file to include the Numpy C-API headers. + * + * This should only be included by source files in the boost.numpy library itself. + */ + +#include <boost/python.hpp> +#ifdef BOOST_PYTHON_NUMPY_INTERNAL +#define NO_IMPORT_ARRAY +#define NO_IMPORT_UFUNC +#else +#ifndef BOOST_PYTHON_NUMPY_INTERNAL_MAIN +ERROR_internal_hpp_is_for_internal_use_only +#endif +#endif +#define PY_ARRAY_UNIQUE_SYMBOL BOOST_NUMPY_ARRAY_API +#define PY_UFUNC_UNIQUE_SYMBOL BOOST_UFUNC_ARRAY_API +#include <numpy/arrayobject.h> +#include <numpy/ufuncobject.h> +#include <boost/python/numpy.hpp> + +#define NUMPY_OBJECT_MANAGER_TRAITS_IMPL(pytype,manager) \ + PyTypeObject const * object_manager_traits<manager>::get_pytype() { return &pytype; } + +#endif diff --git a/boost/python/numpy/invoke_matching.hpp b/boost/python/numpy/invoke_matching.hpp new file mode 100644 index 0000000000..90ec8ae2cb --- /dev/null +++ b/boost/python/numpy/invoke_matching.hpp @@ -0,0 +1,186 @@ +// Copyright Jim Bosch 2010-2012. +// Copyright Stefan Seefeld 2016. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef boost_python_numpy_invoke_matching_hpp_ +#define boost_python_numpy_invoke_matching_hpp_ + +/** + * @brief Template invocation based on dtype matching. + */ + +#include <boost/python/numpy/dtype.hpp> +#include <boost/python/numpy/ndarray.hpp> +#include <boost/mpl/integral_c.hpp> + +namespace boost { namespace python { namespace numpy { +namespace detail +{ + +struct add_pointer_meta +{ + template <typename T> + struct apply + { + typedef typename boost::add_pointer<T>::type type; + }; + +}; + +struct dtype_template_match_found {}; +struct nd_template_match_found {}; + +template <typename Function> +struct dtype_template_invoker +{ + + template <typename T> + void operator()(T *) const + { + if (dtype::get_builtin<T>() == m_dtype) + { + m_func.Function::template apply<T>(); + throw dtype_template_match_found(); + } + } + + dtype_template_invoker(dtype const & dtype_, Function func) + : m_dtype(dtype_), m_func(func) {} + +private: + dtype const & m_dtype; + Function m_func; +}; + +template <typename Function> +struct dtype_template_invoker< boost::reference_wrapper<Function> > +{ + + template <typename T> + void operator()(T *) const + { + if (dtype::get_builtin<T>() == m_dtype) + { + m_func.Function::template apply<T>(); + throw dtype_template_match_found(); + } + } + + dtype_template_invoker(dtype const & dtype_, Function & func) + : m_dtype(dtype_), m_func(func) {} + +private: + dtype const & m_dtype; + Function & m_func; +}; + +template <typename Function> +struct nd_template_invoker +{ + template <int N> + void operator()(boost::mpl::integral_c<int,N> *) const + { + if (m_nd == N) + { + m_func.Function::template apply<N>(); + throw nd_template_match_found(); + } + } + + nd_template_invoker(int nd, Function func) : m_nd(nd), m_func(func) {} + +private: + int m_nd; + Function m_func; +}; + +template <typename Function> +struct nd_template_invoker< boost::reference_wrapper<Function> > +{ + template <int N> + void operator()(boost::mpl::integral_c<int,N> *) const + { + if (m_nd == N) + { + m_func.Function::template apply<N>(); + throw nd_template_match_found(); + } + } + + nd_template_invoker(int nd, Function & func) : m_nd(nd), m_func(func) {} + +private: + int m_nd; + Function & m_func; +}; + +} // namespace boost::python::numpy::detail + +template <typename Sequence, typename Function> +void invoke_matching_nd(int nd, Function f) +{ + detail::nd_template_invoker<Function> invoker(nd, f); + try { boost::mpl::for_each< Sequence, detail::add_pointer_meta >(invoker);} + catch (detail::nd_template_match_found &) { return;} + PyErr_SetString(PyExc_TypeError, "number of dimensions not found in template list."); + python::throw_error_already_set(); +} + +template <typename Sequence, typename Function> +void invoke_matching_dtype(dtype const & dtype_, Function f) +{ + detail::dtype_template_invoker<Function> invoker(dtype_, f); + try { boost::mpl::for_each< Sequence, detail::add_pointer_meta >(invoker);} + catch (detail::dtype_template_match_found &) { return;} + PyErr_SetString(PyExc_TypeError, "dtype not found in template list."); + python::throw_error_already_set(); +} + +namespace detail +{ + +template <typename T, typename Function> +struct array_template_invoker_wrapper_2 +{ + template <int N> + void apply() const { m_func.Function::template apply<T,N>();} + array_template_invoker_wrapper_2(Function & func) : m_func(func) {} + +private: + Function & m_func; +}; + +template <typename DimSequence, typename Function> +struct array_template_invoker_wrapper_1 +{ + template <typename T> + void apply() const { invoke_matching_nd<DimSequence>(m_nd, array_template_invoker_wrapper_2<T,Function>(m_func));} + array_template_invoker_wrapper_1(int nd, Function & func) : m_nd(nd), m_func(func) {} + +private: + int m_nd; + Function & m_func; +}; + +template <typename DimSequence, typename Function> +struct array_template_invoker_wrapper_1< DimSequence, boost::reference_wrapper<Function> > + : public array_template_invoker_wrapper_1< DimSequence, Function > +{ + array_template_invoker_wrapper_1(int nd, Function & func) + : array_template_invoker_wrapper_1< DimSequence, Function >(nd, func) {} +}; + +} // namespace boost::python::numpy::detail + +template <typename TypeSequence, typename DimSequence, typename Function> +void invoke_matching_array(ndarray const & array_, Function f) +{ + detail::array_template_invoker_wrapper_1<DimSequence,Function> wrapper(array_.get_nd(), f); + invoke_matching_dtype<TypeSequence>(array_.get_dtype(), wrapper); +} + +}}} // namespace boost::python::numpy + +#endif diff --git a/boost/python/numpy/matrix.hpp b/boost/python/numpy/matrix.hpp new file mode 100644 index 0000000000..af20e8f9be --- /dev/null +++ b/boost/python/numpy/matrix.hpp @@ -0,0 +1,82 @@ +// Copyright Jim Bosch 2010-2012. +// Copyright Stefan Seefeld 2016. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef boost_python_numpy_matrix_hpp_ +#define boost_python_numpy_matrix_hpp_ + +/** + * @brief Object manager for numpy.matrix. + */ + +#include <boost/python.hpp> +#include <boost/python/numpy/numpy_object_mgr_traits.hpp> +#include <boost/python/numpy/ndarray.hpp> + +namespace boost { namespace python { namespace numpy { + +/** + * @brief A boost.python "object manager" (subclass of object) for numpy.matrix. + * + * @internal numpy.matrix is defined in Python, so object_manager_traits<matrix>::get_pytype() + * is implemented by importing numpy and getting the "matrix" attribute of the module. + * We then just hope that doesn't get destroyed while we need it, because if we put + * a dynamic python object in a static-allocated boost::python::object or handle<>, + * bad things happen when Python shuts down. I think this solution is safe, but I'd + * love to get that confirmed. + */ +class matrix : public ndarray +{ + static object construct(object_cref obj, dtype const & dt, bool copy); + static object construct(object_cref obj, bool copy); +public: + + BOOST_PYTHON_FORWARD_OBJECT_CONSTRUCTORS(matrix, ndarray); + + /// @brief Equivalent to "numpy.matrix(obj,dt,copy)" in Python. + explicit matrix(object const & obj, dtype const & dt, bool copy=true) + : ndarray(extract<ndarray>(construct(obj, dt, copy))) {} + + /// @brief Equivalent to "numpy.matrix(obj,copy=copy)" in Python. + explicit matrix(object const & obj, bool copy=true) + : ndarray(extract<ndarray>(construct(obj, copy))) {} + + /// \brief Return a view of the matrix with the given dtype. + matrix view(dtype const & dt) const; + + /// \brief Copy the scalar (deep for all non-object fields). + matrix copy() const; + + /// \brief Transpose the matrix. + matrix transpose() const; + +}; + +/** + * @brief CallPolicies that causes a function that returns a numpy.ndarray to + * return a numpy.matrix instead. + */ +template <typename Base = default_call_policies> +struct as_matrix : Base +{ + static PyObject * postcall(PyObject *, PyObject * result) + { + object a = object(handle<>(result)); + numpy::matrix m(a, false); + Py_INCREF(m.ptr()); + return m.ptr(); + } +}; + +} // namespace boost::python::numpy + +namespace converter +{ + +NUMPY_OBJECT_MANAGER_TRAITS(numpy::matrix); + +}}} // namespace boost::python::converter + +#endif diff --git a/boost/python/numpy/ndarray.hpp b/boost/python/numpy/ndarray.hpp new file mode 100644 index 0000000000..2985907b5b --- /dev/null +++ b/boost/python/numpy/ndarray.hpp @@ -0,0 +1,296 @@ +// Copyright Jim Bosch 2010-2012. +// Copyright Stefan Seefeld 2016. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef boost_python_numpy_ndarray_hpp_ +#define boost_python_numpy_ndarray_hpp_ + +/** + * @brief Object manager and various utilities for numpy.ndarray. + */ + +#include <boost/python.hpp> +#include <boost/utility/enable_if.hpp> +#include <boost/type_traits/is_integral.hpp> +#include <boost/python/numpy/numpy_object_mgr_traits.hpp> +#include <boost/python/numpy/dtype.hpp> +#include <vector> + +namespace boost { namespace python { namespace numpy { + +/** + * @brief A boost.python "object manager" (subclass of object) for numpy.ndarray. + * + * @todo This could have a lot more functionality (like boost::python::numeric::array). + * Right now all that exists is what was needed to move raw data between C++ and Python. + */ +class ndarray : public object +{ + + /** + * @brief An internal struct that's byte-compatible with PyArrayObject. + * + * This is just a hack to allow inline access to this stuff while hiding numpy/arrayobject.h + * from the user. + */ + struct array_struct + { + PyObject_HEAD + char * data; + int nd; + Py_intptr_t * shape; + Py_intptr_t * strides; + PyObject * base; + PyObject * descr; + int flags; + PyObject * weakreflist; + }; + + /// @brief Return the held Python object as an array_struct. + array_struct * get_struct() const { return reinterpret_cast<array_struct*>(this->ptr()); } + +public: + + /** + * @brief Enum to represent (some) of Numpy's internal flags. + * + * These don't match the actual Numpy flag values; we can't get those without including + * numpy/arrayobject.h or copying them directly. That's very unfortunate. + * + * @todo I'm torn about whether this should be an enum. It's very convenient to not + * make these simple integer values for overloading purposes, but the need to + * define every possible combination and custom bitwise operators is ugly. + */ + enum bitflag + { + NONE=0x0, C_CONTIGUOUS=0x1, F_CONTIGUOUS=0x2, V_CONTIGUOUS=0x1|0x2, + ALIGNED=0x4, WRITEABLE=0x8, BEHAVED=0x4|0x8, + CARRAY_RO=0x1|0x4, CARRAY=0x1|0x4|0x8, CARRAY_MIS=0x1|0x8, + FARRAY_RO=0x2|0x4, FARRAY=0x2|0x4|0x8, FARRAY_MIS=0x2|0x8, + UPDATE_ALL=0x1|0x2|0x4, VARRAY=0x1|0x2|0x8, ALL=0x1|0x2|0x4|0x8 + }; + + BOOST_PYTHON_FORWARD_OBJECT_CONSTRUCTORS(ndarray, object); + + /// @brief Return a view of the scalar with the given dtype. + ndarray view(dtype const & dt) const; + + /// @brief Copy the array, cast to a specified type. + ndarray astype(dtype const & dt) const; + + /// @brief Copy the scalar (deep for all non-object fields). + ndarray copy() const; + + /// @brief Return the size of the nth dimension. + Py_intptr_t shape(int n) const { return get_shape()[n]; } + + /// @brief Return the stride of the nth dimension. + Py_intptr_t strides(int n) const { return get_strides()[n]; } + + /** + * @brief Return the array's raw data pointer. + * + * This returns char so stride math works properly on it. It's pretty much + * expected that the user will have to reinterpret_cast it. + */ + char * get_data() const { return get_struct()->data; } + + /// @brief Return the array's data-type descriptor object. + dtype get_dtype() const; + + /// @brief Return the object that owns the array's data, or None if the array owns its own data. + object get_base() const; + + /// @brief Set the object that owns the array's data. Use with care. + void set_base(object const & base); + + /// @brief Return the shape of the array as an array of integers (length == get_nd()). + Py_intptr_t const * get_shape() const { return get_struct()->shape; } + + /// @brief Return the stride of the array as an array of integers (length == get_nd()). + Py_intptr_t const * get_strides() const { return get_struct()->strides; } + + /// @brief Return the number of array dimensions. + int get_nd() const { return get_struct()->nd; } + + /// @brief Return the array flags. + bitflag get_flags() const; + + /// @brief Reverse the dimensions of the array. + ndarray transpose() const; + + /// @brief Eliminate any unit-sized dimensions. + ndarray squeeze() const; + + /// @brief Equivalent to self.reshape(*shape) in Python. + ndarray reshape(python::tuple const & shape) const; + + /** + * @brief If the array contains only a single element, return it as an array scalar; otherwise return + * the array. + * + * @internal This is simply a call to PyArray_Return(); + */ + object scalarize() const; +}; + +/** + * @brief Construct a new array with the given shape and data type, with data initialized to zero. + */ +ndarray zeros(python::tuple const & shape, dtype const & dt); +ndarray zeros(int nd, Py_intptr_t const * shape, dtype const & dt); + +/** + * @brief Construct a new array with the given shape and data type, with data left uninitialized. + */ +ndarray empty(python::tuple const & shape, dtype const & dt); +ndarray empty(int nd, Py_intptr_t const * shape, dtype const & dt); + +/** + * @brief Construct a new array from an arbitrary Python sequence. + * + * @todo This does't seem to handle ndarray subtypes the same way that "numpy.array" does in Python. + */ +ndarray array(object const & obj); +ndarray array(object const & obj, dtype const & dt); + +namespace detail +{ + +ndarray from_data_impl(void * data, + dtype const & dt, + std::vector<Py_intptr_t> const & shape, + std::vector<Py_intptr_t> const & strides, + object const & owner, + bool writeable); + +template <typename Container> +ndarray from_data_impl(void * data, + dtype const & dt, + Container shape, + Container strides, + object const & owner, + bool writeable, + typename boost::enable_if< boost::is_integral<typename Container::value_type> >::type * enabled = NULL) +{ + std::vector<Py_intptr_t> shape_(shape.begin(),shape.end()); + std::vector<Py_intptr_t> strides_(strides.begin(), strides.end()); + return from_data_impl(data, dt, shape_, strides_, owner, writeable); +} + +ndarray from_data_impl(void * data, + dtype const & dt, + object const & shape, + object const & strides, + object const & owner, + bool writeable); + +} // namespace boost::python::numpy::detail + +/** + * @brief Construct a new ndarray object from a raw pointer. + * + * @param[in] data Raw pointer to the first element of the array. + * @param[in] dt Data type descriptor. Often retrieved with dtype::get_builtin(). + * @param[in] shape Shape of the array as STL container of integers; must have begin() and end(). + * @param[in] strides Shape of the array as STL container of integers; must have begin() and end(). + * @param[in] owner An arbitray Python object that owns that data pointer. The array object will + * keep a reference to the object, and decrement it's reference count when the + * array goes out of scope. Pass None at your own peril. + * + * @todo Should probably take ranges of iterators rather than actual container objects. + */ +template <typename Container> +inline ndarray from_data(void * data, + dtype const & dt, + Container shape, + Container strides, + python::object const & owner) +{ + return numpy::detail::from_data_impl(data, dt, shape, strides, owner, true); +} + +/** + * @brief Construct a new ndarray object from a raw pointer. + * + * @param[in] data Raw pointer to the first element of the array. + * @param[in] dt Data type descriptor. Often retrieved with dtype::get_builtin(). + * @param[in] shape Shape of the array as STL container of integers; must have begin() and end(). + * @param[in] strides Shape of the array as STL container of integers; must have begin() and end(). + * @param[in] owner An arbitray Python object that owns that data pointer. The array object will + * keep a reference to the object, and decrement it's reference count when the + * array goes out of scope. Pass None at your own peril. + * + * This overload takes a const void pointer and sets the "writeable" flag of the array to false. + * + * @todo Should probably take ranges of iterators rather than actual container objects. + */ +template <typename Container> +inline ndarray from_data(void const * data, + dtype const & dt, + Container shape, + Container strides, + python::object const & owner) +{ + return numpy::detail::from_data_impl(const_cast<void*>(data), dt, shape, strides, owner, false); +} + +/** + * @brief Transform an arbitrary object into a numpy array with the given requirements. + * + * @param[in] obj An arbitrary python object to convert. Arrays that meet the requirements + * will be passed through directly. + * @param[in] dt Data type descriptor. Often retrieved with dtype::get_builtin(). + * @param[in] nd_min Minimum number of dimensions. + * @param[in] nd_max Maximum number of dimensions. + * @param[in] flags Bitwise OR of flags specifying additional requirements. + */ +ndarray from_object(object const & obj, dtype const & dt, + int nd_min, int nd_max, ndarray::bitflag flags=ndarray::NONE); + +inline ndarray from_object(object const & obj, dtype const & dt, + int nd, ndarray::bitflag flags=ndarray::NONE) +{ + return from_object(obj, dt, nd, nd, flags); +} + +inline ndarray from_object(object const & obj, dtype const & dt, ndarray::bitflag flags=ndarray::NONE) +{ + return from_object(obj, dt, 0, 0, flags); +} + +ndarray from_object(object const & obj, int nd_min, int nd_max, + ndarray::bitflag flags=ndarray::NONE); + +inline ndarray from_object(object const & obj, int nd, ndarray::bitflag flags=ndarray::NONE) +{ + return from_object(obj, nd, nd, flags); +} + +inline ndarray from_object(object const & obj, ndarray::bitflag flags=ndarray::NONE) +{ + return from_object(obj, 0, 0, flags); +} + +inline ndarray::bitflag operator|(ndarray::bitflag a, ndarray::bitflag b) +{ + return ndarray::bitflag(int(a) | int(b)); +} + +inline ndarray::bitflag operator&(ndarray::bitflag a, ndarray::bitflag b) +{ + return ndarray::bitflag(int(a) & int(b)); +} + +} // namespace boost::python::numpy + +namespace converter +{ + +NUMPY_OBJECT_MANAGER_TRAITS(numpy::ndarray); + +}}} // namespace boost::python::converter + +#endif diff --git a/boost/python/numpy/numpy_object_mgr_traits.hpp b/boost/python/numpy/numpy_object_mgr_traits.hpp new file mode 100644 index 0000000000..8f9f444074 --- /dev/null +++ b/boost/python/numpy/numpy_object_mgr_traits.hpp @@ -0,0 +1,36 @@ +// Copyright Jim Bosch 2010-2012. +// Copyright Stefan Seefeld 2016. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef boost_python_numpy_numpy_object_mgr_traits_hpp_ +#define boost_python_numpy_numpy_object_mgr_traits_hpp_ + +/** + * @brief Macro that specializes object_manager_traits by requiring a + * source-file implementation of get_pytype(). + */ + +#define NUMPY_OBJECT_MANAGER_TRAITS(manager) \ +template <> \ +struct object_manager_traits<manager> \ +{ \ + BOOST_STATIC_CONSTANT(bool, is_specialized = true); \ + static inline python::detail::new_reference adopt(PyObject* x) \ + { \ + return python::detail::new_reference(python::pytype_check((PyTypeObject*)get_pytype(), x)); \ + } \ + static bool check(PyObject* x) \ + { \ + return ::PyObject_IsInstance(x, (PyObject*)get_pytype()); \ + } \ + static manager* checked_downcast(PyObject* x) \ + { \ + return python::downcast<manager>((checked_downcast_impl)(x, (PyTypeObject*)get_pytype())); \ + } \ + static PyTypeObject const * get_pytype(); \ +} + +#endif + diff --git a/boost/python/numpy/scalars.hpp b/boost/python/numpy/scalars.hpp new file mode 100644 index 0000000000..0ba23c41ac --- /dev/null +++ b/boost/python/numpy/scalars.hpp @@ -0,0 +1,58 @@ +// Copyright Jim Bosch 2010-2012. +// Copyright Stefan Seefeld 2016. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef boost_python_numpy_scalars_hpp_ +#define boost_python_numpy_scalars_hpp_ + +/** + * @brief Object managers for array scalars (currently only numpy.void is implemented). + */ + +#include <boost/python.hpp> +#include <boost/python/numpy/numpy_object_mgr_traits.hpp> +#include <boost/python/numpy/dtype.hpp> + +namespace boost { namespace python { namespace numpy { + +/** + * @brief A boost.python "object manager" (subclass of object) for numpy.void. + * + * @todo This could have a lot more functionality. + */ +class void_ : public object +{ + static python::detail::new_reference convert(object_cref arg, bool align); +public: + + /** + * @brief Construct a new array scalar with the given size and void dtype. + * + * Data is initialized to zero. One can create a standalone scalar object + * with a certain dtype "dt" with: + * @code + * void_ scalar = void_(dt.get_itemsize()).view(dt); + * @endcode + */ + explicit void_(Py_ssize_t size); + + BOOST_PYTHON_FORWARD_OBJECT_CONSTRUCTORS(void_, object); + + /// @brief Return a view of the scalar with the given dtype. + void_ view(dtype const & dt) const; + + /// @brief Copy the scalar (deep for all non-object fields). + void_ copy() const; + +}; + +} // namespace boost::python::numpy + +namespace converter +{ +NUMPY_OBJECT_MANAGER_TRAITS(numpy::void_); +}}} // namespace boost::python::converter + +#endif diff --git a/boost/python/numpy/ufunc.hpp b/boost/python/numpy/ufunc.hpp new file mode 100644 index 0000000000..9262b37840 --- /dev/null +++ b/boost/python/numpy/ufunc.hpp @@ -0,0 +1,205 @@ +// Copyright Jim Bosch 2010-2012. +// Copyright Stefan Seefeld 2016. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef boost_python_numpy_ufunc_hpp_ +#define boost_python_numpy_ufunc_hpp_ + +/** + * @brief Utilities to create ufunc-like broadcasting functions out of C++ functors. + */ + +#include <boost/python.hpp> +#include <boost/python/numpy/numpy_object_mgr_traits.hpp> +#include <boost/python/numpy/dtype.hpp> +#include <boost/python/numpy/ndarray.hpp> + +namespace boost { namespace python { namespace numpy { + +/** + * @brief A boost.python "object manager" (subclass of object) for PyArray_MultiIter. + * + * multi_iter is a Python object, but a very low-level one. It should generally only be used + * in loops of the form: + * @code + * while (iter.not_done()) { + * ... + * iter.next(); + * } + * @endcode + * + * @todo I can't tell if this type is exposed in Python anywhere; if it is, we should use that name. + * It's more dangerous than most object managers, however - maybe it actually belongs in + * a detail namespace? + */ +class multi_iter : public object +{ +public: + + BOOST_PYTHON_FORWARD_OBJECT_CONSTRUCTORS(multi_iter, object); + + /// @brief Increment the iterator. + void next(); + + /// @brief Check if the iterator is at its end. + bool not_done() const; + + /// @brief Return a pointer to the element of the nth broadcasted array. + char * get_data(int n) const; + + /// @brief Return the number of dimensions of the broadcasted array expression. + int get_nd() const; + + /// @brief Return the shape of the broadcasted array expression as an array of integers. + Py_intptr_t const * get_shape() const; + + /// @brief Return the shape of the broadcasted array expression in the nth dimension. + Py_intptr_t shape(int n) const; + +}; + +/// @brief Construct a multi_iter over a single sequence or scalar object. +multi_iter make_multi_iter(object const & a1); + +/// @brief Construct a multi_iter by broadcasting two objects. +multi_iter make_multi_iter(object const & a1, object const & a2); + +/// @brief Construct a multi_iter by broadcasting three objects. +multi_iter make_multi_iter(object const & a1, object const & a2, object const & a3); + +/** + * @brief Helps wrap a C++ functor taking a single scalar argument as a broadcasting ufunc-like + * Python object. + * + * Typical usage looks like this: + * @code + * struct TimesPI + * { + * typedef double argument_type; + * typedef double result_type; + * double operator()(double input) const { return input * M_PI; } + * }; + * + * BOOST_PYTHON_MODULE(example) + * { + * class_< TimesPI >("TimesPI") + * .def("__call__", unary_ufunc<TimesPI>::make()); + * } + * @endcode + * + */ +template <typename TUnaryFunctor, + typename TArgument=typename TUnaryFunctor::argument_type, + typename TResult=typename TUnaryFunctor::result_type> +struct unary_ufunc +{ + + /** + * @brief A C++ function with object arguments that broadcasts its arguments before + * passing them to the underlying C++ functor. + */ + static object call(TUnaryFunctor & self, object const & input, object const & output) + { + dtype in_dtype = dtype::get_builtin<TArgument>(); + dtype out_dtype = dtype::get_builtin<TResult>(); + ndarray in_array = from_object(input, in_dtype, ndarray::ALIGNED); + ndarray out_array = (output != object()) ? + from_object(output, out_dtype, ndarray::ALIGNED | ndarray::WRITEABLE) + : zeros(in_array.get_nd(), in_array.get_shape(), out_dtype); + multi_iter iter = make_multi_iter(in_array, out_array); + while (iter.not_done()) + { + TArgument * argument = reinterpret_cast<TArgument*>(iter.get_data(0)); + TResult * result = reinterpret_cast<TResult*>(iter.get_data(1)); + *result = self(*argument); + iter.next(); + } + return out_array.scalarize(); + } + + /** + * @brief Construct a boost.python function object from call() with reasonable keyword names. + * + * Users will often want to specify their own keyword names with the same signature, but this + * is a convenient shortcut. + */ + static object make() + { + return make_function(call, default_call_policies(), (arg("input"), arg("output")=object())); + } +}; + +/** + * @brief Helps wrap a C++ functor taking a pair of scalar arguments as a broadcasting ufunc-like + * Python object. + * + * Typical usage looks like this: + * @code + * struct CosSum + * { + * typedef double first_argument_type; + * typedef double second_argument_type; + * typedef double result_type; + * double operator()(double input1, double input2) const { return std::cos(input1 + input2); } + * }; + * + * BOOST_PYTHON_MODULE(example) + * { + * class_< CosSum >("CosSum") + * .def("__call__", binary_ufunc<CosSum>::make()); + * } + * @endcode + * + */ +template <typename TBinaryFunctor, + typename TArgument1=typename TBinaryFunctor::first_argument_type, + typename TArgument2=typename TBinaryFunctor::second_argument_type, + typename TResult=typename TBinaryFunctor::result_type> +struct binary_ufunc +{ + + static object + call(TBinaryFunctor & self, object const & input1, object const & input2, + object const & output) + { + dtype in1_dtype = dtype::get_builtin<TArgument1>(); + dtype in2_dtype = dtype::get_builtin<TArgument2>(); + dtype out_dtype = dtype::get_builtin<TResult>(); + ndarray in1_array = from_object(input1, in1_dtype, ndarray::ALIGNED); + ndarray in2_array = from_object(input2, in2_dtype, ndarray::ALIGNED); + multi_iter iter = make_multi_iter(in1_array, in2_array); + ndarray out_array = (output != object()) + ? from_object(output, out_dtype, ndarray::ALIGNED | ndarray::WRITEABLE) + : zeros(iter.get_nd(), iter.get_shape(), out_dtype); + iter = make_multi_iter(in1_array, in2_array, out_array); + while (iter.not_done()) + { + TArgument1 * argument1 = reinterpret_cast<TArgument1*>(iter.get_data(0)); + TArgument2 * argument2 = reinterpret_cast<TArgument2*>(iter.get_data(1)); + TResult * result = reinterpret_cast<TResult*>(iter.get_data(2)); + *result = self(*argument1, *argument2); + iter.next(); + } + return out_array.scalarize(); + } + + static object make() + { + return make_function(call, default_call_policies(), + (arg("input1"), arg("input2"), arg("output")=object())); + } + +}; + +} // namespace boost::python::numpy + +namespace converter +{ + +NUMPY_OBJECT_MANAGER_TRAITS(numpy::multi_iter); + +}}} // namespace boost::python::converter + +#endif |