chombo-discharge
CD_LoadBalancingImplem.H
Go to the documentation of this file.
1 /* chombo-discharge
2  * Copyright © 2021 SINTEF Energy Research.
3  * Please refer to Copyright.txt and LICENSE in the chombo-discharge root directory.
4  */
5 
12 #ifndef CD_LoadBalanceImplem_H
13 #define CD_LoadBalanceImplem_H
14 
15 // Std includes
16 #include <algorithm>
17 #include <random>
18 #include <chrono>
19 
20 // Chombo includes
21 #include <LoadBalance.H>
22 #include <BoxLayout.H>
23 #include <CH_Timer.H>
24 
25 // Our includes
26 #include <CD_LoadBalancing.H>
27 #include <CD_NamespaceHeader.H>
28 
29 template <class T>
30 void
31 LoadBalancing::makeBalance(Vector<int>& a_ranks, const Vector<T>& a_loads, const Vector<Box>& a_boxes)
32 {
33  CH_TIME("LoadBalancing::makeBalance");
34 
35  // LoadBalance(a_ranks, a_loads, a_boxes);
36  Loads rankLoads;
37  LoadBalancing::makeBalance<T>(a_ranks, rankLoads, a_loads, a_boxes);
38 }
39 
40 template <class T>
41 void
42 LoadBalancing::makeBalance(Vector<int>& a_ranks,
43  Loads& a_rankLoads,
44  const Vector<T>& a_boxLoads,
45  const Vector<Box>& a_boxes)
46 {
47  CH_TIME("LoadBalancing::makeBalance");
48 
49  // Convert everything to floating points
50  Vector<Real> boxLoads;
51  for (int i = 0; i < a_boxLoads.size(); i++) {
52  boxLoads.push_back(1.0 * a_boxLoads[i]);
53  }
54 
55  // Minimum number of grid subsets is the number of boxe, and the maximum number of grid subsets
56  // is the number of ranks.
57  const int numBoxes = a_boxes.size();
58  const int numRanks = numProc();
59  const int numSubsets = std::min(numBoxes, numRanks);
60 
61  a_ranks.resize(numBoxes);
62 
63  if (numSubsets > 0) {
64 
65  // Figure out the total and target load (load per subset) on this level.
66  Real totalLoad = 0.0;
67  for (int ibox = 0; ibox < numBoxes; ibox++) {
68  totalLoad += boxLoads[ibox];
69  }
70 
71  Real staticTargetLoad = totalLoad / numSubsets;
72 
73  // Build the grid subsets. When we do this we iterate through the boxes and try to ensure that we partition
74  // the subsets such that the subsetLoad is as close to the dynamic targetLoad as possible.
75  //
76  // The pair contains the starting index for the subset and the computational load for the subset.
77  using Span = std::pair<int, int>;
78  using Subset = std::pair<Span, Real>;
79 
80  std::vector<Subset> subsets(numSubsets);
81 
82  int firstSubsetBox = 0;
83 
84  Real remainingLoad = totalLoad;
85 
86  for (int curSubset = 0; curSubset < numSubsets; curSubset++) {
87 
88  // The firstSubsetBox is the index for the first box in this subset (always assigned).
89  Real subsetLoad = boxLoads[firstSubsetBox];
90 
91  int lastSubsetBox = firstSubsetBox;
92 
93  const int subsetsLeft = numSubsets - (curSubset + 1);
94  const int boxesLeft = numBoxes - (firstSubsetBox + 1);
95 
96  if (boxesLeft > subsetsLeft) {
97  for (int ibox = firstSubsetBox + 1; ibox < numBoxes; ibox++) {
98 
99  // Hook for catching case when we add too many boxes to this subset. Each remaining subset must have at least one box.
100  if (numBoxes - lastSubsetBox - 1 <= subsetsLeft) {
101  break;
102  }
103 
104  // Check if we should add this box - we do this by making sure that the dynamically moving target load stays as close
105  // to the static load as possible.
106  //
107  // In the below, '1' is the load without ibox, and '2' is the load with ibox
108  const Real load1 = subsetLoad;
109  const Real load2 = subsetLoad + boxLoads[ibox];
110 
111  // Check if we should add this box.
112  bool addBoxToSubset = false;
113 
114  if (boxLoads[ibox] <= std::numeric_limits<Real>::epsilon()) {
115  addBoxToSubset = true;
116  }
117  else if (load1 > staticTargetLoad) {
118  addBoxToSubset = false;
119  }
120  else if (load2 <= staticTargetLoad) {
121  addBoxToSubset = true;
122  }
123  else if (load1 <= staticTargetLoad && load2 > staticTargetLoad) {
124  // Compute the new average load if we add or don't add this box to the current subset. Accept the answer
125  // that leads to a smallest deviation from the static target load.
126  const Real loadErrWithoutBox = std::abs(load1 - staticTargetLoad);
127  const Real loadErrWithBox = std::abs(load2 - staticTargetLoad);
128 
129  if (loadErrWithBox <= loadErrWithoutBox) {
130  addBoxToSubset = true;
131  }
132  }
133 
134  // Add box or break out of box iteration.
135  if (addBoxToSubset) {
136  subsetLoad = subsetLoad + boxLoads[ibox];
137  lastSubsetBox = ibox;
138 
139  continue;
140  }
141  else {
142  lastSubsetBox = ibox - 1;
143 
144  break;
145  }
146  }
147  }
148 
149  // Create the subset
150  subsets[curSubset] = std::make_pair(std::make_pair(firstSubsetBox, lastSubsetBox), subsetLoad);
151 
152  // Update the remaining load.
153  remainingLoad = remainingLoad - subsetLoad;
154  staticTargetLoad = remainingLoad / subsetsLeft;
155 
156  // Update start box for next iteration.
157  firstSubsetBox = lastSubsetBox + 1;
158  }
159 
160  // Sort the subsets from largest to smallest computational load.
161  std::sort(subsets.begin(), subsets.end(), [](const Subset& A, const Subset& B) -> bool {
162  return A.second > B.second;
163  });
164 
165  // Get the accumulated loads per rank, sorted by lowest-to-highest load.
166  const std::vector<std::pair<int, Real>> sortedRankLoads = a_rankLoads.getSortedLoads();
167 
168  // Assign the most expensive grid subset to the rank with the lowest accumulated load.
169  for (int i = 0; i < subsets.size(); i++) {
170  const int startIndex = subsets[i].first.first;
171  const int endIndex = subsets[i].first.second;
172  const Real subsetLoad = subsets[i].second;
173  const int rank = sortedRankLoads[i].first;
174 
175  // Assign to rank
176  for (int ibox = startIndex; ibox <= endIndex; ibox++) {
177  a_ranks[ibox] = rank;
178  }
179 
180  // Update load on this rank.
181  a_rankLoads.incrementLoad(rank, subsetLoad);
182  }
183  }
184  else {
185  a_ranks.resize(0);
186  }
187 }
188 
189 template <class T>
190 std::vector<std::pair<Box, T>>
191 LoadBalancing::packPairs(const Vector<Box>& a_boxes, const Vector<T>& a_loads)
192 {
193  CH_TIME("LoadBalancing::packPairs");
194 
195  std::vector<std::pair<Box, T>> vec;
196  for (int i = 0; i < a_boxes.size(); i++) {
197  vec.emplace_back(a_boxes[i], a_loads[i]);
198  }
199 
200  return vec;
201 }
202 
203 template <class T>
204 void
205 LoadBalancing::unpackPairs(Vector<Box>& a_boxes, Vector<T>& a_loads, const std::vector<std::pair<Box, T>>& a_pairs)
206 {
207  CH_TIME("LoadBalancing::unpackPairs");
208 
209  // Reconstruct boxes and loads
210  a_boxes.resize(0);
211  a_loads.resize(0);
212 
213  for (const auto& v : a_pairs) {
214  a_boxes.push_back(v.first);
215  a_loads.push_back(v.second);
216  }
217 }
218 
219 template <typename T>
220 void
221 LoadBalancing::sort(Vector<Vector<Box>>& a_boxes, Vector<Vector<T>>& a_loads, const BoxSorting a_which)
222 {
223  CH_TIME("LoadBalancing::sort");
224 
225  for (int lvl = 0; lvl < a_boxes.size(); lvl++) {
226  LoadBalancing::sort(a_boxes[lvl], a_loads[lvl], a_which);
227  }
228 }
229 
230 template <typename T>
231 void
232 LoadBalancing::sort(Vector<Box>& a_boxes, Vector<T>& a_loads, const BoxSorting a_which)
233 {
234  CH_TIME("LoadBalancing::sort");
235 
236  switch (a_which) {
237  case BoxSorting::None: {
238  break;
239  }
240  case BoxSorting::Std: {
241  LoadBalancing::standardSort(a_boxes, a_loads);
242 
243  break;
244  }
245  case BoxSorting::Shuffle: {
246  LoadBalancing::shuffleSort(a_boxes, a_loads);
247 
248  break;
249  }
250  case BoxSorting::Morton: {
251  LoadBalancing::mortonSort(a_boxes, a_loads);
252 
253  break;
254  }
255  default: {
256  MayDay::Abort("LoadBalancing::sort_boxes - unknown algorithm requested");
257 
258  break;
259  }
260  }
261 }
262 
263 template <class T>
264 void
265 LoadBalancing::standardSort(Vector<Box>& a_boxes, Vector<T>& a_loads)
266 {
267  CH_TIME("LoadBalancing::standardSort");
268 
269  std::vector<std::pair<Box, T>> vec = packPairs(a_boxes, a_loads);
270 
271  // Call std::sort, using box1 < box2 lambda as sorting criterion.
272  std::sort(std::begin(vec), std::end(vec), [](const std::pair<Box, T>& v1, const std::pair<Box, T>& v2) {
273  return v1.first < v2.first;
274  });
275 
276  unpackPairs(a_boxes, a_loads, vec);
277 }
278 
279 template <class T>
280 void
281 LoadBalancing::shuffleSort(Vector<Box>& a_boxes, Vector<T>& a_loads)
282 {
283  CH_TIME("LoadBalancing::shuffleSort");
284 
285  auto vec = packPairs(a_boxes, a_loads);
286 
287  // Set up RNG
288  int seed = std::chrono::system_clock::now().time_since_epoch().count();
289 #ifdef CH_MPI // Broadcast
290  MPI_Bcast(&seed, 1, MPI_INT, 0, Chombo_MPI::comm);
291 #endif
292 
293  // Shuffle vector
294  std::default_random_engine e(seed);
295  std::shuffle(vec.begin(), vec.end(), e);
296 
297  // Split boxes and loads
298 
299  unpackPairs(a_boxes, a_loads, vec);
300 }
301 
302 template <class T>
303 void
304 LoadBalancing::mortonSort(Vector<Box>& a_boxes, Vector<T>& a_loads)
305 {
306  CH_TIME("LoadBalancing::mortonSort");
307 
308  auto vec = packPairs(a_boxes, a_loads);
309 
310  // Get max bits
311  std::vector<Box>& b = a_boxes.stdVector();
312  int bits = maxBits(b.begin(), b.end());
313 
314  // Morton sort.
315  std::sort(std::begin(vec), std::end(vec), [bits](const std::pair<Box, T>& v1, const std::pair<Box, T>& v2) -> bool {
316  return mortonComparator(bits, v1, v2);
317  });
318 
319  // Put back in normal form
320  unpackPairs(a_boxes, a_loads, vec);
321 }
322 
323 template <class T>
324 bool
325 LoadBalancing::mortonComparator(const int a_maxBits, const std::pair<Box, T>& a_lhs, const std::pair<Box, T>& a_rhs)
326 {
327  const Box& lbox = a_lhs.first;
328  const Box& rbox = a_rhs.first;
329 
330  const IntVect l = lbox.smallEnd();
331  const IntVect r = rbox.smallEnd();
332 
333  for (int i = a_maxBits; i > 0; i--) {
334 
335  // March from most significant bit to least.
336  const int N = (1 << i);
337 
338  for (int dir = CH_SPACEDIM - 1; dir >= 0; dir--) {
339  if ((l[dir] / N) < (r[dir] / N)) {
340  return true;
341  }
342  else if ((l[dir] / N) > (r[dir] / N)) {
343  return false;
344  }
345  }
346  }
347 
348  return false;
349 }
350 
351 #include <CD_NamespaceFooter.H>
352 
353 #endif
BoxSorting
Enum for sorting boxes.
Definition: CD_BoxSorting.H:21
Declaration of a static class for various load balancing operations.
static int maxBits(std::vector< Box >::iterator a_first, std::vector< Box >::iterator a_last)
Utility function for Morton sorting which needs the maximum bits.
Definition: CD_LoadBalancing.cpp:279
static void makeBalance(Vector< int > &a_ranks, const Vector< T > &a_loads, const Vector< Box > &a_boxes)
Load balancing, assigning ranks to boxes.
Definition: CD_LoadBalancingImplem.H:31
static void sort(Vector< Vector< Box >> &a_boxes, Vector< Vector< T >> &a_loads, const BoxSorting a_whichSorting)
Sorts boxes and loads over a hierarchy according to some sorting criterion.
Definition: CD_LoadBalancingImplem.H:221
static void standardSort(Vector< Box > &a_boxes, Vector< T > &a_loads)
Standard box sorting, calls C++ std::sort.
Definition: CD_LoadBalancingImplem.H:265
static void mortonSort(Vector< Box > &a_boxes, Vector< T > &a_loads)
Sort boxes using Morton code.
Definition: CD_LoadBalancingImplem.H:304
static bool mortonComparator(const int a_maxBits, const std::pair< Box, T > &a_lhs, const std::pair< Box, T > &a_rhs)
Morton comparator.
Definition: CD_LoadBalancingImplem.H:325
static std::vector< std::pair< Box, T > > packPairs(const Vector< Box > &a_boxes, const Vector< T > &a_loads)
Utility function which packs boxes and loads into a vector of pairs.
Definition: CD_LoadBalancingImplem.H:191
static void unpackPairs(Vector< Box > &a_boxes, Vector< T > &a_loads, const std::vector< std::pair< Box, T >> &a_pairs)
Splits vector pair into separate boxes and loads.
Definition: CD_LoadBalancingImplem.H:205
static void shuffleSort(Vector< Box > &a_boxes, Vector< T > &a_loads)
Randomly shuffles boxes and loads.
Definition: CD_LoadBalancingImplem.H:281
Class for holding computational loads.
Definition: CD_Loads.H:30
virtual void incrementLoad(const int a_rank, const Real a_increment) noexcept
Increment load on rank.
Definition: CD_Loads.cpp:168
virtual std::vector< std::pair< int, Real > > getSortedLoads() const noexcept
Get sorted loads.
Definition: CD_Loads.cpp:183
Real min(const Real &a_input) noexcept
Get the minimum of the input, reduced over MPI ranks (in the Chombo communicator)
Definition: CD_ParallelOpsImplem.H:58