xtensor
Loading...
Searching...
No Matches
xaccumulator.hpp
1/***************************************************************************
2 * Copyright (c) Johan Mabille, Sylvain Corlay and Wolf Vollprecht *
3 * Copyright (c) QuantStack *
4 * *
5 * Distributed under the terms of the BSD 3-Clause License. *
6 * *
7 * The full license is in the file LICENSE, distributed with this software. *
8 ****************************************************************************/
9
10#ifndef XTENSOR_ACCUMULATOR_HPP
11#define XTENSOR_ACCUMULATOR_HPP
12
13#include <algorithm>
14#include <cstddef>
15#include <numeric>
16#include <type_traits>
17
18#include "xexpression.hpp"
19#include "xstrides.hpp"
20#include "xtensor_config.hpp"
21#include "xtensor_forward.hpp"
22
23namespace xt
24{
25
26#define DEFAULT_STRATEGY_ACCUMULATORS evaluation_strategy::immediate_type
27
28 namespace detail
29 {
30 template <class V = void>
31 struct accumulator_identity : xtl::identity
32 {
33 using value_type = V;
34 };
35 }
36
37 /**************
38 * accumulate *
39 **************/
40
41 template <class ACCUMULATE_FUNC, class INIT_FUNC = detail::accumulator_identity<void>>
42 struct xaccumulator_functor : public std::tuple<ACCUMULATE_FUNC, INIT_FUNC>
43 {
45 using base_type = std::tuple<ACCUMULATE_FUNC, INIT_FUNC>;
46 using accumulate_functor_type = ACCUMULATE_FUNC;
47 using init_functor_type = INIT_FUNC;
48 using init_value_type = typename init_functor_type::value_type;
49
51 : base_type()
52 {
53 }
54
55 template <class RF>
56 xaccumulator_functor(RF&& accumulate_func)
57 : base_type(std::forward<RF>(accumulate_func), INIT_FUNC())
58 {
59 }
60
61 template <class RF, class IF>
62 xaccumulator_functor(RF&& accumulate_func, IF&& init_func)
63 : base_type(std::forward<RF>(accumulate_func), std::forward<IF>(init_func))
64 {
65 }
66 };
67
68 template <class RF>
69 auto make_xaccumulator_functor(RF&& accumulate_func)
70 {
72 return accumulator_type(std::forward<RF>(accumulate_func));
73 }
74
75 template <class RF, class IF>
76 auto make_xaccumulator_functor(RF&& accumulate_func, IF&& init_func)
77 {
78 using accumulator_type = xaccumulator_functor<std::remove_reference_t<RF>, std::remove_reference_t<IF>>;
79 return accumulator_type(std::forward<RF>(accumulate_func), std::forward<IF>(init_func));
80 }
81
82 namespace detail
83 {
84 template <class F, class E, class EVS>
85 xarray<typename std::decay_t<E>::value_type> accumulator_impl(F&&, E&&, std::size_t, EVS)
86 {
87 static_assert(
88 !std::is_same<evaluation_strategy::lazy_type, EVS>::value,
89 "Lazy accumulators not yet implemented."
90 );
91 }
92
93 template <class F, class E, class EVS>
94 xarray<typename std::decay_t<E>::value_type> accumulator_impl(F&&, E&&, EVS)
95 {
96 static_assert(
97 !std::is_same<evaluation_strategy::lazy_type, EVS>::value,
98 "Lazy accumulators not yet implemented."
99 );
100 }
101
102 template <class T, class R>
103 struct xaccumulator_return_type
104 {
105 using type = xarray<R>;
106 };
107
108 template <class T, layout_type L, class R>
109 struct xaccumulator_return_type<xarray<T, L>, R>
110 {
111 using type = xarray<R, L>;
112 };
113
114 template <class T, std::size_t N, layout_type L, class R>
115 struct xaccumulator_return_type<xtensor<T, N, L>, R>
116 {
117 using type = xtensor<R, N, L>;
118 };
119
120 template <class T, std::size_t... I, layout_type L, class R>
121 struct xaccumulator_return_type<xtensor_fixed<T, xshape<I...>, L>, R>
122 {
123 using type = xtensor_fixed<R, xshape<I...>, L>;
124 };
125
126 template <class T, class R>
127 using xaccumulator_return_type_t = typename xaccumulator_return_type<T, R>::type;
128
129 template <class T>
130 struct fixed_compute_size;
131
132 template <class T, class R>
133 struct xaccumulator_linear_return_type
134 {
135 using type = xtensor<R, 1>;
136 };
137
138 template <class T, layout_type L, class R>
139 struct xaccumulator_linear_return_type<xarray<T, L>, R>
140 {
141 using type = xtensor<R, 1, L>;
142 };
143
144 template <class T, std::size_t N, layout_type L, class R>
145 struct xaccumulator_linear_return_type<xtensor<T, N, L>, R>
146 {
147 using type = xtensor<R, 1, L>;
148 };
149
150 template <class T, std::size_t... I, layout_type L, class R>
151 struct xaccumulator_linear_return_type<xtensor_fixed<T, xshape<I...>, L>, R>
152 {
153 using type = xtensor_fixed<R, xshape<fixed_compute_size<xshape<I...>>::value>, L>;
154 };
155
156 template <class T, class R>
157 using xaccumulator_linear_return_type_t = typename xaccumulator_linear_return_type<T, R>::type;
158
159 template <class F, class E>
160 inline auto accumulator_init_with_f(F&& f, E& e, std::size_t axis)
161 {
162 // this function is the equivalent (but hopefully faster) to (if axis == 1)
163 // e[:, 0, :, :, ...] = f(e[:, 0, :, :, ...])
164 // so that all "first" values are initialized in a first pass
165
166 std::size_t outer_loop_size, inner_loop_size, pos = 0;
167 std::size_t outer_stride, inner_stride;
168
169 auto set_loop_sizes = [&outer_loop_size, &inner_loop_size](auto first, auto last, std::ptrdiff_t ax)
170 {
171 outer_loop_size = std::accumulate(
172 first,
173 first + ax,
174 std::size_t(1),
175 std::multiplies<std::size_t>()
176 );
177 inner_loop_size = std::accumulate(
178 first + ax + 1,
179 last,
180 std::size_t(1),
181 std::multiplies<std::size_t>()
182 );
183 };
184
185 // Note: add check that strides > 0
186 auto set_loop_strides = [&outer_stride, &inner_stride](auto first, auto last, std::ptrdiff_t ax)
187 {
188 outer_stride = static_cast<std::size_t>(ax == 0 ? 1 : *std::min_element(first, first + ax));
189 inner_stride = static_cast<std::size_t>(
190 (ax == std::distance(first, last) - 1) ? 1 : *std::min_element(first + ax + 1, last)
191 );
192 };
193
194 set_loop_sizes(e.shape().begin(), e.shape().end(), static_cast<std::ptrdiff_t>(axis));
195 set_loop_strides(e.strides().begin(), e.strides().end(), static_cast<std::ptrdiff_t>(axis));
196
197 if (e.layout() == layout_type::column_major)
198 {
199 // swap for better memory locality (smaller stride in the inner loop)
200 std::swap(outer_loop_size, inner_loop_size);
201 std::swap(outer_stride, inner_stride);
202 }
203
204 for (std::size_t i = 0; i < outer_loop_size; ++i)
205 {
206 pos = i * outer_stride;
207 for (std::size_t j = 0; j < inner_loop_size; ++j)
208 {
209 e.storage()[pos] = f(e.storage()[pos]);
210 pos += inner_stride;
211 }
212 }
213 }
214
215 template <class F, class E>
216 inline auto accumulator_impl(F&& f, E&& e, std::size_t axis, evaluation_strategy::immediate_type)
217 {
218 using init_type = typename F::init_value_type;
219 using accumulate_functor_type = typename F::accumulate_functor_type;
220 using expr_value_type = typename std::decay_t<E>::value_type;
221 // using return_type = std::conditional_t<std::is_same<init_type, void>::value, typename
222 // std::decay_t<E>::value_type, init_type>;
223
224 using return_type = std::decay_t<decltype(std::declval<accumulate_functor_type>()(
225 std::declval<init_type>(),
226 std::declval<expr_value_type>()
227 ))>;
228
229 using result_type = xaccumulator_return_type_t<std::decay_t<E>, return_type>;
230
231 if (axis >= e.dimension())
232 {
233 XTENSOR_THROW(std::runtime_error, "Axis larger than expression dimension in accumulator.");
234 }
235
236 result_type res = e; // assign + make a copy, we need it anyways
237
238 if (res.shape(axis) != std::size_t(0))
239 {
240 std::size_t inner_stride = static_cast<std::size_t>(res.strides()[axis]);
241 std::size_t outer_stride = 1; // either row- or column-wise (strides.back / strides.front)
242 std::size_t outer_loop_size = 0;
243 std::size_t inner_loop_size = 0;
244 std::size_t init_size = e.shape()[axis] != std::size_t(1) ? std::size_t(1) : std::size_t(0);
245
246 auto set_loop_sizes =
247 [&outer_loop_size, &inner_loop_size, init_size](auto first, auto last, std::ptrdiff_t ax)
248 {
249 outer_loop_size = std::accumulate(first, first + ax, init_size, std::multiplies<std::size_t>());
250
251 inner_loop_size = std::accumulate(
252 first + ax,
253 last,
254 std::size_t(1),
255 std::multiplies<std::size_t>()
256 );
257 };
258
259 if (result_type::static_layout == layout_type::row_major)
260 {
261 set_loop_sizes(res.shape().cbegin(), res.shape().cend(), static_cast<std::ptrdiff_t>(axis));
262 }
263 else
264 {
265 set_loop_sizes(res.shape().cbegin(), res.shape().cend(), static_cast<std::ptrdiff_t>(axis + 1));
266 std::swap(inner_loop_size, outer_loop_size);
267 }
268
269 std::size_t pos = 0;
270
271 inner_loop_size = inner_loop_size - inner_stride;
272
273 // activate the init loop if we have an init function other than identity
274 if (!std::is_same<
275 std::decay_t<typename F::init_functor_type>,
276 typename detail::accumulator_identity<init_type>>::value)
277 {
278 accumulator_init_with_f(xt::get<1>(f), res, axis);
279 }
280
281 pos = 0;
282 for (std::size_t i = 0; i < outer_loop_size; ++i)
283 {
284 for (std::size_t j = 0; j < inner_loop_size; ++j)
285 {
286 res.storage()[pos + inner_stride] = xt::get<0>(f)(
287 res.storage()[pos],
288 res.storage()[pos + inner_stride]
289 );
290
291 pos += outer_stride;
292 }
293 pos += inner_stride;
294 }
295 }
296 return res;
297 }
298
299 template <class F, class E>
300 inline auto accumulator_impl(F&& f, E&& e, evaluation_strategy::immediate_type)
301 {
302 using init_type = typename F::init_value_type;
303 using expr_value_type = typename std::decay_t<E>::value_type;
304 using accumulate_functor_type = typename F::accumulate_functor_type;
305 using return_type = std::decay_t<decltype(std::declval<accumulate_functor_type>()(
306 std::declval<init_type>(),
307 std::declval<expr_value_type>()
308 ))>;
309 // using return_type = std::conditional_t<std::is_same<init_type, void>::value, typename
310 // std::decay_t<E>::value_type, init_type>;
311
312 using result_type = xaccumulator_return_type_t<std::decay_t<E>, return_type>;
313
314 std::size_t sz = e.size();
315 auto result = result_type::from_shape({sz});
316
317 if (sz != std::size_t(0))
318 {
319 auto it = e.template begin<XTENSOR_DEFAULT_TRAVERSAL>();
320 result.storage()[0] = xt::get<1>(f)(*it);
321 ++it;
322
323 for (std::size_t idx = 0; it != e.template end<XTENSOR_DEFAULT_TRAVERSAL>(); ++it)
324 {
325 result.storage()[idx + 1] = xt::get<0>(f)(result.storage()[idx], *it);
326 ++idx;
327 }
328 }
329 return result;
330 }
331 }
332
343 template <class F, class E, class EVS = DEFAULT_STRATEGY_ACCUMULATORS, XTL_REQUIRES(is_evaluation_strategy<EVS>)>
344 inline auto accumulate(F&& f, E&& e, EVS evaluation_strategy = EVS())
345 {
346 // Note we need to check is_integral above in order to prohibit EVS = int, and not taking the
347 // std::size_t overload below!
348 return detail::accumulator_impl(std::forward<F>(f), std::forward<E>(e), evaluation_strategy);
349 }
350
362 template <class F, class E, class EVS = DEFAULT_STRATEGY_ACCUMULATORS>
363 inline auto accumulate(F&& f, E&& e, std::ptrdiff_t axis, EVS evaluation_strategy = EVS())
364 {
365 std::size_t ax = normalize_axis(e.dimension(), axis);
366 return detail::accumulator_impl(std::forward<F>(f), std::forward<E>(e), ax, evaluation_strategy);
367 }
368}
369
370#endif
standard mathematical functions for xexpressions
xtensor_container< uvector< T, A >, N, L > xtensor
Alias template on xtensor_container with default parameters for data container type.
xarray_container< uvector< T, A >, L, xt::svector< typename uvector< T, A >::size_type, 4, SA, true > > xarray
Alias template on xarray_container with default parameters for data container type and shape / stride...
layout_type
Definition xlayout.hpp:24
auto accumulate(F &&f, E &&e, EVS evaluation_strategy=EVS())
Accumulate and flatten array NOTE This function is not lazy!
fixed_shape< N... > xshape
Alias template for fixed_shape allows for a shorter template shape definition in xtensor_fixed.
xfixed_container< T, FSH, L, Sharable > xtensor_fixed
Alias template on xfixed_container with default parameters for layout type.