1 // -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 // vi: set et ts=4 sw=2 sts=2: 3 #ifndef DUNE_GRID_YASPGRID_TORUS_HH 4 #define DUNE_GRID_YASPGRID_TORUS_HH 5 6 #include <array> 7 #include <bitset> 8 #include <cmath> 9 #include <deque> 10 #include <iostream> 11 #include <vector> 12 13 #if HAVE_MPI 14 #include <mpi.h> 15 #endif 16 17 #include <dune/common/binaryfunctions.hh> 18 #include <dune/common/streamoperators.hh> 19 #include <dune/grid/common/exceptions.hh> 20 21 #include "partitioning.hh" 22 23 /** \file 24 * \brief This file provides the infrastructure for toroidal communication in YaspGrid. 25 */ 26 27 namespace Dune 28 { 29 30 /*! Torus provides all the functionality to handle a toroidal communication structure: 31 32 - Map a set of processes (given by an MPI communicator) to a torus of dimension d. The "optimal" 33 torus dimensions are determined by a coarse mesh. The maximum side length is minimized. 34 35 - Provide lists of neighboring processes and a method for nearest neighbor exchange 36 using asynchronous communication with MPI. The periodic case is handled where one process 37 might have to exchange several messages with the same process. (Logically, a process has always 38 \f$3^d-1\f$ neighbors, but several of these logical neighbors might be identical) 39 40 - Provide means to partition a grid to the torus. 41 42 */ 43 template<class CollectiveCommunication, int d> 44 class Torus { 45 public: 46 //! type used to pass tupels in and out 47 typedef std::array<int, d> iTupel; 48 49 50 private: 51 struct CommPartner { 52 int rank; 53 iTupel delta; 54 int index; 55 }; 56 57 struct CommTask { 58 int rank; // process to send to / receive from 59 int size; // size of buffer 60 void *buffer; // buffer to send / receive 61 }; 62 63 public: 64 //! constructor making uninitialized object Torus()65 Torus () 66 {} 67 68 //! make partitioner from communicator and coarse mesh size Torus(CollectiveCommunication comm,int tag,iTupel size,const YLoadBalance<d> * lb)69 Torus (CollectiveCommunication comm, int tag, iTupel size, const YLoadBalance<d>* lb) 70 : _comm(comm), _tag(tag) 71 { 72 // determine dimensions 73 lb->loadbalance(size, _comm.size(), _dims); 74 75 // compute increments for lexicographic ordering 76 int inc = 1; 77 for (int i=0; i<d; i++) 78 { 79 _increment[i] = inc; 80 inc *= _dims[i]; 81 } 82 83 // check whether the load balancing matches the size of the communicator 84 if (inc != _comm.size()) 85 DUNE_THROW(Dune::Exception, "Communicator size and result of the given load balancer do not match!"); 86 87 // make full schedule 88 proclists(); 89 } 90 91 //! return own rank rank() const92 int rank () const 93 { 94 return _comm.rank(); 95 } 96 97 //! return own coordinates coord() const98 iTupel coord () const 99 { 100 return rank_to_coord(_comm.rank()); 101 } 102 103 //! return number of processes procs() const104 int procs () const 105 { 106 return _comm.size(); 107 } 108 109 //! return dimensions of torus dims() const110 const iTupel & dims () const 111 { 112 return _dims; 113 } 114 115 //! return dimensions of torus in direction i dims(int i) const116 int dims (int i) const 117 { 118 return _dims[i]; 119 } 120 121 //! return communicator comm() const122 CollectiveCommunication comm () const 123 { 124 return _comm; 125 } 126 127 //! return tag used by torus tag() const128 int tag () const 129 { 130 return _tag; 131 } 132 133 //! return true if coordinate is inside torus inside(iTupel c) const134 bool inside (iTupel c) const 135 { 136 for (int i=d-1; i>=0; i--) 137 if (c[i]<0 || c[i]>=_dims[i]) return false; 138 return true; 139 } 140 141 //! map rank to coordinate in torus using lexicographic ordering rank_to_coord(int rank) const142 iTupel rank_to_coord (int rank) const 143 { 144 iTupel coord; 145 rank = rank%_comm.size(); 146 for (int i=d-1; i>=0; i--) 147 { 148 coord[i] = rank/_increment[i]; 149 rank = rank%_increment[i]; 150 } 151 return coord; 152 } 153 154 //! map coordinate in torus to rank using lexicographic ordering coord_to_rank(iTupel coord) const155 int coord_to_rank (iTupel coord) const 156 { 157 for (int i=0; i<d; i++) coord[i] = coord[i]%_dims[i]; 158 int rank = 0; 159 for (int i=0; i<d; i++) rank += coord[i]*_increment[i]; 160 return rank; 161 } 162 163 //! return rank of process where its coordinate in direction dir has offset cnt (handles periodic case) rank_relative(int rank,int dir,int cnt) const164 int rank_relative (int rank, int dir, int cnt) const 165 { 166 iTupel coord = rank_to_coord(rank); 167 coord[dir] = (coord[dir]+_dims[dir]+cnt)%_dims[dir]; 168 return coord_to_rank(coord); 169 } 170 171 //! assign color to given coordinate color(const iTupel & coord) const172 int color (const iTupel & coord) const 173 { 174 int c = 0; 175 int power = 1; 176 177 // interior coloring 178 for (int i=0; i<d; i++) 179 { 180 if (coord[i]%2==1) c += power; 181 power *= 2; 182 } 183 184 // extra colors for boundary processes 185 for (int i=0; i<d; i++) 186 { 187 if (_dims[i]>1 && coord[i]==_dims[i]-1) c += power; 188 power *= 2; 189 } 190 191 return c; 192 } 193 194 //! assign color to given rank color(int rank) const195 int color (int rank) const 196 { 197 return color(rank_to_coord(rank)); 198 } 199 200 //! return the number of neighbors, which is \f$3^d-1\f$ neighbors() const201 int neighbors () const 202 { 203 int n=1; 204 for (int i=0; i<d; ++i) 205 n *= 3; 206 return n-1; 207 } 208 209 //! return true if neighbor with given delta is a neighbor under the given periodicity is_neighbor(iTupel delta,std::bitset<d> periodic) const210 bool is_neighbor (iTupel delta, std::bitset<d> periodic) const 211 { 212 iTupel coord = rank_to_coord(_comm.rank()); // my own coordinate with 0 <= c_i < dims_i 213 214 for (int i=0; i<d; i++) 215 { 216 if (delta[i]<0) 217 { 218 // if I am on the boundary and domain is not periodic => no neighbor 219 if (coord[i]==0 && periodic[i]==false) return false; 220 } 221 if (delta[i]>0) 222 { 223 // if I am on the boundary and domain is not periodic => no neighbor 224 if (coord[i]==_dims[i]-1 && periodic[i]==false) return false; 225 } 226 } 227 return true; 228 } 229 230 /** \brief partition the given grid onto the torus and return the piece of the process with given rank; returns load imbalance 231 * @param rank rank of our processor 232 * @param origin_in global origin 233 * @param size_in global size 234 * @param origin_out origin of this processors interior 235 * @param size_out size of this processors interior 236 */ partition(int rank,iTupel origin_in,iTupel size_in,iTupel & origin_out,iTupel & size_out) const237 double partition (int rank, iTupel origin_in, iTupel size_in, iTupel& origin_out, iTupel& size_out) const 238 { 239 iTupel coord = rank_to_coord(rank); 240 double maxsize = 1; 241 double sz = 1; 242 243 // make a tensor product partition 244 for (int i=0; i<d; i++) 245 { 246 // determine 247 int m = size_in[i]/_dims[i]; 248 int r = size_in[i]%_dims[i]; 249 250 sz *= size_in[i]; 251 252 if (coord[i]<_dims[i]-r) 253 { 254 origin_out[i] = origin_in[i] + coord[i]*m; 255 size_out[i] = m; 256 maxsize *= m; 257 } 258 else 259 { 260 origin_out[i] = origin_in[i] + (_dims[i]-r)*m + (coord[i]-(_dims[i]-r))*(m+1); 261 size_out[i] = m+1; 262 maxsize *= m+1; 263 } 264 } 265 return maxsize/(sz/_comm.size()); 266 } 267 268 /*! 269 ProcListIterator provides access to a list of neighboring processes. There are always 270 \f$ 3^d-1 \f$ entries in such a list. Two lists are maintained, one for sending and one for 271 receiving. The lists are sorted in such a way that in sequence message delivery ensures that 272 e.g. a message send to the left neighbor is received as a message from the right neighbor. 273 */ 274 class ProcListIterator { 275 public: 276 //! make an iterator ProcListIterator(typename std::deque<CommPartner>::const_iterator iter)277 ProcListIterator (typename std::deque<CommPartner>::const_iterator iter) 278 { 279 i = iter; 280 } 281 282 //! return rank of neighboring process rank() const283 int rank () const 284 { 285 return i->rank; 286 } 287 288 //! return distance vector delta() const289 iTupel delta () const 290 { 291 return i->delta; 292 } 293 294 //! return index in proclist index() const295 int index () const 296 { 297 return i->index; 298 } 299 300 //! return 1-norm of distance vector distance() const301 int distance () const 302 { 303 int dist = 0; 304 iTupel delta=i->delta; 305 for (int j=0; j<d; ++j) 306 dist += std::abs(delta[j]); 307 return dist; 308 } 309 310 //! Return true when two iterators point to same member operator ==(const ProcListIterator & iter) const311 bool operator== (const ProcListIterator& iter) const 312 { 313 return i == iter.i; 314 } 315 316 317 //! Return true when two iterators do not point to same member operator !=(const ProcListIterator & iter) const318 bool operator!= (const ProcListIterator& iter) const 319 { 320 return i != iter.i; 321 } 322 323 //! Increment iterator to next cell. operator ++()324 ProcListIterator& operator++ () 325 { 326 ++i; 327 return *this; 328 } 329 330 private: 331 typename std::deque<CommPartner>::const_iterator i; 332 }; 333 334 //! first process in send list sendbegin() const335 ProcListIterator sendbegin () const 336 { 337 return ProcListIterator(_sendlist.begin()); 338 } 339 340 //! end of send list sendend() const341 ProcListIterator sendend () const 342 { 343 return ProcListIterator(_sendlist.end()); 344 } 345 346 //! first process in receive list recvbegin() const347 ProcListIterator recvbegin () const 348 { 349 return ProcListIterator(_recvlist.begin()); 350 } 351 352 //! last process in receive list recvend() const353 ProcListIterator recvend () const 354 { 355 return ProcListIterator(_recvlist.end()); 356 } 357 358 //! store a send request; buffers are sent in order; handles also local requests with memcpy send(int rank,void * buffer,int size) const359 void send (int rank, void* buffer, int size) const 360 { 361 CommTask task; 362 task.rank = rank; 363 task.buffer = buffer; 364 task.size = size; 365 if (rank!=_comm.rank()) 366 _sendrequests.push_back(task); 367 else 368 _localsendrequests.push_back(task); 369 } 370 371 //! store a receive request; buffers are received in order; handles also local requests with memcpy recv(int rank,void * buffer,int size) const372 void recv (int rank, void* buffer, int size) const 373 { 374 CommTask task; 375 task.rank = rank; 376 task.buffer = buffer; 377 task.size = size; 378 if (rank!=_comm.rank()) 379 _recvrequests.push_back(task); 380 else 381 _localrecvrequests.push_back(task); 382 } 383 384 //! exchange messages stored in request buffers; clear request buffers afterwards exchange() const385 void exchange () const 386 { 387 // handle local requests first 388 if (_localsendrequests.size()!=_localrecvrequests.size()) 389 { 390 std::cout << "[" << rank() << "]: ERROR: local sends/receives do not match in exchange!" << std::endl; 391 return; 392 } 393 for (unsigned int i=0; i<_localsendrequests.size(); i++) 394 { 395 if (_localsendrequests[i].size!=_localrecvrequests[i].size) 396 { 397 std::cout << "[" << rank() << "]: ERROR: size in local sends/receive does not match in exchange!" << std::endl; 398 return; 399 } 400 memcpy(_localrecvrequests[i].buffer,_localsendrequests[i].buffer,_localsendrequests[i].size); 401 } 402 _localsendrequests.clear(); 403 _localrecvrequests.clear(); 404 405 #if HAVE_MPI 406 // handle foreign requests 407 408 std::vector<MPI_Request> requests(_sendrequests.size() + _recvrequests.size()); 409 MPI_Request* req = requests.data(); 410 411 // issue sends to foreign processes 412 for (unsigned int i=0; i<_sendrequests.size(); i++) 413 if (_sendrequests[i].rank!=rank()) 414 { 415 // std::cout << "[" << rank() << "]" << " send " << _sendrequests[i].size << " bytes " 416 // << "to " << _sendrequests[i].rank << " p=" << _sendrequests[i].buffer << std::endl; 417 MPI_Isend(_sendrequests[i].buffer, _sendrequests[i].size, MPI_BYTE, 418 _sendrequests[i].rank, _tag, _comm, req++); 419 } 420 421 // issue receives from foreign processes 422 for (unsigned int i=0; i<_recvrequests.size(); i++) 423 if (_recvrequests[i].rank!=rank()) 424 { 425 // std::cout << "[" << rank() << "]" << " recv " << _recvrequests[i].size << " bytes " 426 // << "fm " << _recvrequests[i].rank << " p=" << _recvrequests[i].buffer << std::endl; 427 MPI_Irecv(_recvrequests[i].buffer, _recvrequests[i].size, MPI_BYTE, 428 _recvrequests[i].rank, _tag, _comm, req++); 429 } 430 431 // Wait for communication to complete 432 MPI_Waitall(requests.size(), requests.data(), MPI_STATUSES_IGNORE); 433 434 // clear request buffers 435 _sendrequests.clear(); 436 _recvrequests.clear(); 437 #endif 438 } 439 440 //! global max global_max(double x) const441 double global_max (double x) const 442 { 443 double res = 0.0; 444 _comm.template allreduce<Dune::Max<double>,double>(&x, &res, 1); 445 return res; 446 } 447 448 //! print contents of torus object print(std::ostream & s) const449 void print (std::ostream& s) const 450 { 451 s << "[" << rank() << "]: Torus " << procs() << " processor(s) arranged as " << dims() << std::endl; 452 for (ProcListIterator i=sendbegin(); i!=sendend(); ++i) 453 { 454 s << "[" << rank() << "]: send to " 455 << "rank=" << i.rank() 456 << " index=" << i.index() 457 << " delta=" << i.delta() << " dist=" << i.distance() << std::endl; 458 } 459 for (ProcListIterator i=recvbegin(); i!=recvend(); ++i) 460 { 461 s << "[" << rank() << "]: recv from " 462 << "rank=" << i.rank() 463 << " index=" << i.index() 464 << " delta=" << i.delta() << " dist=" << i.distance() << std::endl; 465 } 466 } 467 468 private: 469 proclists()470 void proclists () 471 { 472 // compile the full neighbor list 473 CommPartner cp; 474 iTupel delta; 475 476 std::fill(delta.begin(), delta.end(), -1); 477 bool ready = false; 478 iTupel me, nb; 479 me = rank_to_coord(_comm.rank()); 480 int index = 0; 481 int last = neighbors()-1; 482 while (!ready) 483 { 484 // find neighbors coordinates 485 for (int i=0; i<d; i++) 486 nb[i] = ( me[i]+_dims[i]+delta[i] ) % _dims[i]; 487 488 // find neighbors rank 489 int nbrank = coord_to_rank(nb); 490 491 // check if delta is not zero 492 for (int i=0; i<d; i++) 493 if (delta[i]!=0) 494 { 495 cp.rank = nbrank; 496 cp.delta = delta; 497 cp.index = index; 498 _recvlist.push_back(cp); 499 cp.index = last-index; 500 _sendlist.push_front(cp); 501 index++; 502 break; 503 } 504 505 // next neighbor 506 ready = true; 507 for (int i=0; i<d; i++) 508 if (delta[i]<1) 509 { 510 (delta[i])++; 511 ready=false; 512 break; 513 } 514 else 515 { 516 delta[i] = -1; 517 } 518 } 519 520 } 521 522 CollectiveCommunication _comm; 523 524 iTupel _dims; 525 iTupel _increment; 526 int _tag; 527 std::deque<CommPartner> _sendlist; 528 std::deque<CommPartner> _recvlist; 529 530 mutable std::vector<CommTask> _sendrequests; 531 mutable std::vector<CommTask> _recvrequests; 532 mutable std::vector<CommTask> _localsendrequests; 533 mutable std::vector<CommTask> _localrecvrequests; 534 535 }; 536 537 //! Output operator for Torus 538 template <class CollectiveCommunication, int d> operator <<(std::ostream & s,const Torus<CollectiveCommunication,d> & t)539 inline std::ostream& operator<< (std::ostream& s, const Torus<CollectiveCommunication, d> & t) 540 { 541 t.print(s); 542 return s; 543 } 544 } 545 546 #endif 547