1 #ifndef XTENSOR_XBLOCKWISE_REDUCER_HPP
2 #define XTENSOR_XBLOCKWISE_REDUCER_HPP
3 
4 #include "xtl/xclosure.hpp"
5 #include "xtl/xsequence.hpp"
6 
7 #include "xshape.hpp"
8 #include "xblockwise_reducer_functors.hpp"
9 #include "xmultiindex_iterator.hpp"
10 #include "xreducer.hpp"
11 
12 namespace xt
13 {
14 
15 template<class CT, class F, class X, class O>
16 class xblockwise_reducer
17 {
18 public:
19 
20     using self_type = xblockwise_reducer<CT, F, X, O>;
21     using raw_options_type = std::decay_t<O>;
22     using keep_dims = xtl::mpl::contains<raw_options_type, xt::keep_dims_type>;
23     using xexpression_type = std::decay_t<CT>;
24     using shape_type = typename xreducer_shape_type<typename xexpression_type::shape_type, std::decay_t<X>, keep_dims>::type;
25     using functor_type = F;
26     using value_type = typename functor_type::value_type;
27     using input_shape_type = typename xexpression_type::shape_type;
28     using input_chunk_index_type = filter_fixed_shape_t<input_shape_type>;
29     using input_grid_strides = filter_fixed_shape_t<input_shape_type>;
30     using axes_type = X;
31     using chunk_shape_type = filter_fixed_shape_t<shape_type>;
32 
33 
34     template<class E, class BS, class XX, class OO, class FF>
35     xblockwise_reducer(E && e, BS && block_shape,  XX && axes, OO && options, FF && functor);
36 
37     const input_shape_type & input_shape()const;
38     const axes_type & axes() const;
39 
40     std::size_t dimension() const;
41 
42     const shape_type & shape() const;
43 
44     const chunk_shape_type & chunk_shape() const;
45 
46     template<class R>
47     void assign_to(R & result) const;
48 
49 private:
50     using mapping_type = filter_fixed_shape_t<shape_type>;
51     using input_chunked_view_type = xchunked_view<const std::decay_t<CT> &>;
52     using input_const_chunked_iterator_type = typename input_chunked_view_type::const_chunk_iterator;
53     using input_chunk_range_type = std::array<xmultiindex_iterator<input_chunk_index_type>, 2>;
54 
55     template<class CI>
56     void assign_to_chunk(CI & result_chunk_iter) const;
57 
58     template<class CI>
59     input_chunk_range_type compute_input_chunk_range(CI & result_chunk_iter) const;
60 
61     input_const_chunked_iterator_type get_input_chunk_iter(input_chunk_index_type  input_chunk_index) const;
62     void init_shapes();
63 
64     CT m_e;
65     xchunked_view<const std::decay_t<CT> &>  m_e_chunked_view;
66     axes_type m_axes;
67     raw_options_type m_options;
68     functor_type m_functor;
69     shape_type m_result_shape;
70     chunk_shape_type m_result_chunk_shape;
71     mapping_type m_mapping;
72     input_grid_strides m_input_grid_strides;
73 };
74 
75 
76 template<class CT, class F, class X, class O>
77 template<class E, class BS, class XX, class OO, class FF>
xblockwise_reducer(E && e,BS && block_shape,XX && axes,OO && options,FF && functor)78 xblockwise_reducer<CT, F, X, O>::xblockwise_reducer(E && e, BS && block_shape,  XX && axes, OO && options, FF && functor)
79 :   m_e(std::forward<E>(e)),
80     m_e_chunked_view(m_e, std::forward<BS>(block_shape)),
81     m_axes(std::forward<XX>(axes)),
82     m_options(std::forward<OO>(options)),
83     m_functor(std::forward<FF>(functor)),
84     m_result_shape(),
85     m_result_chunk_shape(),
86     m_mapping(),
87     m_input_grid_strides()
88 {
89     init_shapes();
90     resize_container(m_input_grid_strides, m_e.dimension());
91     std::size_t stride = 1;
92 
93     for (std::size_t i = m_input_grid_strides.size(); i != 0; --i)
94     {
95         m_input_grid_strides[i-1] = stride;
96         stride *= m_e_chunked_view.grid_shape()[i - 1];
97     }
98 }
99 
100 template<class CT, class F, class X, class O>
input_shape() const101 inline auto xblockwise_reducer<CT, F, X, O>::input_shape()const -> const input_shape_type &
102 {
103     return m_e.shape();
104 }
105 
106 template<class CT, class F, class X, class O>
axes() const107 inline auto xblockwise_reducer<CT, F, X, O>::axes() const -> const axes_type &
108 {
109     return m_axes;
110 }
111 
112 template<class CT, class F, class X, class O>
dimension() const113 inline std::size_t xblockwise_reducer<CT, F, X, O>::dimension() const
114 {
115     return m_result_shape.size();
116 }
117 
118 template<class CT, class F, class X, class O>
shape() const119 inline auto xblockwise_reducer<CT, F, X, O>::shape() const -> const shape_type &
120 {
121     return m_result_shape;
122 }
123 
124 template<class CT, class F, class X, class O>
chunk_shape() const125 inline auto xblockwise_reducer<CT, F, X, O>::chunk_shape() const -> const chunk_shape_type &
126 {
127     return m_result_chunk_shape;
128 }
129 
130 template<class CT, class F, class X, class O>
131 template<class R>
assign_to(R & result) const132 inline void xblockwise_reducer<CT, F, X, O>::assign_to(R & result) const
133 {
134     auto result_chunked_view = as_chunked(result, m_result_chunk_shape);
135     for(auto chunk_iter =  result_chunked_view.chunk_begin(); chunk_iter != result_chunked_view.chunk_end(); ++chunk_iter)
136     {
137         assign_to_chunk(chunk_iter);
138     }
139 }
140 
141 template<class CT, class F, class X, class O>
get_input_chunk_iter(input_chunk_index_type input_chunk_index) const142 auto xblockwise_reducer<CT, F, X, O>::get_input_chunk_iter(input_chunk_index_type  input_chunk_index) const ->input_const_chunked_iterator_type
143 {
144     std::size_t chunk_linear_index = 0;
145     for(std::size_t i=0; i<m_e_chunked_view.dimension(); ++i)
146     {
147         chunk_linear_index += input_chunk_index[i] * m_input_grid_strides[i];
148     }
149     return input_const_chunked_iterator_type(m_e_chunked_view, std::move(input_chunk_index), chunk_linear_index);
150 }
151 
152 template<class CT, class F, class X, class O>
153 template<class CI>
assign_to_chunk(CI & result_chunk_iter) const154 void xblockwise_reducer<CT, F, X, O>::assign_to_chunk(CI & result_chunk_iter) const
155 {
156     auto result_chunk_view = *result_chunk_iter;
157     auto reduction_variable = m_functor.reduction_variable(result_chunk_view);
158 
159     // get the range of input chunks we need to compute the desired ouput chunk
160     auto range = compute_input_chunk_range(result_chunk_iter);
161 
162     // iterate over input chunk (indics)
163     auto first = true;
164     // std::for_each(std::get<0>(range), std::get<1>(range), [&](auto && input_chunk_index)
165     auto iter = std::get<0>(range);
166     while(iter != std::get<1>(range))
167     {
168         const auto & input_chunk_index = *iter;
169         // get input chunk iterator from chunk index
170         auto chunked_input_iter = this->get_input_chunk_iter(input_chunk_index);
171         auto input_chunk_view = *chunked_input_iter;
172 
173         // compute the per block result
174         auto block_res = m_functor.compute(input_chunk_view, m_axes, m_options);
175 
176         // merge
177         m_functor.merge(block_res, first,  result_chunk_view, reduction_variable);
178         first = false;
179         ++iter;
180     }
181 
182     // finalize (ie smth like normalization)
183     m_functor.finalize(reduction_variable, result_chunk_view, *this);
184 }
185 
186 template<class CT, class F, class X, class O>
187 template<class CI>
compute_input_chunk_range(CI & result_chunk_iter) const188 auto xblockwise_reducer<CT, F, X, O>::compute_input_chunk_range(CI & result_chunk_iter) const -> input_chunk_range_type
189 {
190     auto input_chunks_begin = xtl::make_sequence<input_chunk_index_type>(m_e_chunked_view.dimension(), 0);
191     auto input_chunks_end = xtl::make_sequence<input_chunk_index_type>(m_e_chunked_view.dimension());
192 
193     XTENSOR_ASSERT(input_chunks_begin.size() == m_e_chunked_view.dimension());
194     XTENSOR_ASSERT(input_chunks_end.size() == m_e_chunked_view.dimension());
195 
196     std::copy(m_e_chunked_view.grid_shape().begin(), m_e_chunked_view.grid_shape().end(),  input_chunks_end.begin());
197 
198     const auto & chunk_index = result_chunk_iter.chunk_index();
199     for(std::size_t result_ax_index=0; result_ax_index<m_result_shape.size(); ++result_ax_index)
200     {
201         if(m_result_shape[result_ax_index] != 1)
202         {
203             const auto input_ax_index = m_mapping[result_ax_index];
204             input_chunks_begin[input_ax_index] = chunk_index[result_ax_index];
205             input_chunks_end[input_ax_index] = chunk_index[result_ax_index] + 1;
206         }
207     }
208     return input_chunk_range_type{
209         multiindex_iterator_begin<input_chunk_index_type>(input_chunks_begin, input_chunks_end),
210         multiindex_iterator_end<input_chunk_index_type>(input_chunks_begin, input_chunks_end)
211     };
212 }
213 
214 
215 template<class CT, class F, class X, class O>
init_shapes()216 void xblockwise_reducer<CT, F, X, O>::init_shapes()
217 {
218     const auto & shape = m_e.shape();
219     const auto dimension = m_e.dimension();
220     const auto & block_shape = m_e_chunked_view.chunk_shape();
221     if (xtl::mpl::contains<raw_options_type, xt::keep_dims_type>::value)
222     {
223         resize_container(m_result_shape, dimension);
224         resize_container(m_result_chunk_shape, dimension);
225         resize_container(m_mapping, dimension);
226         for (std::size_t i = 0; i < dimension; ++i)
227         {
228             m_mapping[i] = i;
229             if (std::find(m_axes.begin(), m_axes.end(), i) == m_axes.end())
230             {
231                 // i not in m_axes!
232                 m_result_shape[i] = shape[i];
233                 m_result_chunk_shape[i] = block_shape[i];
234             }
235             else
236             {
237                 m_result_shape[i] = 1;
238                 m_result_chunk_shape[i] = 1;
239             }
240         }
241     }
242     else
243     {
244         const auto result_dim =  dimension - m_axes.size();
245         resize_container(m_result_shape, result_dim);
246         resize_container(m_result_chunk_shape, result_dim);
247         resize_container(m_mapping, result_dim);
248 
249         for (std::size_t i = 0, idx = 0; i < dimension; ++i)
250         {
251             if (std::find(m_axes.begin(), m_axes.end(), i) == m_axes.end())
252             {
253                 // i not in axes!
254                 m_result_shape[idx] = shape[i];
255                 m_result_chunk_shape[idx] = block_shape[i];
256                 m_mapping[idx] = i;
257                 ++idx;
258             }
259         }
260     }
261 }
262 
263 template<class E, class CS, class A, class O, class FF>
blockwise_reducer(E && e,CS && chunk_shape,A && axes,O && raw_options,FF && functor)264 inline auto blockwise_reducer(E && e, CS && chunk_shape, A && axes, O && raw_options, FF && functor)
265 {
266     using functor_type = std::decay_t<FF>;
267     using closure_type = xtl::const_closure_type_t<E>;
268     using axes_type = std::decay_t<A>;
269 
270     return xblockwise_reducer<
271         closure_type,
272         functor_type,
273         axes_type,
274         O
275     >(
276         std::forward<E>(e),
277         std::forward<CS>(chunk_shape),
278         std::forward<A>(axes),
279         std::forward<O>(raw_options),
280         std::forward<FF>(functor)
281     );
282 }
283 
284 namespace blockwise
285 {
286 
287     #define XTENSOR_BLOCKWISE_REDUCER_FUNC(FNAME, FUNCTOR)\
288         template<class T=void, class E, class BS, class X, class O = DEFAULT_STRATEGY_REDUCERS,\
289         XTL_REQUIRES(xtl::negation<is_reducer_options<X>>, xtl::negation<xtl::is_integral<std::decay_t<X>>>)\
290         >\
291         auto FNAME(E && e, BS && block_shape, X && axes, O options = O())\
292         {\
293             using input_expression_type = std::decay_t<E>;\
294             using functor_type = FUNCTOR <typename input_expression_type::value_type, T>;\
295             return blockwise_reducer(\
296                 std::forward<E>(e), \
297                 std::forward<BS>(block_shape), \
298                 std::forward<X>(axes),\
299                 std::forward<O>(options),\
300                 functor_type());\
301         }\
302         template<class T=void, class E, class BS, class X, class O = DEFAULT_STRATEGY_REDUCERS,\
303         XTL_REQUIRES(xtl::is_integral<std::decay_t<X>>)\
304         >\
305         auto FNAME(E && e, BS && block_shape, X axis, O options = O())\
306         {\
307             std::array<X,1> axes{axis};\
308             using input_expression_type = std::decay_t<E>;\
309             using functor_type = FUNCTOR <typename input_expression_type::value_type, T>;\
310             return blockwise_reducer(\
311                 std::forward<E>(e), \
312                 std::forward<BS>(block_shape), \
313                 axes,\
314                 std::forward<O>(options),\
315                 functor_type());\
316         }\
317         template<class T=void, class E, class BS, class O = DEFAULT_STRATEGY_REDUCERS,\
318         XTL_REQUIRES(is_reducer_options<O>, xtl::negation<xtl::is_integral<std::decay_t<O>>>)\
319         >\
320         auto FNAME(E && e, BS && block_shape, O options = O())\
321         {\
322             using input_expression_type = std::decay_t<E>;\
323             using axes_type = filter_fixed_shape_t<typename input_expression_type::shape_type>;\
324             axes_type axes = xtl::make_sequence<axes_type>(e.dimension());\
325             XTENSOR_ASSERT(axes.size() == e.dimension());\
326             std::iota(axes.begin(), axes.end(), 0);\
327             using functor_type = FUNCTOR <typename input_expression_type::value_type, T>;\
328             return blockwise_reducer(\
329                 std::forward<E>(e), \
330                 std::forward<BS>(block_shape), \
331                 std::move(axes),\
332                 std::forward<O>(options),\
333                 functor_type());\
334         }\
335         template<class T=void, class E, class BS, class I, std::size_t N, class O = DEFAULT_STRATEGY_REDUCERS>\
336         auto FNAME(E && e, BS && block_shape, const I (&axes)[N], O options = O())\
337         {\
338             using input_expression_type = std::decay_t<E>;\
339             using functor_type = FUNCTOR <typename input_expression_type::value_type, T>;\
340             using axes_type = std::array<std::size_t, N>;\
341             auto ax = xt::forward_normalize<axes_type>(e, axes);\
342             return blockwise_reducer(\
343                 std::forward<E>(e), \
344                 std::forward<BS>(block_shape), \
345                 std::move(ax),\
346                 std::forward<O>(options),\
347                 functor_type());\
348         }
349     XTENSOR_BLOCKWISE_REDUCER_FUNC(sum, xt::detail::blockwise::sum_functor)
350     XTENSOR_BLOCKWISE_REDUCER_FUNC(prod, xt::detail::blockwise::prod_functor)
351     XTENSOR_BLOCKWISE_REDUCER_FUNC(amin, xt::detail::blockwise::amin_functor)
352     XTENSOR_BLOCKWISE_REDUCER_FUNC(amax, xt::detail::blockwise::amax_functor)
353     XTENSOR_BLOCKWISE_REDUCER_FUNC(mean, xt::detail::blockwise::mean_functor)
354     XTENSOR_BLOCKWISE_REDUCER_FUNC(variance, xt::detail::blockwise::variance_functor)
355     XTENSOR_BLOCKWISE_REDUCER_FUNC(stddev, xt::detail::blockwise::stddev_functor)
356 
357     #undef XTENSOR_BLOCKWISE_REDUCER_FUNC
358 
359 
360     // norm reducers do *not* allow to to pass a template
361     // parameter to specifiy the internal computation type
362     #define XTENSOR_BLOCKWISE_NORM_REDUCER_FUNC(FNAME, FUNCTOR)\
363         template<class E, class BS, class X, class O = DEFAULT_STRATEGY_REDUCERS,\
364         XTL_REQUIRES(xtl::negation<is_reducer_options<X>>, xtl::negation<xtl::is_integral<std::decay_t<X>>>)\
365         >\
366         auto FNAME(E && e, BS && block_shape, X && axes, O options = O())\
367         {\
368             using input_expression_type = std::decay_t<E>;\
369             using functor_type = FUNCTOR <typename input_expression_type::value_type>;\
370             return blockwise_reducer(\
371                 std::forward<E>(e), \
372                 std::forward<BS>(block_shape), \
373                 std::forward<X>(axes),\
374                 std::forward<O>(options),\
375                 functor_type());\
376         }\
377         template<class E, class BS, class X, class O = DEFAULT_STRATEGY_REDUCERS,\
378         XTL_REQUIRES(xtl::is_integral<std::decay_t<X>>)\
379         >\
380         auto FNAME(E && e, BS && block_shape, X axis, O options = O())\
381         {\
382             std::array<X,1> axes{axis};\
383             using input_expression_type = std::decay_t<E>;\
384             using functor_type = FUNCTOR <typename input_expression_type::value_type>;\
385             return blockwise_reducer(\
386                 std::forward<E>(e), \
387                 std::forward<BS>(block_shape), \
388                 axes,\
389                 std::forward<O>(options),\
390                 functor_type());\
391         }\
392         template<class E, class BS, class O = DEFAULT_STRATEGY_REDUCERS,\
393         XTL_REQUIRES(is_reducer_options<O>, xtl::negation<xtl::is_integral<std::decay_t<O>>>)\
394         >\
395         auto FNAME(E && e, BS && block_shape, O options = O())\
396         {\
397             using input_expression_type = std::decay_t<E>;\
398             using axes_type = filter_fixed_shape_t<typename input_expression_type::shape_type>;\
399             axes_type axes = xtl::make_sequence<axes_type>(e.dimension());\
400             XTENSOR_ASSERT(axes.size() == e.dimension());\
401             std::iota(axes.begin(), axes.end(), 0);\
402             using functor_type = FUNCTOR <typename input_expression_type::value_type>;\
403             return blockwise_reducer(\
404                 std::forward<E>(e), \
405                 std::forward<BS>(block_shape), \
406                 std::move(axes),\
407                 std::forward<O>(options),\
408                 functor_type());\
409         }\
410         template<class E, class BS, class I, std::size_t N, class O = DEFAULT_STRATEGY_REDUCERS>\
411         auto FNAME(E && e, BS && block_shape, const I (&axes)[N], O options = O())\
412         {\
413             using input_expression_type = std::decay_t<E>;\
414             using functor_type = FUNCTOR <typename input_expression_type::value_type>;\
415             using axes_type = std::array<std::size_t, N>;\
416             auto ax = xt::forward_normalize<axes_type>(e, axes);\
417             return blockwise_reducer(\
418                 std::forward<E>(e), \
419                 std::forward<BS>(block_shape), \
420                 std::move(ax),\
421                 std::forward<O>(options),\
422                 functor_type());\
423         }
424     XTENSOR_BLOCKWISE_NORM_REDUCER_FUNC(norm_l0, xt::detail::blockwise::norm_l0_functor)
425     XTENSOR_BLOCKWISE_NORM_REDUCER_FUNC(norm_l1, xt::detail::blockwise::norm_l1_functor)
426     XTENSOR_BLOCKWISE_NORM_REDUCER_FUNC(norm_l2, xt::detail::blockwise::norm_l2_functor)
427     XTENSOR_BLOCKWISE_NORM_REDUCER_FUNC(norm_sq, xt::detail::blockwise::norm_sq_functor)
428     XTENSOR_BLOCKWISE_NORM_REDUCER_FUNC(norm_linf, xt::detail::blockwise::norm_linf_functor)
429 
430     #undef XTENSOR_BLOCKWISE_NORM_REDUCER_FUNC
431 
432 
433     #define XTENSOR_BLOCKWISE_NORM_REDUCER_FUNC(FNAME, FUNCTOR)\
434         template<class E, class BS, class X, class O = DEFAULT_STRATEGY_REDUCERS,\
435         XTL_REQUIRES(xtl::negation<is_reducer_options<X>>, xtl::negation<xtl::is_integral<std::decay_t<X>>>)\
436         >\
437         auto FNAME(E && e, BS && block_shape, double p, X && axes, O options = O())\
438         {\
439             using input_expression_type = std::decay_t<E>;\
440             using functor_type = FUNCTOR <typename input_expression_type::value_type>;\
441             return blockwise_reducer(\
442                 std::forward<E>(e), \
443                 std::forward<BS>(block_shape), \
444                 std::forward<X>(axes),\
445                 std::forward<O>(options),\
446                 functor_type(p));\
447         }\
448         template<class E, class BS, class X, class O = DEFAULT_STRATEGY_REDUCERS,\
449         XTL_REQUIRES(xtl::is_integral<std::decay_t<X>>)\
450         >\
451         auto FNAME(E && e, BS && block_shape, double p, X axis, O options = O())\
452         {\
453             std::array<X,1> axes{axis};\
454             using input_expression_type = std::decay_t<E>;\
455             using functor_type = FUNCTOR <typename input_expression_type::value_type>;\
456             return blockwise_reducer(\
457                 std::forward<E>(e), \
458                 std::forward<BS>(block_shape), \
459                 axes,\
460                 std::forward<O>(options),\
461                 functor_type(p));\
462         }\
463         template<class E, class BS, class O = DEFAULT_STRATEGY_REDUCERS,\
464         XTL_REQUIRES(is_reducer_options<O>, xtl::negation<xtl::is_integral<std::decay_t<O>>>)\
465         >\
466         auto FNAME(E && e, BS && block_shape, double p, O options = O())\
467         {\
468             using input_expression_type = std::decay_t<E>;\
469             using axes_type = filter_fixed_shape_t<typename input_expression_type::shape_type>;\
470             axes_type axes = xtl::make_sequence<axes_type>(e.dimension());\
471             XTENSOR_ASSERT(axes.size() == e.dimension());\
472             std::iota(axes.begin(), axes.end(), 0);\
473             using functor_type = FUNCTOR <typename input_expression_type::value_type>;\
474             return blockwise_reducer(\
475                 std::forward<E>(e), \
476                 std::forward<BS>(block_shape), \
477                 std::move(axes),\
478                 std::forward<O>(options),\
479                 functor_type(p));\
480         }\
481         template<class E, class BS, class I, std::size_t N, class O = DEFAULT_STRATEGY_REDUCERS>\
482         auto FNAME(E && e, BS && block_shape, double p, const I (&axes)[N], O options = O())\
483         {\
484             using input_expression_type = std::decay_t<E>;\
485             using functor_type = FUNCTOR <typename input_expression_type::value_type>;\
486             using axes_type = std::array<std::size_t, N>;\
487             auto ax = xt::forward_normalize<axes_type>(e, axes);\
488             return blockwise_reducer(\
489                 std::forward<E>(e), \
490                 std::forward<BS>(block_shape), \
491                 std::move(ax),\
492                 std::forward<O>(options),\
493                 functor_type(p));\
494         }
495 
496     XTENSOR_BLOCKWISE_NORM_REDUCER_FUNC(norm_lp_to_p, xt::detail::blockwise::norm_lp_to_p_functor);
497     XTENSOR_BLOCKWISE_NORM_REDUCER_FUNC(norm_lp, xt::detail::blockwise::norm_lp_functor);
498 
499     #undef XTENSOR_BLOCKWISE_NORM_REDUCER_FUNC
500 }
501 
502 }
503 
504 #endif