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