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