OP  0.1
OP is a optimization solver plugin package
 All Classes Namespaces Functions Variables Typedefs Enumerations Friends
op_utility.hpp
1 #pragma once
2 
3 #include "op_mpi.hpp"
4 
5 namespace op {
6 
8 namespace utility {
9 
17 template <typename T>
19  std::unordered_map<int, T> recv;
20  std::unordered_map<int, T> send;
21  using value_type = T;
22  using key_type = int;
23 };
24 
26 template <typename T>
27 struct CommPattern {
28  op::utility::RankCommunication<T> rank_communication;
29  T owned_variable_list;
30  T local_variable_list;
31 };
32 
33 template <typename T>
35 
42 template <typename T>
43 std::vector<T> buildInclusiveOffsets(std::vector<T>& values_per_rank)
44 {
45  std::vector<T> inclusive_offsets(values_per_rank.size() + 1);
46  T offset = 0;
47  std::transform(values_per_rank.begin(), values_per_rank.end(), inclusive_offsets.begin() + 1,
48  [&](T& value) { return offset += value; });
49  return inclusive_offsets;
50 }
51 
53 namespace parallel {
62 template <typename T>
63 std::tuple<T, std::vector<T>> gatherVariablesPerRank(std::size_t local_vector_size, bool gatherAll = true, int root = 0,
64  MPI_Comm comm = MPI_COMM_WORLD)
65 {
66  std::vector<T> local_size{static_cast<T>(local_vector_size)};
67 
68  auto nranks = static_cast<std::size_t>(mpi::getNRanks(comm));
69  std::vector<T> size_on_rank(nranks);
70  std::vector<int> ones(nranks, 1);
71  std::vector<int> offsets(nranks);
72  std::iota(offsets.begin(), offsets.end(), 0);
73  if (gatherAll) {
74  mpi::Allgatherv(local_size, size_on_rank, ones, offsets, comm);
75  } else {
76  mpi::Gatherv(local_size, size_on_rank, ones, offsets, root, comm);
77  }
78 
79  T global_size = 0;
80  for (auto lsize : size_on_rank) {
81  global_size += lsize;
82  }
83  return std::make_tuple(global_size, size_on_rank);
84 }
85 
98 template <typename V>
99 V concatGlobalVector(typename V::size_type global_size, std::vector<int>& variables_per_rank, std::vector<int>& offsets,
100  V& local_vector, bool gatherAll = true, int root = 0, MPI_Comm comm = MPI_COMM_WORLD)
101 {
102  V global_vector(global_size);
103 
104  if (gatherAll) {
105  mpi::Allgatherv(local_vector, global_vector, variables_per_rank, offsets, comm);
106  } else {
107  mpi::Gatherv(local_vector, global_vector, variables_per_rank, offsets, root, comm);
108  }
109  return global_vector;
110 }
111 
113 template <typename V>
114 V concatGlobalVector(typename V::size_type global_size, std::vector<int>& variables_per_rank, V& local_vector,
115  bool gatherAll = true, int root = 0, MPI_Comm comm = MPI_COMM_WORLD)
116 {
117  V global_vector(global_size);
118 
119  // build offsets
120  auto offsets = buildInclusiveOffsets(variables_per_rank);
121  return concatGlobalVector(global_size, variables_per_rank, offsets, local_vector, gatherAll, root, comm);
122 }
123 
135 template <typename T, typename M, typename I>
136 RankCommunication<T> generateSendRecievePerRank(M local_ids, T& all_global_local_ids, I& offsets,
137  MPI_Comm comm = MPI_COMM_WORLD)
138 {
139  int my_rank = mpi::getRank(comm);
140  // Go through global_local_ids looking for local_ids and add either to send_map or recv_map
141  typename I::value_type current_rank = 0;
142 
143  RankCommunication<T> comm_info;
144  std::unordered_map<int, T>& recv = comm_info.recv;
145  std::unordered_map<int, T>& send = comm_info.send;
146 
147  // check if local_ids.size() == 0, this case can only occur if the task has no optimization variables whatsoever
148  if (local_ids.size() > 0) {
149  for (const auto& global_local_id : all_global_local_ids) {
150  // keep current_rank up-to-date
151  auto global_offset = &global_local_id - &all_global_local_ids.front();
152  // Implementation Note: If a rank has no optimization variables, we need to find the actual rank a dependent rank would have
153  const auto global_offset_index = static_cast<typename I::value_type>(global_offset);
154  if (global_offset_index == offsets[current_rank + 1]) {
155  current_rank++;
156  while (global_offset_index == offsets[current_rank + 1]) {
157  current_rank++;
158  }
159  }
160 
161  typename M::iterator found;
162  // skip if it's our rank
163  if (current_rank != my_rank && ((found = local_ids.find(global_local_id)) != local_ids.end())) {
164  // The global_local_id is one of ours check to see if we need to send
165 
166  if (current_rank < my_rank) {
167  // append local_id to variables to send to this rank
168  send[current_rank].insert(send[current_rank].end(), (found->second).begin(), (found->second).end());
169 
170  // erase it from our local_ids copy since we've found where to send it
171  local_ids.erase(found);
172  } else if (current_rank > my_rank) {
173  // check to see if we already will recieve data from this rank
174  // we are already recieving data from this rank
175  recv[current_rank].push_back(found->second[0]);
176  }
177  }
178 
179  }
180  }
181 
182  // go through all recv keys and sort them. We can sort them because we "own" them and non-owning nodes will send them in our order
183  for (auto & [rank, values] : recv) {
184  std::sort(values.begin(), values.end());
185  }
186 
187  return comm_info;
188 }
189 
198 template <typename V, typename T>
199 std::unordered_map<int, V> sendToOwners(RankCommunication<T>& info, V& local_data, MPI_Comm comm = MPI_COMM_WORLD)
200 {
201  std::unordered_map<int, T>& recv = info.recv;
202  std::unordered_map<int, T>& send = info.send;
203 
204  // initiate Irecv first requests
205  std::vector<MPI_Request> requests;
206  std::unordered_map<int, V> recv_data;
207  for (auto [recv_rank, recv_rank_vars] : recv) {
208  // allocate space to recieve
209  recv_data[recv_rank] = V(recv_rank_vars.size());
210  requests.push_back(MPI_Request());
211  // initiate recvieve from rank
212  mpi::Irecv(recv_data[recv_rank], recv_rank, &requests.back());
213  }
214 
215  MPI_Barrier(comm);
216  std::unordered_map<int, V> send_data;
217  for (auto [send_to_rank, send_rank_vars] : send) {
218  send_data[send_to_rank] = V();
219  for (auto s : send_rank_vars) {
220  send_data[send_to_rank].push_back(local_data[s]);
221  }
222  requests.push_back(MPI_Request());
223  // initiate recvieve from rank
224  mpi::Isend(send_data[send_to_rank], send_to_rank, &requests.back());
225  }
226 
227  std::vector<MPI_Status> stats(requests.size());
228  auto error = mpi::Waitall(requests, stats);
229  if (error != MPI_SUCCESS) std::cout << "sendToOwner issue : " << error << std::endl;
230 
231  return recv_data;
232 }
233 
243 template <typename V, typename T>
244 auto returnToSender(RankCommunication<T>& info, const V& local_data, MPI_Comm comm = MPI_COMM_WORLD)
245 {
246  std::unordered_map<int, T>& recv = info.recv;
247  std::unordered_map<int, T>& send = info.send;
248 
249  // initiate Irecv first requests
250  std::vector<MPI_Request> requests;
251 
252  std::unordered_map<int, V> send_data;
253  for (auto [send_to_rank, send_rank_vars] : send) {
254  // populate data to send
255  send_data[send_to_rank] = V(send_rank_vars.size());
256  requests.push_back(MPI_Request());
257  // initiate recvieve from rank
258  mpi::Irecv(send_data[send_to_rank], send_to_rank, &requests.back());
259  }
260 
261  MPI_Barrier(comm);
262  std::unordered_map<int, V> recv_data;
263  for (auto [recv_rank, recv_rank_vars] : recv) {
264  // allocate space to recieve
265  recv_data[recv_rank] = V();
266  for (auto r : recv_rank_vars) {
267  recv_data[recv_rank].push_back(local_data[r]);
268  }
269 
270  requests.push_back(MPI_Request());
271  // initiate recvieve from rank
272  mpi::Isend(recv_data[recv_rank], recv_rank, &requests.back());
273  }
274 
275  std::vector<MPI_Status> stats(requests.size());
276  auto error = mpi::Waitall(requests, stats);
277  if (error != MPI_SUCCESS) std::cout << "returnToSender issue : " << error << std::endl;
278 
279  return send_data;
280 }
281 
282 } // namespace parallel
283 
302 template <typename T, typename M>
303 void accessPermuteStore(T& vector, M& map, T& results)
304 {
305  assert(results.size() >= vector.size());
306  // check only if in debug mode and map.size > 0
307  assert(map.size() == 0 || (map.size() > 0 && static_cast<typename T::size_type>(
308  *std::max_element(map.begin(), map.end())) <= results.size()));
309  for (typename T::size_type i = 0; i < vector.size(); i++) {
310  results[map[i]] = vector[i];
311  }
312 }
313 
336 template <typename T, typename M>
337 T accessPermuteStore(T& vector, M& map, typename T::value_type pad_value,
338  std::optional<typename T::size_type> arg_size = std::nullopt)
339 {
340  // if arg_size is specified we'll use that.. otherwise we'll use T
341  typename T::size_type results_size = arg_size ? *arg_size : vector.size();
342  assert(results_size >= vector.size());
343  assert(static_cast<typename T::size_type>(*std::max_element(map.begin(), map.end())) <= results_size);
344  T results(results_size, pad_value);
345  accessPermuteStore(vector, map, results);
346  return results;
347 }
348 
367 template <typename T, typename M>
368 T permuteAccessStore(T& vector, M& map)
369 {
370  assert((map.size() > 0 &&
371  static_cast<typename T::size_type>(*std::max_element(map.begin(), map.end())) <= vector.size()) ||
372  (map.size() == 0));
373  assert(map.size() <= vector.size());
374  T result(map.size());
375  for (typename T::size_type i = 0; i < result.size(); i++) {
376  result[i] = vector[map[i]];
377  }
378  return result;
379 }
380 
389 template <typename T, typename M, typename I>
390 T permuteMapAccessStore(T& vector, M& map, I& global_ids_of_local_vector)
391 {
392  assert(map.size() <= vector.size());
393  T result(map.size());
394  for (typename T::size_type i = 0; i < result.size(); i++) {
395  result[i] = vector[global_ids_of_local_vector[map[i]][0]];
396  }
397  return result;
398 }
399 
400 template <typename T>
401 using inverseMapType = std::unordered_map<typename T::value_type, T>;
402 
416 template <typename T>
417 auto inverseMap(T& vector_map)
418 {
419  inverseMapType<T> map;
420  typename T::size_type counter = 0;
421  for (auto v : vector_map) {
422  if (map.find(v) != map.end()) {
423  map[v].push_back(counter);
424  } else {
425  // initialize map[v]
426  map[v] = T{counter};
427  }
428  counter++;
429  }
430  // sort the map
431  for (auto& [k, v] : map) {
432  std::sort(v.begin(), v.end());
433  }
434 
435  return map;
436 }
437 
445 template <typename K, typename V>
446 auto mapToVector(std::unordered_map<K, V>& map)
447 {
448  std::vector<V> vect;
449  for (auto [k, v] : map) {
450  vect.push_back(v);
451  }
452  return vect;
453 }
454 
464 template <typename T, typename V>
465 std::unordered_map<typename T::value_type, V> remapRecvData(std::unordered_map<int, T>& recv,
466  std::unordered_map<int, V>& recv_data)
467 {
468  // recv[from_rank] = {contributions to local indices, will point to first local index corresponding to global index}
469 
470  std::unordered_map<typename T::value_type, V> remap;
471  for (auto [recv_rank, local_inds] : recv) {
472  for (auto& local_ind : local_inds) {
473  auto index = &local_ind - &local_inds.front();
474  auto value = recv_data[recv_rank][static_cast<size_t>(index)];
475 
476  // local_ind is a key in remap
477  remap[local_ind].push_back(value);
478  }
479  }
480 
481  return remap;
482 }
483 
496 template <typename T, typename V>
497 auto remapRecvDataIncludeLocal(std::unordered_map<int, T>& recv, std::unordered_map<int, V>& recv_data,
498  std::unordered_map<typename T::value_type, T>& global_to_local_map, V& local_variables)
499 {
500  auto remap = remapRecvData(recv, recv_data);
501 
502  // add our own local data to the remapped data
503  for (auto [_, local_ids] : global_to_local_map) {
504  for (auto local_id : local_ids) {
505  remap[local_ids[0]].push_back(local_variables.at(local_id));
506  }
507  }
508 
509  return remap;
510 }
511 
518 template <typename M>
519 typename M::mapped_type reduceRecvData(
520  M& remapped_data, std::function<typename M::mapped_type::value_type(const typename M::mapped_type&)> reduce_op)
521 {
522  typename M::mapped_type reduced_data(remapped_data.size());
523  for (auto [local_ind, data_to_reduce] : remapped_data) {
524  reduced_data[local_ind] = reduce_op(data_to_reduce);
525  }
526  return reduced_data;
527 }
528 
530 namespace reductions {
534 template <typename V>
535 static typename V::value_type sumOfCollection(const V& collection)
536 {
537  typename V::value_type sum = 0;
538  for (auto val : collection) {
539  sum += val;
540  }
541  return sum;
542 }
543 
547 template <typename V>
548 static typename V::value_type firstOfCollection(const V& collection)
549 {
550  return collection[0];
551 }
552 } // namespace reductions
553 
560 template <typename T>
561 auto filterOut(const T& global_local_ids, std::unordered_map<int, std::vector<typename T::size_type>>& filter)
562 {
563  std::vector<typename T::size_type> remove_ids;
564  for (auto [_, local_ids] : filter) {
565  remove_ids.insert(std::end(remove_ids), std::begin(local_ids), std::end(local_ids));
566  }
567  // sort the ids that we want to remove
568  std::sort(remove_ids.begin(), remove_ids.end());
569 
570  // filter out all local variables that we are sending
571  std::vector<typename T::size_type> local_id_range(global_local_ids.size());
572  std::vector<typename T::size_type> filtered(local_id_range.size());
573  std::iota(local_id_range.begin(), local_id_range.end(), 0);
574  if (remove_ids.size() > 0) {
575  auto it = std::set_difference(local_id_range.begin(), local_id_range.end(), remove_ids.begin(), remove_ids.end(),
576  filtered.begin());
577  filtered.resize(it - filtered.begin());
578  } else {
579  filtered = local_id_range;
580  }
581 
582  // map local ids
583  T mapped(filtered.size());
584  std::transform(filtered.begin(), filtered.end(), mapped.begin(), [&](auto& v) { return global_local_ids[v]; });
585  return mapped;
586 }
587 
588 } // namespace utility
589 
590 } // namespace op
int getRank(MPI_Comm comm=MPI_COMM_WORLD)
Get rank.
Definition: op_mpi.hpp:50
int Waitall(std::vector< MPI_Request > &requests, std::vector< MPI_Status > &status)
A wrapper to MPI_Waitall to wait for all the requests to be fulfilled.
Definition: op_mpi.hpp:227
auto inverseMap(T &vector_map)
Inverts a vector that providse a map into an unordered_map.
Definition: op_utility.hpp:417
auto filterOut(const T &global_local_ids, std::unordered_map< int, std::vector< typename T::size_type >> &filter)
remove values in filter that correspond to global_local_ids
Definition: op_utility.hpp:561
auto returnToSender(RankCommunication< T > &info, const V &local_data, MPI_Comm comm=MPI_COMM_WORLD)
transfer back data in reverse from sendToOwners
Definition: op_utility.hpp:244
V concatGlobalVector(typename V::size_type global_size, std::vector< int > &variables_per_rank, std::vector< int > &offsets, V &local_vector, bool gatherAll=true, int root=0, MPI_Comm comm=MPI_COMM_WORLD)
Assemble a vector by concatination of local_vector across all ranks on a communicator.
Definition: op_utility.hpp:99
T permuteAccessStore(T &vector, M &map)
Retrieves from T using a permuted mapping M and stores in order, result[i] = T[M[i]].
Definition: op_utility.hpp:368
int Irecv(T &buf, int send_rank, MPI_Request *request, int tag=0, MPI_Comm comm=MPI_COMM_WORLD)
Recieve a buffer from a specified rank and create a handle for the MPI_Request.
Definition: op_mpi.hpp:197
auto remapRecvDataIncludeLocal(std::unordered_map< int, T > &recv, std::unordered_map< int, V > &recv_data, std::unordered_map< typename T::value_type, T > &global_to_local_map, V &local_variables)
rearrange data so that map[rank]-&gt;local_ids and map[rank] -&gt; V becomes map[local_ids]-&gt;values ...
Definition: op_utility.hpp:497
void accessPermuteStore(T &vector, M &map, T &results)
Retrieves from T and stores in permuted mapping M, result[M[i]] = T[i].
Definition: op_utility.hpp:303
T permuteMapAccessStore(T &vector, M &map, I &global_ids_of_local_vector)
Retrieves from T using a permuted mapping M and index mapping I stores in order, result[i] = T[I[M[i]...
Definition: op_utility.hpp:390
std::unordered_map< typename T::value_type, V > remapRecvData(std::unordered_map< int, T > &recv, std::unordered_map< int, V > &recv_data)
rearrange data so that map[rank]-&gt;local_ids and map[rank] -&gt; V becomes map[local_ids]-&gt;values ...
Definition: op_utility.hpp:465
Complete Op communication pattern information.
Definition: op_utility.hpp:27
std::vector< T > buildInclusiveOffsets(std::vector< T > &values_per_rank)
Takes in sizes per index and and performs a rank-local inclusive offset.
Definition: op_utility.hpp:43
int Allgatherv(T &buf, T &values_on_rank, std::vector< int > &size_on_rank, std::vector< int > &offsets_on_rank, MPI_Comm comm=MPI_COMM_WORLD)
gathers a local collections from all ranks on all ranks on a communicator
Definition: op_mpi.hpp:137
int Gatherv(T &buf, T &values_on_rank, std::vector< int > &size_on_rank, std::vector< int > &offsets_on_rank, int root=0, MPI_Comm comm=MPI_COMM_WORLD)
gathers a local collections from all ranks only on the root rank
Definition: op_mpi.hpp:157
std::unordered_map< int, V > sendToOwners(RankCommunication< T > &info, V &local_data, MPI_Comm comm=MPI_COMM_WORLD)
transfer data to owners
Definition: op_utility.hpp:199
M::mapped_type reduceRecvData(M &remapped_data, std::function< typename M::mapped_type::value_type(const typename M::mapped_type &)> reduce_op)
apply reduction operation to recieved data
Definition: op_utility.hpp:519
int getNRanks(MPI_Comm comm=MPI_COMM_WORLD)
Get number of ranks.
Definition: op_mpi.hpp:58
int Isend(T &buf, int recv_rank, MPI_Request *request, int tag=0, MPI_Comm comm=MPI_COMM_WORLD)
Send a buffer to a specified rank and create a handle for the MPI_Request.
Definition: op_mpi.hpp:214
Holds communication information to and from rank.
Definition: op_utility.hpp:18
auto mapToVector(std::unordered_map< K, V > &map)
Converts an inverseMap back to the vector representation.
Definition: op_utility.hpp:446
std::tuple< T, std::vector< T > > gatherVariablesPerRank(std::size_t local_vector_size, bool gatherAll=true, int root=0, MPI_Comm comm=MPI_COMM_WORLD)
Get number of variables on each rank in parallel.
Definition: op_utility.hpp:63
RankCommunication< T > generateSendRecievePerRank(M local_ids, T &all_global_local_ids, I &offsets, MPI_Comm comm=MPI_COMM_WORLD)
given a map of local_ids and global_ids determine send and recv communications
Definition: op_utility.hpp:136