// 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 #include #include #include 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::make()); * } * @endcode * */ template 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(); dtype out_dtype = dtype::get_builtin(); 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(iter.get_data(0)); TResult * result = reinterpret_cast(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::make()); * } * @endcode * */ template struct binary_ufunc { static object call(TBinaryFunctor & self, object const & input1, object const & input2, object const & output) { dtype in1_dtype = dtype::get_builtin(); dtype in2_dtype = dtype::get_builtin(); dtype out_dtype = dtype::get_builtin(); 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(iter.get_data(0)); TArgument2 * argument2 = reinterpret_cast(iter.get_data(1)); TResult * result = reinterpret_cast(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