DIY  3.0
data-parallel out-of-core C++ library
 All Classes Namespaces Functions Typedefs Groups Pages
decomposition.hpp
1 #ifndef DIY_DECOMPOSITION_HPP
2 #define DIY_DECOMPOSITION_HPP
3 
4 #include <vector>
5 #include <algorithm>
6 #include <iostream>
7 #include <cmath>
8 #include <sstream>
9 #include <stdexcept>
10 
11 #include "link.hpp"
12 #include "assigner.hpp"
13 #include "master.hpp"
14 
15 namespace diy
16 {
17 namespace detail
18 {
19  template<class Bounds_, class Enable = void>
20  struct BoundsHelper;
21 
22  // discrete bounds
23  template<class Bounds>
24  struct BoundsHelper<Bounds, typename std::enable_if<std::is_integral<typename Bounds::Coordinate>::value>::type>
25  {
26  using Coordinate = typename Bounds::Coordinate;
27 
28  static Coordinate from(int i, int n, Coordinate min, Coordinate max, bool) { return min + (max - min + 1)/n * i; }
29  static Coordinate to (int i, int n, Coordinate min, Coordinate max, bool shared_face)
30  {
31  if (i == n - 1)
32  return max;
33  else
34  return from(i+1, n, min, max, shared_face) - (shared_face ? 0 : 1);
35  }
36 
37  static int lower(Coordinate x, int n, Coordinate min, Coordinate max, bool shared)
38  {
39  Coordinate width = (max - min + 1)/n;
40  Coordinate res = (x - min)/width;
41  if (res >= n) res = n - 1;
42 
43  if (shared && x == from(res, n, min, max, shared))
44  --res;
45  return res;
46  }
47  static int upper(Coordinate x, int n, Coordinate min, Coordinate max, bool shared)
48  {
49  Coordinate width = (max - min + 1)/n;
50  Coordinate res = (x - min)/width + 1;
51  if (shared && x == from(res, n, min, max, shared))
52  ++res;
53  return res;
54  }
55  };
56 
57  // continuous bounds
58  template<class Bounds>
59  struct BoundsHelper<Bounds, typename std::enable_if<std::is_floating_point<typename Bounds::Coordinate>::value>::type>
60  {
61  using Coordinate = typename Bounds::Coordinate;
62 
63  static Coordinate from(int i, int n, Coordinate min, Coordinate max, bool) { return min + (max - min)/n * i; }
64  static Coordinate to (int i, int n, Coordinate min, Coordinate max, bool) { return min + (max - min)/n * (i+1); }
65 
66  static int lower(Coordinate x, int n, Coordinate min, Coordinate max, bool) { Coordinate width = (max - min)/n; Coordinate res = std::floor((x - min)/width); if (min + res*width == x) return (res - 1); else return res; }
67  static int upper(Coordinate x, int n, Coordinate min, Coordinate max, bool) { Coordinate width = (max - min)/n; Coordinate res = std::ceil ((x - min)/width); if (min + res*width == x) return (res + 1); else return res; }
68  };
69 }
70 
74  template<class Bounds_>
76  {
77  typedef Bounds_ Bounds;
78  typedef typename BoundsValue<Bounds>::type Coordinate;
79  typedef typename RegularLinkSelector<Bounds>::type Link;
80 
81  using Creator = std::function<void(int, Bounds, Bounds, Bounds, Link)>;
82  using Updater = std::function<void(int, int, Bounds, Bounds, Bounds, Link)>;
83 
84  typedef std::vector<bool> BoolVector;
85  typedef std::vector<Coordinate> CoordinateVector;
86  typedef std::vector<int> DivisionsVector;
87 
97  const Bounds& domain_,
98  int nblocks_,
99  BoolVector share_face_ = BoolVector(),
100  BoolVector wrap_ = BoolVector(),
101  CoordinateVector ghosts_ = CoordinateVector(),
102  DivisionsVector divisions_ = DivisionsVector()):
103  dim(dim_), domain(domain_), nblocks(nblocks_),
104  share_face(share_face_),
105  wrap(wrap_), ghosts(ghosts_), divisions(divisions_)
106  {
107  if ((int) share_face.size() < dim) share_face.resize(dim);
108  if ((int) wrap.size() < dim) wrap.resize(dim);
109  if ((int) ghosts.size() < dim) ghosts.resize(dim);
110  if ((int) divisions.size() < dim) divisions.resize(dim);
111 
112  fill_divisions(divisions);
113  }
114 
115  // Calls create(int gid, const Bounds& bounds, const Link& link)
116  void decompose(int rank, const Assigner& assigner, const Creator& create);
117 
118  void decompose(int rank, const Assigner& assigner, Master& master, const Updater& update);
119 
120  void decompose(int rank, const Assigner& assigner, Master& master);
121 
122  // find lowest gid that owns a particular point
123  template<class Point>
124  int lowest_gid(const Point& p) const;
125 
126  void gid_to_coords(int gid, DivisionsVector& coords) const { gid_to_coords(gid, coords, divisions); }
127  int coords_to_gid(const DivisionsVector& coords) const { return coords_to_gid(coords, divisions); }
128  void fill_divisions(std::vector<int>& divisions) const;
129 
130  void fill_bounds(Bounds& bounds, const DivisionsVector& coords, bool add_ghosts = false) const;
131  void fill_bounds(Bounds& bounds, int gid, bool add_ghosts = false) const;
132 
133  static bool all(const std::vector<int>& v, int x);
134  static void gid_to_coords(int gid, DivisionsVector& coords, const DivisionsVector& divisions);
135  static int coords_to_gid(const DivisionsVector& coords, const DivisionsVector& divisions);
136 
137  static void factor(std::vector<unsigned>& factors, int n);
138 
139  // Point to GIDs functions
140  template<class Point>
141  void point_to_gids(std::vector<int>& gids, const Point& p) const;
142 
144  template<class Point>
145  int point_to_gid(const Point& p) const;
146 
147  template<class Point>
148  int num_gids(const Point& p) const;
149 
150  template<class Point>
151  void top_bottom(int& top, int& bottom, const Point& p, int axis) const;
152 
153 
154  int dim;
155  Bounds domain;
156  int nblocks;
157  BoolVector share_face;
158  BoolVector wrap;
159  CoordinateVector ghosts;
160  DivisionsVector divisions;
161 
162  };
163 
180  template<class Bounds>
181  void decompose(int dim,
182  int rank,
183  const Bounds& domain,
184  const Assigner& assigner,
185  const typename RegularDecomposer<Bounds>::Creator& create,
186  typename RegularDecomposer<Bounds>::BoolVector share_face = typename RegularDecomposer<Bounds>::BoolVector(),
187  typename RegularDecomposer<Bounds>::BoolVector wrap = typename RegularDecomposer<Bounds>::BoolVector(),
188  typename RegularDecomposer<Bounds>::CoordinateVector ghosts = typename RegularDecomposer<Bounds>::CoordinateVector(),
189  typename RegularDecomposer<Bounds>::DivisionsVector divs = typename RegularDecomposer<Bounds>::DivisionsVector())
190  {
191  RegularDecomposer<Bounds>(dim, domain, assigner.nblocks(), share_face, wrap, ghosts, divs).decompose(rank, assigner, create);
192  }
193 
210  template<class Bounds>
211  void decompose(int dim,
212  int rank,
213  const Bounds& domain,
214  const Assigner& assigner,
215  Master& master,
216  typename RegularDecomposer<Bounds>::BoolVector share_face = typename RegularDecomposer<Bounds>::BoolVector(),
217  typename RegularDecomposer<Bounds>::BoolVector wrap = typename RegularDecomposer<Bounds>::BoolVector(),
218  typename RegularDecomposer<Bounds>::CoordinateVector ghosts = typename RegularDecomposer<Bounds>::CoordinateVector(),
219  typename RegularDecomposer<Bounds>::DivisionsVector divs = typename RegularDecomposer<Bounds>::DivisionsVector())
220  {
221  RegularDecomposer<Bounds>(dim, domain, assigner.nblocks(), share_face, wrap, ghosts, divs).decompose(rank, assigner, master);
222  }
223 
233  inline
234  void decompose(int rank,
235  const Assigner& assigner,
236  Master& master)
237  {
238  std::vector<int> local_gids;
239  assigner.local_gids(rank, local_gids);
240 
241  for (size_t i = 0; i < local_gids.size(); ++i)
242  master.add(local_gids[i], master.create(), new diy::Link);
243  }
244 
254  template<class Bounds>
255  void decompose(int dim,
256  int rank,
257  const Bounds& domain,
258  const Assigner& assigner,
259  Master& master,
260  const typename RegularDecomposer<Bounds>::Updater& update,
261  typename RegularDecomposer<Bounds>::BoolVector share_face =
262  typename RegularDecomposer<Bounds>::BoolVector(),
263  typename RegularDecomposer<Bounds>::BoolVector wrap =
264  typename RegularDecomposer<Bounds>::BoolVector(),
265  typename RegularDecomposer<Bounds>::CoordinateVector ghosts =
266  typename RegularDecomposer<Bounds>::CoordinateVector(),
267  typename RegularDecomposer<Bounds>::DivisionsVector divs =
268  typename RegularDecomposer<Bounds>::DivisionsVector())
269  {
270  RegularDecomposer<Bounds>(dim, domain, assigner.nblocks(), share_face, wrap, ghosts, divs).
271  decompose(rank, assigner, master, update);
272  }
273 
276 }
277 
278 // decomposes domain and adds blocks to the master
279 template<class Bounds>
280 void
282 decompose(int rank, const Assigner& assigner, Master& master)
283 {
284  decompose(rank, assigner, [&master](int gid, const Bounds& core, const Bounds& bounds, const Bounds& domain, const Link& link)
285  {
286  void* b = master.create();
287  Link* l = new Link(link);
288  master.add(gid, b, l);
289  });
290 }
291 
292 template<class Bounds>
293 void
295 decompose(int rank, const Assigner& assigner, const Creator& create)
296 {
297  std::vector<int> gids;
298  assigner.local_gids(rank, gids);
299  for (int i = 0; i < (int)gids.size(); ++i)
300  {
301  int gid = gids[i];
302 
303  DivisionsVector coords;
304  gid_to_coords(gid, coords);
305 
306  Bounds core, bounds;
307  fill_bounds(core, coords);
308  fill_bounds(bounds, coords, true);
309 
310  // Fill link with all the neighbors
311  Link link(dim, core, bounds);
312  std::vector<int> offsets(dim, -1);
313  offsets[0] = -2;
314  while (!all(offsets, 1))
315  {
316  // next offset
317  int i;
318  for (i = 0; i < dim; ++i)
319  if (offsets[i] == 1)
320  offsets[i] = -1;
321  else
322  break;
323  ++offsets[i];
324 
325  if (all(offsets, 0)) continue; // skip ourselves
326 
327  DivisionsVector nhbr_coords(dim);
328  Direction dir, wrap_dir;
329  bool inbounds = true;
330  for (int i = 0; i < dim; ++i)
331  {
332  nhbr_coords[i] = coords[i] + offsets[i];
333 
334  // wrap
335  if (nhbr_coords[i] < 0)
336  {
337  if (wrap[i])
338  {
339  nhbr_coords[i] = divisions[i] - 1;
340  wrap_dir[i] = -1;
341  }
342  else
343  inbounds = false;
344  }
345 
346  if (nhbr_coords[i] >= divisions[i])
347  {
348  if (wrap[i])
349  {
350  nhbr_coords[i] = 0;
351  wrap_dir[i] = 1;
352  }
353  else
354  inbounds = false;
355  }
356 
357  // NB: this needs to match the addressing scheme in dir_t (in constants.h)
358  if (offsets[i] == -1 || offsets[i] == 1)
359  dir[i] = offsets[i];
360  }
361  if (!inbounds) continue;
362 
363  int nhbr_gid = coords_to_gid(nhbr_coords);
364  BlockID bid; bid.gid = nhbr_gid; bid.proc = assigner.rank(nhbr_gid);
365  link.add_neighbor(bid);
366 
367  Bounds nhbr_bounds;
368  fill_bounds(nhbr_bounds, nhbr_coords);
369  link.add_bounds(nhbr_bounds);
370 
371  link.add_direction(dir);
372  link.add_wrap(wrap_dir);
373  }
374 
375  create(gid, core, bounds, domain, link);
376  }
377 }
378 
379 // decomposes domain but does not add blocks to master, assumes they were added already
380 template<class Bounds>
381 void
383 decompose(int rank, const Assigner& assigner, Master& master, const Updater& update)
384 {
385  decompose(rank, assigner, [&master,&update](int gid, const Bounds& core, const Bounds& bounds, const Bounds& domain, const Link& link)
386  {
387  int lid = master.lid(gid);
388  Link* l = new Link(link);
389  master.replace_link(lid, l);
390  update(gid, lid, core, bounds, domain, *l);
391  });
392 }
393 
394 template<class Bounds>
395 bool
397 all(const std::vector<int>& v, int x)
398 {
399  for (unsigned i = 0; i < v.size(); ++i)
400  if (v[i] != x)
401  return false;
402  return true;
403 }
404 
405 template<class Bounds>
406 void
408 gid_to_coords(int gid, DivisionsVector& coords, const DivisionsVector& divisions)
409 {
410  int dim = divisions.size();
411  for (int i = 0; i < dim; ++i)
412  {
413  coords.push_back(gid % divisions[i]);
414  gid /= divisions[i];
415  }
416 }
417 
418 template<class Bounds>
419 int
421 coords_to_gid(const DivisionsVector& coords, const DivisionsVector& divisions)
422 {
423  int gid = 0;
424  for (int i = coords.size() - 1; i >= 0; --i)
425  {
426  gid *= divisions[i];
427  gid += coords[i];
428  }
429  return gid;
430 }
431 
434 template<class Bounds>
435 void
437 fill_bounds(Bounds& bounds,
438  const DivisionsVector& coords,
439  bool add_ghosts)
440  const
441 {
442  for (int i = 0; i < dim; ++i)
443  {
444  bounds.min[i] = detail::BoundsHelper<Bounds>::from(coords[i], divisions[i], domain.min[i], domain.max[i], share_face[i]);
445  bounds.max[i] = detail::BoundsHelper<Bounds>::to (coords[i], divisions[i], domain.min[i], domain.max[i], share_face[i]);
446  }
447 
448  for (int i = dim; i < DIY_MAX_DIM; ++i) // set the unused dimension to 0
449  {
450  bounds.min[i] = 0;
451  bounds.max[i] = 0;
452  }
453 
454  if (!add_ghosts)
455  return;
456 
457  for (int i = 0; i < dim; ++i)
458  {
459  if (wrap[i])
460  {
461  bounds.min[i] -= ghosts[i];
462  bounds.max[i] += ghosts[i];
463  } else
464  {
465  bounds.min[i] = std::max(domain.min[i], bounds.min[i] - ghosts[i]);
466  bounds.max[i] = std::min(domain.max[i], bounds.max[i] + ghosts[i]);
467  }
468  }
469 }
470 
473 template<class Bounds>
474 void
476 fill_bounds(Bounds& bounds,
477  int gid,
478  bool add_ghosts)
479  const
480 {
481  DivisionsVector coords;
482  gid_to_coords(gid, coords);
483  if (add_ghosts)
484  fill_bounds(bounds, coords, true);
485  else
486  fill_bounds(bounds, coords);
487 }
488 
489 namespace diy { namespace detail {
490 // current state of division in one dimension used in fill_divisions below
491 template<class Coordinate>
492 struct Div
493 {
494  int dim; // 0, 1, 2, etc. e.g. for x, y, z etc.
495  int nb; // number of blocks so far in this dimension
496  Coordinate b_size; // block size so far in this dimension
497 
498  // sort on descending block size unless tied, in which case
499  // sort on ascending num blocks in current dim unless tied, in which case
500  // sort on ascending dimension
501  bool operator<(Div rhs) const
502  {
503  // sort on second value of the pair unless tied, in which case sort on first
504  if (b_size == rhs.b_size)
505  {
506  if (nb == rhs.nb)
507  return(dim < rhs.dim);
508  return(nb < rhs.nb);
509  }
510  return(b_size > rhs.b_size);
511  }
512 };
513 } }
514 
515 template<class Bounds>
516 void
518 fill_divisions(std::vector<int>& divisions) const
519 {
520  // prod = number of blocks unconstrained by user; c = number of unconstrained dimensions
521  int prod = 1; int c = 0;
522  for (int i = 0; i < dim; ++i)
523  if (divisions[i] != 0)
524  {
525  prod *= divisions[i];
526  ++c;
527  }
528 
529  if (nblocks % prod != 0)
530  throw std::runtime_error("Total number of blocks cannot be factored into provided divs");
531 
532  if (c == (int) divisions.size()) // nothing to do; user provided all divs
533  return;
534 
535  // factor number of blocks left in unconstrained dimensions
536  // factorization is sorted from smallest to largest factors
537  std::vector<unsigned> factors;
538  factor(factors, nblocks/prod);
539 
540  using detail::Div;
541  std::vector< Div<Coordinate> > missing_divs; // pairs consisting of (dim, #divs)
542 
543  // init missing_divs
544  for (int i = 0; i < dim; i++)
545  {
546  if (divisions[i] == 0)
547  {
548  Div<Coordinate> div;
549  div.dim = i;
550  div.nb = 1;
551  div.b_size = domain.max[i] - domain.min[i];
552  missing_divs.push_back(div);
553  }
554  }
555 
556  // iterate over factorization of number of blocks (factors are sorted smallest to largest)
557  // NB: using int instead of size_t because must be negative in order to break out of loop
558  for (int i = factors.size() - 1; i >= 0; --i)
559  {
560  // fill in missing divs by dividing dimension w/ largest block size
561  // except when this would be illegal (resulting in bounds.max < bounds.min;
562  // only a problem for discrete bounds
563 
564  // sort on decreasing block size
565  std::sort(missing_divs.begin(), missing_divs.end());
566 
567  // split the dimension with the largest block size (first element in vector)
568  Coordinate min =
569  detail::BoundsHelper<Bounds>::from(0,
570  missing_divs[0].nb * factors[i],
571  domain.min[missing_divs[0].dim],
572  domain.max[missing_divs[0].dim],
573  share_face[missing_divs[0].dim]);
574  Coordinate max =
575  detail::BoundsHelper<Bounds>::to(0,
576  missing_divs[0].nb * factors[i],
577  domain.min[missing_divs[0].dim],
578  domain.max[missing_divs[0].dim],
579  share_face[missing_divs[0].dim]);
580  if (max >= min)
581  {
582  missing_divs[0].nb *= factors[i];
583  missing_divs[0].b_size = max - min;
584  }
585  else
586  {
587  std::ostringstream oss;
588  oss << "Unable to decompose domain into " << nblocks << " blocks: " << min << " " << max;
589  throw std::runtime_error(oss.str());
590  }
591  }
592 
593  // assign the divisions
594  for (size_t i = 0; i < missing_divs.size(); i++)
595  divisions[missing_divs[i].dim] = missing_divs[i].nb;
596 }
597 
598 template<class Bounds>
599 void
601 factor(std::vector<unsigned>& factors, int n)
602 {
603  while (n != 1)
604  for (int i = 2; i <= n; ++i)
605  {
606  if (n % i == 0)
607  {
608  factors.push_back(i);
609  n /= i;
610  break;
611  }
612  }
613 }
614 
615 // Point to GIDs
616 // TODO: deal with wrap correctly
617 // TODO: add an optional ghosts argument to ignore ghosts (if we want to find the true owners, or something like that)
618 template<class Bounds>
619 template<class Point>
620 void
622 point_to_gids(std::vector<int>& gids, const Point& p) const
623 {
624  std::vector< std::pair<int, int> > ranges(dim);
625  for (int i = 0; i < dim; ++i)
626  top_bottom(ranges[i].second, ranges[i].first, p, i);
627 
628  // look up gids for all combinations
629  DivisionsVector coords(dim), location(dim);
630  while(location.back() < ranges.back().second - ranges.back().first)
631  {
632  for (int i = 0; i < dim; ++i)
633  coords[i] = ranges[i].first + location[i];
634  gids.push_back(coords_to_gid(coords, divisions));
635 
636  location[0]++;
637  unsigned i = 0;
638  while (i < dim-1 && location[i] == ranges[i].second - ranges[i].first)
639  {
640  location[i] = 0;
641  ++i;
642  location[i]++;
643  }
644  }
645 }
646 
647 template<class Bounds>
648 template<class Point>
649 int
651 point_to_gid(const Point& p) const
652 {
653  int gid = 0;
654  for (int axis = dim - 1; axis >= 0; --axis)
655  {
656  int bottom = detail::BoundsHelper<Bounds>::lower(p[axis], divisions[axis], domain.min[axis], domain.max[axis], share_face[axis]);
657  bottom = std::max(0, bottom);
658 
659  // coupled with coords_to_gid
660  gid *= divisions[axis];
661  gid += bottom;
662  }
663 
664  return gid;
665 }
666 
667 template<class Bounds>
668 template<class Point>
669 int
671 num_gids(const Point& p) const
672 {
673  int res = 1;
674  for (int i = 0; i < dim; ++i)
675  {
676  int top, bottom;
677  top_bottom(top, bottom, p, i);
678  res *= top - bottom;
679  }
680  return res;
681 }
682 
683 template<class Bounds>
684 template<class Point>
685 void
687 top_bottom(int& top, int& bottom, const Point& p, int axis) const
688 {
689  Coordinate l = p[axis] - ghosts[axis];
690  Coordinate r = p[axis] + ghosts[axis];
691 
692  top = detail::BoundsHelper<Bounds>::upper(r, divisions[axis], domain.min[axis], domain.max[axis], share_face[axis]);
693  bottom = detail::BoundsHelper<Bounds>::lower(l, divisions[axis], domain.min[axis], domain.max[axis], share_face[axis]);
694 
695  if (!wrap[axis])
696  {
697  bottom = std::max(0, bottom);
698  top = std::min(divisions[axis], top);
699  }
700 }
701 
702 // find lowest gid that owns a particular point
703 template<class Bounds>
704 template<class Point>
705 int
707 lowest_gid(const Point& p) const
708 {
709  // TODO: optimize - no need to compute all gids
710  std::vector<int> gids;
711  point_to_gids(gids, p);
712  std::sort(gids.begin(), gids.end());
713  return gids[0];
714 }
715 
716 #endif
int point_to_gid(const Point &p) const
returns gid of a block that contains the point; ignores ghosts
Definition: decomposition.hpp:651
int nblocks() const
returns the total number of global blocks
Definition: assigner.hpp:26
Decomposes a regular (discrete or continuous) domain into even blocks; creates Links with Bounds alon...
Definition: decomposition.hpp:75
void decompose(int dim, int rank, const Bounds &domain, const Assigner &assigner, Master &master, const typename RegularDecomposer< Bounds >::Updater &update, typename RegularDecomposer< Bounds >::BoolVector share_face=typename RegularDecomposer< Bounds >::BoolVector(), typename RegularDecomposer< Bounds >::BoolVector wrap=typename RegularDecomposer< Bounds >::BoolVector(), typename RegularDecomposer< Bounds >::CoordinateVector ghosts=typename RegularDecomposer< Bounds >::CoordinateVector(), typename RegularDecomposer< Bounds >::DivisionsVector divs=typename RegularDecomposer< Bounds >::DivisionsVector())
Add a decomposition (modify links) of an existing set of blocks that were added to the master previou...
Definition: decomposition.hpp:255
Definition: assigner.hpp:11
void decompose(int dim, int rank, const Bounds &domain, const Assigner &assigner, const typename RegularDecomposer< Bounds >::Creator &create, typename RegularDecomposer< Bounds >::BoolVector share_face=typename RegularDecomposer< Bounds >::BoolVector(), typename RegularDecomposer< Bounds >::BoolVector wrap=typename RegularDecomposer< Bounds >::BoolVector(), typename RegularDecomposer< Bounds >::CoordinateVector ghosts=typename RegularDecomposer< Bounds >::CoordinateVector(), typename RegularDecomposer< Bounds >::DivisionsVector divs=typename RegularDecomposer< Bounds >::DivisionsVector())
Decomposes the domain into a prescribed pattern of blocks.
Definition: decomposition.hpp:181
int add(int gid, void *b, Link *l)
add a block
Definition: master.hpp:658
Definition: master.hpp:35
Definition: point.hpp:15
Definition: types.hpp:16
virtual void local_gids(int rank, std::vector< int > &gids) const =0
gets the local gids for a given process rank
RegularDecomposer(int dim_, const Bounds &domain_, int nblocks_, BoolVector share_face_=BoolVector(), BoolVector wrap_=BoolVector(), CoordinateVector ghosts_=CoordinateVector(), DivisionsVector divisions_=DivisionsVector())
Definition: decomposition.hpp:96
void fill_bounds(Bounds &bounds, const DivisionsVector &coords, bool add_ghosts=false) const
Gets the bounds, with or without ghosts, for a block specified by its block coordinates.
Definition: decomposition.hpp:437
void sort(Master &master, const Assigner &assigner, std::vector< T > Block::*values, std::vector< T > Block::*samples, size_t num_samples, const Cmp &cmp, int k=2, bool samples_only=false)
sample sort values of each block, store the boundaries between blocks in samples
Definition: algorithms.hpp:25