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