ufunc.hpp 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. // Copyright Jim Bosch 2010-2012.
  2. // Copyright Stefan Seefeld 2016.
  3. // Distributed under the Boost Software License, Version 1.0.
  4. // (See accompanying file LICENSE_1_0.txt or copy at
  5. // http://www.boost.org/LICENSE_1_0.txt)
  6. #ifndef boost_python_numpy_ufunc_hpp_
  7. #define boost_python_numpy_ufunc_hpp_
  8. /**
  9. * @brief Utilities to create ufunc-like broadcasting functions out of C++ functors.
  10. */
  11. #include <boost/python.hpp>
  12. #include <boost/python/numpy/numpy_object_mgr_traits.hpp>
  13. #include <boost/python/numpy/dtype.hpp>
  14. #include <boost/python/numpy/ndarray.hpp>
  15. #include <boost/python/numpy/config.hpp>
  16. namespace boost { namespace python { namespace numpy {
  17. /**
  18. * @brief A boost.python "object manager" (subclass of object) for PyArray_MultiIter.
  19. *
  20. * multi_iter is a Python object, but a very low-level one. It should generally only be used
  21. * in loops of the form:
  22. * @code
  23. * while (iter.not_done()) {
  24. * ...
  25. * iter.next();
  26. * }
  27. * @endcode
  28. *
  29. * @todo I can't tell if this type is exposed in Python anywhere; if it is, we should use that name.
  30. * It's more dangerous than most object managers, however - maybe it actually belongs in
  31. * a detail namespace?
  32. */
  33. class BOOST_NUMPY_DECL multi_iter : public object
  34. {
  35. public:
  36. BOOST_PYTHON_FORWARD_OBJECT_CONSTRUCTORS(multi_iter, object);
  37. /// @brief Increment the iterator.
  38. void next();
  39. /// @brief Check if the iterator is at its end.
  40. bool not_done() const;
  41. /// @brief Return a pointer to the element of the nth broadcasted array.
  42. char * get_data(int n) const;
  43. /// @brief Return the number of dimensions of the broadcasted array expression.
  44. int get_nd() const;
  45. /// @brief Return the shape of the broadcasted array expression as an array of integers.
  46. Py_intptr_t const * get_shape() const;
  47. /// @brief Return the shape of the broadcasted array expression in the nth dimension.
  48. Py_intptr_t shape(int n) const;
  49. };
  50. /// @brief Construct a multi_iter over a single sequence or scalar object.
  51. BOOST_NUMPY_DECL multi_iter make_multi_iter(object const & a1);
  52. /// @brief Construct a multi_iter by broadcasting two objects.
  53. BOOST_NUMPY_DECL multi_iter make_multi_iter(object const & a1, object const & a2);
  54. /// @brief Construct a multi_iter by broadcasting three objects.
  55. BOOST_NUMPY_DECL multi_iter make_multi_iter(object const & a1, object const & a2, object const & a3);
  56. /**
  57. * @brief Helps wrap a C++ functor taking a single scalar argument as a broadcasting ufunc-like
  58. * Python object.
  59. *
  60. * Typical usage looks like this:
  61. * @code
  62. * struct TimesPI
  63. * {
  64. * typedef double argument_type;
  65. * typedef double result_type;
  66. * double operator()(double input) const { return input * M_PI; }
  67. * };
  68. *
  69. * BOOST_PYTHON_MODULE(example)
  70. * {
  71. * class_< TimesPI >("TimesPI")
  72. * .def("__call__", unary_ufunc<TimesPI>::make());
  73. * }
  74. * @endcode
  75. *
  76. */
  77. template <typename TUnaryFunctor,
  78. typename TArgument=typename TUnaryFunctor::argument_type,
  79. typename TResult=typename TUnaryFunctor::result_type>
  80. struct unary_ufunc
  81. {
  82. /**
  83. * @brief A C++ function with object arguments that broadcasts its arguments before
  84. * passing them to the underlying C++ functor.
  85. */
  86. static object call(TUnaryFunctor & self, object const & input, object const & output)
  87. {
  88. dtype in_dtype = dtype::get_builtin<TArgument>();
  89. dtype out_dtype = dtype::get_builtin<TResult>();
  90. ndarray in_array = from_object(input, in_dtype, ndarray::ALIGNED);
  91. ndarray out_array = ! output.is_none() ?
  92. from_object(output, out_dtype, ndarray::ALIGNED | ndarray::WRITEABLE)
  93. : zeros(in_array.get_nd(), in_array.get_shape(), out_dtype);
  94. multi_iter iter = make_multi_iter(in_array, out_array);
  95. while (iter.not_done())
  96. {
  97. TArgument * argument = reinterpret_cast<TArgument*>(iter.get_data(0));
  98. TResult * result = reinterpret_cast<TResult*>(iter.get_data(1));
  99. *result = self(*argument);
  100. iter.next();
  101. }
  102. return out_array.scalarize();
  103. }
  104. /**
  105. * @brief Construct a boost.python function object from call() with reasonable keyword names.
  106. *
  107. * Users will often want to specify their own keyword names with the same signature, but this
  108. * is a convenient shortcut.
  109. */
  110. static object make()
  111. {
  112. return make_function(call, default_call_policies(), (arg("input"), arg("output")=object()));
  113. }
  114. };
  115. /**
  116. * @brief Helps wrap a C++ functor taking a pair of scalar arguments as a broadcasting ufunc-like
  117. * Python object.
  118. *
  119. * Typical usage looks like this:
  120. * @code
  121. * struct CosSum
  122. * {
  123. * typedef double first_argument_type;
  124. * typedef double second_argument_type;
  125. * typedef double result_type;
  126. * double operator()(double input1, double input2) const { return std::cos(input1 + input2); }
  127. * };
  128. *
  129. * BOOST_PYTHON_MODULE(example)
  130. * {
  131. * class_< CosSum >("CosSum")
  132. * .def("__call__", binary_ufunc<CosSum>::make());
  133. * }
  134. * @endcode
  135. *
  136. */
  137. template <typename TBinaryFunctor,
  138. typename TArgument1=typename TBinaryFunctor::first_argument_type,
  139. typename TArgument2=typename TBinaryFunctor::second_argument_type,
  140. typename TResult=typename TBinaryFunctor::result_type>
  141. struct binary_ufunc
  142. {
  143. static object
  144. call(TBinaryFunctor & self, object const & input1, object const & input2,
  145. object const & output)
  146. {
  147. dtype in1_dtype = dtype::get_builtin<TArgument1>();
  148. dtype in2_dtype = dtype::get_builtin<TArgument2>();
  149. dtype out_dtype = dtype::get_builtin<TResult>();
  150. ndarray in1_array = from_object(input1, in1_dtype, ndarray::ALIGNED);
  151. ndarray in2_array = from_object(input2, in2_dtype, ndarray::ALIGNED);
  152. multi_iter iter = make_multi_iter(in1_array, in2_array);
  153. ndarray out_array = !output.is_none()
  154. ? from_object(output, out_dtype, ndarray::ALIGNED | ndarray::WRITEABLE)
  155. : zeros(iter.get_nd(), iter.get_shape(), out_dtype);
  156. iter = make_multi_iter(in1_array, in2_array, out_array);
  157. while (iter.not_done())
  158. {
  159. TArgument1 * argument1 = reinterpret_cast<TArgument1*>(iter.get_data(0));
  160. TArgument2 * argument2 = reinterpret_cast<TArgument2*>(iter.get_data(1));
  161. TResult * result = reinterpret_cast<TResult*>(iter.get_data(2));
  162. *result = self(*argument1, *argument2);
  163. iter.next();
  164. }
  165. return out_array.scalarize();
  166. }
  167. static object make()
  168. {
  169. return make_function(call, default_call_policies(),
  170. (arg("input1"), arg("input2"), arg("output")=object()));
  171. }
  172. };
  173. } // namespace boost::python::numpy
  174. namespace converter
  175. {
  176. NUMPY_OBJECT_MANAGER_TRAITS(numpy::multi_iter);
  177. }}} // namespace boost::python::converter
  178. #endif