HiPipe  0.7.0
C++17 data pipeline with Python bindings.
ndim.hpp
1 /****************************************************************************
2  * hipipe library
3  * Copyright (c) 2017, Cognexa Solutions s.r.o.
4  * Copyright (c) 2018, Iterait a.s.
5  * Author(s) Filip Matzner
6  *
7  * This file is distributed under the MIT License.
8  * See the accompanying file LICENSE.txt for the complete license agreement.
9  ****************************************************************************/
11 
12 #pragma once
13 
14 #include <hipipe/core/utility/random.hpp>
15 
16 #include <range/v3/action/reverse.hpp>
17 #include <range/v3/algorithm/adjacent_find.hpp>
18 #include <range/v3/algorithm/all_of.hpp>
19 #include <range/v3/algorithm/fill.hpp>
20 #include <range/v3/algorithm/find.hpp>
21 #include <range/v3/algorithm/max.hpp>
22 #include <range/v3/numeric/accumulate.hpp>
23 #include <range/v3/numeric/partial_sum.hpp>
24 #include <range/v3/view/all.hpp>
25 #include <range/v3/view/chunk.hpp>
26 #include <range/v3/view/for_each.hpp>
27 
28 #include <cassert>
29 #include <cmath>
30 #include <functional>
31 #include <memory>
32 #include <random>
33 #include <type_traits>
34 #include <utility>
35 #include <vector>
36 
37 namespace hipipe::utility {
38 
39 namespace rga = ranges::actions;
40 namespace rgv = ranges::views;
41 
50 template<typename Rng, typename PrevRng = void, bool IsRange = ranges::range<Rng>>
51 struct ndims {
52 };
53 
54 template<typename Rng, typename PrevRng>
55 struct ndims<Rng, PrevRng, false> : std::integral_constant<long, 0L> {
56 };
57 
58 template<typename Rng, typename PrevRng>
59 struct ndims<Rng, PrevRng, true>
60  : std::integral_constant<long, ndims<ranges::range_value_t<Rng>, Rng>{} + 1> {
61 };
62 
63 // For recursive ranges, such as std::filesystem::path, do not recurse further and
64 // consider it to be a scalar.
65 template<typename Rng>
66 struct ndims<Rng, Rng, true> : std::integral_constant<long, -1L> {
67 };
68 
77 template<typename, template<typename...> typename>
78 struct is_specialization : std::false_type {};
79 
80 template<template<typename...> typename Template, typename... Args>
81 struct is_specialization<Template<Args...>, Template> : std::true_type {};
82 
98 template<typename T, long Dims, template<typename...> typename Container = std::vector>
99 struct ndim_container {
100  using type = Container<typename ndim_container<T, Dims-1, Container>::type>;
101  static_assert(Dims >= 0, "The number of dimensions has to be non-negative.");
102 };
103 
104 template<typename T, template<typename...> typename Container>
105 struct ndim_container<T, 0, Container> {
106  using type = T;
107 };
108 
109 template<typename T, long Dims, template<typename...> typename Container = std::vector>
110 using ndim_container_t = typename ndim_container<T, Dims, Container>::type;
111 
124 template<typename Rng, long Dim = -1L>
125 struct ndim_type
127  : ndim_type<typename ranges::range_value_t<Rng>, Dim - 1L> {
128  static_assert(Dim > 0, "Dimension has to be positive, zero or -1.");
130 };
131 
132 template<typename T>
133 struct ndim_type<T, 0L> {
134  using type = T;
135 };
136 
137 template<typename Rng>
138 struct ndim_type<Rng, -1L>
139  : ndim_type<Rng, ndims<Rng>{}> {
140 };
141 
143 template<typename T, long Dim = -1L>
144 using ndim_type_t = typename ndim_type<T, Dim>::type;
145 
146 // multidimensional range size //
147 
148 namespace detail {
149 
150  template<typename Rng, long Dim, long NDims>
151  struct ndim_size_impl {
152  static void impl(const Rng& rng, std::vector<std::vector<long>>& size_out)
153  {
154  size_out[Dim-1].push_back(ranges::size(rng));
155  for (auto& subrng : rng) {
156  ndim_size_impl<ranges::range_value_t<Rng>, Dim+1, NDims>
157  ::impl(subrng, size_out);
158  }
159  }
160  };
161 
162  template<typename Rng, long Dim>
163  struct ndim_size_impl<Rng, Dim, Dim> {
164  static void impl(const Rng& rng, std::vector<std::vector<long>>& size_out)
165  {
166  size_out[Dim-1].push_back(ranges::size(rng));
167  }
168  };
169 
170 } // namespace detail
171 
191 template<long NDims, typename Rng>
192 std::vector<std::vector<long>> ndim_size(const Rng& rng)
193 {
194  static_assert(NDims > 0);
195  std::vector<std::vector<long>> size_out(NDims);
196  detail::ndim_size_impl<Rng, 1, NDims>::impl(rng, size_out);
197  return size_out;
198 }
199 
204 template<typename Rng>
205 std::vector<std::vector<long>> ndim_size(const Rng& rng)
206 {
207  return utility::ndim_size<ndims<Rng>{}>(rng);
208 }
209 
210 // multidimensional range resize //
211 
212 namespace detail {
213 
214  template<long Dim, long NDims>
215  struct ndim_resize_impl {
216  template<typename Rng, typename ValT>
217  static void impl(Rng& vec,
218  const std::vector<std::vector<long>>& vec_size,
219  std::vector<long>& vec_size_idx,
220  const ValT& val)
221  {
222  vec.resize(vec_size[Dim-1][vec_size_idx[Dim-1]++]);
223  for (auto& subvec : vec) {
224  ndim_resize_impl<Dim+1, NDims>::impl(subvec, vec_size, vec_size_idx, val);
225  }
226  }
227  };
228 
229  template<long Dim>
230  struct ndim_resize_impl<Dim, Dim> {
231  template<typename Rng, typename ValT>
232  static void impl(Rng& vec,
233  const std::vector<std::vector<long>>& vec_size,
234  std::vector<long>& vec_size_idx,
235  const ValT& val)
236  {
237  vec.resize(vec_size[Dim-1][vec_size_idx[Dim-1]++], val);
238  }
239  };
240 
241 } // namespace detail
242 
262 template<long NDims, typename Rng, typename ValT = ndim_type_t<Rng, NDims>>
263 Rng& ndim_resize(Rng& vec,
264  const std::vector<std::vector<long>>& vec_size,
265  ValT val = ValT{})
266 {
267  // check that the size is valid
268  assert(vec_size.size() == NDims);
269  static_assert(NDims <= ndims<Rng>{} - ndims<ValT>{});
270  for (std::size_t i = 1; i < vec_size.size(); ++i) {
271  assert(vec_size[i].size() == ranges::accumulate(vec_size[i-1], 0UL));
272  }
273  // build initial indices
274  std::vector<long> vec_size_idx(vec_size.size());
275  // recursively resize
276  detail::ndim_resize_impl<1, NDims>::impl(vec, vec_size, vec_size_idx, val);
277  return vec;
278 }
279 
281 template<typename Rng, typename ValT = ndim_type_t<Rng>>
282 Rng& ndim_resize(Rng& vec,
283  const std::vector<std::vector<long>>& vec_size,
284  ValT val = ValT{})
285 {
286  return ndim_resize<ndims<Rng>{}-ndims<ValT>{}>(vec, vec_size, std::move(val));
287 }
288 
309 template<long NDims, typename Rng, typename ValT = ndim_type_t<Rng, NDims>>
310 Rng& ndim_pad(Rng& vec, ValT val = ValT{})
311 {
312  static_assert(NDims <= ndims<Rng>{} - ndims<ValT>{});
313  std::vector<std::vector<long>> vec_size = ndim_size<NDims>(vec);
314  // replace the sizes in each dimension with the max size in the same dimension
315  for (std::size_t i = 1; i < vec_size.size(); ++i) {
316  long max_size = ranges::max(vec_size[i]);
317  ranges::fill(vec_size[i], max_size);
318  }
319  return utility::ndim_resize<NDims>(vec, vec_size, std::move(val));
320 }
321 
323 template<typename Rng, typename ValT = ndim_type_t<Rng>>
324 Rng& ndim_pad(Rng& vec, ValT val = ValT{})
325 {
326  return utility::ndim_pad<ndims<Rng>{}-ndims<ValT>{}>(vec, std::move(val));
327 }
328 
329 // multidimensional range shape //
330 
331 namespace detail {
332 
333  template<typename Rng, long Dim, long NDims>
334  struct shape_impl {
335  static void impl(const Rng& rng, std::vector<long>& shape)
336  {
337  shape[Dim-1] = ranges::size(rng);
338  if (ranges::size(rng)) {
339  shape_impl<typename ranges::range_value_t<Rng>, Dim+1, NDims>
340  ::impl(*ranges::begin(rng), shape);
341  }
342  }
343  };
344 
345  template<typename Rng, long Dim>
346  struct shape_impl<Rng, Dim, Dim> {
347  static void impl(const Rng& rng, std::vector<long>& shape)
348  {
349  shape[Dim-1] = ranges::size(rng);
350  }
351  };
352 
353  // this is just for assertion purposes - check that the entire vector has a rectangular shape
354  template<typename Rng, long NDims>
355  struct check_rectangular_shape {
356  static bool all_same(std::vector<long>& vec)
357  {
358  return ranges::adjacent_find(vec, std::not_equal_to<long>{}) == ranges::end(vec);
359  }
360 
361  static bool impl(const Rng& rng)
362  {
363  std::vector<std::vector<long>> size = utility::ndim_size<NDims>(rng);
364  return ranges::all_of(size, all_same);
365  }
366  };
367 
368 } // namespace detail
369 
387 template<long NDims, typename Rng>
388 std::vector<long> shape(const Rng& rng)
389 {
390  static_assert(NDims > 0);
391  std::vector<long> shape(NDims);
392  // the ndim_size is not used for efficiency in ndebug mode (only the 0-th element is inspected)
393  detail::shape_impl<Rng, 1, NDims>::impl(rng, shape);
394  assert((detail::check_rectangular_shape<Rng, NDims>::impl(rng)));
395  return shape;
396 }
397 
402 template<typename Rng>
403 std::vector<long> shape(const Rng& rng)
404 {
405  return utility::shape<ndims<Rng>{}>(rng);
406 }
407 
408 // recursive range flatten //
409 
410 namespace detail {
411 
412  // recursively join the vector
413  template<long Dim>
414  struct flat_view_impl {
415  static auto impl()
416  {
417  return rgv::for_each([](auto& subrng) {
418  return flat_view_impl<Dim-1>::impl()(subrng);
419  });
420  }
421  };
422 
423  // for 0, return the original vector
424  template<>
425  struct flat_view_impl<1> {
426  static auto impl()
427  {
428  return rgv::all;
429  }
430  };
431 
432 } // namespace detail
433 
450 template<long NDims, typename Rng>
451 auto flat_view(Rng& rng)
452 {
453  static_assert(NDims > 0);
454  return detail::flat_view_impl<NDims>::impl()(rng);
455 }
456 
458 template<long NDims, typename Rng>
459 auto flat_view(const Rng& rng)
460 {
461  static_assert(NDims > 0);
462  return detail::flat_view_impl<NDims>::impl()(rng);
463 }
464 
466 template<typename Rng>
467 auto flat_view(Rng&& rng)
468 {
469  return utility::flat_view<ndims<Rng>{}>(std::forward<Rng>(rng));
470 }
471 
472 // reshaped view of a multidimensional range //
473 
474 namespace detail {
475 
476  template<long N>
477  struct reshaped_view_impl_go {
478  static auto impl(const std::shared_ptr<std::vector<long>>& shape_ptr)
479  {
480  return rgv::chunk((*shape_ptr)[N-2])
481  | rgv::transform([shape_ptr](auto subview) {
482  return reshaped_view_impl_go<N-1>::impl(shape_ptr)(std::move(subview));
483  });
484  }
485  };
486 
487  template<>
488  struct reshaped_view_impl_go<1> {
489  static auto impl(const std::shared_ptr<std::vector<long>>&)
490  {
491  return rgv::all;
492  }
493  };
494 
495  template<long N, typename Rng>
496  auto reshaped_view_impl(Rng& vec, std::vector<long> shape)
497  {
498  assert(shape.size() == N);
499  auto flat = flat_view(vec);
500 
501  // if -1 present in the shape list, deduce the dimension
502  auto deduced_pos = ranges::find(shape, -1);
503  if (deduced_pos != shape.end()) {
504  auto flat_size = ranges::distance(flat);
505  auto shape_prod = -ranges::accumulate(shape, 1, std::multiplies<>{});
506  assert(flat_size % shape_prod == 0);
507  *deduced_pos = flat_size / shape_prod;
508  }
509 
510  // check that all the requested dimenstions have positive size
511  assert(ranges::all_of(shape, [](long s) { return s > 0; }));
512  // check that the user requests the same number of elements as there really is
513  assert(ranges::distance(flat) == ranges::accumulate(shape, 1, std::multiplies<>{}));
514  // calculate the cummulative product of the shape list in reverse order
515  shape |= rga::reverse;
516  ranges::partial_sum(shape, shape, std::multiplies<>{});
517  // the recursive chunks will share a single copy of the shape list (performance)
518  auto shape_ptr = std::make_shared<std::vector<long>>(std::move(shape));
519  return detail::reshaped_view_impl_go<N>::impl(shape_ptr)(std::move(flat));
520  }
521 
522 } // namespace detail
523 
540 template<long N, typename Rng>
541 auto reshaped_view(Rng& rng, std::vector<long> shape)
542 {
543  return detail::reshaped_view_impl<N, Rng>(rng, std::move(shape));
544 }
545 
547 template<long N, typename Rng>
548 auto reshaped_view(const Rng& rng, std::vector<long> shape)
549 {
550  return detail::reshaped_view_impl<N, const Rng>(rng, std::move(shape));
551 }
552 
553 // generate //
554 
555 namespace detail {
556 
557  template<long Dim, long NDims>
558  struct generate_impl {
559  template<typename Rng, typename Gen>
560  static void impl(Rng& rng, Gen& gen, long gendims)
561  {
562  if (Dim > gendims) {
563  auto val = std::invoke(gen);
564  for (auto& elem : flat_view<NDims-Dim+1>(rng)) elem = val;
565  } else {
566  for (auto& subrng : rng) {
567  detail::generate_impl<Dim+1, NDims>::impl(subrng, gen, gendims);
568  }
569  }
570  }
571  };
572 
573  template<long Dim>
574  struct generate_impl<Dim, Dim> {
575  template<typename Rng, typename Gen>
576  static void impl(Rng& rng, Gen& gen, long gendims)
577  {
578  if (Dim > gendims) ranges::fill(rng, std::invoke(gen));
579  else for (auto& val : rng) val = std::invoke(gen);
580  }
581  };
582 
583 } // namespace detail
584 
628 template<long NDims, typename Rng, typename Gen>
629 void generate(Rng&& rng,
630  Gen&& gen,
631  long gendims = std::numeric_limits<long>::max())
632 {
633  static_assert(NDims > 0);
634  detail::generate_impl<1, NDims>::impl(rng, gen, gendims);
635 }
636 
638 template<typename Rng, typename Gen>
639 void generate(Rng&& rng,
640  Gen&& gen,
641  long gendims = std::numeric_limits<long>::max())
642 {
643  using GenT = std::result_of_t<Gen()>;
644  detail::generate_impl<1, ndims<Rng>{}-ndims<GenT>{}>::impl(rng, gen, gendims);
645 }
646 
679 template<long NDims,
680  typename Rng,
681  typename Prng = std::mt19937&,
682  typename Dist = std::uniform_real_distribution<double>>
683 void random_fill(Rng&& rng,
684  Dist&& dist = Dist{0, 1},
685  Prng&& prng = utility::random_generator,
686  long gendims = std::numeric_limits<long>::max())
687 {
688  auto gen = [&dist, &prng]() { return std::invoke(dist, prng); };
689  utility::generate<NDims>(std::forward<Rng>(rng), std::move(gen), gendims);
690 }
691 
693 template<typename Rng,
694  typename Prng = std::mt19937&,
695  typename Dist = std::uniform_real_distribution<double>>
696 void random_fill(Rng&& rng,
697  Dist&& dist = Dist{0, 1},
698  Prng&& prng = utility::random_generator,
699  long gendims = std::numeric_limits<long>::max())
700 {
701  auto gen = [&dist, &prng]() { return std::invoke(dist, prng); };
702  utility::generate(std::forward<Rng>(rng), std::move(gen), gendims);
703 }
704 
705 namespace detail {
706 
707  struct same_size_impl {
708  template<typename Rng, typename... Rngs>
709  bool operator()(Rng&& rng, Rngs&&... rngs) const
710  {
711  return (... && (ranges::size(rng) == ranges::size(rngs)));
712  }
713 
714  bool operator()() const
715  {
716  return true;
717  }
718  };
719 
720 } // namespace detail
721 
735 template<typename Tuple>
736 bool same_size(Tuple&& rngs)
737 {
738  return std::apply(detail::same_size_impl{}, rngs);
739 }
740 
741 } // namespace hipipe::utility
hipipe::utility::random_generator
static thread_local std::mt19937 random_generator
Thread local pseudo-random number generator seeded by std::random_device.
Definition: random.hpp:20
hipipe::utility::ndim_container
Fast declaration of a multidimensional container.
Definition: ndim.hpp:98
flat_view
auto flat_view(Rng &rng)
Makes a flat view out of a multidimensional range.
Definition: ndim.hpp:450
hipipe::utility::ndims
Gets the number of dimensions of a multidimensional range.
Definition: ndim.hpp:50
same_size
bool same_size(Tuple &&rngs)
Utility function which checks that all the ranges in a tuple have the same size.
Definition: ndim.hpp:735
ndim_pad
Rng & ndim_pad(Rng &vec, ValT val=ValT{})
Pads a mutlidimensional range to a rectangular size.
Definition: ndim.hpp:309
ndim_size
std::vector< std::vector< long > > ndim_size(const Rng &rng)
Calculates the size of a multidimensional range.
Definition: ndim.hpp:191
hipipe::utility::is_specialization
Detect whether the given type is a specialization of the given container template.
Definition: ndim.hpp:77
ndim_resize
Rng & ndim_resize(Rng &vec, const std::vector< std::vector< long >> &vec_size, ValT val=ValT{})
Resizes a multidimensional range to the given size.
Definition: ndim.hpp:262
shape
std::vector< long > shape(const Rng &rng)
Calculates the shape of a multidimensional range.
Definition: ndim.hpp:387
hipipe::stream::for_each
auto for_each(from_t< FromColumns... > f, Fun fun, dim_t< Dim > d=dim_t< 1 >{})
Apply a function to a subset of stream columns.
Definition: for_each.hpp:68
random_fill
void random_fill(Rng &&rng, Dist &&dist=Dist{0, 1}, Prng &&prng=utility::random_generator, long gendims=std::numeric_limits< long >::max())
Fill a multidimensional range with random values.
Definition: ndim.hpp:682
generate
void generate(Rng &&rng, Gen &&gen, long gendims=std::numeric_limits< long >::max())
Fill a multidimensional range with values generated by a nullary function.
Definition: ndim.hpp:628
reshaped_view
auto reshaped_view(Rng &rng, std::vector< long > shape)
Makes a view of a multidimensional range with a specific shape.
Definition: ndim.hpp:540
hipipe::stream::transform
auto transform(from_t< FromColumns... > f, to_t< ToColumns... > t, Fun fun, dim_t< Dim > d=dim_t< 1 >{})
Transform a subset of hipipe columns to a different subset of hipipe columns.
Definition: transform.hpp:218