MFEM v4.10.0
Finite element discretization library
Loading...
Searching...
No Matches
communication.cpp
Go to the documentation of this file.
1// Copyright (c) 2010-2026, Lawrence Livermore National Security, LLC. Produced
2// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
3// LICENSE and NOTICE for details. LLNL-CODE-806117.
4//
5// This file is part of the MFEM library. For more information and source code
6// availability visit https://mfem.org.
7//
8// MFEM is free software; you can redistribute it and/or modify it under the
9// terms of the BSD-3 license. We welcome feedback and contributions, see file
10// CONTRIBUTING.md for details.
11
12#include "../config/config.hpp"
13
14#ifdef MFEM_USE_MPI
15
16#include <mpi.h>
17#ifdef __bgq__
18#include <mpix.h>
19#endif
20
21#include "array.hpp"
22#include "table.hpp"
23#include "sets.hpp"
24#include "communication.hpp"
25#include "device.hpp"
26#include "forall.hpp"
27#include "text.hpp"
28#include "sort_pairs.hpp"
29#include "globals.hpp"
30
31#ifdef MFEM_USE_STRUMPACK
32#include <StrumpackConfig.hpp> // STRUMPACK_USE_PTSCOTCH, etc.
33#endif
34
35#include <iostream>
36#include <map>
37#include <utility> // std::as_const
38
39using namespace std;
40
41namespace mfem
42{
43
44#if defined(MFEM_USE_STRUMPACK) && \
45 (defined(STRUMPACK_USE_PTSCOTCH) || defined(STRUMPACK_USE_SLATE_SCALAPACK))
46int Mpi::default_thread_required = MPI_THREAD_MULTIPLE;
47#else
48int Mpi::default_thread_required = MPI_THREAD_SINGLE;
49#endif
50
51
53 : MyComm(gt.MyComm),
54 group_lproc(gt.group_lproc)
55{
56 gt.groupmaster_lproc.Copy(groupmaster_lproc);
57 gt.lproc_proc.Copy(lproc_proc);
58 gt.group_mgroup.Copy(group_mgroup);
59}
60
61void GroupTopology::ProcToLProc()
62{
63 int NRanks;
64 MPI_Comm_size(MyComm, &NRanks);
65
66 map<int, int> proc_lproc;
67
68 // The local processor ids are assigned following the group order and within
69 // a group following their ordering in the group. In other words, the ids are
70 // assigned based on their order in the J array of group_lproc.
71 int lproc_counter = 0;
72 for (int i = 0; i < group_lproc.Size_of_connections(); i++)
73 {
74 const pair<const int, int> p(group_lproc.GetJ()[i], lproc_counter);
75 if (proc_lproc.insert(p).second)
76 {
77 lproc_counter++;
78 }
79 }
80 // Note: group_lproc.GetJ()[0] == MyRank --> proc_lproc[MyRank] == 0
81
82 lproc_proc.SetSize(lproc_counter);
83 for (map<int, int>::iterator it = proc_lproc.begin();
84 it != proc_lproc.end(); ++it)
85 {
86 lproc_proc[it->second] = it->first;
87 }
88
89 for (int i = 0; i < group_lproc.Size_of_connections(); i++)
90 {
91 group_lproc.GetJ()[i] = proc_lproc[group_lproc.GetJ()[i]];
92 }
93
94 for (int i = 0; i < NGroups(); i++)
95 {
96 groupmaster_lproc[i] = proc_lproc[groupmaster_lproc[i]];
97 }
98}
99
101{
102 groups.AsTable(group_lproc); // group_lproc = group_proc
103
104 Table group_mgroupandproc;
105 group_mgroupandproc.SetDims(NGroups(),
106 group_lproc.Size_of_connections() + NGroups());
107 for (int i = 0; i < NGroups(); i++)
108 {
109 int j = group_mgroupandproc.GetI()[i];
110 group_mgroupandproc.GetI()[i+1] = j + group_lproc.RowSize(i) + 1;
111 group_mgroupandproc.GetJ()[j] = i;
112 j++;
113 for (int k = group_lproc.GetI()[i];
114 j < group_mgroupandproc.GetI()[i+1]; j++, k++)
115 {
116 group_mgroupandproc.GetJ()[j] = group_lproc.GetJ()[k];
117 }
118 }
119
120 // build groupmaster_lproc with lproc = proc
121 groupmaster_lproc.SetSize(NGroups());
122
123 // simplest choice of the group owner
124 for (int i = 0; i < NGroups(); i++)
125 {
126 groupmaster_lproc[i] = groups.PickElementInSet(i);
127 }
128
129 // load-balanced choice of the group owner, which however can lead to
130 // isolated dofs
131 // for (i = 0; i < NGroups(); i++)
132 // groupmaster_lproc[i] = groups.PickRandomElementInSet(i);
133
134 ProcToLProc();
135
136 // Build 'group_mgroup':
137
138 // Use aggregated neighbor communication: at most one send to and/or one
139 // receive from each neighbor.
140
141 group_mgroup.SetSize(NGroups());
142 MFEM_DEBUG_DO(group_mgroup = -1);
143 for (int g = 0; g < NGroups(); g++)
144 {
145 if (IAmMaster(g)) { group_mgroup[g] = g; }
146 }
147
148 // The Table 'lproc_cgroup': for each lproc, list the groups that are owned
149 // by this rank or by that lproc.
150 Table lproc_cgroup;
151 {
152 Array<Connection> lproc_cgroup_list;
153 for (int g = 1; g < NGroups(); g++)
154 {
155 if (IAmMaster(g))
156 {
157 const int gs = GetGroupSize(g);
158 const int *lprocs = GetGroup(g);
159 for (int i = 0; i < gs; i++)
160 {
161 if (lprocs[i])
162 {
163 lproc_cgroup_list.Append(Connection(lprocs[i],g));
164 }
165 }
166 }
167 else
168 {
169 lproc_cgroup_list.Append(Connection(GetGroupMaster(g),g));
170 }
171 }
172 lproc_cgroup_list.Sort();
173 lproc_cgroup_list.Unique();
174 lproc_cgroup.MakeFromList(GetNumNeighbors(), lproc_cgroup_list);
175 }
176
177 // Determine size of the send-receive buffer. For each neighbor the buffer
178 // contains: <send-part><receive-part> with each part consisting of a list of
179 // groups. Each group, g, has group_lproc.RowSize(g)+2 integers: the first
180 // entry is group_lproc.RowSize(g) - the number of processors in the group,
181 // followed by the group-id in the master processor, followed by the ranks of
182 // the processors in the group.
183 Table buffer;
184 buffer.MakeI(2*lproc_cgroup.Size()-2); // excluding the "local" lproc, 0
185 for (int nbr = 1; nbr < lproc_cgroup.Size(); nbr++)
186 {
187 const int send_row = 2*(nbr-1);
188 const int recv_row = send_row+1;
189 const int ng = lproc_cgroup.RowSize(nbr);
190 const int *g = lproc_cgroup.GetRow(nbr);
191 for (int j = 0; j < ng; j++)
192 {
193 const int gs = group_lproc.RowSize(g[j]);
194 if (IAmMaster(g[j]))
195 {
196 buffer.AddColumnsInRow(send_row, gs+2);
197 }
198 else
199 {
200 MFEM_ASSERT(GetGroupMaster(g[j]) == nbr, "internal error");
201 buffer.AddColumnsInRow(recv_row, gs+2);
202 }
203 }
204 }
205 buffer.MakeJ();
206 for (int nbr = 1; nbr < lproc_cgroup.Size(); nbr++)
207 {
208 const int send_row = 2*(nbr-1);
209 const int recv_row = send_row+1;
210 const int ng = lproc_cgroup.RowSize(nbr);
211 const int *g = lproc_cgroup.GetRow(nbr);
212 for (int j = 0; j < ng; j++)
213 {
214 const int gs = group_lproc.RowSize(g[j]);
215 if (IAmMaster(g[j]))
216 {
217 buffer.AddConnection(send_row, gs);
218 buffer.AddConnections(
219 send_row, group_mgroupandproc.GetRow(g[j]), gs+1);
220 }
221 else
222 {
223 buffer.AddColumnsInRow(recv_row, gs+2);
224 }
225 }
226 }
227 buffer.ShiftUpI();
228 Array<MPI_Request> send_requests(lproc_cgroup.Size()-1);
229 Array<MPI_Request> recv_requests(lproc_cgroup.Size()-1);
230 send_requests = MPI_REQUEST_NULL;
231 recv_requests = MPI_REQUEST_NULL;
232 for (int nbr = 1; nbr < lproc_cgroup.Size(); nbr++)
233 {
234 const int send_row = 2*(nbr-1);
235 const int recv_row = send_row+1;
236 const int send_size = buffer.RowSize(send_row);
237 const int recv_size = buffer.RowSize(recv_row);
238 if (send_size > 0)
239 {
240 MPI_Isend(buffer.GetRow(send_row), send_size, MPI_INT, lproc_proc[nbr],
241 mpitag, MyComm, &send_requests[nbr-1]);
242 }
243 if (recv_size > 0)
244 {
245 MPI_Irecv(buffer.GetRow(recv_row), recv_size, MPI_INT, lproc_proc[nbr],
246 mpitag, MyComm, &recv_requests[nbr-1]);
247 }
248 }
249
250 if (recv_requests.Size() > 0)
251 {
252 int idx;
253 IntegerSet group;
254 while (MPI_Waitany(recv_requests.Size(), recv_requests.GetData(), &idx,
255 MPI_STATUS_IGNORE),
256 idx != MPI_UNDEFINED)
257 {
258 const int recv_size = buffer.RowSize(2*idx+1);
259 const int *recv_buf = buffer.GetRow(2*idx+1);
260 for (int s = 0; s < recv_size; s += recv_buf[s]+2)
261 {
262 group.Recreate(recv_buf[s], recv_buf+s+2);
263 const int g = groups.Lookup(group);
264 MFEM_ASSERT(group_mgroup[g] == -1, "communication error");
265 group_mgroup[g] = recv_buf[s+1];
266 }
267 }
268 }
269
270 MPI_Waitall(send_requests.Size(), send_requests.GetData(),
271 MPI_STATUSES_IGNORE);
272
273 // debug barrier: MPI_Barrier(MyComm);
274}
275
276void GroupTopology::Save(ostream &os) const
277{
278 os << "\ncommunication_groups\n";
279 os << "number_of_groups " << NGroups() << "\n\n";
280
281 os << "# number of entities in each group, followed by ranks in group\n";
282 for (int group_id = 0; group_id < NGroups(); ++group_id)
283 {
284 int group_size = GetGroupSize(group_id);
285 const int * group_ptr = GetGroup(group_id);
286 os << group_size;
287 for ( int group_member_index = 0; group_member_index < group_size;
288 ++group_member_index)
289 {
290 os << " " << GetNeighborRank( group_ptr[group_member_index] );
291 }
292 os << "\n";
293 }
294
295 // For future use, optional ownership strategy.
296 // os << "# ownership";
297}
298
299void GroupTopology::Load(istream &in)
300{
301 // Load in group topology and create list of integer sets. Use constructor
302 // that uses list of integer sets.
303 std::string ident;
304
305 // Read in number of groups
306 int number_of_groups = -1;
307 in >> ident;
308 MFEM_VERIFY(ident == "number_of_groups",
309 "GroupTopology::Load - expected 'number_of_groups' entry.");
310 in >> number_of_groups;
311
312 // Skip number of entries in each group comment.
313 skip_comment_lines(in, '#');
314
315 ListOfIntegerSets integer_sets;
316 for (int group_id = 0; group_id < number_of_groups; ++group_id)
317 {
318 IntegerSet integer_set;
319 Array<int>& array = integer_set;
320 int group_size;
321 in >> group_size;
322 array.Reserve(group_size);
323 for ( int index = 0; index < group_size; ++index )
324 {
325 int value;
326 in >> value;
327 array.Append(value);
328 }
329 integer_sets.Insert(integer_set);
330 }
331
332 Create(integer_sets, 823);
333}
334
336{
337 copy.SetComm(MyComm);
338 group_lproc.Copy(copy.group_lproc);
339 groupmaster_lproc.Copy(copy.groupmaster_lproc);
340 lproc_proc.Copy(copy.lproc_proc);
341 group_mgroup.Copy(copy.group_mgroup);
342}
343
345{
346 mfem::Swap(MyComm, other.MyComm);
347 mfem::Swap(group_lproc, other.group_lproc);
348 mfem::Swap(groupmaster_lproc, other.groupmaster_lproc);
349 mfem::Swap(lproc_proc, other.lproc_proc);
350 mfem::Swap(group_mgroup, other.group_mgroup);
351}
352
353/// \cond DO_NOT_DOCUMENT
354// Initialize the static mpi_type for the specializations of MPITypeMap:
355const MPI_Datatype MPITypeMap<bool>::mpi_type = MFEM_MPI_CXX_BOOL;
356const MPI_Datatype MPITypeMap<char>::mpi_type = MPI_CHAR;
357const MPI_Datatype MPITypeMap<unsigned char>::mpi_type = MPI_UNSIGNED_CHAR;
358const MPI_Datatype MPITypeMap<short>::mpi_type = MPI_SHORT;
359const MPI_Datatype MPITypeMap<unsigned short>::mpi_type = MPI_UNSIGNED_SHORT;
360const MPI_Datatype MPITypeMap<int>::mpi_type = MPI_INT;
361const MPI_Datatype MPITypeMap<unsigned int>::mpi_type = MPI_UNSIGNED;
362const MPI_Datatype MPITypeMap<long>::mpi_type = MPI_LONG;
363const MPI_Datatype MPITypeMap<unsigned long>::mpi_type = MPI_UNSIGNED_LONG;
364const MPI_Datatype MPITypeMap<long long>::mpi_type = MPI_LONG_LONG;
366 MPI_UNSIGNED_LONG_LONG;
367const MPI_Datatype MPITypeMap<float>::mpi_type = MPI_FLOAT;
368const MPI_Datatype MPITypeMap<double>::mpi_type = MPI_DOUBLE;
369/// \endcond DO_NOT_DOCUMENT
370
372 : gtopo(gt), mode(m)
373{
374 group_buf_size = 0;
375 requests = NULL;
376 // statuses = NULL;
377 comm_lock = 0;
378 num_requests = 0;
379 request_marker = NULL;
380 buf_offsets = NULL;
381 have_ltdof_ldof = false;
382 ldof_size = -1; // unknown ldof_size
383 device_gc = NULL;
384}
385
387{
388 MFEM_VERIFY(buf_offsets == nullptr,
389 "the GroupCommunicator is already Finalized!");
390
392 for (int i = 0; i < ldof_group.Size(); i++)
393 {
394 int group = ldof_group[i];
395 if (group != 0)
396 {
398 }
399 }
401
402 for (int i = 0; i < ldof_group.Size(); i++)
403 {
404 int group = ldof_group[i];
405 if (group != 0)
406 {
407 group_ldof.AddConnection(group, i);
408 }
409 }
411
412 Finalize();
413}
414
416{
417 if (buf_offsets) { return; } // Finalize() was already called.
418
419 int request_counter = 0;
420
421 // size buf_offsets = max(number of groups, number of neighbors)
422 buf_offsets = new int[max(group_ldof.Size(), gtopo.GetNumNeighbors())];
423 buf_offsets[0] = 0;
424 for (int gr = 1; gr < group_ldof.Size(); gr++)
425 {
426 if (group_ldof.RowSize(gr) != 0)
427 {
428 int gr_requests;
429 if (!gtopo.IAmMaster(gr)) // we are not the master
430 {
431 gr_requests = 1;
432 }
433 else
434 {
435 gr_requests = gtopo.GetGroupSize(gr)-1;
436 }
437
438 request_counter += gr_requests;
439 group_buf_size += gr_requests * group_ldof.RowSize(gr);
440 }
441 }
442
443 requests = new MPI_Request[request_counter];
444 // statuses = new MPI_Status[request_counter];
445 request_marker = new int[request_counter];
446
447 // Construct nbr_send_groups and nbr_recv_groups: (nbr 0 = me)
450 for (int gr = 1; gr < group_ldof.Size(); gr++)
451 {
452 const int nldofs = group_ldof.RowSize(gr);
453 if (nldofs == 0) { continue; }
454
455 if (!gtopo.IAmMaster(gr)) // we are not the master
456 {
458 }
459 else // we are the master
460 {
461 const int grp_size = gtopo.GetGroupSize(gr);
462 const int *grp_nbr_list = gtopo.GetGroup(gr);
463 for (int i = 0; i < grp_size; i++)
464 {
465 if (grp_nbr_list[i] != 0)
466 {
467 nbr_send_groups.AddAColumnInRow(grp_nbr_list[i]);
468 }
469 }
470 }
471 }
474 for (int gr = 1; gr < group_ldof.Size(); gr++)
475 {
476 const int nldofs = group_ldof.RowSize(gr);
477 if (nldofs == 0) { continue; }
478
479 if (!gtopo.IAmMaster(gr)) // we are not the master
480 {
482 }
483 else // we are the master
484 {
485 const int grp_size = gtopo.GetGroupSize(gr);
486 const int *grp_nbr_list = gtopo.GetGroup(gr);
487 for (int i = 0; i < grp_size; i++)
488 {
489 if (grp_nbr_list[i] != 0)
490 {
491 nbr_send_groups.AddConnection(grp_nbr_list[i], gr);
492 }
493 }
494 }
495 }
498 // The above construction creates the Tables with the column indices
499 // sorted, i.e. the group lists are sorted. To coordinate this order between
500 // processors, we will sort the group lists in the nbr_recv_groups Table
501 // according to their indices in the master. This does not require any
502 // communication because we have access to the group indices in the master
503 // by calling: master_group_id = gtopo.GetGroupMasterGroup(my_group_id).
504 Array<Pair<int,int> > group_ids;
505 for (int nbr = 1; nbr < nbr_recv_groups.Size(); nbr++)
506 {
507 const int num_recv_groups = nbr_recv_groups.RowSize(nbr);
508 if (num_recv_groups > 0)
509 {
510 int *grp_list = nbr_recv_groups.GetRow(nbr);
511 group_ids.SetSize(num_recv_groups);
512 for (int i = 0; i < num_recv_groups; i++)
513 {
514 group_ids[i].one = gtopo.GetGroupMasterGroup(grp_list[i]);
515 group_ids[i].two = grp_list[i]; // my_group_id
516 }
517 group_ids.Sort();
518 for (int i = 0; i < num_recv_groups; i++)
519 {
520 grp_list[i] = group_ids[i].two;
521 }
522 }
523 }
524}
525
527{
528 MFEM_VERIFY(!have_ltdof_ldof,
529 "SetLTDofTable() should be called at most once!");
530
532 for (int gr = 1; gr < group_ldof.Size(); gr++)
533 {
534 if (gtopo.IAmMaster(gr))
535 {
537 }
538 }
540 for (int gr = 1; gr < group_ldof.Size(); gr++)
541 {
542 if (gtopo.IAmMaster(gr))
543 {
544 const int *ldofs = group_ldof.GetRow(gr);
545 const int nldofs = group_ldof.RowSize(gr);
546 for (int i = 0; i < nldofs; i++)
547 {
548 group_ltdof.AddConnection(gr, ldof_ltdof[ldofs[i]]);
549 }
550 }
551 }
553
554 ldof_size = ldof_ltdof.Size();
555 const int ltdof_size = (ldof_size == 0) ? 0 :
556 std::max(ldof_ltdof.Max() + 1, 0);
557 ltdof_ldof.SetSize(ltdof_size);
558#ifdef MFEM_DEBUG
559 int ltdof_counter = 0;
560 ltdof_ldof = -1;
561#endif
562 for (int ldof = 0; ldof < ldof_ltdof.Size(); ldof++)
563 {
564 const int ltdof = ldof_ltdof[ldof];
565 if (ltdof >= 0)
566 {
567#ifdef MFEM_DEBUG
568 ltdof_counter++;
569#endif
570 MFEM_ASSERT(ltdof_ldof[ltdof] == -1, "repeated ltdof indices found!");
571 ltdof_ldof[ltdof] = ldof;
572 }
573 }
574 MFEM_ASSERT(ltdof_counter == ltdof_size, "unassigned ltdof indices found!");
575 have_ltdof_ldof = true;
576}
577
578namespace internal
579{
580
581static void BuildNeighborDofTable(const Table &group_dof,
582 const Table &nbr_groups,
583 Table &nbr_dof)
584{
585 nbr_dof.MakeI(nbr_groups.Size());
586 for (int nbr = 1; nbr < nbr_groups.Size(); nbr++)
587 {
588 const int num_groups = nbr_groups.RowSize(nbr);
589 if (num_groups == 0) { continue; }
590 const int *grp_list = nbr_groups.GetRow(nbr);
591 for (int i = 0; i < num_groups; i++)
592 {
593 const int group = grp_list[i];
594 const int ndofs = group_dof.RowSize(group);
595 nbr_dof.AddColumnsInRow(nbr, ndofs);
596 }
597 }
598 nbr_dof.MakeJ();
599 for (int nbr = 1; nbr < nbr_groups.Size(); nbr++)
600 {
601 const int num_groups = nbr_groups.RowSize(nbr);
602 if (num_groups == 0) { continue; }
603 const int *grp_list = nbr_groups.GetRow(nbr);
604 for (int i = 0; i < num_groups; i++)
605 {
606 const int group = grp_list[i];
607 const int ndofs = group_dof.RowSize(group);
608 const int *dofs = group_dof.GetRow(group);
609 nbr_dof.AddConnections(nbr, dofs, ndofs);
610 }
611 }
612 nbr_dof.ShiftUpI();
613}
614
615} // namespace internal
616
618{
619 internal::BuildNeighborDofTable(group_ltdof, nbr_send_groups, nbr_ltdof);
620}
621
623{
624 internal::BuildNeighborDofTable(group_ldof, nbr_recv_groups, nbr_ldof);
625}
626
628{
629 if (!device_gc)
630 {
631 // The ctor of DeviceGroupCommunicator verifies that the GroupCommunicator
632 // meets all requirements: mode == byNeighbor and have_ltdof_ldof == true.
634 }
635 return *device_gc;
636}
637
638template <class T>
639T *GroupCommunicator::CopyGroupToBuffer(const T *ldata, T *buf, int group,
640 int layout) const
641{
642 switch (layout)
643 {
644 case 1:
645 {
646 return std::copy(ldata + group_ldof.GetI()[group],
647 ldata + group_ldof.GetI()[group+1],
648 buf);
649 }
650 case 2:
651 {
652 const int nltdofs = group_ltdof.RowSize(group);
653 const int *ltdofs = group_ltdof.GetRow(group);
654 for (int j = 0; j < nltdofs; j++)
655 {
656 buf[j] = ldata[ltdofs[j]];
657 }
658 return buf + nltdofs;
659 }
660 default:
661 {
662 const int nldofs = group_ldof.RowSize(group);
663 const int *ldofs = group_ldof.GetRow(group);
664 for (int j = 0; j < nldofs; j++)
665 {
666 buf[j] = ldata[ldofs[j]];
667 }
668 return buf + nldofs;
669 }
670 }
671}
672
673template <class T>
674const T *GroupCommunicator::CopyGroupFromBuffer(const T *buf, T *ldata,
675 int group, int layout) const
676{
677 const int nldofs = group_ldof.RowSize(group);
678 switch (layout)
679 {
680 case 1:
681 {
682 std::copy(buf, buf + nldofs, ldata + group_ldof.GetI()[group]);
683 break;
684 }
685 case 2:
686 {
687 const int *ltdofs = group_ltdof.GetRow(group);
688 for (int j = 0; j < nldofs; j++)
689 {
690 ldata[ltdofs[j]] = buf[j];
691 }
692 break;
693 }
694 default:
695 {
696 const int *ldofs = group_ldof.GetRow(group);
697 for (int j = 0; j < nldofs; j++)
698 {
699 ldata[ldofs[j]] = buf[j];
700 }
701 break;
702 }
703 }
704 return buf + nldofs;
705}
706
707template <class T>
708const T *GroupCommunicator::ReduceGroupFromBuffer(const T *buf, T *ldata,
709 int group, int layout,
710 void (*Op)(OpData<T>)) const
711{
712 OpData<T> opd;
713 opd.ldata = ldata;
714 opd.nldofs = group_ldof.RowSize(group);
715 opd.nb = 1;
716 opd.buf = const_cast<T*>(buf);
717
718 switch (layout)
719 {
720 case 1:
721 {
722 MFEM_ABORT("layout 1 is not supported");
723 T *dest = ldata + group_ldof.GetI()[group];
724 for (int j = 0; j < opd.nldofs; j++)
725 {
726 dest[j] += buf[j];
727 }
728 break;
729 }
730 case 2:
731 {
732 opd.ldofs = const_cast<int*>(group_ltdof.GetRow(group));
733 Op(opd);
734 break;
735 }
736 default:
737 {
738 opd.ldofs = const_cast<int*>(group_ldof.GetRow(group));
739 Op(opd);
740 break;
741 }
742 }
743 return buf + opd.nldofs;
744}
745
746template <class T>
747void GroupCommunicator::BcastBegin(T *ldata, int layout) const
748{
749 MFEM_VERIFY(comm_lock == 0, "object is already in use");
750 MFEM_ASSERT(0 <= layout && layout <= 2, "invalid layout: " << layout);
751
752 if (group_buf_size == 0)
753 {
754 comm_lock = 1; // 1 - locked for Bcast
755 return;
756 }
757
758 int request_counter = 0;
759 switch (mode)
760 {
761 case byGroup: // ***** Communication by groups *****
762 {
763 T *buf;
764 if (layout != 1)
765 {
767 buf = (T *)group_buf.GetData();
768 MFEM_VERIFY(layout != 2 || group_ltdof.Size() == group_ldof.Size(),
769 "'group_ltdof' is not set, use SetLTDofTable()");
770 }
771 else
772 {
773 buf = ldata;
774 }
775
776 for (int gr = 1; gr < group_ldof.Size(); gr++)
777 {
778 const int nldofs = group_ldof.RowSize(gr);
779
780 // ignore groups without dofs
781 if (nldofs == 0) { continue; }
782
783 if (!gtopo.IAmMaster(gr)) // we are not the master
784 {
785 MPI_Irecv(buf,
786 nldofs,
789 40822 + gtopo.GetGroupMasterGroup(gr),
790 gtopo.GetComm(),
791 &requests[request_counter]);
792 request_marker[request_counter] = gr;
793 request_counter++;
794 }
795 else // we are the master
796 {
797 if (layout != 1)
798 {
799 CopyGroupToBuffer(ldata, buf, gr, layout);
800 }
801 const int gs = gtopo.GetGroupSize(gr);
802 const int *nbs = gtopo.GetGroup(gr);
803 for (int i = 0; i < gs; i++)
804 {
805 if (nbs[i] != 0)
806 {
807 MPI_Isend(buf,
808 nldofs,
810 gtopo.GetNeighborRank(nbs[i]),
811 40822 + gtopo.GetGroupMasterGroup(gr),
812 gtopo.GetComm(),
813 &requests[request_counter]);
814 request_marker[request_counter] = -1; // mark as send req.
815 request_counter++;
816 }
817 }
818 }
819 buf += nldofs;
820 }
821 break;
822 }
823
824 case byNeighbor: // ***** Communication by neighbors *****
825 {
827 T *buf = (T *)group_buf.GetData();
828 for (int nbr = 1; nbr < nbr_send_groups.Size(); nbr++)
829 {
830 const int num_send_groups = nbr_send_groups.RowSize(nbr);
831 if (num_send_groups > 0)
832 {
833 // Possible optimization:
834 // if (num_send_groups == 1) and (layout == 1) then we do not
835 // need to copy the data in order to send it.
836 T *buf_start = buf;
837 const int *grp_list = nbr_send_groups.GetRow(nbr);
838 for (int i = 0; i < num_send_groups; i++)
839 {
840 buf = CopyGroupToBuffer(ldata, buf, grp_list[i], layout);
841 }
842 MPI_Isend(buf_start,
843 buf - buf_start,
846 40822,
847 gtopo.GetComm(),
848 &requests[request_counter]);
849 request_marker[request_counter] = -1; // mark as send request
850 request_counter++;
851 }
852
853 const int num_recv_groups = nbr_recv_groups.RowSize(nbr);
854 if (num_recv_groups > 0)
855 {
856 // Possible optimization (requires interface change):
857 // if (num_recv_groups == 1) and the (output layout == 1) then
858 // we can receive directly in the output buffer; however, at
859 // this point we do not have that information.
860 const int *grp_list = nbr_recv_groups.GetRow(nbr);
861 int recv_size = 0;
862 for (int i = 0; i < num_recv_groups; i++)
863 {
864 recv_size += group_ldof.RowSize(grp_list[i]);
865 }
866 MPI_Irecv(buf,
867 recv_size,
870 40822,
871 gtopo.GetComm(),
872 &requests[request_counter]);
873 request_marker[request_counter] = nbr;
874 request_counter++;
875 buf_offsets[nbr] = buf - (T*)group_buf.GetData();
876 buf += recv_size;
877 }
878 }
879 MFEM_ASSERT(buf - (T*)group_buf.GetData() == group_buf_size, "");
880 break;
881 }
882 }
883
884 comm_lock = 1; // 1 - locked for Bcast
885 num_requests = request_counter;
886}
887
888template <class T>
889void GroupCommunicator::BcastBegin(Array<T> &ldata, int layout) const
890{
891 MFEM_VERIFY(comm_lock == 0, "object is already in use");
892 MFEM_ASSERT(0 <= layout && layout <= 2, "invalid layout: " << layout);
893#ifdef MFEM_DEBUG
894 // for layouts 0 and 2, ldata_size is known only when have_ltdof_ldof is true
895 if (layout == 1 || have_ltdof_ldof)
896 {
897 // FIXME: Currently, this check causes a failure in the unit test
898 // "Parallel Variable Order FiniteElementSpace" "Quad mesh"
899 // Re-enable this check when the issue is fixed.
900
901 // const int ldata_size = layout == 0 ? ldof_size :
902 // layout == 1 ? group_ldof.Size_of_connections() :
903 // ltdof_ldof.Size();
904 // MFEM_ASSERT(ldata.Size() == ldata_size, "invalid 'ldata' size");
905 }
906#endif
907
908 if (group_buf_size == 0)
909 {
910 comm_lock = 1; // 1 - locked for Bcast
911 return;
912 }
913
914 // Use 'while' instead of 'if' so that we can break out without using 'goto'.
915 while (ldata.UseDevice() &&
918 mode == byNeighbor)
919 {
920 if (layout == 0) // input is ldofs array
921 {
923 }
924 else if (layout == 2) // input is ltdofs array
925 {
927 }
928 else
929 {
930 break;
931 }
932 comm_lock = 1; // 1 - locked for Bcast
933 return;
934 }
935
936 // Call the host version of this method with the data moved to host
937 BcastBegin(ldata.HostReadWrite(), layout);
938 // comm_lock is set by the above call
939}
940
941template <class T>
942void GroupCommunicator::BcastEnd(T *ldata, int layout) const
943{
944 // Is there a real case where we want to allow BcastEnd without corresponding
945 // BcastBegin?
946 // if (comm_lock == 0) { return; }
947
948 MFEM_VERIFY(comm_lock == 1, "object is NOT locked for Bcast");
949 MFEM_ASSERT(layout == 0 || layout == 1, "invalid layout: " << layout);
950
951 if (group_buf_size == 0)
952 {
953 comm_lock = 0; // 0 - no lock
954 return;
955 }
956
957 switch (mode)
958 {
959 case byGroup: // ***** Communication by groups *****
960 {
961 if (layout == 1)
962 {
963 MPI_Waitall(num_requests, requests, MPI_STATUSES_IGNORE);
964 }
965 else if (layout == 0)
966 {
967 // copy the received data from the buffer to ldata, as it arrives
968 int idx;
969 while (MPI_Waitany(num_requests, requests, &idx, MPI_STATUS_IGNORE),
970 idx != MPI_UNDEFINED)
971 {
972 int gr = request_marker[idx];
973 if (gr == -1) { continue; } // skip send requests
974
975 // groups without dofs are skipped, so here nldofs > 0.
976 T *buf = (T *)group_buf.GetData() + group_ldof.GetI()[gr];
977 CopyGroupFromBuffer(buf, ldata, gr, layout);
978 }
979 }
980 break;
981 }
982
983 case byNeighbor: // ***** Communication by neighbors *****
984 {
985 // copy the received data from the buffer to ldata, as it arrives
986 int idx;
987 while (MPI_Waitany(num_requests, requests, &idx, MPI_STATUS_IGNORE),
988 idx != MPI_UNDEFINED)
989 {
990 int nbr = request_marker[idx];
991 if (nbr == -1) { continue; } // skip send requests
992
993 const int num_recv_groups = nbr_recv_groups.RowSize(nbr);
994 if (num_recv_groups > 0)
995 {
996 const int *grp_list = nbr_recv_groups.GetRow(nbr);
997 const T *buf = (T*)group_buf.GetData() + buf_offsets[nbr];
998 for (int i = 0; i < num_recv_groups; i++)
999 {
1000 buf = CopyGroupFromBuffer(buf, ldata, grp_list[i], layout);
1001 }
1002 }
1003 }
1004 break;
1005 }
1006 }
1007
1008 comm_lock = 0; // 0 - no lock
1009 num_requests = 0;
1010}
1011
1012template <class T>
1013void GroupCommunicator::BcastEnd(Array<T> &ldata, int layout) const
1014{
1015 MFEM_VERIFY(comm_lock == 1, "object is NOT locked for Bcast");
1016 MFEM_ASSERT(layout == 0 || layout == 1, "invalid layout: " << layout);
1017#ifdef MFEM_DEBUG
1018 // for layouts 0 and 2, ldata_size is known only when have_ltdof_ldof is true
1019 if (layout == 1 || have_ltdof_ldof)
1020 {
1021 // FIXME: Currently, this check causes a failure in the unit test
1022 // "Parallel Variable Order FiniteElementSpace" "Quad mesh"
1023 // Re-enable this check when the issue is fixed.
1024
1025 // const int ldata_size = layout == 0 ? ldof_size :
1026 // group_ldof.Size_of_connections();
1027 // MFEM_ASSERT(ldata.Size() == ldata_size, "invalid 'ldata' size");
1028 }
1029#endif
1030
1031 if (group_buf_size == 0)
1032 {
1033 comm_lock = 0; // 0 - no lock
1034 return;
1035 }
1036
1037 if (ldata.UseDevice() &&
1040 mode == byNeighbor &&
1041 layout == 0) // output is ldofs array
1042 {
1044 comm_lock = 0; // 0 - no lock
1045 return;
1046 }
1047
1048 // call the host version of this method with the data moved to host
1049 BcastEnd(ldata.HostReadWrite(), layout);
1050 // comm_lock is set by the above call
1051}
1052
1053template <class T>
1054void GroupCommunicator::ReduceBegin(const T *ldata) const
1055{
1056 MFEM_VERIFY(comm_lock == 0, "object is already in use");
1057
1058 if (group_buf_size == 0)
1059 {
1060 comm_lock = 2; // 2 - locked for Reduce
1061 return;
1062 }
1063
1064 // Set the reduce_op to nullptr -- unknown reduce operation
1065 reduce_op = nullptr;
1066
1067 int request_counter = 0;
1068 group_buf.SetSize(group_buf_size*sizeof(T));
1069 T *buf = (T *)group_buf.GetData();
1070 switch (mode)
1071 {
1072 case byGroup: // ***** Communication by groups *****
1073 {
1074 for (int gr = 1; gr < group_ldof.Size(); gr++)
1075 {
1076 const int nldofs = group_ldof.RowSize(gr);
1077 // ignore groups without dofs
1078 if (nldofs == 0) { continue; }
1079
1080 if (!gtopo.IAmMaster(gr)) // we are not the master
1081 {
1082 const int layout = 0;
1083 CopyGroupToBuffer(ldata, buf, gr, layout);
1084 MPI_Isend(buf,
1085 nldofs,
1088 43822 + gtopo.GetGroupMasterGroup(gr),
1089 gtopo.GetComm(),
1090 &requests[request_counter]);
1091 request_marker[request_counter] = -1; // mark as send request
1092 request_counter++;
1093 buf += nldofs;
1094 }
1095 else // we are the master
1096 {
1097 const int gs = gtopo.GetGroupSize(gr);
1098 const int *nbs = gtopo.GetGroup(gr);
1099 buf_offsets[gr] = buf - (T *)group_buf.GetData();
1100 for (int i = 0; i < gs; i++)
1101 {
1102 if (nbs[i] != 0)
1103 {
1104 MPI_Irecv(buf,
1105 nldofs,
1107 gtopo.GetNeighborRank(nbs[i]),
1108 43822 + gtopo.GetGroupMasterGroup(gr),
1109 gtopo.GetComm(),
1110 &requests[request_counter]);
1111 request_marker[request_counter] = gr;
1112 request_counter++;
1113 buf += nldofs;
1114 }
1115 }
1116 }
1117 }
1118 break;
1119 }
1120
1121 case byNeighbor: // ***** Communication by neighbors *****
1122 {
1123 for (int nbr = 1; nbr < nbr_send_groups.Size(); nbr++)
1124 {
1125 // In Reduce operation: send_groups <--> recv_groups
1126 const int num_send_groups = nbr_recv_groups.RowSize(nbr);
1127 if (num_send_groups > 0)
1128 {
1129 T *buf_start = buf;
1130 const int *grp_list = nbr_recv_groups.GetRow(nbr);
1131 for (int i = 0; i < num_send_groups; i++)
1132 {
1133 const int layout = 0; // ldata is an array on all ldofs
1134 buf = CopyGroupToBuffer(ldata, buf, grp_list[i], layout);
1135 }
1136 MPI_Isend(buf_start,
1137 buf - buf_start,
1140 43822,
1141 gtopo.GetComm(),
1142 &requests[request_counter]);
1143 request_marker[request_counter] = -1; // mark as send request
1144 request_counter++;
1145 }
1146
1147 // In Reduce operation: send_groups <--> recv_groups
1148 const int num_recv_groups = nbr_send_groups.RowSize(nbr);
1149 if (num_recv_groups > 0)
1150 {
1151 const int *grp_list = nbr_send_groups.GetRow(nbr);
1152 int recv_size = 0;
1153 for (int i = 0; i < num_recv_groups; i++)
1154 {
1155 recv_size += group_ldof.RowSize(grp_list[i]);
1156 }
1157 MPI_Irecv(buf,
1158 recv_size,
1161 43822,
1162 gtopo.GetComm(),
1163 &requests[request_counter]);
1164 request_marker[request_counter] = nbr;
1165 request_counter++;
1166 buf_offsets[nbr] = buf - (T*)group_buf.GetData();
1167 buf += recv_size;
1168 }
1169 }
1170 MFEM_ASSERT(buf - (T*)group_buf.GetData() == group_buf_size, "");
1171 break;
1172 }
1173 }
1174
1175 comm_lock = 2; // 2 - locked for Reduce
1176 num_requests = request_counter;
1177}
1178
1179template <typename T>
1180static inline bool OpIsSupportedDeviceOp(
1181 void (*Op)(GroupCommunicator::OpData<T>),
1182 DeviceGroupCommunicator::Op &device_op)
1183{
1184 // Materialize typed function pointers before comparison so stricter
1185 // GPU toolchains do not have to resolve overloaded template names
1186 // here.
1187 using OpFunc = void (*)(GroupCommunicator::OpData<T>);
1188 const OpFunc sum_op = &GroupCommunicator::template Sum<T>;
1189 const OpFunc min_op = &GroupCommunicator::template Min<T>;
1190 const OpFunc max_op = &GroupCommunicator::template Max<T>;
1191 if (Op == sum_op)
1192 {
1194 }
1195 else if (Op == min_op)
1196 {
1198 }
1199 else if (Op == max_op)
1200 {
1202 }
1203 else
1204 {
1205 return false; // Op is not supported on device
1206 }
1207 return true; // Op is supported on device
1208}
1209
1210template <class T>
1212 void (*Op)(OpData<T>)) const
1213{
1214 MFEM_VERIFY(comm_lock == 0, "object is already in use");
1215 // layout is 0
1216#ifdef MFEM_DEBUG
1217 if (ldof_size >= 0) // ldof_size is -1 when it is unknown
1218 {
1219 // FIXME: Currently, this check causes a failure in the unit test
1220 // "Parallel Variable Order FiniteElementSpace" "Quad mesh"
1221 // Re-enable this check when the issue is fixed.
1222
1223 // MFEM_ASSERT(ldata.Size() == ldof_size, "invalid 'ldata' size");
1224 }
1225#endif
1226
1227 // Store the provided Op for inspection in ReduceEnd().
1228 reduce_op = reinterpret_cast<decltype(reduce_op)>(Op);
1229
1230 if (group_buf_size == 0)
1231 {
1232 comm_lock = 2; // 2 - locked for Reduce
1233 return;
1234 }
1235
1236 if (ldata.UseDevice() &&
1239 mode == byNeighbor)
1240 {
1241 DeviceGroupCommunicator::Op device_op{};
1242 if (Op == nullptr || OpIsSupportedDeviceOp(Op, device_op))
1243 {
1245 comm_lock = 2; // 2 - locked for Reduce
1246 return;
1247 }
1248 }
1249
1250 // call the host version of this method with the data copied to host
1251 ReduceBegin(ldata.HostRead());
1252 // comm_lock is set by the above call
1253 // reduce_op is set to nullptr by the above call -- restore its value:
1254 reduce_op = reinterpret_cast<decltype(reduce_op)>(Op);
1255}
1256
1257template <class T>
1258void GroupCommunicator::ReduceEnd(T *ldata, int layout,
1259 void (*Op)(OpData<T>)) const
1260{
1261 // Is there a real case where we want to allow ReduceEnd without
1262 // corresponding ReduceBegin?
1263 // if (comm_lock == 0) { return; }
1264
1265 MFEM_VERIFY(comm_lock == 2, "object is NOT locked for Reduce");
1266 MFEM_ASSERT(layout == 0 || layout == 2, "invalid layout: " << layout);
1267
1268 if (group_buf_size == 0)
1269 {
1270 comm_lock = 0; // 0 - no lock
1271 return;
1272 }
1273
1274 switch (mode)
1275 {
1276 case byGroup: // ***** Communication by groups *****
1277 {
1278 OpData<T> opd;
1279 opd.ldata = ldata;
1280 Array<int> group_num_req(group_ldof.Size());
1281 for (int gr = 1; gr < group_ldof.Size(); gr++)
1282 {
1283 group_num_req[gr] =
1284 gtopo.IAmMaster(gr) ? gtopo.GetGroupSize(gr)-1 : 0;
1285 }
1286 int idx;
1287 while (MPI_Waitany(num_requests, requests, &idx, MPI_STATUS_IGNORE),
1288 idx != MPI_UNDEFINED)
1289 {
1290 int gr = request_marker[idx];
1291 if (gr == -1) { continue; } // skip send requests
1292
1293 // Delay the processing of a group until all receive requests, for
1294 // that group, are done:
1295 if ((--group_num_req[gr]) != 0) { continue; }
1296
1297 opd.nldofs = group_ldof.RowSize(gr);
1298 // groups without dofs are skipped, so here nldofs > 0.
1299
1300 opd.buf = (T *)group_buf.GetData() + buf_offsets[gr];
1301 opd.ldofs = (layout == 0) ?
1303 opd.nb = gtopo.GetGroupSize(gr)-1;
1304 Op(opd);
1305 }
1306 break;
1307 }
1308
1309 case byNeighbor: // ***** Communication by neighbors *****
1310 {
1311 MPI_Waitall(num_requests, requests, MPI_STATUSES_IGNORE);
1312
1313 for (int nbr = 1; nbr < nbr_send_groups.Size(); nbr++)
1314 {
1315 // In Reduce operation: send_groups <--> recv_groups
1316 const int num_recv_groups = nbr_send_groups.RowSize(nbr);
1317 if (num_recv_groups > 0)
1318 {
1319 const int *grp_list = nbr_send_groups.GetRow(nbr);
1320 const T *buf = (T*)group_buf.GetData() + buf_offsets[nbr];
1321 for (int i = 0; i < num_recv_groups; i++)
1322 {
1323 buf = ReduceGroupFromBuffer(buf, ldata, grp_list[i],
1324 layout, Op);
1325 }
1326 }
1327 }
1328 break;
1329 }
1330 }
1331
1332 comm_lock = 0; // 0 - no lock
1333 num_requests = 0;
1334}
1335
1336template <class T>
1338 void (*Op)(OpData<T>)) const
1339{
1340 MFEM_VERIFY(comm_lock == 2, "object is NOT locked for Reduce");
1341 MFEM_ASSERT(layout == 0 || layout == 2, "invalid layout: " << layout);
1342#ifdef MFEM_DEBUG
1343 // for layouts 0 and 2, ldata_size is known only when have_ltdof_ldof is true
1344 if (have_ltdof_ldof)
1345 {
1346 // FIXME: Currently, this check causes a failure in the unit test
1347 // "Parallel Variable Order FiniteElementSpace" "Quad mesh"
1348 // Re-enable this check when the issue is fixed.
1349
1350 // const int ldata_size = layout == 0 ? ldof_size : ltdof_ldof.Size();
1351 // MFEM_ASSERT(ldata.Size() == ldata_size, "invalid 'ldata' size");
1352 }
1353#endif
1354
1355 if (group_buf_size == 0)
1356 {
1357 comm_lock = 0; // 0 - no lock
1358 return;
1359 }
1360
1361 if (ldata.UseDevice() &&
1364 mode == byNeighbor)
1365 {
1366 auto BeginOp = reinterpret_cast<void(*)(OpData<T>)>(reduce_op);
1367 MFEM_VERIFY(BeginOp == nullptr || BeginOp == Op,
1368 "the reduction operations given to ReduceBegin() and "
1369 "ReduceEnd() do not match!");
1370 DeviceGroupCommunicator::Op device_op{};
1371 const bool op_is_supported_device_op =
1372 OpIsSupportedDeviceOp(Op, device_op);
1373 // The primal definition of 'reduce_began_on_device' is:
1374 // (BeginOp == nullptr) || OpIsSupportedDeviceOp(BeginOp, device_op)
1375 // However, due to the above MFEM_VERIFY, this is equivalent to the
1376 // expression used below.
1377 const bool reduce_began_on_device =
1378 (BeginOp == nullptr) || op_is_supported_device_op;
1379 if (reduce_began_on_device)
1380 {
1381 MFEM_VERIFY(op_is_supported_device_op,
1382 "the reduce operation 'Op' is not supported on device!"
1383 "\n\tTo resolve this error, provide 'Op' to ReduceBegin() "
1384 "in the second argument.");
1385 if (layout == 0) // output is ldofs array
1386 {
1387 GetDeviceComm().ReduceEndLDofs(ldata, device_op);
1388 }
1389 else // layout == 2 -- output is ltdofs array
1390 {
1391 GetDeviceComm().ReduceEndTDofs(ldata, device_op);
1392 }
1393 comm_lock = 0; // 0 - no lock
1394 return;
1395 }
1396 }
1397
1398 // call the host version of this method with the data moved to host
1399 ReduceEnd(ldata.HostReadWrite(), layout, Op);
1400 // comm_lock is set by the above call
1401}
1402
1403template <class T>
1404void GroupCommunicator::ReduceMarked(T *ldata, const Array<int> &marker,
1405 int layout,
1406 void (*Op)(OpData<T>)) const
1407{
1408 if (comm_lock == 0) { return; }
1409 // The above also handles the case (group_buf_size == 0).
1410 MFEM_VERIFY(comm_lock == 2, "object is NOT locked for Reduce");
1411
1412 switch (mode)
1413 {
1414 case byGroup: // ***** Communication by groups *****
1415 {
1416 OpData<T> opd;
1417 opd.ldata = ldata;
1418 Array<int> group_num_req(group_ldof.Size());
1419 for (int gr = 1; gr < group_ldof.Size(); gr++)
1420 {
1421 group_num_req[gr] =
1422 gtopo.IAmMaster(gr) ? gtopo.GetGroupSize(gr)-1 : 0;
1423 }
1424 int idx;
1425 while (MPI_Waitany(num_requests, requests, &idx, MPI_STATUS_IGNORE),
1426 idx != MPI_UNDEFINED)
1427 {
1428 int gr = request_marker[idx];
1429 if (gr == -1) { continue; } // skip send requests
1430
1431 // Delay the processing of a group until all receive requests, for
1432 // that group, are done:
1433 if ((--group_num_req[gr]) != 0) { continue; }
1434
1435 opd.nldofs = group_ldof.RowSize(gr);
1436 // groups without dofs are skipped, so here nldofs > 0.
1437
1438 opd.buf = (T *)group_buf.GetData() + buf_offsets[gr];
1439 opd.ldofs = (layout == 0) ?
1441 opd.nb = gtopo.GetGroupSize(gr)-1;
1442
1443 // Apply operation only to marked DOFs. The receive buffer is
1444 // neighbor-major with stride opd.nldofs, i.e. the contributions to
1445 // DOF i are buf[j*opd.nldofs + i] for j = 0 ... opd.nb-1. Setting
1446 // nldofs = 1 for a single DOF changes that stride to 1, so the
1447 // strided values must first be gathered into a contiguous buffer.
1448 Array<T> single_buf(opd.nb);
1449 for (int i = 0; i < opd.nldofs; i++)
1450 {
1451 if (marker[opd.ldofs[i]])
1452 {
1453 for (int j = 0; j < opd.nb; j++)
1454 {
1455 single_buf[j] = opd.buf[j*opd.nldofs + i];
1456 }
1457
1458 // Create a temporary OpData with just this one DOF
1459 OpData<T> single_opd;
1460 single_opd.ldata = ldata;
1461 single_opd.buf = single_buf.GetData();
1462 single_opd.ldofs = opd.ldofs + i;
1463 single_opd.nldofs = 1;
1464 single_opd.nb = opd.nb;
1465
1466 // Apply the operation
1467 Op(single_opd);
1468 }
1469 }
1470 }
1471 break;
1472 }
1473
1474 case byNeighbor: // ***** Communication by neighbors *****
1475 {
1476 MPI_Waitall(num_requests, requests, MPI_STATUSES_IGNORE);
1477
1478 for (int nbr = 1; nbr < nbr_send_groups.Size(); nbr++)
1479 {
1480 // In Reduce operation: send_groups <--> recv_groups
1481 const int num_recv_groups = nbr_send_groups.RowSize(nbr);
1482 if (num_recv_groups > 0)
1483 {
1484 const int *grp_list = nbr_send_groups.GetRow(nbr);
1485 const T *buf = (T*)group_buf.GetData() + buf_offsets[nbr];
1486 for (int i = 0; i < num_recv_groups; i++)
1487 {
1488 // Custom version of ReduceGroupFromBuffer that checks marker
1489 int gr = grp_list[i];
1490 const int *ldofs = (layout == 0) ?
1492 const int nldofs = group_ldof.RowSize(gr);
1493
1494 for (int j = 0; j < nldofs; j++)
1495 {
1496 if (marker[ldofs[j]])
1497 {
1498 // Create a temporary OpData with just this one DOF
1499 OpData<T> opd;
1500 opd.ldata = ldata;
1501 opd.buf = const_cast<T*>(buf) + j;
1502 opd.ldofs = ldofs + j;
1503 opd.nldofs = 1;
1504 opd.nb = 1;
1505
1506 // Apply the operation
1507 Op(opd);
1508 }
1509 }
1510
1511 buf += nldofs;
1512 }
1513 }
1514 }
1515 break;
1516 }
1517 }
1518
1519 comm_lock = 0; // 0 - no lock
1520 num_requests = 0;
1521}
1522
1523template <class T>
1525{
1526 if (opd.nb == 1)
1527 {
1528 for (int i = 0; i < opd.nldofs; i++)
1529 {
1530 opd.ldata[opd.ldofs[i]] += opd.buf[i];
1531 }
1532 }
1533 else
1534 {
1535 for (int i = 0; i < opd.nldofs; i++)
1536 {
1537 T data = opd.ldata[opd.ldofs[i]];
1538 for (int j = 0; j < opd.nb; j++)
1539 {
1540 data += opd.buf[j*opd.nldofs+i];
1541 }
1542 opd.ldata[opd.ldofs[i]] = data;
1543 }
1544 }
1545}
1546
1547template <class T>
1549{
1550 for (int i = 0; i < opd.nldofs; i++)
1551 {
1552 T data = opd.ldata[opd.ldofs[i]];
1553 for (int j = 0; j < opd.nb; j++)
1554 {
1555 T b = opd.buf[j*opd.nldofs+i];
1556 if (data > b)
1557 {
1558 data = b;
1559 }
1560 }
1561 opd.ldata[opd.ldofs[i]] = data;
1562 }
1563}
1564
1565template <class T>
1567{
1568 for (int i = 0; i < opd.nldofs; i++)
1569 {
1570 T data = opd.ldata[opd.ldofs[i]];
1571 for (int j = 0; j < opd.nb; j++)
1572 {
1573 T b = opd.buf[j*opd.nldofs+i];
1574 if (data < b)
1575 {
1576 data = b;
1577 }
1578 }
1579 opd.ldata[opd.ldofs[i]] = data;
1580 }
1581}
1582
1583template <class T>
1585{
1586 static_assert(std::is_integral<T>::value,
1587 "BitOR reduction requires an integral type.");
1588 for (int i = 0; i < opd.nldofs; i++)
1589 {
1590 T data = opd.ldata[opd.ldofs[i]];
1591 for (int j = 0; j < opd.nb; j++)
1592 {
1593 data |= opd.buf[j*opd.nldofs+i];
1594 }
1595 opd.ldata[opd.ldofs[i]] = data;
1596 }
1597}
1598
1599template <class T>
1601{
1602 for (int i = 0; i < opd.nldofs; i++)
1603 {
1604 T data = opd.ldata[opd.ldofs[i]];
1605 T abs_data = std::abs(data);
1606
1607 for (int j = 0; j < opd.nb; j++)
1608 {
1609 T b = opd.buf[j*opd.nldofs+i];
1610 T abs_b = std::abs(b);
1611
1612 // On an equal-magnitude tie keep the more positive value, so
1613 // opposite-sign ties resolve deterministically to the positive one.
1614 if (abs_data < abs_b || (abs_data == abs_b && data < b))
1615 {
1616 data = b;
1617 abs_data = abs_b;
1618 }
1619 }
1620
1621 opd.ldata[opd.ldofs[i]] = data;
1622 }
1623}
1624
1625
1626void GroupCommunicator::PrintInfo(std::ostream &os) const
1627{
1628 char c = '\0';
1629 const int tag = 46800;
1630 const int myid = gtopo.MyRank();
1631
1632 int num_sends = 0, num_recvs = 0;
1633 size_t mem_sends = 0, mem_recvs = 0;
1634 int num_master_groups = 0, num_empty_groups = 0;
1635 int num_active_neighbors = 0; // for mode == byNeighbor
1636 switch (mode)
1637 {
1638 case byGroup:
1639 for (int gr = 1; gr < group_ldof.Size(); gr++)
1640 {
1641 const int nldofs = group_ldof.RowSize(gr);
1642 if (nldofs == 0)
1643 {
1644 num_empty_groups++;
1645 continue;
1646 }
1647 if (gtopo.IAmMaster(gr))
1648 {
1649 num_sends += (gtopo.GetGroupSize(gr)-1);
1650 mem_sends += sizeof(double)*nldofs*(gtopo.GetGroupSize(gr)-1);
1651 num_master_groups++;
1652 }
1653 else
1654 {
1655 num_recvs++;
1656 mem_recvs += sizeof(double)*nldofs;
1657 }
1658 }
1659 break;
1660
1661 case byNeighbor:
1662 for (int gr = 1; gr < group_ldof.Size(); gr++)
1663 {
1664 const int nldofs = group_ldof.RowSize(gr);
1665 if (nldofs == 0)
1666 {
1667 num_empty_groups++;
1668 continue;
1669 }
1670 if (gtopo.IAmMaster(gr))
1671 {
1672 num_master_groups++;
1673 }
1674 }
1675 for (int nbr = 1; nbr < nbr_send_groups.Size(); nbr++)
1676 {
1677 const int num_send_groups = nbr_send_groups.RowSize(nbr);
1678 if (num_send_groups > 0)
1679 {
1680 const int *grp_list = nbr_send_groups.GetRow(nbr);
1681 for (int i = 0; i < num_send_groups; i++)
1682 {
1683 mem_sends += sizeof(double)*group_ldof.RowSize(grp_list[i]);
1684 }
1685 num_sends++;
1686 }
1687
1688 const int num_recv_groups = nbr_recv_groups.RowSize(nbr);
1689 if (num_recv_groups > 0)
1690 {
1691 const int *grp_list = nbr_recv_groups.GetRow(nbr);
1692 for (int i = 0; i < num_recv_groups; i++)
1693 {
1694 mem_recvs += sizeof(double)*group_ldof.RowSize(grp_list[i]);
1695 }
1696 num_recvs++;
1697 }
1698 if (num_send_groups > 0 || num_recv_groups > 0)
1699 {
1700 num_active_neighbors++;
1701 }
1702 }
1703 break;
1704 }
1705 if (myid != 0)
1706 {
1707 MPI_Recv(&c, 1, MPI_CHAR, myid-1, tag, gtopo.GetComm(),
1708 MPI_STATUS_IGNORE);
1709 }
1710 else
1711 {
1712 os << "\nGroupCommunicator:\n";
1713 }
1714 os << "Rank " << myid << ":\n"
1715 " mode = " <<
1716 (mode == byGroup ? "byGroup" : "byNeighbor") << "\n"
1717 " number of sends = " << num_sends <<
1718 " (" << mem_sends << " bytes)\n"
1719 " number of recvs = " << num_recvs <<
1720 " (" << mem_recvs << " bytes)\n";
1721 os <<
1722 " num groups = " << group_ldof.Size() << " = " <<
1723 num_master_groups << " + " <<
1724 group_ldof.Size()-num_master_groups-num_empty_groups << " + " <<
1725 num_empty_groups << " (master + slave + empty)\n";
1726 if (mode == byNeighbor)
1727 {
1728 os <<
1729 " num neighbors = " << nbr_send_groups.Size() << " = " <<
1730 num_active_neighbors << " + " <<
1731 nbr_send_groups.Size()-num_active_neighbors <<
1732 " (active + inactive)\n";
1733 }
1734 if (myid != gtopo.NRanks()-1)
1735 {
1736 os << std::flush;
1737 MPI_Send(&c, 1, MPI_CHAR, myid+1, tag, gtopo.GetComm());
1738 }
1739 else
1740 {
1741 os << std::endl;
1742 }
1743 MPI_Barrier(gtopo.GetComm());
1744}
1745
1747{
1748 delete device_gc;
1749 delete [] buf_offsets;
1750 delete [] request_marker;
1751 // delete [] statuses;
1752 delete [] requests;
1753}
1754
1755
1756namespace internal
1757{
1758
1759/** @brief Extract a sub-array: xout[i] = xin[indices[i]].
1760 Note that the 'indices' can contain repeated integers. */
1761template <typename T>
1762static void ExtractSubArray(const Array<int> &indices,
1763 const Array<T> &xin,
1764 Array<T> &xout)
1765{
1766 MFEM_ASSERT(indices.Size() == xout.Size(), "incompatible sizes!");
1767 auto y = xout.Write();
1768 const auto x = xin.Read();
1769 const auto I = indices.Read();
1770 mfem::forall(indices.Size(), [=] MFEM_HOST_DEVICE (int i)
1771 {
1772 y[i] = x[I[i]];
1773 });
1774}
1775
1776/** @brief Set a sub-array: xout[indices[i]] = xin[i].
1777 Note that the 'indices' can NOT contain repeated integers because that will
1778 create a race condition during parallel execution. */
1779template <typename T>
1780static void SetSubArray(const Array<int> &indices,
1781 const Array<T> &xin,
1782 Array<T> &xout)
1783{
1784 MFEM_ASSERT(indices.Size() == xin.Size(), "incompatible sizes!");
1785 // Use ReadWrite() since we modify only a subset of the indices:
1786 auto y = xout.ReadWrite();
1787 const auto x = xin.Read();
1788 const auto I = indices.Read();
1789 mfem::forall(indices.Size(), [=] MFEM_HOST_DEVICE (int i)
1790 {
1791 y[I[i]] = x[i];
1792 });
1793}
1794
1795/** @brief Set a sub-array: xout[indices[i]] = val.
1796 Note that the 'indices' can contain repeated integers. Since the same value
1797 is assigned to all given entries, there no real race condition during
1798 parallel execution. */
1799template <typename T>
1800static void SetSubArray(const Array<int> &indices, Array<T> &xout, T val)
1801{
1802 // Use ReadWrite() since we modify only a subset of the indices:
1803 auto y = xout.ReadWrite();
1804 const auto I = indices.Read();
1805 mfem::forall(indices.Size(), [=] MFEM_HOST_DEVICE (int i)
1806 {
1807 y[I[i]] = val;
1808 });
1809}
1810
1811/** @brief Perform the operation: dst += A src, where:
1812 - A is a Boolean matrix
1813 - unique_dst_indices are the nonzeros rows of A
1814 - unique_to_src_offsets and unique_to_src_indices are the I and J arrays of
1815 the csr format of A restricted to its nonzero rows. */
1816template <typename T>
1817static void BooleanAddMult(const Array<int> &unique_dst_indices,
1818 const Array<int> &unique_to_src_offsets,
1819 const Array<int> &unique_to_src_indices,
1820 const Array<T> &src,
1821 Array<T> &dst)
1822{
1823 auto y = dst.ReadWrite();
1824 const auto x = src.Read();
1825 const auto DST_I = unique_dst_indices.Read();
1826 const auto SRC_O = unique_to_src_offsets.Read();
1827 const auto SRC_I = unique_to_src_indices.Read();
1828 mfem::forall(unique_dst_indices.Size(), [=] MFEM_HOST_DEVICE (int i)
1829 {
1830 const int dst_idx = DST_I[i];
1831 T sum = y[dst_idx];
1832 const int end = SRC_O[i+1];
1833 for (int j = SRC_O[i]; j != end; ++j) { sum += x[SRC_I[j]]; }
1834 y[dst_idx] = sum;
1835 });
1836}
1837
1838/** @brief Operation similar to BooleanAddMult(): dst += A src, where:
1839 - the addition operations are replaced by the reduction operation op
1840 - only nonzero entries of A participate in the reduction operation
1841 - A is a Boolean matrix
1842 - unique_dst_indices are the nonzeros rows of A
1843 - unique_to_src_offsets and unique_to_src_indices are the I and J arrays of
1844 the csr format of A restricted to its nonzero rows. */
1845template <typename T>
1846static void BooleanReduceApply(const Array<int> &unique_dst_indices,
1847 const Array<int> &unique_to_src_offsets,
1848 const Array<int> &unique_to_src_indices,
1849 const Array<T> &src,
1850 Array<T> &dst,
1852{
1853 auto y = dst.ReadWrite();
1854 const auto x = src.Read();
1855 const auto DST_I = unique_dst_indices.Read();
1856 const auto SRC_O = unique_to_src_offsets.Read();
1857 const auto SRC_I = unique_to_src_indices.Read();
1858 mfem::forall(unique_dst_indices.Size(), [=] MFEM_HOST_DEVICE (int i)
1859 {
1860 const int dst_idx = DST_I[i];
1861 T val = y[dst_idx];
1862 const int end = SRC_O[i+1];
1863 switch (op)
1864 {
1866 for (int j = SRC_O[i]; j != end; ++j) { val += x[SRC_I[j]]; }
1867 break;
1869 for (int j = SRC_O[i]; j != end; ++j)
1870 {
1871 const T xj = x[SRC_I[j]];
1872 val = (xj < val) ? xj : val;
1873 }
1874 break;
1876 for (int j = SRC_O[i]; j != end; ++j)
1877 {
1878 const T xj = x[SRC_I[j]];
1879 val = (xj > val) ? xj : val;
1880 }
1881 break;
1882 }
1883 y[dst_idx] = val;
1884 });
1885}
1886
1887} // namespace internal
1888
1889
1891 : gc(gc_)
1892{
1893 MFEM_VERIFY(gc.mode == gc.byNeighbor,
1894 "Device group-communicator requires neighbor mode.");
1895 MFEM_VERIFY(gc.have_ltdof_ldof,
1896 "The GroupCommunicator method SetLTDofTable() must be called "
1897 "before constructing the DeviceGroupCommunicator!");
1898 {
1899 Table nbr_ltdof;
1900 gc.GetNeighborLTDofTable(nbr_ltdof);
1901 // Transfer the I and J arrays of nbr_ltdof to shr_buf_offsets and
1902 // shr_ltdof, respectively:
1904 nbr_ltdof.Size()+1, true);
1906 nbr_ltdof.Size_of_connections(), true);
1907 nbr_ltdof.LoseData();
1908 }
1910 // shr_ldof[i] = gc.ltdof_ldof[shr_ltdof[i]]:
1911 internal::ExtractSubArray(shr_ltdof, gc.ltdof_ldof, shr_ldof);
1912 {
1913 // Sort() is a host method, so initialize 'unique_ltdof' on host:
1914 Array<int> unique_ltdof(shr_ltdof.Size());
1915 unique_ltdof.CopyFrom(shr_ltdof.HostRead());
1916 unique_ltdof.Sort();
1917 unique_ltdof.Unique();
1918 unq_ltdof = unique_ltdof;
1919 }
1920 {
1921 Array<int> shr_unique(shr_ltdof.Size());
1922 for (int i = 0; i < shr_unique.Size(); i++)
1923 {
1924 shr_unique[i] = unq_ltdof.FindSorted(std::as_const(shr_ltdof)[i]);
1925 MFEM_ASSERT(shr_unique[i] != -1, "internal error");
1926 }
1927 Table unique_shr;
1928 Transpose(shr_unique, unique_shr, unq_ltdof.Size());
1929 // Transfer the I and J arrays of unique_shr to unq_shr_i and unq_shr_j,
1930 // respectively:
1932 unique_shr.Size()+1, true);
1934 unique_shr.Size_of_connections(), true);
1935 unique_shr.LoseData();
1936 }
1938 // unq_ldof[i] = gc.ltdof_ldof[unq_ltdof[i]]:
1939 internal::ExtractSubArray(unq_ltdof, gc.ltdof_ldof, unq_ldof);
1940 {
1941 Table nbr_ldof;
1942 gc.GetNeighborLDofTable(nbr_ldof);
1943 // Transfer the I and J arrays of nbr_ldof to ext_buf_offsets and
1944 // ext_ldof, respectively:
1946 nbr_ldof.Size()+1, true);
1948 nbr_ldof.Size_of_connections(), true);
1950 nbr_ldof.LoseData();
1951 }
1952
1955 shr_buf.Write();
1956 ext_buf.Write();
1957
1958 const GroupTopology &gtopo = gc.GetGroupTopology();
1959 int req_counter = 0;
1960 for (int nbr = 1; nbr < gtopo.GetNumNeighbors(); nbr++)
1961 {
1962 const int send_offset = shr_buf_offsets[nbr];
1963 const int send_size = shr_buf_offsets[nbr+1] - send_offset;
1964 if (send_size > 0) { req_counter++; }
1965
1966 const int recv_offset = ext_buf_offsets[nbr];
1967 const int recv_size = ext_buf_offsets[nbr+1] - recv_offset;
1968 if (recv_size > 0) { req_counter++; }
1969 }
1970 requests.SetSize(req_counter);
1971 num_requests = 0;
1972}
1973
1974template <typename T>
1976{
1977 TypedBufferView<T> shr_buf_t(shr_buf);
1978 TypedBufferView<T> ext_buf_t(ext_buf);
1979 BcastBeginCopyTDofs(x_tdof, shr_buf_t.view);
1980 ExchangeSharedToExternal(shr_buf_t.view, ext_buf_t.view);
1981}
1982
1983template <typename T>
1985{
1986 TypedBufferView<T> shr_buf_t(shr_buf);
1987 TypedBufferView<T> ext_buf_t(ext_buf);
1988 BcastBeginCopyLDofs(x_ldof, shr_buf_t.view);
1989 ExchangeSharedToExternal(shr_buf_t.view, ext_buf_t.view);
1990}
1991
1992template <typename T>
1994{
1995 TypedBufferView<T> ext_buf_t(ext_buf);
1996 WaitAll();
1997 BcastEndCopy(ext_buf_t.view, x_ldof);
1998}
1999
2000template <typename T>
2002{
2003 TypedBufferView<T> shr_buf_t(shr_buf);
2004 TypedBufferView<T> ext_buf_t(ext_buf);
2005 ReduceBeginCopy(x_ldof, ext_buf_t.view);
2006 ExchangeExternalToShared(ext_buf_t.view, shr_buf_t.view);
2007}
2008
2009template <typename T>
2011{
2012 TypedBufferView<T> shr_buf_t(shr_buf);
2013 WaitAll();
2014 ReduceEndAssembleTDofs(shr_buf_t.view, x_tdof, op);
2015}
2016
2017template <typename T>
2019{
2020 TypedBufferView<T> shr_buf_t(shr_buf);
2021 WaitAll();
2022 if (unq_ldof.Size() == 0) { return; }
2023 if (op == Op::Sum)
2024 {
2025 internal::BooleanAddMult(unq_ldof, unq_shr_i, unq_shr_j,
2026 shr_buf_t.view, x_ldof);
2027 }
2028 else
2029 {
2030 internal::BooleanReduceApply(unq_ldof, unq_shr_i, unq_shr_j,
2031 shr_buf_t.view, x_ldof, op);
2032 }
2033}
2034
2035template <typename T>
2037 Array<T> &x_ldof) const
2038{
2039 if (gc.ltdof_ldof.Size() == 0) { return; }
2040 internal::SetSubArray(gc.ltdof_ldof, x_tdof, x_ldof);
2041}
2042
2043template <typename T>
2045 Array<T> &x_ldof) const
2046{
2047 MFEM_ASSERT(x_tdof.Size() == gc.ltdof_ldof.Size(), "incompatible sizes!");
2048 MFEM_ASSERT(x_ldof.Size() == gc.ldof_size, "incompatible sizes!");
2049 TypedBufferView<T> shr_buf_t(shr_buf);
2050 TypedBufferView<T> ext_buf_t(ext_buf);
2051 BcastBeginCopyTDofs(x_tdof, shr_buf_t.view);
2052 ExchangeSharedToExternal(shr_buf_t.view, ext_buf_t.view);
2053 CopyTDofsToLDofs(x_tdof, x_ldof);
2054 WaitAll();
2055 BcastEndCopy(ext_buf_t.view, x_ldof);
2056}
2057
2058template <typename T>
2060 Array<T> &x_tdof,
2061 Op op) const
2062{
2063 MFEM_ASSERT(x_ldof.Size() == gc.ldof_size, "incompatible sizes!");
2064 MFEM_ASSERT(x_tdof.Size() == gc.ltdof_ldof.Size(), "incompatible sizes!");
2065 TypedBufferView<T> shr_buf_t(shr_buf);
2066 TypedBufferView<T> ext_buf_t(ext_buf);
2067 ReduceBeginCopy(x_ldof, ext_buf_t.view);
2068 ExchangeExternalToShared(ext_buf_t.view, shr_buf_t.view);
2069 Restrict(x_ldof, x_tdof);
2070 WaitAll();
2071 ReduceEndAssembleTDofs(shr_buf_t.view, x_tdof, op);
2072}
2073
2074template <typename T>
2076 Array<T> &x_tdof) const
2077{
2078 if (gc.ltdof_ldof.Size() == 0) { return; }
2079 internal::ExtractSubArray(gc.ltdof_ldof, x_ldof, x_tdof);
2080}
2081
2082template <typename T>
2084 Array<T> &x_ldof) const
2085{
2086 CopyTDofsToLDofs(x_tdof, x_ldof);
2087 internal::SetSubArray(ext_ldof, x_ldof, T(0));
2088}
2089
2090template <typename T>
2092 const Array<int> &send_offsets,
2093 Array<T> &recv_buf,
2094 const Array<int> &recv_offsets,
2095 int tag) const
2096{
2097 const GroupTopology &gtopo = gc.GetGroupTopology();
2098 const bool mpi_gpu_aware = Device::GetGPUAwareMPI();
2099 auto send_ptr = mpi_gpu_aware ? send_buf.Read() : send_buf.HostRead();
2100 auto recv_ptr = mpi_gpu_aware ? recv_buf.Write() : recv_buf.HostWrite();
2101 num_requests = 0;
2102 for (int nbr = 1; nbr < gtopo.GetNumNeighbors(); nbr++)
2103 {
2104 const int send_offset = send_offsets[nbr];
2105 const int send_size = send_offsets[nbr+1] - send_offset;
2106 if (send_size > 0)
2107 {
2108 MPI_Isend(send_ptr + send_offset, send_size, MPITypeMap<T>::mpi_type,
2109 gtopo.GetNeighborRank(nbr), tag, gtopo.GetComm(),
2111 }
2112 const int recv_offset = recv_offsets[nbr];
2113 const int recv_size = recv_offsets[nbr+1] - recv_offset;
2114 if (recv_size > 0)
2115 {
2116 MPI_Irecv(recv_ptr + recv_offset, recv_size, MPITypeMap<T>::mpi_type,
2117 gtopo.GetNeighborRank(nbr), tag, gtopo.GetComm(),
2119 }
2120 }
2121}
2122
2123template <typename T>
2125 const Array<T> &shr_buf_t, Array<T> &ext_buf_t) const
2126{
2127 const int tag = 41822;
2128 Exchange(shr_buf_t, shr_buf_offsets, ext_buf_t, ext_buf_offsets, tag);
2129}
2130
2131template <typename T>
2133 const Array<T> &ext_buf_t, Array<T> &shr_buf_t) const
2134{
2135 const int tag = 41823;
2136 Exchange(ext_buf_t, ext_buf_offsets, shr_buf_t, shr_buf_offsets, tag);
2137}
2138
2139template <typename T>
2141 Array<T> &ext_buf_t) const
2142{
2143 if (ext_ldof.Size() == 0) { return; }
2144 internal::ExtractSubArray(ext_ldof, x_ldof, ext_buf_t);
2145 if (Device::GetGPUAwareMPI()) { MFEM_STREAM_SYNC; }
2146}
2147
2148template <typename T>
2150 Array<T> &x_tdof,
2151 Op op) const
2152{
2153 if (unq_ltdof.Size() == 0) { return; }
2154 if (op == Op::Sum)
2155 {
2156 internal::BooleanAddMult(unq_ltdof, unq_shr_i, unq_shr_j,
2157 shr_buf_t, x_tdof);
2158 }
2159 else
2160 {
2161 internal::BooleanReduceApply(unq_ltdof, unq_shr_i, unq_shr_j,
2162 shr_buf_t, x_tdof, op);
2163 }
2164}
2165
2166template <typename T>
2168 Array<T> &shr_buf_t) const
2169{
2170 if (shr_ltdof.Size() == 0) { return; }
2171 internal::ExtractSubArray(shr_ltdof, x_tdof, shr_buf_t);
2172 if (Device::GetGPUAwareMPI()) { MFEM_STREAM_SYNC; }
2173}
2174
2175template <typename T>
2177 Array<T> &shr_buf_t) const
2178{
2179 if (shr_ldof.Size() == 0) { return; }
2180 internal::ExtractSubArray(shr_ldof, x_ldof, shr_buf_t);
2181 if (Device::GetGPUAwareMPI()) { MFEM_STREAM_SYNC; }
2182}
2183
2184template <typename T>
2186 Array<T> &x_ldof) const
2187{
2188 if (ext_ldof.Size() == 0) { return; }
2189 internal::SetSubArray(ext_ldof, ext_buf_t, x_ldof);
2190}
2191
2193{
2194 MPI_Waitall(num_requests, requests.GetData(), MPI_STATUSES_IGNORE);
2195}
2196
2197/// @cond DOXYGEN_SKIP
2198
2199// instantiate GroupCommunicator::Bcast and Reduce for int, double, and float
2200template void GroupCommunicator::BcastBegin<int>(int *, int) const;
2201template void GroupCommunicator::BcastBegin<int>(Array<int> &, int) const;
2202template void GroupCommunicator::BcastEnd<int>(int *, int) const;
2203template void GroupCommunicator::BcastEnd<int>(Array<int> &, int) const;
2204template void GroupCommunicator::ReduceBegin<int>(const int *) const;
2206 const Array<int> &, void (*)(OpData<int>)) const;
2208 int *, int, void (*)(OpData<int>)) const;
2210 Array<int> &, int, void (*)(OpData<int>)) const;
2212 int*, const Array<int>&, int, void (*)(OpData<int>)) const;
2213
2214template void GroupCommunicator::BcastBegin<double>(double *, int) const;
2215template void GroupCommunicator::BcastBegin<double>(Array<double> &, int) const;
2216template void GroupCommunicator::BcastEnd<double>(double *, int) const;
2217template void GroupCommunicator::BcastEnd<double>(Array<double> &, int) const;
2218template void GroupCommunicator::ReduceBegin<double>(const double *) const;
2220 const Array<double> &, void (*)(OpData<double>)) const;
2222 double *, int, void (*)(OpData<double>)) const;
2224 Array<double> &, int, void (*)(OpData<double>)) const;
2226 double*, const Array<int>&, int, void (*)(OpData<double>)) const;
2227
2228template void GroupCommunicator::BcastBegin<float>(float *, int) const;
2229template void GroupCommunicator::BcastBegin<float>(Array<float> &, int) const;
2230template void GroupCommunicator::BcastEnd<float>(float *, int) const;
2231template void GroupCommunicator::BcastEnd<float>(Array<float> &, int) const;
2232template void GroupCommunicator::ReduceBegin<float>(const float *) const;
2234 const Array<float> &, void (*)(OpData<float>)) const;
2236 float *, int, void (*)(OpData<float>)) const;
2238 Array<float> &, int, void (*)(OpData<float>)) const;
2240 float*, const Array<int>&, int, void (*)(OpData<float>)) const;
2241
2242/// @endcond
2243
2244// instantiate reduce operators for int, double, and float
2245template void GroupCommunicator::Sum<int>(OpData<int>);
2246template void GroupCommunicator::Min<int>(OpData<int>);
2247template void GroupCommunicator::Max<int>(OpData<int>);
2248template void GroupCommunicator::BitOR<int>(OpData<int>);
2249template void GroupCommunicator::MaxAbs<int>(OpData<int>);
2250
2251template void GroupCommunicator::Sum<double>(OpData<double>);
2252template void GroupCommunicator::Min<double>(OpData<double>);
2253template void GroupCommunicator::Max<double>(OpData<double>);
2254template void GroupCommunicator::MaxAbs<double>(OpData<double>);
2255
2256template void GroupCommunicator::Sum<float>(OpData<float>);
2257template void GroupCommunicator::Min<float>(OpData<float>);
2258template void GroupCommunicator::Max<float>(OpData<float>);
2259template void GroupCommunicator::MaxAbs<float>(OpData<float>);
2260
2261
2262/// @cond DOXYGEN_SKIP
2263
2268 const Array<int> &) const;
2270 Array<int> &, Op) const;
2272 Array<int> &, Op) const;
2274 const Array<int> &, Array<int> &) const;
2276 const Array<int> &, Array<int> &) const;
2278 const Array<int> &, Array<int> &, Op) const;
2280 const Array<int> &, Array<int> &) const;
2282 const Array<int> &, Array<int> &) const;
2283
2285 Array<real_t> &) const;
2287 Array<real_t> &) const;
2289 Array<real_t> &) const;
2291 const Array<real_t> &) const;
2293 Array<real_t> &, Op) const;
2295 Array<real_t> &, Op) const;
2297 const Array<real_t> &, Array<real_t> &) const;
2299 const Array<real_t> &, Array<real_t> &) const;
2301 const Array<real_t> &, Array<real_t> &, Op) const;
2303 const Array<real_t> &, Array<real_t> &) const;
2305 const Array<real_t> &, Array<real_t> &) const;
2306
2307/// @endcond
2308
2309
2310#ifdef __bgq__
2311static void DebugRankCoords(int** coords, int dim, int size)
2312{
2313 for (int i = 0; i < size; i++)
2314 {
2315 mfem::out << "Rank " << i << " coords: ";
2316 for (int j = 0; j < dim; j++)
2317 {
2318 mfem::out << coords[i][j] << " ";
2319 }
2320 mfem::out << endl;
2321 }
2322}
2323
2324struct CompareCoords
2325{
2326 CompareCoords(int coord) : coord(coord) {}
2327 int coord;
2328
2329 bool operator()(int* const &a, int* const &b) const
2330 { return a[coord] < b[coord]; }
2331};
2332
2333void KdTreeSort(int** coords, int d, int dim, int size)
2334{
2335 if (size > 1)
2336 {
2337 bool all_same = true;
2338 for (int i = 1; i < size && all_same; i++)
2339 {
2340 for (int j = 0; j < dim; j++)
2341 {
2342 if (coords[i][j] != coords[0][j]) { all_same = false; break; }
2343 }
2344 }
2345 if (all_same) { return; }
2346
2347 // sort by coordinate 'd'
2348 std::sort(coords, coords + size, CompareCoords(d));
2349 int next = (d + 1) % dim;
2350
2351 if (coords[0][d] < coords[size-1][d])
2352 {
2353 KdTreeSort(coords, next, dim, size/2);
2354 KdTreeSort(coords + size/2, next, dim, size - size/2);
2355 }
2356 else
2357 {
2358 // skip constant dimension
2359 KdTreeSort(coords, next, dim, size);
2360 }
2361 }
2362}
2363
2364MPI_Comm ReorderRanksZCurve(MPI_Comm comm)
2365{
2366 MPI_Status status;
2367
2368 int rank, size;
2369 MPI_Comm_rank(comm, &rank);
2370 MPI_Comm_size(comm, &size);
2371
2372 int dim;
2373 MPIX_Torus_ndims(&dim);
2374
2375 int* mycoords = new int[dim + 1];
2376 MPIX_Rank2torus(rank, mycoords);
2377
2378 MPI_Send(mycoords, dim, MPI_INT, 0, 111, comm);
2379 delete [] mycoords;
2380
2381 if (rank == 0)
2382 {
2383 int** coords = new int*[size];
2384 for (int i = 0; i < size; i++)
2385 {
2386 coords[i] = new int[dim + 1];
2387 coords[i][dim] = i;
2388 MPI_Recv(coords[i], dim, MPI_INT, i, 111, comm, &status);
2389 }
2390
2391 KdTreeSort(coords, 0, dim, size);
2392
2393 // DebugRankCoords(coords, dim, size);
2394
2395 for (int i = 0; i < size; i++)
2396 {
2397 MPI_Send(&coords[i][dim], 1, MPI_INT, i, 112, comm);
2398 delete [] coords[i];
2399 }
2400 delete [] coords;
2401 }
2402
2403 int new_rank;
2404 MPI_Recv(&new_rank, 1, MPI_INT, 0, 112, comm, &status);
2405
2406 MPI_Comm new_comm;
2407 MPI_Comm_split(comm, 0, new_rank, &new_comm);
2408 return new_comm;
2409}
2410
2411#else // __bgq__
2412
2413MPI_Comm ReorderRanksZCurve(MPI_Comm comm)
2414{
2415 // pass
2416 return comm;
2417}
2418#endif // __bgq__
2419
2420} // namespace mfem
2421
2422#endif
Memory< T > & GetMemory()
Return a reference to the Memory object used by the Array.
Definition array.hpp:164
T Max() const
Find the maximal element in the array, using the comparison operator < for class T.
Definition array.cpp:69
int FindSorted(const T &el) const
Do bisection search for 'el' in a sorted array; return -1 if not found.
Definition array.hpp:1010
const T * HostRead() const
Shortcut for mfem::Read(a.GetMemory(), a.Size(), false).
Definition array.hpp:414
void Sort()
Sorts the array in ascending order. This requires operator< to be defined for T.
Definition array.hpp:341
void CopyFrom(const U *src)
Copy from src into this array. Copies enough entries to fill the Capacity size of this array....
Definition array.hpp:387
void SetSize(int nsize)
Change the logical size of the array, keep existing entries.
Definition array.hpp:869
int Size() const
Return the logical size of the array.
Definition array.hpp:192
T * Write(bool on_dev=true)
Shortcut for mfem::Write(a.GetMemory(), a.Size(), on_dev).
Definition array.hpp:418
int Append(const T &el)
Append element 'el' to array, resize if necessary.
Definition array.hpp:941
void UseDevice(bool use_dev) const
Set the device flag of the Array, i.e. the device flag of the Memory object used by the Array.
Definition array.hpp:174
T * GetData()
Returns the data.
Definition array.hpp:159
void Copy(Array &copy) const
Create a copy of the internal array to the provided copy.
Definition array.hpp:1071
void Unique()
Removes duplicities from a sorted array. This requires operator== to be defined for T.
Definition array.hpp:349
const T * Read(bool on_dev=true) const
Shortcut for mfem::Read(a.GetMemory(), a.Size(), on_dev).
Definition array.hpp:410
T * HostReadWrite()
Shortcut for mfem::ReadWrite(a.GetMemory(), a.Size(), false).
Definition array.hpp:430
void NewMemoryAndSize(const Memory< T > &mem, int s, bool own_mem)
Reset the Array to use the given external Memory mem and size s.
Definition array.hpp:1114
T * HostWrite()
Shortcut for mfem::Write(a.GetMemory(), a.Size(), false).
Definition array.hpp:422
Auxiliary class used by class GroupCommunicator implementing its device (GPU) code paths for data pas...
Array< buffer_max_type > ext_buf
void RestrictTranspose(const Array< T > &x_tdof, Array< T > &x_ldof) const
Transpose of Restrict(): copy the true-dof data x_tdof into the owned local dofs of x_ldof and set th...
void Restrict(const Array< T > &x_ldof, Array< T > &x_tdof) const
Kernel: copy owned ldofs from x_ldof to ltdofs in x_tdof, i.e. x_tdof[i] = x_ldof[ltdof_ldof[i]].
void BcastEndCopy(const Array< T > &ext_buf_t, Array< T > &x_ldof) const
void CopyTDofsToLDofs(const Array< T > &x_tdof, Array< T > &x_ldof) const
Kernel: copy ltdofs from x_tdof to ldofs in x_ldof, i.e. x_ldof[ltdof_ldof[i]] = x_tdof[i].
void ReduceEndLDofs(Array< T > &x_ldof, Op op) const
Finalize a group reduction into the local-dof data x_ldof, applying the reduction operation op.
void BcastBeginTDofs(Array< T > &x_tdof) const
Begin a group broadcast of the true-dof data x_tdof.
const GroupCommunicator & gc
void ProlongateTranspose(const Array< T > &x_ldof, Array< T > &x_tdof, Op op=Op::Sum) const
Transpose of Prolongate(): reduce the local-dof data x_ldof into the true-dof data x_tdof,...
void ReduceEndAssembleTDofs(const Array< T > &shr_buf_t, Array< T > &x_tdof, Op op) const
Array< buffer_max_type > shr_buf
void BcastBeginCopyTDofs(const Array< T > &x_tdof, Array< T > &shr_buf_t) const
void ReduceEndTDofs(Array< T > &x_tdof, Op op) const
Finalize a group reduction into the true-dof data x_tdof, applying the reduction operation op.
void ReduceBeginLDofs(const Array< T > &x_ldof) const
Begin a group reduction of the local-dof data x_ldof.
void ExchangeSharedToExternal(const Array< T > &shr_buf, Array< T > &ext_buf) const
Op
Reduction operation applied at the receiving dofs.
void BcastBeginLDofs(Array< T > &x_ldof) const
Begin a group broadcast of the local-dof data x_ldof.
void ReduceBeginCopy(const Array< T > &x_ldof, Array< T > &ext_buf_t) const
DeviceGroupCommunicator(const GroupCommunicator &gc_)
Construct a device communicator based on the GroupCommunicator gc_.
void BcastEndLDofs(Array< T > &x_ldof) const
Finalize a group broadcast into the local-dof data x_ldof.
void BcastBeginCopyLDofs(const Array< T > &x_ldof, Array< T > &shr_buf_t) const
Array< MPI_Request > requests
void ExchangeExternalToShared(const Array< T > &ext_buf, Array< T > &shr_buf) const
void Prolongate(const Array< T > &x_tdof, Array< T > &x_ldof) const
Prolongate the true-dof data x_tdof to the local-dof data x_ldof.
void Exchange(const Array< T > &send_buf, const Array< int > &send_offsets, Array< T > &recv_buf, const Array< int > &recv_offsets, int tag) const
static bool Allows(unsigned long b_mask)
Return true if any of the backends in the backend mask, b_mask, are allowed.
Definition device.hpp:271
static bool GetGPUAwareMPI()
Get the status of GPU-aware MPI flag.
Definition device.hpp:319
Communicator performing operations within groups defined by a GroupTopology with arbitrary-size data ...
GroupCommunicator(const GroupTopology &gt, Mode m=byNeighbor)
Construct a GroupCommunicator object.
DeviceGroupCommunicator * device_gc
void GetNeighborLTDofTable(Table &nbr_ltdof) const
Dofs to be sent (during Bcast) to communication neighbors.
Mode
Communication mode.
@ byGroup
Communications are performed one group at a time.
void GetNeighborLDofTable(Table &nbr_ldof) const
Dofs to be received (during Bcast) from communication neighbors.
const T * ReduceGroupFromBuffer(const T *buf, T *ldata, int group, int layout, void(*Op)(OpData< T >)) const
Perform the reduction operation Op to the entries of group group using the values from the buffer buf...
void ReduceMarked(T *ldata, const Array< int > &marker, int layout, void(*Op)(OpData< T >)) const
Finalize reduction operation started with ReduceBegin(), but only apply the reduction to DOFs marked ...
static void MaxAbs(OpData< T >)
void ReduceEnd(T *ldata, int layout, void(*Op)(OpData< T >)) const
Finalize reduction operation started with the host version of ReduceBegin().
const GroupTopology & GetGroupTopology() const
Get a const reference to the associated GroupTopology object.
const GroupTopology & gtopo
T * CopyGroupToBuffer(const T *ldata, T *buf, int group, int layout) const
Copy the entries corresponding to the group group from the local array ldata to the buffer buf.
const T * CopyGroupFromBuffer(const T *buf, T *ldata, int group, int layout) const
Copy the entries corresponding to the group group from the buffer buf to the local array ldata.
void BcastEnd(T *ldata, int layout) const
Finalize a broadcast started with the host version of BcastBegin().
void ReduceBegin(const T *ldata) const
Begin reduction operation within each group where the master is the root, host version.
void Create(const Array< int > &ldof_group)
Initialize the communicator from a local-dof to group map. Finalize() is called internally.
~GroupCommunicator()
Destroy a GroupCommunicator object, deallocating internal data structures and buffers.
friend class DeviceGroupCommunicator
static void Sum(OpData< T >)
Reduce operation Sum, instantiated for int, double and float.
void SetLTDofTable(const Array< int > &ldof_ltdof)
Initialize the internal group_ltdof Table.
static void BitOR(OpData< T >)
Reduce operation bitwise OR, instantiated for int only.
static void Max(OpData< T >)
Reduce operation Max, instantiated for int, double and float.
static void Min(OpData< T >)
Reduce operation Min, instantiated for int, double and float.
void BcastBegin(T *ldata, int layout) const
Begin a broadcast within each group where the master is the root, host version.
void Finalize()
Allocate internal buffers after the GroupLDofTable is defined.
void PrintInfo(std::ostream &out=mfem::out) const
Print information about the GroupCommunicator from all MPI ranks.
const DeviceGroupCommunicator & GetDeviceComm() const
Return the device communicator, 'device_gc', constructing it if it was not already constructed.
int GetNeighborRank(int i) const
Return the MPI rank of neighbor 'i'.
int NRanks() const
Return the number of MPI ranks within this object's communicator.
bool IAmMaster(int g) const
Return true if I am master for group 'g'.
void Swap(GroupTopology &other)
Swap the internal data with another GroupTopology object.
void Save(std::ostream &out) const
Save the data in a stream.
const int * GetGroup(int g) const
Return a pointer to a list of neighbors for a given group. Neighbor 0 is the local processor.
int MyRank() const
Return the MPI rank within this object's communicator.
int GetGroupSize(int g) const
Get the number of processors in a group.
int GetGroupMaster(int g) const
Return the neighbor index of the group master for a given group. Neighbor 0 is the local processor.
void Load(std::istream &in)
Load the data from a stream.
GroupTopology()
Constructor with the MPI communicator = 0.
int GetGroupMasterRank(int g) const
Return the rank of the group master for group 'g'.
void Create(ListOfIntegerSets &groups, int mpitag)
Set up the group topology given the list of sets of shared entities.
MPI_Comm GetComm() const
Return the MPI communicator.
void Copy(GroupTopology &copy) const
Copy the internal data to the external 'copy'.
int GetNumNeighbors() const
Return the number of neighbors including the local processor.
int NGroups() const
Return the number of groups.
int GetGroupMasterGroup(int g) const
Return the group number in the master for group 'g'.
A set of integers.
Definition sets.hpp:24
void Recreate(const int n, const int *p)
Create an integer set from C-array 'p' of 'n' integers. Overwrites any existing set data.
Definition sets.cpp:33
List of integer sets.
Definition sets.hpp:51
int Insert(const IntegerSet &s)
Check to see if set 's' is in the list. If not append it to the end of the list. Returns the index of...
Definition sets.cpp:56
void AsTable(Table &t) const
Write the list of sets into table 't'.
Definition sets.cpp:81
int PickElementInSet(int i) const
Return the value of the first element of the ith set.
Definition sets.hpp:61
int Lookup(const IntegerSet &s) const
Definition sets.cpp:69
bool UseDevice() const
Read the internal device flag.
static MFEM_EXPORT int default_thread_required
Default level of thread support for MPI_Init_thread.
Table stores the connectivity of elements of TYPE I to elements of TYPE II. For example,...
Definition table.hpp:43
void LoseData()
Releases ownership of and null-ifies the data.
Definition table.hpp:184
int * GetJ()
Definition table.hpp:128
void AddConnections(int r, const int *c, int nc)
Definition table.cpp:152
int RowSize(int i) const
Definition table.hpp:122
void ShiftUpI()
Definition table.cpp:163
void GetRow(int i, Array< int > &row) const
Return row i in array row (the Table must be finalized)
Definition table.cpp:233
void AddConnection(int r, int c)
Definition table.hpp:89
void MakeI(int nrows)
Definition table.cpp:130
int Size() const
Returns the number of TYPE I elements.
Definition table.hpp:103
int Size_of_connections() const
Returns the number of connections in the table.
Definition table.hpp:110
void AddColumnsInRow(int r, int ncol)
Definition table.hpp:87
void MakeFromList(int nrows, const Array< Connection > &list)
Create the table from a list of connections {(from, to)}, where 'from' is a TYPE I index and 'to' is ...
Definition table.cpp:322
void Copy(Table &copy) const
Definition table.cpp:427
void MakeJ()
Definition table.cpp:140
Memory< int > & GetJMemory()
Definition table.hpp:133
int * GetI()
Definition table.hpp:127
void AddAColumnInRow(int r)
Definition table.hpp:86
void SetDims(int rows, int nnz)
Set the rows and the number of all connections for the table.
Definition table.cpp:188
Memory< int > & GetIMemory()
Definition table.hpp:132
int dim
Definition ex24.cpp:53
int index(int i, int j, int nx, int ny)
Definition life.cpp:236
real_t b
Definition lissajous.cpp:42
real_t a
Definition lissajous.cpp:41
OutStream out(std::cout)
Global stream used by the library for standard output. Initially it uses the same std::streambuf as s...
Definition globals.hpp:66
void Transpose(const Table &A, Table &At, int ncols_A_)
Transpose a Table.
Definition table.cpp:443
void Swap(T &a, T &b)
Swap objects of type T. The operation is performed using the most specialized swap function from the ...
Definition array.hpp:767
void KdTreeSort(int **coords, int d, int dim, int size)
MPI_Comm ReorderRanksZCurve(MPI_Comm comm)
void forall(int N, lambda &&body)
Definition forall.hpp:1134
void skip_comment_lines(std::istream &is, const char comment_char)
Check if the stream starts with comment_char. If so skip it.
Definition text.hpp:31
STL namespace.
real_t p(const Vector &x, real_t t)
@ DEVICE_MASK
Biwise-OR of all device backends.
Definition device.hpp:104
Helper struct for defining a connectivity table, see Table::MakeFromList.
Definition table.hpp:28
Data structure on which we define reduce operations. The data is associated with (and the operation i...
Helper struct to convert a C++ type to an MPI type.