MFEM v4.7.0
Finite element discretization library
Loading...
Searching...
No Matches
pfespace.cpp
Go to the documentation of this file.
1// Copyright (c) 2010-2024, 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 "pfespace.hpp"
17#include "prestriction.hpp"
18#include "../general/forall.hpp"
22
23#include <limits>
24#include <list>
25
26namespace mfem
27{
28
30 const ParFiniteElementSpace &orig, ParMesh *pmesh,
31 const FiniteElementCollection *fec)
32 : FiniteElementSpace(orig, pmesh, fec)
33{
34 ParInit(pmesh ? pmesh : orig.pmesh);
35}
36
38 const FiniteElementSpace &orig, ParMesh &pmesh,
39 const FiniteElementCollection *fec)
40 : FiniteElementSpace(orig, &pmesh, fec)
41{
42 ParInit(&pmesh);
43}
44
46 ParMesh *pm, const FiniteElementSpace *global_fes, const int *partitioning,
48 : FiniteElementSpace(pm, MakeLocalNURBSext(global_fes->GetNURBSext(),
49 pm->NURBSext),
50 f ? f : global_fes->FEColl(),
51 global_fes->GetVDim(), global_fes->GetOrdering())
52{
53 ParInit(pm);
54 // For NURBS spaces, the variable-order data is contained in the
55 // NURBSExtension of 'global_fes' and inside the ParNURBSExtension of 'pm'.
56
57 // TODO: when general variable-order support is added, copy the local portion
58 // of the variable-order data from 'global_fes' to 'this'.
59}
60
62 ParMesh *pm, const FiniteElementCollection *f, int dim, int ordering)
63 : FiniteElementSpace(pm, f, dim, ordering)
64{
65 ParInit(pm);
66}
67
70 int dim, int ordering)
71 : FiniteElementSpace(pm, ext, f, dim, ordering)
72{
73 ParInit(pm);
74}
75
76// static method
77ParNURBSExtension *ParFiniteElementSpace::MakeLocalNURBSext(
78 const NURBSExtension *globNURBSext, const NURBSExtension *parNURBSext)
79{
80 if (globNURBSext == NULL) { return NULL; }
81 const ParNURBSExtension *pNURBSext =
82 dynamic_cast<const ParNURBSExtension*>(parNURBSext);
83 MFEM_ASSERT(pNURBSext, "need a ParNURBSExtension");
84 // make a copy of globNURBSext:
85 NURBSExtension *tmp_globNURBSext = new NURBSExtension(*globNURBSext);
86 // tmp_globNURBSext will be deleted by the following ParNURBSExtension ctor:
87 return new ParNURBSExtension(tmp_globNURBSext, pNURBSext);
88}
89
90void ParFiniteElementSpace::ParInit(ParMesh *pm)
91{
92 pmesh = pm;
93 pncmesh = nullptr;
94
95 MyComm = pmesh->GetComm();
96 NRanks = pmesh->GetNRanks();
97 MyRank = pmesh->GetMyRank();
98
99 gcomm = nullptr;
100
101 P = nullptr;
102 Pconf = nullptr;
103 nonconf_P = false;
104 Rconf = nullptr;
105 R = nullptr;
107
108 if (NURBSext && !pNURBSext())
109 {
110 // This is necessary in some cases: e.g. when the FiniteElementSpace
111 // constructor creates a serial NURBSExtension of higher order than the
112 // mesh NURBSExtension.
113 MFEM_ASSERT(own_ext, "internal error");
114
115 ParNURBSExtension *pNe = new ParNURBSExtension(
116 NURBSext, dynamic_cast<ParNURBSExtension *>(pmesh->NURBSext));
117 // serial NURBSext is destroyed by the above constructor
118 NURBSext = pNe;
119 UpdateNURBS();
120 }
121
122 Construct(); // parallel version of Construct().
123
124 // Apply the ldof_signs to the elem_dof Table
125 if (Conforming() && !NURBSext)
126 {
127 ApplyLDofSigns(*elem_dof);
128 }
129
130 // Check for shared triangular faces with interior Nedelec DoFs
131 CheckNDSTriaDofs();
132}
133
134void ParFiniteElementSpace::Construct()
135{
136 MFEM_VERIFY(!IsVariableOrder(), "variable orders are not implemented"
137 " for ParFiniteElementSpace yet.");
138
139 if (NURBSext)
140 {
141 ConstructTrueNURBSDofs();
142 GenerateGlobalOffsets();
143 }
144 else if (Conforming())
145 {
146 ConstructTrueDofs();
147 GenerateGlobalOffsets();
148 }
149 else // Nonconforming()
150 {
151 pncmesh = pmesh->pncmesh;
152
153 // Initialize 'gcomm' for the cut (aka "partially conforming") space.
154 // In the process, the array 'ldof_ltdof' is also initialized (for the cut
155 // space) and used; however, it will be overwritten below with the real
156 // true dofs. Also, 'ldof_sign' and 'ldof_group' are constructed for the
157 // cut space.
158 ConstructTrueDofs();
159
160 ngedofs = ngfdofs = 0;
161
162 // calculate number of ghost DOFs
163 ngvdofs = pncmesh->GetNGhostVertices()
165
166 if (pmesh->Dimension() > 1)
167 {
168 ngedofs = pncmesh->GetNGhostEdges()
170 }
171
172 if (pmesh->Dimension() > 2)
173 {
174 ngfdofs = pncmesh->GetNGhostFaces()
176 }
177
178 // total number of ghost DOFs. Ghost DOFs start at index 'ndofs', i.e.,
179 // after all regular DOFs
180 ngdofs = ngvdofs + ngedofs + ngfdofs;
181
182 // get P and R matrices, initialize DOF offsets, etc. NOTE: in the NC
183 // case this needs to be done here to get the number of true DOFs
184 ltdof_size = BuildParallelConformingInterpolation(
185 &P, &R, dof_offsets, tdof_offsets, &ldof_ltdof, false);
186
187 // TODO future: split BuildParallelConformingInterpolation into two parts
188 // to overlap its communication with processing between this constructor
189 // and the point where the P matrix is actually needed.
190 }
191}
192
194{
195 long long ltdofs = ltdof_size;
196 long long min_ltdofs, max_ltdofs, sum_ltdofs;
197
198 MPI_Reduce(&ltdofs, &min_ltdofs, 1, MPI_LONG_LONG, MPI_MIN, 0, MyComm);
199 MPI_Reduce(&ltdofs, &max_ltdofs, 1, MPI_LONG_LONG, MPI_MAX, 0, MyComm);
200 MPI_Reduce(&ltdofs, &sum_ltdofs, 1, MPI_LONG_LONG, MPI_SUM, 0, MyComm);
201
202 if (MyRank == 0)
203 {
204 real_t avg = real_t(sum_ltdofs) / NRanks;
205 mfem::out << "True DOF partitioning: min " << min_ltdofs
206 << ", avg " << std::fixed << std::setprecision(1) << avg
207 << ", max " << max_ltdofs
208 << ", (max-avg)/avg " << 100.0*(max_ltdofs - avg)/avg
209 << "%" << std::endl;
210 }
211
212 if (NRanks <= 32)
213 {
214 if (MyRank == 0)
215 {
216 mfem::out << "True DOFs by rank: " << ltdofs;
217 for (int i = 1; i < NRanks; i++)
218 {
219 MPI_Status status;
220 MPI_Recv(&ltdofs, 1, MPI_LONG_LONG, i, 123, MyComm, &status);
221 mfem::out << " " << ltdofs;
222 }
223 mfem::out << "\n";
224 }
225 else
226 {
227 MPI_Send(&ltdofs, 1, MPI_LONG_LONG, 0, 123, MyComm);
228 }
229 }
230}
231
232void ParFiniteElementSpace::GetGroupComm(
233 GroupCommunicator &gc, int ldof_type, Array<int> *g_ldof_sign)
234{
235 int gr;
236 int ng = pmesh->GetNGroups();
237 int nvd, ned, ntd = 0, nqd = 0;
238 Array<int> dofs;
239
240 int group_ldof_counter;
241 Table &group_ldof = gc.GroupLDofTable();
242
245
246 if (mesh->Dimension() >= 3)
247 {
249 {
251 }
253 {
255 }
256 }
257
258 if (g_ldof_sign)
259 {
260 g_ldof_sign->SetSize(GetNDofs());
261 *g_ldof_sign = 1;
262 }
263
264 // count the number of ldofs in all groups (excluding the local group 0)
265 group_ldof_counter = 0;
266 for (gr = 1; gr < ng; gr++)
267 {
268 group_ldof_counter += nvd * pmesh->GroupNVertices(gr);
269 group_ldof_counter += ned * pmesh->GroupNEdges(gr);
270 group_ldof_counter += ntd * pmesh->GroupNTriangles(gr);
271 group_ldof_counter += nqd * pmesh->GroupNQuadrilaterals(gr);
272 }
273 if (ldof_type)
274 {
275 group_ldof_counter *= vdim;
276 }
277 // allocate the I and J arrays in group_ldof
278 group_ldof.SetDims(ng, group_ldof_counter);
279
280 // build the full group_ldof table
281 group_ldof_counter = 0;
282 group_ldof.GetI()[0] = group_ldof.GetI()[1] = 0;
283 for (gr = 1; gr < ng; gr++)
284 {
285 int j, k, l, m, o, nv, ne, nt, nq;
286 const int *ind;
287
288 nv = pmesh->GroupNVertices(gr);
289 ne = pmesh->GroupNEdges(gr);
290 nt = pmesh->GroupNTriangles(gr);
291 nq = pmesh->GroupNQuadrilaterals(gr);
292
293 // vertices
294 if (nvd > 0)
295 {
296 for (j = 0; j < nv; j++)
297 {
298 k = pmesh->GroupVertex(gr, j);
299
300 dofs.SetSize(nvd);
301 m = nvd * k;
302 for (l = 0; l < nvd; l++, m++)
303 {
304 dofs[l] = m;
305 }
306
307 if (ldof_type)
308 {
309 DofsToVDofs(dofs);
310 }
311
312 for (l = 0; l < dofs.Size(); l++)
313 {
314 group_ldof.GetJ()[group_ldof_counter++] = dofs[l];
315 }
316 }
317 }
318
319 // edges
320 if (ned > 0)
321 {
322 for (j = 0; j < ne; j++)
323 {
324 pmesh->GroupEdge(gr, j, k, o);
325
326 dofs.SetSize(ned);
327 m = nvdofs+k*ned;
329 for (l = 0; l < ned; l++)
330 {
331 if (ind[l] < 0)
332 {
333 dofs[l] = m + (-1-ind[l]);
334 if (g_ldof_sign)
335 {
336 (*g_ldof_sign)[dofs[l]] = -1;
337 }
338 }
339 else
340 {
341 dofs[l] = m + ind[l];
342 }
343 }
344
345 if (ldof_type)
346 {
347 DofsToVDofs(dofs);
348 }
349
350 for (l = 0; l < dofs.Size(); l++)
351 {
352 group_ldof.GetJ()[group_ldof_counter++] = dofs[l];
353 }
354 }
355 }
356
357 // triangles
358 if (ntd > 0)
359 {
360 for (j = 0; j < nt; j++)
361 {
362 pmesh->GroupTriangle(gr, j, k, o);
363
364 dofs.SetSize(ntd);
365 m = nvdofs + nedofs + FirstFaceDof(k);
367 for (l = 0; l < ntd; l++)
368 {
369 if (ind[l] < 0)
370 {
371 dofs[l] = m + (-1-ind[l]);
372 if (g_ldof_sign)
373 {
374 (*g_ldof_sign)[dofs[l]] = -1;
375 }
376 }
377 else
378 {
379 dofs[l] = m + ind[l];
380 }
381 }
382
383 if (ldof_type)
384 {
385 DofsToVDofs(dofs);
386 }
387
388 for (l = 0; l < dofs.Size(); l++)
389 {
390 group_ldof.GetJ()[group_ldof_counter++] = dofs[l];
391 }
392 }
393 }
394
395 // quadrilaterals
396 if (nqd > 0)
397 {
398 for (j = 0; j < nq; j++)
399 {
400 pmesh->GroupQuadrilateral(gr, j, k, o);
401
402 dofs.SetSize(nqd);
403 m = nvdofs + nedofs + FirstFaceDof(k);
405 for (l = 0; l < nqd; l++)
406 {
407 if (ind[l] < 0)
408 {
409 dofs[l] = m + (-1-ind[l]);
410 if (g_ldof_sign)
411 {
412 (*g_ldof_sign)[dofs[l]] = -1;
413 }
414 }
415 else
416 {
417 dofs[l] = m + ind[l];
418 }
419 }
420
421 if (ldof_type)
422 {
423 DofsToVDofs(dofs);
424 }
425
426 for (l = 0; l < dofs.Size(); l++)
427 {
428 group_ldof.GetJ()[group_ldof_counter++] = dofs[l];
429 }
430 }
431 }
432
433 group_ldof.GetI()[gr+1] = group_ldof_counter;
434 }
435
436 gc.Finalize();
437}
438
439void ParFiniteElementSpace::ApplyLDofSigns(Array<int> &dofs) const
440{
441 MFEM_ASSERT(Conforming(), "wrong code path");
442
443 for (int i = 0; i < dofs.Size(); i++)
444 {
445 if (dofs[i] < 0)
446 {
447 if (ldof_sign[-1-dofs[i]] < 0)
448 {
449 dofs[i] = -1-dofs[i];
450 }
451 }
452 else
453 {
454 if (ldof_sign[dofs[i]] < 0)
455 {
456 dofs[i] = -1-dofs[i];
457 }
458 }
459 }
460}
461
462void ParFiniteElementSpace::ApplyLDofSigns(Table &el_dof) const
463{
464 Array<int> all_dofs(el_dof.GetJ(), el_dof.Size_of_connections());
465 ApplyLDofSigns(all_dofs);
466}
467
469 DofTransformation &doftrans) const
470{
471 if (elem_dof)
472 {
473 elem_dof->GetRow(i, dofs);
474
476 {
477 Array<int> Fo;
478 elem_fos->GetRow(i, Fo);
479 doftrans.SetDofTransformation(
481 doftrans.SetFaceOrientations(Fo);
482 doftrans.SetVDim();
483 }
484 return;
485 }
486 FiniteElementSpace::GetElementDofs(i, dofs, doftrans);
487 if (Conforming())
488 {
489 ApplyLDofSigns(dofs);
490 }
491}
492
494 DofTransformation &doftrans) const
495{
496 if (bdr_elem_dof)
497 {
498 bdr_elem_dof->GetRow(i, dofs);
499
501 {
502 Array<int> Fo;
503 bdr_elem_fos->GetRow(i, Fo);
504 doftrans.SetDofTransformation(
506 doftrans.SetFaceOrientations(Fo);
507 doftrans.SetVDim();
508 }
509 return;
510 }
511 FiniteElementSpace::GetBdrElementDofs(i, dofs, doftrans);
512 if (Conforming())
513 {
514 ApplyLDofSigns(dofs);
515 }
516}
517
519 int variant) const
520{
521 if (face_dof != nullptr && variant == 0)
522 {
523 face_dof->GetRow(i, dofs);
524 return fec->GetOrder();
525 }
526 int p = FiniteElementSpace::GetFaceDofs(i, dofs, variant);
527 if (Conforming())
528 {
529 ApplyLDofSigns(dofs);
530 }
531 return p;
532}
533
535{
536 int ne = mesh->GetNE();
537 if (i >= ne) { return GetFaceNbrFE(i - ne); }
538 else { return FiniteElementSpace::GetFE(i); }
539}
540
542 ElementDofOrdering f_ordering, FaceType type, L2FaceValues mul) const
543{
544 const bool is_dg_space = IsDGSpace();
545 const L2FaceValues m = (is_dg_space && mul==L2FaceValues::DoubleValued) ?
547 auto key = std::make_tuple(is_dg_space, f_ordering, type, m);
548 auto itr = L2F.find(key);
549 if (itr != L2F.end())
550 {
551 return itr->second;
552 }
553 else
554 {
555 FaceRestriction *res;
556 if (is_dg_space)
557 {
558 if (Conforming())
559 {
560 res = new ParL2FaceRestriction(*this, f_ordering, type, m);
561 }
562 else
563 {
564 res = new ParNCL2FaceRestriction(*this, f_ordering, type, m);
565 }
566 }
567 else
568 {
569 if (Conforming())
570 {
571 res = new ConformingFaceRestriction(*this, f_ordering, type);
572 }
573 else
574 {
575 res = new ParNCH1FaceRestriction(*this, f_ordering, type);
576 }
577 }
578 L2F[key] = res;
579 return res;
580 }
581}
582
584 int group, int ei, Array<int> &dofs) const
585{
586 int l_edge, ori;
587 MFEM_ASSERT(0 <= ei && ei < pmesh->GroupNEdges(group), "invalid edge index");
588 pmesh->GroupEdge(group, ei, l_edge, ori);
589 if (ori > 0) // ori = +1 or -1
590 {
591 GetEdgeDofs(l_edge, dofs);
592 }
593 else
594 {
595 Array<int> rdofs;
596 fec->SubDofOrder(Geometry::SEGMENT, 1, 1, dofs);
597 GetEdgeDofs(l_edge, rdofs);
598 for (int i = 0; i < dofs.Size(); i++)
599 {
600 const int di = dofs[i];
601 dofs[i] = (di >= 0) ? rdofs[di] : -1-rdofs[-1-di];
602 }
603 }
604}
605
607 int group, int fi, Array<int> &dofs) const
608{
609 int l_face, ori;
610 MFEM_ASSERT(0 <= fi && fi < pmesh->GroupNTriangles(group),
611 "invalid triangular face index");
612 pmesh->GroupTriangle(group, fi, l_face, ori);
613 if (ori == 0)
614 {
615 GetFaceDofs(l_face, dofs);
616 }
617 else
618 {
619 Array<int> rdofs;
620 fec->SubDofOrder(Geometry::TRIANGLE, 2, ori, dofs);
621 GetFaceDofs(l_face, rdofs);
622 for (int i = 0; i < dofs.Size(); i++)
623 {
624 const int di = dofs[i];
625 dofs[i] = (di >= 0) ? rdofs[di] : -1-rdofs[-1-di];
626 }
627 }
628}
629
631 int group, int fi, Array<int> &dofs) const
632{
633 int l_face, ori;
634 MFEM_ASSERT(0 <= fi && fi < pmesh->GroupNQuadrilaterals(group),
635 "invalid quadrilateral face index");
636 pmesh->GroupQuadrilateral(group, fi, l_face, ori);
637 if (ori == 0)
638 {
639 GetFaceDofs(l_face, dofs);
640 }
641 else
642 {
643 Array<int> rdofs;
644 fec->SubDofOrder(Geometry::SQUARE, 2, ori, dofs);
645 GetFaceDofs(l_face, rdofs);
646 for (int i = 0; i < dofs.Size(); i++)
647 {
648 const int di = dofs[i];
649 dofs[i] = (di >= 0) ? rdofs[di] : -1-rdofs[-1-di];
650 }
651 }
652}
653
654void ParFiniteElementSpace::GenerateGlobalOffsets() const
655{
656 MFEM_ASSERT(Conforming(), "wrong code path");
657
658 HYPRE_BigInt ldof[2];
659 Array<HYPRE_BigInt> *offsets[2] = { &dof_offsets, &tdof_offsets };
660
661 ldof[0] = GetVSize();
662 ldof[1] = TrueVSize();
663
664 pmesh->GenerateOffsets(2, ldof, offsets);
665
666 if (HYPRE_AssumedPartitionCheck())
667 {
668 // communicate the neighbor offsets in tdof_nb_offsets
669 GroupTopology &gt = GetGroupTopo();
670 int nsize = gt.GetNumNeighbors()-1;
671 MPI_Request *requests = new MPI_Request[2*nsize];
672 MPI_Status *statuses = new MPI_Status[2*nsize];
673 tdof_nb_offsets.SetSize(nsize+1);
674 tdof_nb_offsets[0] = tdof_offsets[0];
675
676 // send and receive neighbors' local tdof offsets
677 int request_counter = 0;
678 for (int i = 1; i <= nsize; i++)
679 {
680 MPI_Irecv(&tdof_nb_offsets[i], 1, HYPRE_MPI_BIG_INT,
681 gt.GetNeighborRank(i), 5365, MyComm,
682 &requests[request_counter++]);
683 }
684 for (int i = 1; i <= nsize; i++)
685 {
686 MPI_Isend(&tdof_nb_offsets[0], 1, HYPRE_MPI_BIG_INT,
687 gt.GetNeighborRank(i), 5365, MyComm,
688 &requests[request_counter++]);
689 }
690 MPI_Waitall(request_counter, requests, statuses);
691
692 delete [] statuses;
693 delete [] requests;
694 }
695}
696
697void ParFiniteElementSpace::CheckNDSTriaDofs()
698{
699 // Check for Nedelec basis
700 bool nd_basis = dynamic_cast<const ND_FECollection*>(fec);
701 if (!nd_basis)
702 {
703 nd_strias = false;
704 return;
705 }
706
707 // Check for interior face dofs on triangles (the use of TETRAHEDRON
708 // is not an error)
709 bool nd_fdof = fec->HasFaceDofs(Geometry::TETRAHEDRON,
711 if (!nd_fdof)
712 {
713 nd_strias = false;
714 return;
715 }
716
717 // Check for shared triangle faces
718 bool strias = false;
719 {
720 int ngrps = pmesh->GetNGroups();
721 for (int g = 1; g < ngrps; g++)
722 {
723 strias |= pmesh->GroupNTriangles(g);
724 }
725 }
726
727 // Combine results
728 int loc_nd_strias = strias ? 1 : 0;
729 int glb_nd_strias = 0;
730 MPI_Allreduce(&loc_nd_strias, &glb_nd_strias, 1,
731 MPI_INTEGER, MPI_SUM, MyComm);
732 nd_strias = glb_nd_strias > 0;
733}
734
735void ParFiniteElementSpace::Build_Dof_TrueDof_Matrix() const // matrix P
736{
737 MFEM_ASSERT(Conforming(), "wrong code path");
738
739 if (P) { return; }
740
741 if (!nd_strias)
742 {
743 // Safe to assume 1-1 correspondence between shared dofs
744 int ldof = GetVSize();
745 int ltdof = TrueVSize();
746
747 HYPRE_Int *i_diag = Memory<HYPRE_Int>(ldof+1);
748 HYPRE_Int *j_diag = Memory<HYPRE_Int>(ltdof);
749 int diag_counter;
750
751 HYPRE_Int *i_offd = Memory<HYPRE_Int>(ldof+1);
752 HYPRE_Int *j_offd = Memory<HYPRE_Int>(ldof-ltdof);
753 int offd_counter;
754
755 HYPRE_BigInt *cmap = Memory<HYPRE_BigInt>(ldof-ltdof);
756
757 HYPRE_BigInt *col_starts = GetTrueDofOffsets();
758 HYPRE_BigInt *row_starts = GetDofOffsets();
759
760 Array<Pair<HYPRE_BigInt, int> > cmap_j_offd(ldof-ltdof);
761
762 i_diag[0] = i_offd[0] = 0;
763 diag_counter = offd_counter = 0;
764 for (int i = 0; i < ldof; i++)
765 {
766 int ltdof_i = GetLocalTDofNumber(i);
767 if (ltdof_i >= 0)
768 {
769 j_diag[diag_counter++] = ltdof_i;
770 }
771 else
772 {
773 cmap_j_offd[offd_counter].one = GetGlobalTDofNumber(i);
774 cmap_j_offd[offd_counter].two = offd_counter;
775 offd_counter++;
776 }
777 i_diag[i+1] = diag_counter;
778 i_offd[i+1] = offd_counter;
779 }
780
781 SortPairs<HYPRE_BigInt, int>(cmap_j_offd, offd_counter);
782
783 for (int i = 0; i < offd_counter; i++)
784 {
785 cmap[i] = cmap_j_offd[i].one;
786 j_offd[cmap_j_offd[i].two] = i;
787 }
788
789 P = new HypreParMatrix(MyComm, MyRank, NRanks, row_starts, col_starts,
790 i_diag, j_diag, i_offd, j_offd,
791 cmap, offd_counter);
792 }
793 else
794 {
795 // Some shared dofs will be linear combinations of others
796 HYPRE_BigInt ldof = GetVSize();
797 HYPRE_BigInt ltdof = TrueVSize();
798
799 HYPRE_BigInt gdof = -1;
800 HYPRE_BigInt gtdof = -1;
801
802 MPI_Allreduce(&ldof, &gdof, 1, HYPRE_MPI_BIG_INT, MPI_SUM, MyComm);
803 MPI_Allreduce(&ltdof, &gtdof, 1, HYPRE_MPI_BIG_INT, MPI_SUM, MyComm);
804
805 // Ensure face orientations have been communicated
806 pmesh->ExchangeFaceNbrData();
807
808 // Locate and count non-zeros in off-diagonal portion of P
809 int nnz_offd = 0;
810 Array<int> ldsize(ldof); ldsize = 0;
811 Array<int> ltori(ldof); ltori = 0; // Local triangle orientations
812 {
813 int ngrps = pmesh->GetNGroups();
815 Array<int> sdofs;
816 for (int g = 1; g < ngrps; g++)
817 {
818 if (pmesh->gtopo.IAmMaster(g))
819 {
820 continue;
821 }
822 for (int ei=0; ei<pmesh->GroupNEdges(g); ei++)
823 {
824 this->GetSharedEdgeDofs(g, ei, sdofs);
825 for (int i=0; i<sdofs.Size(); i++)
826 {
827 int ind = (sdofs[i]>=0) ? sdofs[i] : (-sdofs[i]-1);
828 if (ldsize[ind] == 0) { nnz_offd++; }
829 ldsize[ind] = 1;
830 }
831 }
832 for (int fi=0; fi<pmesh->GroupNTriangles(g); fi++)
833 {
834 int face, ori, info1, info2;
835 pmesh->GroupTriangle(g, fi, face, ori);
836 pmesh->GetFaceInfos(face, &info1, &info2);
837 this->GetSharedTriangleDofs(g, fi, sdofs);
838 for (int i=0; i<3*nedofs; i++)
839 {
840 int ind = (sdofs[i]>=0) ? sdofs[i] : (-sdofs[i]-1);
841 if (ldsize[ind] == 0) { nnz_offd++; }
842 ldsize[ind] = 1;
843 }
844 for (int i=3*nedofs; i<sdofs.Size(); i++)
845 {
846 if (ldsize[sdofs[i]] == 0) { nnz_offd += 2; }
847 ldsize[sdofs[i]] = 2;
848 ltori[sdofs[i]] = info2 % 64;
849 }
850 }
851 for (int fi=0; fi<pmesh->GroupNQuadrilaterals(g); fi++)
852 {
853 this->GetSharedQuadrilateralDofs(g, fi, sdofs);
854 for (int i=0; i<sdofs.Size(); i++)
855 {
856 int ind = (sdofs[i]>=0) ? sdofs[i] : (-sdofs[i]-1);
857 if (ldsize[ind] == 0) { nnz_offd++; }
858 ldsize[ind] = 1;
859 }
860 }
861 }
862 }
863
864 HYPRE_Int *i_diag = Memory<HYPRE_Int>(ldof+1);
865 HYPRE_Int *j_diag = Memory<HYPRE_Int>(ltdof);
866 real_t *d_diag = Memory<real_t>(ltdof);
867 int diag_counter;
868
869 HYPRE_Int *i_offd = Memory<HYPRE_Int>(ldof+1);
870 HYPRE_Int *j_offd = Memory<HYPRE_Int>(nnz_offd);
871 real_t *d_offd = Memory<real_t>(nnz_offd);
872 int offd_counter;
873
874 HYPRE_BigInt *cmap = Memory<HYPRE_BigInt>(ldof-ltdof);
875
876 HYPRE_BigInt *col_starts = GetTrueDofOffsets();
877 HYPRE_BigInt *row_starts = GetDofOffsets();
878
879 Array<Pair<HYPRE_BigInt, int> > cmap_j_offd(ldof-ltdof);
880
881 i_diag[0] = i_offd[0] = 0;
882 diag_counter = offd_counter = 0;
883 int offd_col_counter = 0;
884 for (int i = 0; i < ldof; i++)
885 {
886 int ltdofi = GetLocalTDofNumber(i);
887 if (ltdofi >= 0)
888 {
889 j_diag[diag_counter] = ltdofi;
890 d_diag[diag_counter++] = 1.0;
891 }
892 else
893 {
894 if (ldsize[i] == 1)
895 {
896 cmap_j_offd[offd_col_counter].one = GetGlobalTDofNumber(i);
897 cmap_j_offd[offd_col_counter].two = offd_counter;
898 offd_counter++;
899 offd_col_counter++;
900 }
901 else
902 {
903 cmap_j_offd[offd_col_counter].one = GetGlobalTDofNumber(i);
904 cmap_j_offd[offd_col_counter].two = offd_counter;
905 offd_counter += 2;
906 offd_col_counter++;
907 i_diag[i+1] = diag_counter;
908 i_offd[i+1] = offd_counter;
909 i++;
910 cmap_j_offd[offd_col_counter].one = GetGlobalTDofNumber(i);
911 cmap_j_offd[offd_col_counter].two = offd_counter;
912 offd_counter += 2;
913 offd_col_counter++;
914 }
915 }
916 i_diag[i+1] = diag_counter;
917 i_offd[i+1] = offd_counter;
918 }
919
920 SortPairs<HYPRE_BigInt, int>(cmap_j_offd, offd_col_counter);
921
922 for (int i = 0; i < nnz_offd; i++)
923 {
924 j_offd[i] = -1;
925 d_offd[i] = 0.0;
926 }
927
928 for (int i = 0; i < offd_col_counter; i++)
929 {
930 cmap[i] = cmap_j_offd[i].one;
931 j_offd[cmap_j_offd[i].two] = i;
932 }
933
934 for (int i = 0; i < ldof; i++)
935 {
936 if (i_offd[i+1] == i_offd[i] + 1)
937 {
938 d_offd[i_offd[i]] = 1.0;
939 }
940 else if (i_offd[i+1] == i_offd[i] + 2)
941 {
942 const real_t *T =
944 j_offd[i_offd[i] + 1] = j_offd[i_offd[i]] + 1;
945 d_offd[i_offd[i]] = T[0]; d_offd[i_offd[i] + 1] = T[2];
946 i++;
947 j_offd[i_offd[i] + 1] = j_offd[i_offd[i]];
948 j_offd[i_offd[i]] = j_offd[i_offd[i] + 1] - 1;
949 d_offd[i_offd[i]] = T[1]; d_offd[i_offd[i] + 1] = T[3];
950 }
951 }
952
953 P = new HypreParMatrix(MyComm, gdof, gtdof, row_starts, col_starts,
954 i_diag, j_diag, d_diag, i_offd, j_offd, d_offd,
955 offd_col_counter, cmap);
956 }
957
958 SparseMatrix Pdiag;
959 P->GetDiag(Pdiag);
960 R = Transpose(Pdiag);
961}
962
964{
965 HypreParMatrix *P_pc;
966 Array<HYPRE_BigInt> P_pc_row_starts, P_pc_col_starts;
967 BuildParallelConformingInterpolation(&P_pc, NULL, P_pc_row_starts,
968 P_pc_col_starts, NULL, true);
969 P_pc->CopyRowStarts();
970 P_pc->CopyColStarts();
971 return P_pc;
972}
973
975{
976 GroupTopology &gt = GetGroupTopo();
977 for (int i = 0; i < ldof_group.Size(); i++)
978 {
979 if (gt.IAmMaster(ldof_group[i])) // we are the master
980 {
981 if (ldof_ltdof[i] >= 0) // see note below
982 {
983 vec[ldof_ltdof[i]] /= gt.GetGroupSize(ldof_group[i]);
984 }
985 // NOTE: in NC meshes, ldof_ltdof generated for the gtopo
986 // groups by ConstructTrueDofs gets overwritten by
987 // BuildParallelConformingInterpolation. Some DOFs that are
988 // seen as true by the conforming code are actually slaves and
989 // end up with a -1 in ldof_ltdof.
990 }
991 }
992}
993
995{
996 GroupCommunicator *gc = new GroupCommunicator(GetGroupTopo());
997 if (NURBSext)
998 {
999 gc->Create(pNURBSext()->ldof_group);
1000 }
1001 else
1002 {
1003 GetGroupComm(*gc, 0);
1004 }
1005 return gc;
1006}
1007
1009{
1010 // For non-conforming mesh, synchronization is performed on the cut (aka
1011 // "partially conforming") space.
1012
1013 MFEM_VERIFY(ldof_marker.Size() == GetVSize(), "invalid in/out array");
1014
1015 // implement allreduce(|) as reduce(|) + broadcast
1016 gcomm->Reduce<int>(ldof_marker, GroupCommunicator::BitOR);
1017 gcomm->Bcast(ldof_marker);
1018}
1019
1021 Array<int> &ess_dofs,
1022 int component) const
1023{
1024 FiniteElementSpace::GetEssentialVDofs(bdr_attr_is_ess, ess_dofs, component);
1025
1026 // Make sure that processors without boundary elements mark
1027 // their boundary dofs (if they have any).
1028 Synchronize(ess_dofs);
1029}
1030
1032 &bdr_attr_is_ess,
1033 Array<int> &ess_tdof_list,
1034 int component) const
1035{
1036 Array<int> ess_dofs, true_ess_dofs;
1037
1038 GetEssentialVDofs(bdr_attr_is_ess, ess_dofs, component);
1039 GetRestrictionMatrix()->BooleanMult(ess_dofs, true_ess_dofs);
1040
1041#ifdef MFEM_DEBUG
1042 // Verify that in boolean arithmetic: P^T ess_dofs = R ess_dofs.
1043 Array<int> true_ess_dofs2(true_ess_dofs.Size());
1044 auto Pt = std::unique_ptr<HypreParMatrix>(Dof_TrueDof_Matrix()->Transpose());
1045
1046 const int *ess_dofs_data = ess_dofs.HostRead();
1047 Pt->BooleanMult(1, ess_dofs_data, 0, true_ess_dofs2);
1048 int counter = 0;
1049 const int *ted = true_ess_dofs.HostRead();
1050 std::string error_msg = "failed dof: ";
1051 for (int i = 0; i < true_ess_dofs.Size(); i++)
1052 {
1053 if (bool(ted[i]) != bool(true_ess_dofs2[i]))
1054 {
1055 error_msg += std::to_string(i) += "(R ";
1056 error_msg += std::to_string(bool(ted[i])) += " P^T ";
1057 error_msg += std::to_string(bool(true_ess_dofs2[i])) += ") ";
1058 ++counter;
1059 }
1060 }
1061 MFEM_ASSERT(R->Height() == P->Width(), "!");
1062 MFEM_ASSERT(R->Width() == P->Height(), "!");
1063 MFEM_ASSERT(R->Width() == ess_dofs.Size(), "!");
1064 MFEM_VERIFY(counter == 0, "internal MFEM error: counter = " << counter
1065 << ", rank = " << MyRank << ", " << error_msg);
1066#endif
1067
1068 MarkerToList(true_ess_dofs, ess_tdof_list);
1069}
1070
1072{
1073 if (Nonconforming())
1074 {
1075 Dof_TrueDof_Matrix(); // make sure P has been built
1076
1077 return ldof_ltdof[ldof]; // NOTE: contains -1 for slaves/DOFs we don't own
1078 }
1079 else
1080 {
1081 if (GetGroupTopo().IAmMaster(ldof_group[ldof]))
1082 {
1083 return ldof_ltdof[ldof];
1084 }
1085 else
1086 {
1087 return -1;
1088 }
1089 }
1090}
1091
1093{
1094 if (Nonconforming())
1095 {
1096 MFEM_VERIFY(ldof_ltdof[ldof] >= 0, "ldof " << ldof << " not a true DOF.");
1097
1098 return GetMyTDofOffset() + ldof_ltdof[ldof];
1099 }
1100 else
1101 {
1102 if (HYPRE_AssumedPartitionCheck())
1103 {
1104 return ldof_ltdof[ldof] +
1105 tdof_nb_offsets[GetGroupTopo().GetGroupMaster(ldof_group[ldof])];
1106 }
1107 else
1108 {
1109 return ldof_ltdof[ldof] +
1110 tdof_offsets[GetGroupTopo().GetGroupMasterRank(ldof_group[ldof])];
1111 }
1112 }
1113}
1114
1116{
1117 if (Nonconforming())
1118 {
1119 MFEM_ABORT("Not implemented for NC mesh.");
1120 }
1121
1122 if (HYPRE_AssumedPartitionCheck())
1123 {
1125 {
1126 return ldof_ltdof[sldof] +
1127 tdof_nb_offsets[GetGroupTopo().GetGroupMaster(
1128 ldof_group[sldof])] / vdim;
1129 }
1130 else
1131 {
1132 return (ldof_ltdof[sldof*vdim] +
1133 tdof_nb_offsets[GetGroupTopo().GetGroupMaster(
1134 ldof_group[sldof*vdim])]) / vdim;
1135 }
1136 }
1137
1139 {
1140 return ldof_ltdof[sldof] +
1141 tdof_offsets[GetGroupTopo().GetGroupMasterRank(
1142 ldof_group[sldof])] / vdim;
1143 }
1144 else
1145 {
1146 return (ldof_ltdof[sldof*vdim] +
1147 tdof_offsets[GetGroupTopo().GetGroupMasterRank(
1148 ldof_group[sldof*vdim])]) / vdim;
1149 }
1150}
1151
1153{
1154 return HYPRE_AssumedPartitionCheck() ? dof_offsets[0] : dof_offsets[MyRank];
1155}
1156
1158{
1159 return HYPRE_AssumedPartitionCheck()? tdof_offsets[0] : tdof_offsets[MyRank];
1160}
1161
1163{
1164 if (Conforming())
1165 {
1166 if (Pconf) { return Pconf; }
1167
1168 if (nd_strias) { return Dof_TrueDof_Matrix(); }
1169
1170 if (NRanks == 1)
1171 {
1172 Pconf = new IdentityOperator(GetTrueVSize());
1173 }
1174 else
1175 {
1177 {
1178 Pconf = new ConformingProlongationOperator(*this);
1179 }
1180 else
1181 {
1182 Pconf = new DeviceConformingProlongationOperator(*this);
1183 }
1184 }
1185 return Pconf;
1186 }
1187 else
1188 {
1189 return Dof_TrueDof_Matrix();
1190 }
1191}
1192
1194{
1195 if (Conforming())
1196 {
1197 if (Rconf) { return Rconf; }
1198
1199 if (NRanks == 1)
1200 {
1202 }
1203 else
1204 {
1206 {
1207 R_transpose.reset(new ConformingProlongationOperator(*this, true));
1208 }
1209 else
1210 {
1211 R_transpose.reset(
1212 new DeviceConformingProlongationOperator(*this, true));
1213 }
1214 }
1215 Rconf = new TransposeOperator(*R_transpose);
1216 return Rconf;
1217 }
1218 else
1219 {
1221 if (!R_transpose) { R_transpose.reset(new TransposeOperator(R)); }
1222 return R;
1223 }
1224}
1225
1227{
1228 if (num_face_nbr_dofs >= 0) { return; }
1229
1230 pmesh->ExchangeFaceNbrData();
1231
1232 int num_face_nbrs = pmesh->GetNFaceNeighbors();
1233
1234 if (num_face_nbrs == 0)
1235 {
1237 return;
1238 }
1239
1240 MPI_Request *requests = new MPI_Request[2*num_face_nbrs];
1241 MPI_Request *send_requests = requests;
1242 MPI_Request *recv_requests = requests + num_face_nbrs;
1243 MPI_Status *statuses = new MPI_Status[num_face_nbrs];
1244
1245 Array<int> ldofs;
1246 Array<int> ldof_marker(GetVSize());
1247 ldof_marker = -1;
1248
1249 Table send_nbr_elem_dof;
1250
1251 send_nbr_elem_dof.MakeI(pmesh->send_face_nbr_elements.Size_of_connections());
1252 send_face_nbr_ldof.MakeI(num_face_nbrs);
1253 face_nbr_ldof.MakeI(num_face_nbrs);
1254 int *send_el_off = pmesh->send_face_nbr_elements.GetI();
1255 int *recv_el_off = pmesh->face_nbr_elements_offset;
1256 for (int fn = 0; fn < num_face_nbrs; fn++)
1257 {
1258 int *my_elems = pmesh->send_face_nbr_elements.GetRow(fn);
1259 int num_my_elems = pmesh->send_face_nbr_elements.RowSize(fn);
1260
1261 for (int i = 0; i < num_my_elems; i++)
1262 {
1263 GetElementVDofs(my_elems[i], ldofs);
1264 for (int j = 0; j < ldofs.Size(); j++)
1265 {
1266 int ldof = (ldofs[j] >= 0 ? ldofs[j] : -1-ldofs[j]);
1267
1268 if (ldof_marker[ldof] != fn)
1269 {
1270 ldof_marker[ldof] = fn;
1272 }
1273 }
1274 send_nbr_elem_dof.AddColumnsInRow(send_el_off[fn] + i, ldofs.Size());
1275 }
1276
1277 int nbr_rank = pmesh->GetFaceNbrRank(fn);
1278 int tag = 0;
1279 MPI_Isend(&send_face_nbr_ldof.GetI()[fn], 1, MPI_INT, nbr_rank, tag,
1280 MyComm, &send_requests[fn]);
1281
1282 MPI_Irecv(&face_nbr_ldof.GetI()[fn], 1, MPI_INT, nbr_rank, tag,
1283 MyComm, &recv_requests[fn]);
1284 }
1285
1286 MPI_Waitall(num_face_nbrs, recv_requests, statuses);
1288
1290
1291 MPI_Waitall(num_face_nbrs, send_requests, statuses);
1293
1294 // send/receive the I arrays of send_nbr_elem_dof/face_nbr_element_dof,
1295 // respectively (they contain the number of dofs for each face-neighbor
1296 // element)
1297 face_nbr_element_dof.MakeI(recv_el_off[num_face_nbrs]);
1298
1299 int *send_I = send_nbr_elem_dof.GetI();
1300 int *recv_I = face_nbr_element_dof.GetI();
1301 for (int fn = 0; fn < num_face_nbrs; fn++)
1302 {
1303 int nbr_rank = pmesh->GetFaceNbrRank(fn);
1304 int tag = 0;
1305 MPI_Isend(send_I + send_el_off[fn], send_el_off[fn+1] - send_el_off[fn],
1306 MPI_INT, nbr_rank, tag, MyComm, &send_requests[fn]);
1307
1308 MPI_Irecv(recv_I + recv_el_off[fn], recv_el_off[fn+1] - recv_el_off[fn],
1309 MPI_INT, nbr_rank, tag, MyComm, &recv_requests[fn]);
1310 }
1311
1312 MPI_Waitall(num_face_nbrs, send_requests, statuses);
1313 send_nbr_elem_dof.MakeJ();
1314
1315 ldof_marker = -1;
1316
1317 for (int fn = 0; fn < num_face_nbrs; fn++)
1318 {
1319 int *my_elems = pmesh->send_face_nbr_elements.GetRow(fn);
1320 int num_my_elems = pmesh->send_face_nbr_elements.RowSize(fn);
1321
1322 for (int i = 0; i < num_my_elems; i++)
1323 {
1324 GetElementVDofs(my_elems[i], ldofs);
1325 for (int j = 0; j < ldofs.Size(); j++)
1326 {
1327 int ldof = (ldofs[j] >= 0 ? ldofs[j] : -1-ldofs[j]);
1328
1329 if (ldof_marker[ldof] != fn)
1330 {
1331 ldof_marker[ldof] = fn;
1332 send_face_nbr_ldof.AddConnection(fn, ldofs[j]);
1333 }
1334 }
1335 send_nbr_elem_dof.AddConnections(
1336 send_el_off[fn] + i, ldofs, ldofs.Size());
1337 }
1338 }
1340 send_nbr_elem_dof.ShiftUpI();
1341
1342 // convert the ldof indices in send_nbr_elem_dof
1343 int *send_J = send_nbr_elem_dof.GetJ();
1344 for (int fn = 0, j = 0; fn < num_face_nbrs; fn++)
1345 {
1346 int num_ldofs = send_face_nbr_ldof.RowSize(fn);
1347 int *ldofs_fn = send_face_nbr_ldof.GetRow(fn);
1348 int j_end = send_I[send_el_off[fn+1]];
1349
1350 for (int i = 0; i < num_ldofs; i++)
1351 {
1352 int ldof = (ldofs_fn[i] >= 0 ? ldofs_fn[i] : -1-ldofs_fn[i]);
1353 ldof_marker[ldof] = i;
1354 }
1355
1356 for ( ; j < j_end; j++)
1357 {
1358 int ldof = (send_J[j] >= 0 ? send_J[j] : -1-send_J[j]);
1359 send_J[j] = (send_J[j] >= 0 ? ldof_marker[ldof] : -1-ldof_marker[ldof]);
1360 }
1361 }
1362
1363 MPI_Waitall(num_face_nbrs, recv_requests, statuses);
1365
1366 // send/receive the J arrays of send_nbr_elem_dof/face_nbr_element_dof,
1367 // respectively (they contain the element dofs in enumeration local for
1368 // the face-neighbor pair)
1369 int *recv_J = face_nbr_element_dof.GetJ();
1370 for (int fn = 0; fn < num_face_nbrs; fn++)
1371 {
1372 int nbr_rank = pmesh->GetFaceNbrRank(fn);
1373 int tag = 0;
1374
1375 MPI_Isend(send_J + send_I[send_el_off[fn]],
1376 send_I[send_el_off[fn+1]] - send_I[send_el_off[fn]],
1377 MPI_INT, nbr_rank, tag, MyComm, &send_requests[fn]);
1378
1379 MPI_Irecv(recv_J + recv_I[recv_el_off[fn]],
1380 recv_I[recv_el_off[fn+1]] - recv_I[recv_el_off[fn]],
1381 MPI_INT, nbr_rank, tag, MyComm, &recv_requests[fn]);
1382 }
1383
1384 MPI_Waitall(num_face_nbrs, recv_requests, statuses);
1385
1386 // shift the J array of face_nbr_element_dof
1387 for (int fn = 0, j = 0; fn < num_face_nbrs; fn++)
1388 {
1389 int shift = face_nbr_ldof.GetI()[fn];
1390 int j_end = recv_I[recv_el_off[fn+1]];
1391
1392 for ( ; j < j_end; j++)
1393 {
1394 if (recv_J[j] >= 0)
1395 {
1396 recv_J[j] += shift;
1397 }
1398 else
1399 {
1400 recv_J[j] -= shift;
1401 }
1402 }
1403 }
1404
1405 MPI_Waitall(num_face_nbrs, send_requests, statuses);
1406
1407 // send/receive the J arrays of send_face_nbr_ldof/face_nbr_ldof,
1408 // respectively
1409 for (int fn = 0; fn < num_face_nbrs; fn++)
1410 {
1411 int nbr_rank = pmesh->GetFaceNbrRank(fn);
1412 int tag = 0;
1413
1414 MPI_Isend(send_face_nbr_ldof.GetRow(fn),
1416 MPI_INT, nbr_rank, tag, MyComm, &send_requests[fn]);
1417
1418 MPI_Irecv(face_nbr_ldof.GetRow(fn),
1420 MPI_INT, nbr_rank, tag, MyComm, &recv_requests[fn]);
1421 }
1422
1423 MPI_Waitall(num_face_nbrs, recv_requests, statuses);
1424 MPI_Waitall(num_face_nbrs, send_requests, statuses);
1425
1426 // send my_dof_offset (i.e. my_ldof_offset) to face neighbors and receive
1427 // their offset in dof_face_nbr_offsets, used to define face_nbr_glob_dof_map
1429 Array<HYPRE_BigInt> dof_face_nbr_offsets(num_face_nbrs);
1430 HYPRE_BigInt my_dof_offset = GetMyDofOffset();
1431 for (int fn = 0; fn < num_face_nbrs; fn++)
1432 {
1433 int nbr_rank = pmesh->GetFaceNbrRank(fn);
1434 int tag = 0;
1435
1436 MPI_Isend(&my_dof_offset, 1, HYPRE_MPI_BIG_INT, nbr_rank, tag,
1437 MyComm, &send_requests[fn]);
1438
1439 MPI_Irecv(&dof_face_nbr_offsets[fn], 1, HYPRE_MPI_BIG_INT, nbr_rank, tag,
1440 MyComm, &recv_requests[fn]);
1441 }
1442
1443 MPI_Waitall(num_face_nbrs, recv_requests, statuses);
1444
1445 // set the array face_nbr_glob_dof_map which holds the global ldof indices of
1446 // the face-neighbor dofs
1447 for (int fn = 0, j = 0; fn < num_face_nbrs; fn++)
1448 {
1449 for (int j_end = face_nbr_ldof.GetI()[fn+1]; j < j_end; j++)
1450 {
1451 int ldof = face_nbr_ldof.GetJ()[j];
1452 if (ldof < 0)
1453 {
1454 ldof = -1-ldof;
1455 }
1456
1457 face_nbr_glob_dof_map[j] = dof_face_nbr_offsets[fn] + ldof;
1458 }
1459 }
1460
1461 MPI_Waitall(num_face_nbrs, send_requests, statuses);
1462
1463 delete [] statuses;
1464 delete [] requests;
1465}
1466
1468 int i, Array<int> &vdofs, DofTransformation &doftrans) const
1469{
1470 face_nbr_element_dof.GetRow(i, vdofs);
1471
1472 if (DoFTransArray[GetFaceNbrFE(i)->GetGeomType()])
1473 {
1474 Array<int> F, Fo;
1475 pmesh->GetFaceNbrElementFaces(pmesh->GetNE() + i, F, Fo);
1476 doftrans.SetDofTransformation(
1477 *DoFTransArray[GetFaceNbrFE(i)->GetGeomType()]);
1478 doftrans.SetFaceOrientations(Fo);
1479 doftrans.SetVDim(vdim, ordering);
1480 }
1481}
1482
1490
1492{
1493 // Works for NC mesh where 'i' is an index returned by
1494 // ParMesh::GetSharedFace() such that i >= Mesh::GetNumFaces(), i.e. 'i' is
1495 // the index of a ghost face.
1496 MFEM_ASSERT(Nonconforming() && i >= pmesh->GetNumFaces(), "");
1497 int el1, el2, inf1, inf2;
1498 pmesh->GetFaceElements(i, &el1, &el2);
1499 el2 = -1 - el2;
1500 pmesh->GetFaceInfos(i, &inf1, &inf2);
1501 MFEM_ASSERT(0 <= el2 && el2 < face_nbr_element_dof.Size(), "");
1502 const int nd = face_nbr_element_dof.RowSize(el2);
1503 const int *vol_vdofs = face_nbr_element_dof.GetRow(el2);
1504 const Element *face_nbr_el = pmesh->face_nbr_elements[el2];
1505 Geometry::Type geom = face_nbr_el->GetGeometryType();
1506 const int face_dim = Geometry::Dimension[geom]-1;
1507
1508 fec->SubDofOrder(geom, face_dim, inf2, vdofs);
1509 // Convert local dofs to local vdofs.
1510 Ordering::DofsToVDofs<Ordering::byNODES>(nd/vdim, vdim, vdofs);
1511 // Convert local vdofs to global vdofs.
1512 for (int j = 0; j < vdofs.Size(); j++)
1513 {
1514 const int ldof = vdofs[j];
1515 vdofs[j] = (ldof >= 0) ? vol_vdofs[ldof] : -1-vol_vdofs[-1-ldof];
1516 }
1517}
1518
1520{
1521 const FiniteElement *FE =
1523 pmesh->face_nbr_elements[i]->GetGeometryType());
1524
1525 if (NURBSext)
1526 {
1527 mfem_error("ParFiniteElementSpace::GetFaceNbrFE"
1528 " does not support NURBS!");
1529 }
1530 return FE;
1531}
1532
1534{
1535 // Works for NC mesh where 'i' is an index returned by
1536 // ParMesh::GetSharedFace() such that i >= Mesh::GetNumFaces(), i.e. 'i' is
1537 // the index of a ghost face.
1538 // Works in tandem with GetFaceNbrFaceVDofs() defined above.
1539
1540 MFEM_ASSERT(Nonconforming() && !NURBSext, "");
1541 Geometry::Type face_geom = pmesh->GetFaceGeometry(i);
1542 return fec->FiniteElementForGeometry(face_geom);
1543}
1544
1546{
1547 P -> StealData();
1548#if MFEM_HYPRE_VERSION <= 22200
1549 hypre_ParCSRMatrix *csrP = (hypre_ParCSRMatrix*)(*P);
1550 hypre_ParCSRMatrixOwnsRowStarts(csrP) = 1;
1551 hypre_ParCSRMatrixOwnsColStarts(csrP) = 1;
1552 dof_offsets.LoseData();
1553 tdof_offsets.LoseData();
1554#else
1555 dof_offsets.DeleteAll();
1556 tdof_offsets.DeleteAll();
1557#endif
1558}
1559
1560void ParFiniteElementSpace::ConstructTrueDofs()
1561{
1562 int i, gr, n = GetVSize();
1563 GroupTopology &gt = pmesh->gtopo;
1564 gcomm = new GroupCommunicator(gt);
1565 Table &group_ldof = gcomm->GroupLDofTable();
1566
1567 GetGroupComm(*gcomm, 1, &ldof_sign);
1568
1569 // Define ldof_group and mark ldof_ltdof with
1570 // -1 for ldof that is ours
1571 // -2 for ldof that is in a group with another master
1572 ldof_group.SetSize(n);
1573 ldof_ltdof.SetSize(n);
1574 ldof_group = 0;
1575 ldof_ltdof = -1;
1576
1577 for (gr = 1; gr < group_ldof.Size(); gr++)
1578 {
1579 const int *ldofs = group_ldof.GetRow(gr);
1580 const int nldofs = group_ldof.RowSize(gr);
1581 for (i = 0; i < nldofs; i++)
1582 {
1583 ldof_group[ldofs[i]] = gr;
1584 }
1585
1586 if (!gt.IAmMaster(gr)) // we are not the master
1587 {
1588 for (i = 0; i < nldofs; i++)
1589 {
1590 ldof_ltdof[ldofs[i]] = -2;
1591 }
1592 }
1593 }
1594
1595 // count ltdof_size
1596 ltdof_size = 0;
1597 for (i = 0; i < n; i++)
1598 {
1599 if (ldof_ltdof[i] == -1)
1600 {
1601 ldof_ltdof[i] = ltdof_size++;
1602 }
1603 }
1604 gcomm->SetLTDofTable(ldof_ltdof);
1605
1606 // have the group masters broadcast their ltdofs to the rest of the group
1607 gcomm->Bcast(ldof_ltdof);
1608}
1609
1610void ParFiniteElementSpace::ConstructTrueNURBSDofs()
1611{
1612 int n = GetVSize();
1613 GroupTopology &gt = pNURBSext()->gtopo;
1614 gcomm = new GroupCommunicator(gt);
1615
1616 // pNURBSext()->ldof_group is for scalar space!
1617 if (vdim == 1)
1618 {
1619 ldof_group.MakeRef(pNURBSext()->ldof_group);
1620 }
1621 else
1622 {
1623 const int *scalar_ldof_group = pNURBSext()->ldof_group;
1624 ldof_group.SetSize(n);
1625 for (int i = 0; i < n; i++)
1626 {
1627 ldof_group[i] = scalar_ldof_group[VDofToDof(i)];
1628 }
1629 }
1630
1631 gcomm->Create(ldof_group);
1632
1633 // ldof_sign.SetSize(n);
1634 // ldof_sign = 1;
1635 ldof_sign.DeleteAll();
1636
1637 ltdof_size = 0;
1638 ldof_ltdof.SetSize(n);
1639 for (int i = 0; i < n; i++)
1640 {
1641 if (gt.IAmMaster(ldof_group[i]))
1642 {
1643 ldof_ltdof[i] = ltdof_size;
1644 ltdof_size++;
1645 }
1646 else
1647 {
1648 ldof_ltdof[i] = -2;
1649 }
1650 }
1651 gcomm->SetLTDofTable(ldof_ltdof);
1652
1653 // have the group masters broadcast their ltdofs to the rest of the group
1654 gcomm->Bcast(ldof_ltdof);
1655}
1656
1657void ParFiniteElementSpace::GetGhostVertexDofs(const MeshId &id,
1658 Array<int> &dofs) const
1659{
1661 dofs.SetSize(nv);
1662 for (int j = 0; j < nv; j++)
1663 {
1664 dofs[j] = ndofs + nv*id.index + j;
1665 }
1666}
1667
1668void ParFiniteElementSpace::GetGhostEdgeDofs(const MeshId &edge_id,
1669 Array<int> &dofs) const
1670{
1673 dofs.SetSize(2*nv + ne);
1674
1675 int V[2], ghost = pncmesh->GetNVertices();
1676 pmesh->pncmesh->GetEdgeVertices(edge_id, V);
1677
1678 for (int i = 0; i < 2; i++)
1679 {
1680 int k = (V[i] < ghost) ? V[i]*nv : (ndofs + (V[i] - ghost)*nv);
1681 for (int j = 0; j < nv; j++)
1682 {
1683 dofs[i*nv + j] = k++;
1684 }
1685 }
1686
1687 int k = ndofs + ngvdofs + (edge_id.index - pncmesh->GetNEdges())*ne;
1688 for (int j = 0; j < ne; j++)
1689 {
1690 dofs[2*nv + j] = k++;
1691 }
1692}
1693
1694void ParFiniteElementSpace::GetGhostFaceDofs(const MeshId &face_id,
1695 Array<int> &dofs) const
1696{
1697 int nfv, V[4], E[4], Eo[4];
1698 nfv = pmesh->pncmesh->GetFaceVerticesEdges(face_id, V, E, Eo);
1699
1702 int nf_tri = fec->DofForGeometry(Geometry::TRIANGLE);
1703 int nf_quad = fec->DofForGeometry(Geometry::SQUARE);
1704 int nf = (nfv == 3) ? nf_tri : nf_quad;
1705
1706 dofs.SetSize(nfv*(nv + ne) + nf);
1707
1708 int offset = 0;
1709 for (int i = 0; i < nfv; i++)
1710 {
1711 int ghost = pncmesh->GetNVertices();
1712 int first = (V[i] < ghost) ? V[i]*nv : (ndofs + (V[i] - ghost)*nv);
1713 for (int j = 0; j < nv; j++)
1714 {
1715 dofs[offset++] = first + j;
1716 }
1717 }
1718
1719 for (int i = 0; i < nfv; i++)
1720 {
1721 int ghost = pncmesh->GetNEdges();
1722 int first = (E[i] < ghost) ? nvdofs + E[i]*ne
1723 /* */ : ndofs + ngvdofs + (E[i] - ghost)*ne;
1724 const int *ind = fec->DofOrderForOrientation(Geometry::SEGMENT, Eo[i]);
1725 for (int j = 0; j < ne; j++)
1726 {
1727 dofs[offset++] = (ind[j] >= 0) ? (first + ind[j])
1728 /* */ : (-1 - (first + (-1 - ind[j])));
1729 }
1730 }
1731
1732 const int ghost_face_index = face_id.index - pncmesh->GetNFaces();
1733 int first = ndofs + ngvdofs + ngedofs + nf_quad*ghost_face_index;
1734
1735 for (int j = 0; j < nf; j++)
1736 {
1737 dofs[offset++] = first + j;
1738 }
1739}
1740
1741void ParFiniteElementSpace::GetGhostDofs(int entity, const MeshId &id,
1742 Array<int> &dofs) const
1743{
1744 // helper to get ghost vertex, ghost edge or ghost face DOFs
1745 switch (entity)
1746 {
1747 case 0: GetGhostVertexDofs(id, dofs); break;
1748 case 1: GetGhostEdgeDofs(id, dofs); break;
1749 case 2: GetGhostFaceDofs(id, dofs); break;
1750 }
1751}
1752
1753void ParFiniteElementSpace::GetBareDofs(int entity, int index,
1754 Array<int> &dofs) const
1755{
1756 int ned, ghost, first;
1757 switch (entity)
1758 {
1759 case 0:
1761 ghost = pncmesh->GetNVertices();
1762 first = (index < ghost)
1763 ? index*ned // regular vertex
1764 : ndofs + (index - ghost)*ned; // ghost vertex
1765 break;
1766
1767 case 1:
1769 ghost = pncmesh->GetNEdges();
1770 first = (index < ghost)
1771 ? nvdofs + index*ned // regular edge
1772 : ndofs + ngvdofs + (index - ghost)*ned; // ghost edge
1773 break;
1774
1775 default:
1776 Geometry::Type geom = pncmesh->GetFaceGeometry(index);
1777 MFEM_ASSERT(geom == Geometry::SQUARE ||
1778 geom == Geometry::TRIANGLE, "");
1779
1780 ned = fec->DofForGeometry(geom);
1781 ghost = pncmesh->GetNFaces();
1782
1783 if (index < ghost) // regular face
1784 {
1785 first = nvdofs + nedofs + FirstFaceDof(index);
1786 }
1787 else // ghost face
1788 {
1789 index -= ghost;
1790 int stride = fec->DofForGeometry(Geometry::SQUARE);
1791 first = ndofs + ngvdofs + ngedofs + index*stride;
1792 }
1793 break;
1794 }
1795
1796 dofs.SetSize(ned);
1797 for (int i = 0; i < ned; i++)
1798 {
1799 dofs[i] = first + i;
1800 }
1801}
1802
1803int ParFiniteElementSpace::PackDof(int entity, int index, int edof) const
1804{
1805 // DOFs are ordered as follows:
1806 // vertices | edges | faces | internal | ghost vert. | g. edges | g. faces
1807
1808 int ghost, ned;
1809 switch (entity)
1810 {
1811 case 0:
1812 ghost = pncmesh->GetNVertices();
1814
1815 return (index < ghost)
1816 ? index*ned + edof // regular vertex
1817 : ndofs + (index - ghost)*ned + edof; // ghost vertex
1818
1819 case 1:
1820 ghost = pncmesh->GetNEdges();
1822
1823 return (index < ghost)
1824 ? nvdofs + index*ned + edof // regular edge
1825 : ndofs + ngvdofs + (index - ghost)*ned + edof; // ghost edge
1826
1827 default:
1828 ghost = pncmesh->GetNFaces();
1829 ned = fec->DofForGeometry(pncmesh->GetFaceGeometry(index));
1830
1831 if (index < ghost) // regular face
1832 {
1833 return nvdofs + nedofs + FirstFaceDof(index) + edof;
1834 }
1835 else // ghost face
1836 {
1837 index -= ghost;
1838 int stride = fec->DofForGeometry(Geometry::SQUARE);
1839 return ndofs + ngvdofs + ngedofs + index*stride + edof;
1840 }
1841 }
1842}
1843
1844static int bisect(const int* array, int size, int value)
1845{
1846 const int* end = array + size;
1847 const int* pos = std::upper_bound(array, end, value);
1848 MFEM_VERIFY(pos != array, "value not found");
1849 if (pos == end)
1850 {
1851 MFEM_VERIFY(*(array+size - 1) == value, "Last entry must be exact")
1852 }
1853 return pos - array - 1;
1854}
1855
1856/** Dissect a DOF number to obtain the entity type (0=vertex, 1=edge, 2=face),
1857 * entity index and the DOF number within the entity.
1858 */
1859void ParFiniteElementSpace::UnpackDof(int dof,
1860 int &entity, int &index, int &edof) const
1861{
1862 MFEM_VERIFY(dof >= 0, "");
1863 if (dof < ndofs)
1864 {
1865 if (dof < nvdofs) // regular vertex
1866 {
1868 entity = 0, index = dof / nv, edof = dof % nv;
1869 return;
1870 }
1871 dof -= nvdofs;
1872 if (dof < nedofs) // regular edge
1873 {
1875 entity = 1, index = dof / ne, edof = dof % ne;
1876 return;
1877 }
1878 dof -= nedofs;
1879 if (dof < nfdofs) // regular face
1880 {
1881 if (uni_fdof >= 0) // uniform faces
1882 {
1883 int nf = fec->DofForGeometry(pncmesh->GetFaceGeometry(0));
1884 index = dof / nf, edof = dof % nf;
1885 }
1886 else // mixed faces or var-order space
1887 {
1888 const Table &table = var_face_dofs;
1889
1890 MFEM_ASSERT(table.Size() > 0, "");
1891 int jpos = bisect(table.GetJ(), table.Size_of_connections(), dof);
1892 index = bisect(table.GetI(), table.Size(), jpos);
1893 edof = dof - table.GetRow(index)[0];
1894 }
1895 entity = 2;
1896 return;
1897 }
1898 MFEM_ABORT("Cannot unpack internal DOF");
1899 }
1900 else
1901 {
1902 dof -= ndofs;
1903 if (dof < ngvdofs) // ghost vertex
1904 {
1906 entity = 0, index = pncmesh->GetNVertices() + dof / nv, edof = dof % nv;
1907 return;
1908 }
1909 dof -= ngvdofs;
1910 if (dof < ngedofs) // ghost edge
1911 {
1913 entity = 1, index = pncmesh->GetNEdges() + dof / ne, edof = dof % ne;
1914 return;
1915 }
1916 dof -= ngedofs;
1917 if (dof < ngfdofs) // ghost face
1918 {
1919 int stride = fec->DofForGeometry(Geometry::SQUARE);
1920 index = pncmesh->GetNFaces() + dof / stride, edof = dof % stride;
1921 entity = 2;
1922 return;
1923 }
1924 MFEM_ABORT("Out of range DOF.");
1925 }
1926}
1927
1928/** Represents an element of the P matrix. The column number is global and
1929 * corresponds to vector dimension 0. The other dimension columns are offset
1930 * by 'stride'.
1931 */
1932struct PMatrixElement
1933{
1934 HYPRE_BigInt column;
1935 int stride;
1936 real_t value;
1937
1938 PMatrixElement(HYPRE_BigInt col = 0, int str = 0, real_t val = 0)
1939 : column(col), stride(str), value(val) {}
1940
1941 bool operator<(const PMatrixElement &other) const
1942 { return column < other.column; }
1943
1944 typedef std::vector<PMatrixElement> List;
1945};
1946
1947/** Represents one row of the P matrix, for the construction code below.
1948 * The row is complete: diagonal and offdiagonal elements are not distinguished.
1949 */
1950struct PMatrixRow
1951{
1952 PMatrixElement::List elems;
1953
1954 /// Add other row, times 'coef'.
1955 void AddRow(const PMatrixRow &other, real_t coef)
1956 {
1957 elems.reserve(elems.size() + other.elems.size());
1958 for (const PMatrixElement &oei : other.elems)
1959 {
1960 elems.emplace_back(oei.column, oei.stride, coef * oei.value);
1961 }
1962 }
1963
1964 /// Remove duplicate columns and sum their values.
1965 void Collapse()
1966 {
1967 if (!elems.size()) { return; }
1968 std::sort(elems.begin(), elems.end());
1969
1970 int j = 0;
1971 for (unsigned i = 1; i < elems.size(); i++)
1972 {
1973 if (elems[j].column == elems[i].column)
1974 {
1975 elems[j].value += elems[i].value;
1976 }
1977 else
1978 {
1979 elems[++j] = elems[i];
1980 }
1981 }
1982 elems.resize(j+1);
1983 }
1984
1985 void write(std::ostream &os, real_t sign) const
1986 {
1987 bin_io::write<int>(os, elems.size());
1988 for (unsigned i = 0; i < elems.size(); i++)
1989 {
1990 const PMatrixElement &e = elems[i];
1991 bin_io::write<HYPRE_BigInt>(os, e.column);
1992 bin_io::write<int>(os, e.stride);
1993 bin_io::write<real_t>(os, e.value * sign);
1994 }
1995 }
1996
1997 void read(std::istream &is, real_t sign)
1998 {
1999 elems.resize(bin_io::read<int>(is));
2000 for (unsigned i = 0; i < elems.size(); i++)
2001 {
2002 PMatrixElement &e = elems[i];
2003 e.column = bin_io::read<HYPRE_BigInt>(is);
2004 e.stride = bin_io::read<int>(is);
2005 e.value = bin_io::read<real_t>(is) * sign;
2006 }
2007 }
2008};
2009
2010/** Represents a message to another processor containing P matrix rows.
2011 * Used by ParFiniteElementSpace::ParallelConformingInterpolation.
2012 */
2013class NeighborRowMessage : public VarMessage<314>
2014{
2015public:
2016 typedef NCMesh::MeshId MeshId;
2017 typedef ParNCMesh::GroupId GroupId;
2018 struct RowInfo
2019 {
2020 int entity, index, edof;
2021 GroupId group;
2022 PMatrixRow row;
2023
2024 RowInfo(int ent, int idx, int edof, GroupId grp, const PMatrixRow &row)
2025 : entity(ent), index(idx), edof(edof), group(grp), row(row) {}
2026
2027 RowInfo(int ent, int idx, int edof, GroupId grp)
2028 : entity(ent), index(idx), edof(edof), group(grp) {}
2029 };
2030
2031 NeighborRowMessage() : pncmesh(NULL) {}
2032
2033 void AddRow(int entity, int index, int edof, GroupId group,
2034 const PMatrixRow &row)
2035 {
2036 rows.emplace_back(entity, index, edof, group, row);
2037 }
2038
2039 const std::vector<RowInfo>& GetRows() const { return rows; }
2040
2041 void SetNCMesh(ParNCMesh* pnc) { pncmesh = pnc; }
2042 void SetFEC(const FiniteElementCollection* fec_) { this->fec = fec_; }
2043
2044 typedef std::map<int, NeighborRowMessage> Map;
2045
2046protected:
2047 std::vector<RowInfo> rows;
2048
2049 ParNCMesh *pncmesh;
2050 const FiniteElementCollection* fec;
2051
2052 /// Encode a NeighborRowMessage for sending via MPI.
2053 void Encode(int rank) override;
2054 /// Decode a NeighborRowMessage received via MPI.
2055 void Decode(int rank) override;
2056};
2057
2058void NeighborRowMessage::Encode(int rank)
2059{
2060 std::ostringstream stream;
2061
2062 Array<MeshId> ent_ids[3];
2063 Array<GroupId> group_ids[3];
2064 Array<int> row_idx[3];
2065
2066 // encode MeshIds and groups
2067 for (unsigned i = 0; i < rows.size(); i++)
2068 {
2069 const RowInfo &ri = rows[i];
2070 const MeshId &id = *pncmesh->GetNCList(ri.entity).GetMeshIdAndType(ri.index).id;
2071 ent_ids[ri.entity].Append(id);
2072 row_idx[ri.entity].Append(i);
2073 group_ids[ri.entity].Append(ri.group);
2074 }
2075
2076 Array<GroupId> all_group_ids;
2077 all_group_ids.Reserve(rows.size());
2078 for (int i = 0; i < 3; i++)
2079 {
2080 all_group_ids.Append(group_ids[i]);
2081 }
2082
2083 pncmesh->AdjustMeshIds(ent_ids, rank);
2084 pncmesh->EncodeMeshIds(stream, ent_ids);
2085 pncmesh->EncodeGroups(stream, all_group_ids);
2086
2087 // write all rows to the stream
2088 for (int ent = 0; ent < 3; ent++)
2089 {
2090 const Array<MeshId> &ids = ent_ids[ent];
2091 for (int i = 0; i < ids.Size(); i++)
2092 {
2093 const MeshId &id = ids[i];
2094 const RowInfo &ri = rows[row_idx[ent][i]];
2095 MFEM_ASSERT(ent == ri.entity, "");
2096
2097#ifdef MFEM_DEBUG_PMATRIX
2098 mfem::out << "Rank " << pncmesh->MyRank << " sending to " << rank
2099 << ": ent " << ri.entity << ", index " << ri.index
2100 << ", edof " << ri.edof << " (id " << id.element << "/"
2101 << int(id.local) << ")" << std::endl;
2102#endif
2103
2104 // handle orientation and sign change
2105 int edof = ri.edof;
2106 real_t s = 1.0;
2107 if (ent == 1)
2108 {
2109 int eo = pncmesh->GetEdgeNCOrientation(id);
2110 const int* ind = fec->DofOrderForOrientation(Geometry::SEGMENT, eo);
2111 if ((edof = ind[edof]) < 0)
2112 {
2113 edof = -1 - edof;
2114 s = -1;
2115 }
2116 }
2117
2118 bin_io::write<int>(stream, edof);
2119 ri.row.write(stream, s);
2120 }
2121 }
2122
2123 rows.clear();
2124 stream.str().swap(data);
2125}
2126
2127void NeighborRowMessage::Decode(int rank)
2128{
2129 std::istringstream stream(data);
2130
2131 Array<MeshId> ent_ids[3];
2132 Array<GroupId> group_ids;
2133
2134 // decode vertex/edge/face IDs and groups
2135 pncmesh->DecodeMeshIds(stream, ent_ids);
2136 pncmesh->DecodeGroups(stream, group_ids);
2137
2138 int nrows = ent_ids[0].Size() + ent_ids[1].Size() + ent_ids[2].Size();
2139 MFEM_ASSERT(nrows == group_ids.Size(), "");
2140
2141 rows.clear();
2142 rows.reserve(nrows);
2143
2144 // read rows ent = {0,1,2} means vertex, edge and face entity
2145 for (int ent = 0, gi = 0; ent < 3; ent++)
2146 {
2147 // extract the vertex list, edge list or face list.
2148 const Array<MeshId> &ids = ent_ids[ent];
2149 for (int i = 0; i < ids.Size(); i++)
2150 {
2151 const MeshId &id = ids[i];
2152 // read the particular element dof value off the stream.
2153 int edof = bin_io::read<int>(stream);
2154
2155 // Handle orientation and sign change. This flips the sign on dofs
2156 // where necessary, and for edges and faces also reorders if flipped,
2157 // i.e. an edge with 1 -> 2 -> 3 -> 4 might become -4 -> -3 -> -2 -> -1
2158 // This cannot treat all face dofs, as they can have rotations and
2159 // reflections.
2160 const int *ind = nullptr;
2162 if (ent == 1)
2163 {
2164 // edge NC orientation is element defined.
2165 int eo = pncmesh->GetEdgeNCOrientation(id);
2167 }
2168 else if (ent == 2)
2169 {
2170 geom = pncmesh->GetFaceGeometry(id.index);
2171 int fo = pncmesh->GetFaceOrientation(id.index);
2172 ind = fec->DofOrderForOrientation(geom, fo);
2173 }
2174 // Tri faces with second order basis have dofs that must be processed
2175 // in pairs, as the doftransformation is not diagonal.
2176 const bool process_dof_pairs = (ent == 2 &&
2178 && !Geometry::IsTensorProduct(geom));
2179
2180#ifdef MFEM_DEBUG_PMATRIX
2181 mfem::out << "Rank " << pncmesh->MyRank << " receiving from " << rank
2182 << ": ent " << ent << ", index " << id.index
2183 << ", edof " << edof << " (id " << id.element << "/"
2184 << int(id.local) << ")" << std::endl;
2185#endif
2186
2187 // If edof arrived with a negative index, flip it, and the scaling.
2188 real_t s = (edof < 0) ? -1.0 : 1.0;
2189 edof = (edof < 0) ? -1 - edof : edof;
2190 if (ind && (edof = ind[edof]) < 0)
2191 {
2192 edof = -1 - edof;
2193 s *= -1.0;
2194 }
2195
2196 // Create a row for this entity, recording the index of the mesh
2197 // element
2198 rows.emplace_back(ent, id.index, edof, group_ids[gi++]);
2199 rows.back().row.read(stream, s);
2200
2201#ifdef MFEM_DEBUG_PMATRIX
2202 mfem::out << "Rank " << pncmesh->MyRank << " receiving from " << rank
2203 << ": ent " << rows.back().entity << ", index "
2204 << rows.back().index << ", edof " << rows.back().edof
2205 << std::endl;
2206#endif
2207
2208 if (process_dof_pairs)
2209 {
2210 // ND face dofs need to be processed together, as the transformation
2211 // is given by a 2x2 matrix, so we manually apply an extra increment
2212 // to the loop counter and add in a new row. Once these rows are
2213 // placed, they represent the Identity transformation. To map across
2214 // the processor boundary, we also need to apply a Primal
2215 // Transformation (see doftrans.hpp) to a notional "global dof"
2216 // orientation. For simplicity we perform the action of these 2x2
2217 // matrices manually using the AddRow capability, followed by a
2218 // Collapse.
2219
2220 // To perform the operations, we add and subtract initial versions
2221 // of the rows, that represent [1 0; 0 1] in row major notation. The
2222 // first row represents the 1 at (0,0) in [1 0; 0 1] The second row
2223 // represents the 1 at (1,1) in [1 0; 0 1]
2224
2225 // We can safely bind this reference as rows was reserved above so
2226 // there is no hidden copying that could result in a dangling
2227 // reference.
2228 auto &first_row = rows.back().row;
2229 // This is the first "fundamental unit" used in the transformation.
2230 const auto initial_first_row = first_row;
2231 // Extract the next dof too, and apply any dof order transformation
2232 // expected.
2233 const MeshId &next_id = ids[++i];
2234 const int fo = pncmesh->GetFaceOrientation(next_id.index);
2235 ind = fec->DofOrderForOrientation(geom, fo);
2236 edof = bin_io::read<int>(stream);
2237
2238 // If edof arrived with a negative index, flip it, and the scaling.
2239 s = (edof < 0) ? -1.0 : 1.0;
2240 edof = (edof < 0) ? -1 - edof : edof;
2241 if (ind && (edof = ind[edof]) < 0)
2242 {
2243 edof = -1 - edof;
2244 s *= -1.0;
2245 }
2246
2247 rows.emplace_back(ent, next_id.index, edof, group_ids[gi++]);
2248 rows.back().row.read(stream, s);
2249 auto &second_row = rows.back().row;
2250
2251 // This is the second "fundamental unit" used in the transformation.
2252 const auto initial_second_row = second_row;
2253
2254 // Transform the received dofs by the primal transform. This is
2255 // because within mfem as a face is visited its orientation is
2256 // assigned to match the element that visited it first. Thus on
2257 // processor boundaries, the transform will always be identity going
2258 // into the element. However, the sending processor also thought the
2259 // face orientation was zero, so it has sent the information in a
2260 // different orientation. To map onto the local orientation
2261 // definition, extract the orientation of the sending rank (the
2262 // lower rank face defines the orientation fo), then apply the
2263 // transform to the dependencies. The action of this transform on
2264 // the dependencies is performed by adding scaled versions of the
2265 // original two rows (which by the mfem assumption of face
2266 // orientation, represent the identity transform).
2267 const real_t *T =
2269
2270 MFEM_ASSERT(fo != 2 &&
2271 fo != 4, "This code branch is ambiguous for face orientations 2 and 4."
2272 " Please report this mesh for further testing.\n");
2273
2274 first_row.AddRow(initial_first_row, T[0] - 1.0); // (0,0)
2275 first_row.AddRow(initial_second_row, T[2]); // (0,1)
2276 second_row.AddRow(initial_first_row, T[1]); // (1,0)
2277 second_row.AddRow(initial_second_row, T[3] - 1.0); // (1,1)
2278
2279 first_row.Collapse();
2280 second_row.Collapse();
2281 }
2282 }
2283 }
2284}
2285
2286void
2287ParFiniteElementSpace::ScheduleSendRow(const PMatrixRow &row, int dof,
2288 GroupId group_id,
2289 NeighborRowMessage::Map &send_msg) const
2290{
2291 int ent, idx, edof;
2292 UnpackDof(dof, ent, idx, edof);
2293
2294 for (const auto &rank : pncmesh->GetGroup(group_id))
2295 {
2296 if (rank != MyRank)
2297 {
2298 NeighborRowMessage &msg = send_msg[rank];
2299 msg.AddRow(ent, idx, edof, group_id, row);
2300 msg.SetNCMesh(pncmesh);
2301 msg.SetFEC(fec);
2302#ifdef MFEM_PMATRIX_STATS
2303 n_rows_sent++;
2304#endif
2305 }
2306 }
2307}
2308
2309void ParFiniteElementSpace::ForwardRow(const PMatrixRow &row, int dof,
2310 GroupId group_sent_id, GroupId group_id,
2311 NeighborRowMessage::Map &send_msg) const
2312{
2313 int ent, idx, edof;
2314 UnpackDof(dof, ent, idx, edof);
2315
2316 const ParNCMesh::CommGroup &group = pncmesh->GetGroup(group_id);
2317 for (unsigned i = 0; i < group.size(); i++)
2318 {
2319 int rank = group[i];
2320 if (rank != MyRank && !pncmesh->GroupContains(group_sent_id, rank))
2321 {
2322 NeighborRowMessage &msg = send_msg[rank];
2323 GroupId invalid = -1; // to prevent forwarding again
2324 msg.AddRow(ent, idx, edof, invalid, row);
2325 msg.SetNCMesh(pncmesh);
2326 msg.SetFEC(fec);
2327#ifdef MFEM_PMATRIX_STATS
2328 n_rows_fwd++;
2329#endif
2330#ifdef MFEM_DEBUG_PMATRIX
2331 mfem::out << "Rank " << pncmesh->GetMyRank() << " forwarding to "
2332 << rank << ": ent " << ent << ", index" << idx
2333 << ", edof " << edof << std::endl;
2334#endif
2335 }
2336 }
2337}
2338
2339#ifdef MFEM_DEBUG_PMATRIX
2340void ParFiniteElementSpace
2341::DebugDumpDOFs(std::ostream &os,
2342 const SparseMatrix &deps,
2343 const Array<GroupId> &dof_group,
2344 const Array<GroupId> &dof_owner,
2345 const Array<bool> &finalized) const
2346{
2347 for (int i = 0; i < dof_group.Size(); i++)
2348 {
2349 os << i << ": ";
2350 if (i < (nvdofs + nedofs + nfdofs) || i >= ndofs)
2351 {
2352 int ent, idx, edof;
2353 UnpackDof(i, ent, idx, edof);
2354
2355 os << edof << " @ ";
2356 if (i > ndofs) { os << "ghost "; }
2357 switch (ent)
2358 {
2359 case 0: os << "vertex "; break;
2360 case 1: os << "edge "; break;
2361 default: os << "face "; break;
2362 }
2363 os << idx << "; ";
2364
2365 if (i < deps.Height() && deps.RowSize(i))
2366 {
2367 os << "depends on ";
2368 for (int j = 0; j < deps.RowSize(i); j++)
2369 {
2370 os << deps.GetRowColumns(i)[j] << " ("
2371 << deps.GetRowEntries(i)[j] << ")";
2372 if (j < deps.RowSize(i)-1) { os << ", "; }
2373 }
2374 os << "; ";
2375 }
2376 else
2377 {
2378 os << "no deps; ";
2379 }
2380
2381 os << "group " << dof_group[i] << " (";
2382 const ParNCMesh::CommGroup &g = pncmesh->GetGroup(dof_group[i]);
2383 for (unsigned j = 0; j < g.size(); j++)
2384 {
2385 if (j) { os << ", "; }
2386 os << g[j];
2387 }
2388
2389 os << "), owner " << dof_owner[i] << " (rank "
2390 << pncmesh->GetGroup(dof_owner[i])[0] << "); "
2391 << (finalized[i] ? "finalized" : "NOT finalized");
2392 }
2393 else
2394 {
2395 os << "internal";
2396 }
2397 os << "\n";
2398 }
2399}
2400#endif
2401
2402int ParFiniteElementSpace
2403::BuildParallelConformingInterpolation(HypreParMatrix **P_, SparseMatrix **R_,
2404 Array<HYPRE_BigInt> &dof_offs,
2405 Array<HYPRE_BigInt> &tdof_offs,
2406 Array<int> *dof_tdof,
2407 bool partial) const
2408{
2409 const bool dg = (nvdofs == 0 && nedofs == 0 && nfdofs == 0);
2410
2411#ifdef MFEM_PMATRIX_STATS
2412 n_msgs_sent = n_msgs_recv = 0;
2413 n_rows_sent = n_rows_recv = n_rows_fwd = 0;
2414#endif
2415
2416 // *** STEP 1: build master-slave dependency lists ***
2417
2418 const int total_dofs = ndofs + ngdofs;
2419 SparseMatrix deps(ndofs, total_dofs);
2420
2421 if (!dg && !partial)
2422 {
2423 Array<int> master_dofs, slave_dofs;
2424
2425 // loop through *all* master edges/faces, constrain their slaves
2426 for (int entity = 0; entity <= 2; entity++)
2427 {
2428 const NCMesh::NCList &list = pncmesh->GetNCList(entity);
2429 if (list.masters.Size() == 0) { continue; }
2430
2431 IsoparametricTransformation T;
2432 DenseMatrix I;
2433
2434 // process masters that we own or that affect our edges/faces
2435 for (const auto &mf : list.masters)
2436 {
2437 // get master DOFs
2438 if (pncmesh->IsGhost(entity, mf.index))
2439 {
2440 GetGhostDofs(entity, mf, master_dofs);
2441 }
2442 else
2443 {
2444 GetEntityDofs(entity, mf.index, master_dofs, mf.Geom());
2445 }
2446
2447 if (master_dofs.Size() == 0) { continue; }
2448
2449 const FiniteElement * const fe = fec->FiniteElementForGeometry(mf.Geom());
2450 if (fe == nullptr) { continue; }
2451
2452 switch (mf.Geom())
2453 {
2454 case Geometry::SQUARE: T.SetFE(&QuadrilateralFE); break;
2455 case Geometry::TRIANGLE: T.SetFE(&TriangleFE); break;
2456 case Geometry::SEGMENT: T.SetFE(&SegmentFE); break;
2457 default: MFEM_ABORT("unsupported geometry");
2458 }
2459
2460 // constrain slaves that exist in our mesh
2461 for (int si = mf.slaves_begin; si < mf.slaves_end; si++)
2462 {
2463 const NCMesh::Slave &sf = list.slaves[si];
2464 if (pncmesh->IsGhost(entity, sf.index)) { continue; }
2465
2466 constexpr int variant = 0; // TODO parallel var-order
2467 GetEntityDofs(entity, sf.index, slave_dofs, mf.Geom(), variant);
2468 if (!slave_dofs.Size()) { continue; }
2469
2470 list.OrientedPointMatrix(sf, T.GetPointMat());
2471 fe->GetLocalInterpolation(T, I);
2472
2473 // make each slave DOF dependent on all master DOFs
2474 AddDependencies(deps, master_dofs, slave_dofs, I);
2475 }
2476 }
2477 }
2478 deps.Finalize();
2479 }
2480
2481 // *** STEP 2: initialize group and owner ID for each DOF ***
2482
2483 Array<GroupId> dof_group(total_dofs);
2484 Array<GroupId> dof_owner(total_dofs);
2485 dof_group = 0;
2486 dof_owner = 0;
2487
2488 if (!dg)
2489 {
2490 Array<int> dofs;
2491
2492 auto initialize_group_and_owner = [&dof_group, &dof_owner, &dofs,
2493 this](int entity, const MeshId &id)
2494 {
2495 if (id.index < 0) { return; }
2496
2497 GroupId owner = pncmesh->GetEntityOwnerId(entity, id.index);
2498 GroupId group = pncmesh->GetEntityGroupId(entity, id.index);
2499
2500 GetBareDofs(entity, id.index, dofs);
2501
2502 for (auto dof : dofs)
2503 {
2504 dof_owner[dof] = owner;
2505 dof_group[dof] = group;
2506 }
2507 };
2508
2509 // initialize dof_group[], dof_owner[] in sequence
2510 for (int entity : {0,1,2})
2511 {
2512 for (const auto &id : pncmesh->GetNCList(entity).conforming)
2513 {
2514 initialize_group_and_owner(entity, id);
2515 }
2516 for (const auto &id : pncmesh->GetNCList(entity).masters)
2517 {
2518 initialize_group_and_owner(entity, id);
2519 }
2520 for (const auto &id : pncmesh->GetNCList(entity).slaves)
2521 {
2522 initialize_group_and_owner(entity, id);
2523 }
2524 }
2525 }
2526
2527 // *** STEP 3: count true DOFs and calculate P row/column partitions ***
2528
2529 Array<bool> finalized(total_dofs);
2530 finalized = false;
2531
2532 // DOFs that stayed independent and are ours are true DOFs
2533 int num_true_dofs = 0;
2534 for (int i = 0; i < ndofs; ++i)
2535 {
2536 if (dof_owner[i] == 0 && deps.RowSize(i) == 0)
2537 {
2538 ++num_true_dofs;
2539 finalized[i] = true;
2540 }
2541 }
2542
2543#ifdef MFEM_DEBUG_PMATRIX
2544 // Helper for dumping diagnostics on one dof
2545 auto dof_diagnostics = [&](int dof, bool print_diagnostic)
2546 {
2547 const auto &comm_group = pncmesh->GetGroup(dof_group[dof]);
2548 std::stringstream msg;
2549 msg << std::boolalpha;
2550 msg << "R" << Mpi::WorldRank() << " dof " << dof
2551 << " owner_rank " << pncmesh->GetGroup(dof_owner[dof])[0] << " CommGroup {";
2552 for (const auto &x : comm_group)
2553 {
2554 msg << x << ' ';
2555 }
2556 msg << "} finalized " << finalized[dof];
2557
2558 Array<int> cols;
2559 if (dof < ndofs)
2560 {
2561 Vector row;
2562 deps.GetRow(dof, cols, row);
2563 msg << " deps cols {";
2564 for (const auto &x : cols)
2565 {
2566 msg << x << ' ';
2567 }
2568 msg << '}';
2569 }
2570
2571 int entity, index, edof;
2572 UnpackDof(dof, entity, index, edof);
2573 msg << " entity " << entity << " index " << index << " edof " << edof;
2574 return msg.str();
2575 };
2576#endif
2577
2578 // calculate global offsets
2579 HYPRE_BigInt loc_sizes[2] = { ndofs*vdim, num_true_dofs*vdim };
2580 Array<HYPRE_BigInt>* offsets[2] = { &dof_offs, &tdof_offs };
2581 pmesh->GenerateOffsets(2, loc_sizes, offsets); // calls MPI_Scan, MPI_Bcast
2582
2583 HYPRE_BigInt my_tdof_offset =
2584 tdof_offs[HYPRE_AssumedPartitionCheck() ? 0 : MyRank];
2585
2586 if (R_)
2587 {
2588 // initialize the restriction matrix (also parallel but block-diagonal)
2589 *R_ = new SparseMatrix(num_true_dofs*vdim, ndofs*vdim);
2590 }
2591 if (dof_tdof)
2592 {
2593 dof_tdof->SetSize(ndofs*vdim);
2594 *dof_tdof = -1;
2595 }
2596
2597 std::vector<PMatrixRow> pmatrix(total_dofs);
2598
2599 const bool bynodes = (ordering == Ordering::byNODES);
2600 const int vdim_factor = bynodes ? 1 : vdim;
2601 const int dof_stride = bynodes ? ndofs : 1;
2602 const int tdof_stride = bynodes ? num_true_dofs : 1;
2603
2604 // big container for all messages we send (the list is for iterations)
2605 std::list<NeighborRowMessage::Map> send_msg;
2606 send_msg.emplace_back();
2607
2608 // put identity in P and R for true DOFs, set ldof_ltdof
2609 for (int dof = 0, tdof = 0; dof < ndofs; dof++)
2610 {
2611 if (finalized[dof])
2612 {
2613 pmatrix[dof].elems.emplace_back(
2614 my_tdof_offset + vdim_factor*tdof, tdof_stride, 1.);
2615
2616 // prepare messages to neighbors with identity rows
2617 if (dof_group[dof] != 0)
2618 {
2619 ScheduleSendRow(pmatrix[dof], dof, dof_group[dof], send_msg.back());
2620 }
2621
2622 for (int vd = 0; vd < vdim; vd++)
2623 {
2624 const int vdof = dof*vdim_factor + vd*dof_stride;
2625 const int vtdof = tdof*vdim_factor + vd*tdof_stride;
2626
2627 if (R_) { (*R_)->Add(vtdof, vdof, 1.0); }
2628 if (dof_tdof) { (*dof_tdof)[vdof] = vtdof; }
2629 }
2630 ++tdof;
2631 }
2632 }
2633
2634 // send identity rows
2635 NeighborRowMessage::IsendAll(send_msg.back(), MyComm);
2636#ifdef MFEM_PMATRIX_STATS
2637 n_msgs_sent += send_msg.back().size();
2638#endif
2639
2640 if (R_) { (*R_)->Finalize(); }
2641
2642 // *** STEP 4: main loop ***
2643
2644 // a single instance (recv_msg) is reused for all incoming messages
2645 NeighborRowMessage recv_msg;
2646 recv_msg.SetNCMesh(pncmesh);
2647 recv_msg.SetFEC(fec);
2648
2649 int num_finalized = num_true_dofs;
2650 PMatrixRow buffer;
2651 buffer.elems.reserve(1024);
2652
2653 while (num_finalized < ndofs)
2654 {
2655 // prepare a new round of send buffers
2656 if (send_msg.back().size())
2657 {
2658 send_msg.emplace_back();
2659 }
2660
2661 // check for incoming messages, receive PMatrixRows
2662 int rank, size;
2663 while (NeighborRowMessage::IProbe(rank, size, MyComm))
2664 {
2665 recv_msg.Recv(rank, size, MyComm);
2666#ifdef MFEM_PMATRIX_STATS
2667 n_msgs_recv++;
2668 n_rows_recv += recv_msg.GetRows().size();
2669#endif
2670
2671 for (const auto &ri : recv_msg.GetRows())
2672 {
2673 const int dof = PackDof(ri.entity, ri.index, ri.edof);
2674 pmatrix[dof] = ri.row;
2675
2676 if (dof < ndofs && !finalized[dof]) { ++num_finalized; }
2677 finalized[dof] = true;
2678
2679 if (ri.group >= 0 && dof_group[dof] != ri.group)
2680 {
2681 // the sender didn't see the complete group, forward the message
2682 ForwardRow(ri.row, dof, ri.group, dof_group[dof], send_msg.back());
2683 }
2684 }
2685 }
2686
2687 // finalize all rows that can currently be finalized
2688 bool done = false;
2689 while (!done)
2690 {
2691 done = true;
2692 for (int dof = 0; dof < ndofs; dof++)
2693 {
2694 const bool owned = (dof_owner[dof] == 0);
2695 if (!finalized[dof]
2696 && owned
2697 && DofFinalizable(dof, finalized, deps))
2698 {
2699 int ent, idx, edof;
2700 UnpackDof(dof, ent, idx, edof);
2701
2702 const int* dep_col = deps.GetRowColumns(dof);
2703 const real_t* dep_coef = deps.GetRowEntries(dof);
2704 int num_dep = deps.RowSize(dof);
2705
2706 // form linear combination of rows
2707 buffer.elems.clear();
2708 for (int j = 0; j < num_dep; j++)
2709 {
2710 buffer.AddRow(pmatrix[dep_col[j]], dep_coef[j]);
2711 }
2712 buffer.Collapse();
2713 pmatrix[dof] = buffer;
2714
2715 finalized[dof] = true;
2716 ++num_finalized;
2717 done = false;
2718
2719 // send row to neighbors who need it
2720 const bool shared = (dof_group[dof] != 0);
2721 if (shared)
2722 {
2723 ScheduleSendRow(pmatrix[dof], dof, dof_group[dof],
2724 send_msg.back());
2725 }
2726 }
2727 }
2728 }
2729
2730#ifdef MFEM_DEBUG_PMATRIX
2731 static int dump = 0;
2732 if (dump < 10)
2733 {
2734 char fname[100];
2735 snprintf(fname, 100, "dofs%02d.txt", MyRank);
2736 std::ofstream f(fname);
2737 DebugDumpDOFs(f, deps, dof_group, dof_owner, finalized);
2738 dump++;
2739 }
2740#endif
2741
2742 // send current batch of messages
2743 NeighborRowMessage::IsendAll(send_msg.back(), MyComm);
2744#ifdef MFEM_PMATRIX_STATS
2745 n_msgs_sent += send_msg.back().size();
2746#endif
2747 }
2748
2749 if (P_)
2750 {
2751 *P_ = MakeVDimHypreMatrix(pmatrix, ndofs, num_true_dofs,
2752 dof_offs, tdof_offs);
2753 }
2754
2755 // clean up possible remaining messages in the queue to avoid receiving
2756 // them erroneously in the next run
2757 int rank, size;
2758 while (NeighborRowMessage::IProbe(rank, size, MyComm))
2759 {
2760 recv_msg.RecvDrop(rank, size, MyComm);
2761 }
2762
2763 // make sure we can discard all send buffers
2764 for (auto &msg : send_msg)
2765 {
2767 }
2768
2769#ifdef MFEM_PMATRIX_STATS
2770 int n_rounds = send_msg.size();
2771 int glob_rounds, glob_msgs_sent, glob_msgs_recv;
2772 int glob_rows_sent, glob_rows_recv, glob_rows_fwd;
2773
2774 MPI_Reduce(&n_rounds, &glob_rounds, 1, MPI_INT, MPI_SUM, 0, MyComm);
2775 MPI_Reduce(&n_msgs_sent, &glob_msgs_sent, 1, MPI_INT, MPI_SUM, 0, MyComm);
2776 MPI_Reduce(&n_msgs_recv, &glob_msgs_recv, 1, MPI_INT, MPI_SUM, 0, MyComm);
2777 MPI_Reduce(&n_rows_sent, &glob_rows_sent, 1, MPI_INT, MPI_SUM, 0, MyComm);
2778 MPI_Reduce(&n_rows_recv, &glob_rows_recv, 1, MPI_INT, MPI_SUM, 0, MyComm);
2779 MPI_Reduce(&n_rows_fwd, &glob_rows_fwd, 1, MPI_INT, MPI_SUM, 0, MyComm);
2780
2781 if (MyRank == 0)
2782 {
2783 mfem::out << "P matrix stats (avg per rank): "
2784 << real_t(glob_rounds)/NRanks << " rounds, "
2785 << real_t(glob_msgs_sent)/NRanks << " msgs sent, "
2786 << real_t(glob_msgs_recv)/NRanks << " msgs recv, "
2787 << real_t(glob_rows_sent)/NRanks << " rows sent, "
2788 << real_t(glob_rows_recv)/NRanks << " rows recv, "
2789 << real_t(glob_rows_fwd)/NRanks << " rows forwarded."
2790 << std::endl;
2791 }
2792#endif
2793
2794 return num_true_dofs*vdim;
2795}
2796
2797
2798HypreParMatrix* ParFiniteElementSpace
2799::MakeVDimHypreMatrix(const std::vector<PMatrixRow> &rows,
2800 int local_rows, int local_cols,
2801 Array<HYPRE_BigInt> &row_starts,
2802 Array<HYPRE_BigInt> &col_starts) const
2803{
2804 bool assumed = HYPRE_AssumedPartitionCheck();
2805 bool bynodes = (ordering == Ordering::byNODES);
2806
2807 HYPRE_BigInt first_col = col_starts[assumed ? 0 : MyRank];
2808 HYPRE_BigInt next_col = col_starts[assumed ? 1 : MyRank+1];
2809
2810 // count nonzeros in diagonal/offdiagonal parts
2811 HYPRE_Int nnz_diag = 0, nnz_offd = 0;
2812 std::map<HYPRE_BigInt, int> col_map;
2813 for (int i = 0; i < local_rows; i++)
2814 {
2815 for (unsigned j = 0; j < rows[i].elems.size(); j++)
2816 {
2817 const PMatrixElement &elem = rows[i].elems[j];
2818 HYPRE_BigInt col = elem.column;
2819 if (col >= first_col && col < next_col)
2820 {
2821 nnz_diag += vdim;
2822 }
2823 else
2824 {
2825 nnz_offd += vdim;
2826 for (int vd = 0; vd < vdim; vd++)
2827 {
2828 col_map[col] = -1;
2829 col += elem.stride;
2830 }
2831 }
2832 }
2833 }
2834
2835 // create offd column mapping
2836 HYPRE_BigInt *cmap = Memory<HYPRE_BigInt>(col_map.size());
2837 int offd_col = 0;
2838 for (auto it = col_map.begin(); it != col_map.end(); ++it)
2839 {
2840 cmap[offd_col] = it->first;
2841 it->second = offd_col++;
2842 }
2843
2844 HYPRE_Int *I_diag = Memory<HYPRE_Int>(vdim*local_rows + 1);
2845 HYPRE_Int *I_offd = Memory<HYPRE_Int>(vdim*local_rows + 1);
2846
2847 HYPRE_Int *J_diag = Memory<HYPRE_Int>(nnz_diag);
2848 HYPRE_Int *J_offd = Memory<HYPRE_Int>(nnz_offd);
2849
2850 real_t *A_diag = Memory<real_t>(nnz_diag);
2851 real_t *A_offd = Memory<real_t>(nnz_offd);
2852
2853 int vdim1 = bynodes ? vdim : 1;
2854 int vdim2 = bynodes ? 1 : vdim;
2855 int vdim_offset = bynodes ? local_cols : 1;
2856
2857 // copy the diag/offd elements
2858 nnz_diag = nnz_offd = 0;
2859 int vrow = 0;
2860 for (int vd1 = 0; vd1 < vdim1; vd1++)
2861 {
2862 for (int i = 0; i < local_rows; i++)
2863 {
2864 for (int vd2 = 0; vd2 < vdim2; vd2++)
2865 {
2866 I_diag[vrow] = nnz_diag;
2867 I_offd[vrow++] = nnz_offd;
2868
2869 int vd = bynodes ? vd1 : vd2;
2870 for (unsigned j = 0; j < rows[i].elems.size(); j++)
2871 {
2872 const PMatrixElement &elem = rows[i].elems[j];
2873 if (elem.column >= first_col && elem.column < next_col)
2874 {
2875 J_diag[nnz_diag] = elem.column + vd*vdim_offset - first_col;
2876 A_diag[nnz_diag++] = elem.value;
2877 }
2878 else
2879 {
2880 J_offd[nnz_offd] = col_map[elem.column + vd*elem.stride];
2881 A_offd[nnz_offd++] = elem.value;
2882 }
2883 }
2884 }
2885 }
2886 }
2887 MFEM_ASSERT(vrow == vdim*local_rows, "");
2888 I_diag[vrow] = nnz_diag;
2889 I_offd[vrow] = nnz_offd;
2890
2891 return new HypreParMatrix(MyComm,
2892 row_starts.Last(), col_starts.Last(),
2893 row_starts.GetData(), col_starts.GetData(),
2894 I_diag, J_diag, A_diag,
2895 I_offd, J_offd, A_offd,
2896 col_map.size(), cmap);
2897}
2898
2899template <typename int_type>
2900static int_type* make_i_array(int nrows)
2901{
2902 int_type *I = Memory<int_type>(nrows+1);
2903 for (int i = 0; i <= nrows; i++) { I[i] = -1; }
2904 return I;
2905}
2906
2907template <typename int_type>
2908static int_type* make_j_array(int_type* I, int nrows)
2909{
2910 int nnz = 0;
2911 for (int i = 0; i < nrows; i++)
2912 {
2913 if (I[i] >= 0) { nnz++; }
2914 }
2915 int_type *J = Memory<int_type>(nnz);
2916
2917 I[nrows] = -1;
2918 for (int i = 0, k = 0; i <= nrows; i++)
2919 {
2920 int_type col = I[i];
2921 I[i] = k;
2922 if (col >= 0) { J[k++] = col; }
2923 }
2924 return J;
2925}
2926
2927HypreParMatrix*
2928ParFiniteElementSpace::RebalanceMatrix(int old_ndofs,
2929 const Table* old_elem_dof,
2930 const Table* old_elem_fos)
2931{
2932 MFEM_VERIFY(Nonconforming(), "Only supported for nonconforming meshes.");
2933 MFEM_VERIFY(old_dof_offsets.Size(), "ParFiniteElementSpace::Update needs to "
2934 "be called before ParFiniteElementSpace::RebalanceMatrix");
2935
2936 HYPRE_BigInt old_offset = HYPRE_AssumedPartitionCheck()
2937 ? old_dof_offsets[0] : old_dof_offsets[MyRank];
2938
2939 // send old DOFs of elements we used to own
2940 ParNCMesh* old_pncmesh = pmesh->pncmesh;
2941 old_pncmesh->SendRebalanceDofs(old_ndofs, *old_elem_dof, old_offset, this);
2942
2943 Array<int> dofs;
2944 int vsize = GetVSize();
2945
2946 const Array<int> &old_index = old_pncmesh->GetRebalanceOldIndex();
2947 MFEM_VERIFY(old_index.Size() == pmesh->GetNE(),
2948 "Mesh::Rebalance was not called before "
2949 "ParFiniteElementSpace::RebalanceMatrix");
2950
2951 // prepare the local (diagonal) part of the matrix
2952 HYPRE_Int* i_diag = make_i_array<HYPRE_Int>(vsize);
2953 for (int i = 0; i < pmesh->GetNE(); i++)
2954 {
2955 if (old_index[i] >= 0) // we had this element before
2956 {
2957 const int* old_dofs = old_elem_dof->GetRow(old_index[i]);
2958 GetElementDofs(i, dofs);
2959
2960 for (int vd = 0; vd < vdim; vd++)
2961 {
2962 for (int j = 0; j < dofs.Size(); j++)
2963 {
2964 int row = DofToVDof(dofs[j], vd);
2965 if (row < 0) { row = -1 - row; }
2966
2967 int col = DofToVDof(old_dofs[j], vd, old_ndofs);
2968 if (col < 0) { col = -1 - col; }
2969
2970 i_diag[row] = col;
2971 }
2972 }
2973 }
2974 }
2975 HYPRE_Int* j_diag = make_j_array(i_diag, vsize);
2976
2977 // receive old DOFs for elements we obtained from others in Rebalance
2978 Array<int> new_elements;
2979 Array<long> old_remote_dofs;
2980 old_pncmesh->RecvRebalanceDofs(new_elements, old_remote_dofs);
2981
2982 // create the offdiagonal part of the matrix
2983 HYPRE_BigInt* i_offd = make_i_array<HYPRE_BigInt>(vsize);
2984 for (int i = 0, pos = 0; i < new_elements.Size(); i++)
2985 {
2986 GetElementDofs(new_elements[i], dofs);
2987 const long* old_dofs = &old_remote_dofs[pos];
2988 pos += dofs.Size() * vdim;
2989
2990 for (int vd = 0; vd < vdim; vd++)
2991 {
2992 for (int j = 0; j < dofs.Size(); j++)
2993 {
2994 int row = DofToVDof(dofs[j], vd);
2995 if (row < 0) { row = -1 - row; }
2996
2997 if (i_diag[row] == i_diag[row+1]) // diag row empty?
2998 {
2999 i_offd[row] = old_dofs[j + vd * dofs.Size()];
3000 }
3001 }
3002 }
3003 }
3004 HYPRE_BigInt* j_offd = make_j_array(i_offd, vsize);
3005
3006#ifndef HYPRE_MIXEDINT
3007 HYPRE_Int *i_offd_hi = i_offd;
3008#else
3009 // Copy of i_offd array as array of HYPRE_Int
3010 HYPRE_Int *i_offd_hi = Memory<HYPRE_Int>(vsize + 1);
3011 std::copy(i_offd, i_offd + vsize + 1, i_offd_hi);
3012 Memory<HYPRE_BigInt>(i_offd, vsize + 1, true).Delete();
3013#endif
3014
3015 // create the offd column map
3016 int offd_cols = i_offd_hi[vsize];
3017 Array<Pair<HYPRE_BigInt, int> > cmap_offd(offd_cols);
3018 for (int i = 0; i < offd_cols; i++)
3019 {
3020 cmap_offd[i].one = j_offd[i];
3021 cmap_offd[i].two = i;
3022 }
3023
3024#ifndef HYPRE_MIXEDINT
3025 HYPRE_Int *j_offd_hi = j_offd;
3026#else
3027 HYPRE_Int *j_offd_hi = Memory<HYPRE_Int>(offd_cols);
3028 Memory<HYPRE_BigInt>(j_offd, offd_cols, true).Delete();
3029#endif
3030
3031 SortPairs<HYPRE_BigInt, int>(cmap_offd, offd_cols);
3032
3033 HYPRE_BigInt* cmap = Memory<HYPRE_BigInt>(offd_cols);
3034 for (int i = 0; i < offd_cols; i++)
3035 {
3036 cmap[i] = cmap_offd[i].one;
3037 j_offd_hi[cmap_offd[i].two] = i;
3038 }
3039
3040 HypreParMatrix *M;
3041 M = new HypreParMatrix(MyComm, MyRank, NRanks, dof_offsets, old_dof_offsets,
3042 i_diag, j_diag, i_offd_hi, j_offd_hi, cmap, offd_cols);
3043 return M;
3044}
3045
3046
3047struct DerefDofMessage
3048{
3049 std::vector<HYPRE_BigInt> dofs;
3050 MPI_Request request;
3051};
3052
3053HypreParMatrix*
3054ParFiniteElementSpace::ParallelDerefinementMatrix(int old_ndofs,
3055 const Table* old_elem_dof,
3056 const Table *old_elem_fos)
3057{
3058 int nrk = HYPRE_AssumedPartitionCheck() ? 2 : NRanks;
3059
3060 MFEM_VERIFY(Nonconforming(), "Not implemented for conforming meshes.");
3061 MFEM_VERIFY(old_dof_offsets[nrk], "Missing previous (finer) space.");
3062
3063#if 0 // check no longer seems to work with NC tet refinement
3064 MFEM_VERIFY(dof_offsets[nrk] <= old_dof_offsets[nrk],
3065 "Previous space is not finer.");
3066#endif
3067
3068 // Note to the reader: please make sure you first read
3069 // FiniteElementSpace::RefinementMatrix, then
3070 // FiniteElementSpace::DerefinementMatrix, and only then this function.
3071 // You have been warned! :-)
3072
3073 Mesh::GeometryList elem_geoms(*mesh);
3074
3075 Array<int> dofs, old_dofs, old_vdofs;
3076 Vector row;
3077
3078 ParNCMesh* old_pncmesh = pmesh->pncmesh;
3079
3080 int ldof[Geometry::NumGeom];
3081 for (int i = 0; i < Geometry::NumGeom; i++)
3082 {
3083 ldof[i] = 0;
3084 }
3085 for (int i = 0; i < elem_geoms.Size(); i++)
3086 {
3087 Geometry::Type geom = elem_geoms[i];
3088 ldof[geom] = fec->FiniteElementForGeometry(geom)->GetDof();
3089 }
3090
3091 const CoarseFineTransformations &dtrans =
3092 old_pncmesh->GetDerefinementTransforms();
3093 const Array<int> &old_ranks = old_pncmesh->GetDerefineOldRanks();
3094
3095 std::map<int, DerefDofMessage> messages;
3096
3097 HYPRE_BigInt old_offset = HYPRE_AssumedPartitionCheck()
3098 ? old_dof_offsets[0] : old_dof_offsets[MyRank];
3099
3100 // communicate DOFs for derefinements that straddle processor boundaries,
3101 // note that this is infrequent due to the way elements are ordered
3102 for (int k = 0; k < dtrans.embeddings.Size(); k++)
3103 {
3104 const Embedding &emb = dtrans.embeddings[k];
3105
3106 int fine_rank = old_ranks[k];
3107 int coarse_rank = (emb.parent < 0) ? (-1 - emb.parent)
3108 : old_pncmesh->ElementRank(emb.parent);
3109
3110 if (coarse_rank != MyRank && fine_rank == MyRank)
3111 {
3112 old_elem_dof->GetRow(k, dofs);
3113 DofsToVDofs(dofs, old_ndofs);
3114
3115 DerefDofMessage &msg = messages[k];
3116 msg.dofs.resize(dofs.Size());
3117 for (int i = 0; i < dofs.Size(); i++)
3118 {
3119 msg.dofs[i] = old_offset + dofs[i];
3120 }
3121
3122 MPI_Isend(&msg.dofs[0], msg.dofs.size(), HYPRE_MPI_BIG_INT,
3123 coarse_rank, 291, MyComm, &msg.request);
3124 }
3125 else if (coarse_rank == MyRank && fine_rank != MyRank)
3126 {
3127 MFEM_ASSERT(emb.parent >= 0, "");
3128 Geometry::Type geom = mesh->GetElementBaseGeometry(emb.parent);
3129
3130 DerefDofMessage &msg = messages[k];
3131 msg.dofs.resize(ldof[geom]*vdim);
3132
3133 MPI_Irecv(&msg.dofs[0], ldof[geom]*vdim, HYPRE_MPI_BIG_INT,
3134 fine_rank, 291, MyComm, &msg.request);
3135 }
3136 // TODO: coalesce Isends/Irecvs to the same rank. Typically, on uniform
3137 // derefinement, there should be just one send to MyRank-1 and one recv
3138 // from MyRank+1
3139 }
3140
3141 DenseTensor localR[Geometry::NumGeom];
3142 for (int i = 0; i < elem_geoms.Size(); i++)
3143 {
3144 GetLocalDerefinementMatrices(elem_geoms[i], localR[elem_geoms[i]]);
3145 }
3146
3147 // create the diagonal part of the derefinement matrix
3148 SparseMatrix *diag = new SparseMatrix(ndofs*vdim, old_ndofs*vdim);
3149
3150 Array<char> mark(diag->Height());
3151 mark = 0;
3152
3154
3155 for (int k = 0; k < dtrans.embeddings.Size(); k++)
3156 {
3157 const Embedding &emb = dtrans.embeddings[k];
3158 if (emb.parent < 0) { continue; }
3159
3160 int coarse_rank = old_pncmesh->ElementRank(emb.parent);
3161 int fine_rank = old_ranks[k];
3162
3163 if (coarse_rank == MyRank && fine_rank == MyRank)
3164 {
3165 Geometry::Type geom = mesh->GetElementBaseGeometry(emb.parent);
3166 DenseMatrix &lR = localR[geom](emb.matrix);
3167
3168 elem_dof->GetRow(emb.parent, dofs);
3169 old_elem_dof->GetRow(k, old_dofs);
3170
3171 for (int vd = 0; vd < vdim; vd++)
3172 {
3173 old_dofs.Copy(old_vdofs);
3174 DofsToVDofs(vd, old_vdofs, old_ndofs);
3175
3176 for (int i = 0; i < lR.Height(); i++)
3177 {
3178 if (!std::isfinite(lR(i, 0))) { continue; }
3179
3180 int r = DofToVDof(dofs[i], vd);
3181 int m = (r >= 0) ? r : (-1 - r);
3182
3183 if (is_dg || !mark[m])
3184 {
3185 lR.GetRow(i, row);
3186 diag->SetRow(r, old_vdofs, row);
3187 mark[m] = 1;
3188 }
3189 }
3190 }
3191 }
3192 }
3193 diag->Finalize();
3194
3195 // wait for all sends/receives to complete
3196 for (auto it = messages.begin(); it != messages.end(); ++it)
3197 {
3198 MPI_Wait(&it->second.request, MPI_STATUS_IGNORE);
3199 }
3200
3201 // create the offdiagonal part of the derefinement matrix
3202 SparseMatrix *offd = new SparseMatrix(ndofs*vdim, 1);
3203
3204 std::map<HYPRE_BigInt, int> col_map;
3205 for (int k = 0; k < dtrans.embeddings.Size(); k++)
3206 {
3207 const Embedding &emb = dtrans.embeddings[k];
3208 if (emb.parent < 0) { continue; }
3209
3210 int coarse_rank = old_pncmesh->ElementRank(emb.parent);
3211 int fine_rank = old_ranks[k];
3212
3213 if (coarse_rank == MyRank && fine_rank != MyRank)
3214 {
3215 Geometry::Type geom = mesh->GetElementBaseGeometry(emb.parent);
3216 DenseMatrix &lR = localR[geom](emb.matrix);
3217
3218 elem_dof->GetRow(emb.parent, dofs);
3219
3220 DerefDofMessage &msg = messages[k];
3221 MFEM_ASSERT(msg.dofs.size(), "");
3222
3223 for (int vd = 0; vd < vdim; vd++)
3224 {
3225 MFEM_ASSERT(ldof[geom], "");
3226 HYPRE_BigInt* remote_dofs = &msg.dofs[vd*ldof[geom]];
3227
3228 for (int i = 0; i < lR.Height(); i++)
3229 {
3230 if (!std::isfinite(lR(i, 0))) { continue; }
3231
3232 int r = DofToVDof(dofs[i], vd);
3233 int m = (r >= 0) ? r : (-1 - r);
3234
3235 if (is_dg || !mark[m])
3236 {
3237 lR.GetRow(i, row);
3238 MFEM_ASSERT(ldof[geom] == row.Size(), "");
3239 for (int j = 0; j < ldof[geom]; j++)
3240 {
3241 if (row[j] == 0.0) { continue; } // NOTE: lR thresholded
3242 int &lcol = col_map[remote_dofs[j]];
3243 if (!lcol) { lcol = col_map.size(); }
3244 offd->_Set_(m, lcol-1, row[j]);
3245 }
3246 mark[m] = 1;
3247 }
3248 }
3249 }
3250 }
3251 }
3252
3253 messages.clear();
3254 offd->Finalize(0);
3255 offd->SetWidth(col_map.size());
3256
3257 // create offd column mapping for use by hypre
3258 HYPRE_BigInt *cmap = Memory<HYPRE_BigInt>(offd->Width());
3259 for (auto it = col_map.begin(); it != col_map.end(); ++it)
3260 {
3261 cmap[it->second-1] = it->first;
3262 }
3263
3264 // reorder offd columns so that 'cmap' is monotonic
3265 // NOTE: this is easier and probably faster (offd is small) than making
3266 // sure cmap is determined and sorted before the offd matrix is created
3267 {
3268 int width = offd->Width();
3269 Array<Pair<HYPRE_BigInt, int> > reorder(width);
3270 for (int i = 0; i < width; i++)
3271 {
3272 reorder[i].one = cmap[i];
3273 reorder[i].two = i;
3274 }
3275 reorder.Sort();
3276
3277 Array<int> reindex(width);
3278 for (int i = 0; i < width; i++)
3279 {
3280 reindex[reorder[i].two] = i;
3281 cmap[i] = reorder[i].one;
3282 }
3283
3284 int *J = offd->GetJ();
3285 for (int i = 0; i < offd->NumNonZeroElems(); i++)
3286 {
3287 J[i] = reindex[J[i]];
3288 }
3289 offd->SortColumnIndices();
3290 }
3291
3292 HypreParMatrix* new_R;
3293 new_R = new HypreParMatrix(MyComm, dof_offsets[nrk], old_dof_offsets[nrk],
3294 dof_offsets, old_dof_offsets, diag, offd, cmap,
3295 true);
3296
3297 new_R->SetOwnerFlags(new_R->OwnsDiag(), new_R->OwnsOffd(), 1);
3298
3299 return new_R;
3300}
3301
3302void ParFiniteElementSpace::Destroy()
3303{
3304 ldof_group.DeleteAll();
3305 ldof_ltdof.DeleteAll();
3306 dof_offsets.DeleteAll();
3307 tdof_offsets.DeleteAll();
3308 tdof_nb_offsets.DeleteAll();
3309 // preserve old_dof_offsets
3310 ldof_sign.DeleteAll();
3311
3312 delete P; P = NULL;
3313 delete Pconf; Pconf = NULL;
3314 delete Rconf; Rconf = NULL;
3315 delete R; R = NULL;
3316
3317 delete gcomm; gcomm = NULL;
3318
3319 num_face_nbr_dofs = -1;
3324}
3325
3326void ParFiniteElementSpace::CopyProlongationAndRestriction(
3327 const FiniteElementSpace &fes, const Array<int> *perm)
3328{
3329 const ParFiniteElementSpace *pfes
3330 = dynamic_cast<const ParFiniteElementSpace*>(&fes);
3331 MFEM_VERIFY(pfes != NULL, "");
3332 MFEM_VERIFY(P == NULL, "");
3333 MFEM_VERIFY(R == NULL, "");
3334
3335 // Ensure R and P matrices are built
3336 pfes->Dof_TrueDof_Matrix();
3337
3338 SparseMatrix *perm_mat = NULL, *perm_mat_tr = NULL;
3339 if (perm)
3340 {
3341 // Note: although n and fes.GetVSize() are typically equal, in
3342 // variable-order spaces they may differ, since nonconforming edges/faces
3343 // my have fictitious DOFs.
3344 int n = perm->Size();
3345 perm_mat = new SparseMatrix(n, fes.GetVSize());
3346 for (int i=0; i<n; ++i)
3347 {
3348 real_t s;
3349 int j = DecodeDof((*perm)[i], s);
3350 perm_mat->Set(i, j, s);
3351 }
3352 perm_mat->Finalize();
3353 perm_mat_tr = Transpose(*perm_mat);
3354 }
3355
3356 if (pfes->P != NULL)
3357 {
3358 if (perm) { P = pfes->P->LeftDiagMult(*perm_mat); }
3359 else { P = new HypreParMatrix(*pfes->P); }
3360 nonconf_P = true;
3361 }
3362 else if (perm != NULL)
3363 {
3364 HYPRE_BigInt glob_nrows = GlobalVSize();
3365 HYPRE_BigInt glob_ncols = GlobalTrueVSize();
3366 HYPRE_BigInt *col_starts = GetTrueDofOffsets();
3367 HYPRE_BigInt *row_starts = GetDofOffsets();
3368 P = new HypreParMatrix(MyComm, glob_nrows, glob_ncols, row_starts,
3369 col_starts, perm_mat);
3370 nonconf_P = true;
3371 }
3372 if (pfes->R != NULL)
3373 {
3374 if (perm) { R = Mult(*pfes->R, *perm_mat_tr); }
3375 else { R = new SparseMatrix(*pfes->R); }
3376 }
3377 else if (perm != NULL)
3378 {
3379 R = perm_mat_tr;
3380 perm_mat_tr = NULL;
3381 }
3382
3383 delete perm_mat;
3384 delete perm_mat_tr;
3385}
3386
3388 const FiniteElementSpace &coarse_fes, OperatorHandle &T) const
3389{
3392 GetTransferOperator(coarse_fes, Tgf);
3393 Dof_TrueDof_Matrix(); // Make sure R is built - we need R in all cases.
3394 if (T.Type() == Operator::Hypre_ParCSR)
3395 {
3396 const ParFiniteElementSpace *c_pfes =
3397 dynamic_cast<const ParFiniteElementSpace *>(&coarse_fes);
3398 MFEM_ASSERT(c_pfes != NULL, "coarse_fes must be a parallel space");
3399 SparseMatrix *RA = mfem::Mult(*R, *Tgf.As<SparseMatrix>());
3400 Tgf.Clear();
3401 T.Reset(c_pfes->Dof_TrueDof_Matrix()->
3402 LeftDiagMult(*RA, GetTrueDofOffsets()));
3403 delete RA;
3404 }
3405 else
3406 {
3407 T.Reset(new TripleProductOperator(R, Tgf.Ptr(),
3408 coarse_fes.GetProlongationMatrix(),
3409 false, Tgf.OwnsOperator(), false));
3410 Tgf.SetOperatorOwner(false);
3411 }
3412}
3413
3414void ParFiniteElementSpace::Update(bool want_transform)
3415{
3416 MFEM_VERIFY(!IsVariableOrder(),
3417 "Parallel variable order space not supported yet.");
3418
3419 if (mesh->GetSequence() == mesh_sequence)
3420 {
3421 return; // no need to update, no-op
3422 }
3423 if (want_transform && mesh->GetSequence() != mesh_sequence + 1)
3424 {
3425 MFEM_ABORT("Error in update sequence. Space needs to be updated after "
3426 "each mesh modification.");
3427 }
3428
3429 if (NURBSext)
3430 {
3431 UpdateNURBS();
3432 return;
3433 }
3434
3435 Table* old_elem_dof = NULL;
3436 Table* old_elem_fos = NULL;
3437 int old_ndofs;
3438
3439 // save old DOF table
3440 if (want_transform)
3441 {
3442 old_elem_dof = elem_dof;
3443 old_elem_fos = elem_fos;
3444 elem_dof = NULL;
3445 elem_fos = NULL;
3446 old_ndofs = ndofs;
3447 Swap(dof_offsets, old_dof_offsets);
3448 }
3449
3450 Destroy();
3451 FiniteElementSpace::Destroy(); // calls Th.Clear()
3452
3454 Construct();
3455
3457
3458 if (want_transform)
3459 {
3460 // calculate appropriate GridFunction transformation
3461 switch (mesh->GetLastOperation())
3462 {
3463 case Mesh::REFINE:
3464 {
3466 {
3467 Th.Reset(new RefinementOperator(this, old_elem_dof,
3468 old_elem_fos, old_ndofs));
3469 // The RefinementOperator takes ownership of 'old_elem_dofs', so
3470 // we no longer own it:
3471 old_elem_dof = NULL;
3472 old_elem_fos = NULL;
3473 }
3474 else
3475 {
3476 // calculate fully assembled matrix
3477 Th.Reset(RefinementMatrix(old_ndofs, old_elem_dof, old_elem_fos));
3478 }
3479 break;
3480 }
3481
3482 case Mesh::DEREFINE:
3483 {
3484 Th.Reset(ParallelDerefinementMatrix(old_ndofs, old_elem_dof,
3485 old_elem_fos));
3486 if (Nonconforming())
3487 {
3488 Th.SetOperatorOwner(false);
3489 Th.Reset(new TripleProductOperator(P, R, Th.Ptr(),
3490 false, false, true));
3491 }
3492 break;
3493 }
3494
3495 case Mesh::REBALANCE:
3496 {
3497 Th.Reset(RebalanceMatrix(old_ndofs, old_elem_dof, old_elem_fos));
3498 break;
3499 }
3500
3501 default:
3502 break;
3503 }
3504
3505 delete old_elem_dof;
3506 delete old_elem_fos;
3507 }
3508}
3509
3510void ParFiniteElementSpace::UpdateMeshPointer(Mesh *new_mesh)
3511{
3512 ParMesh *new_pmesh = dynamic_cast<ParMesh*>(new_mesh);
3513 MFEM_VERIFY(new_pmesh != NULL,
3514 "ParFiniteElementSpace::UpdateMeshPointer(...) must be a ParMesh");
3515 mesh = new_mesh;
3516 pmesh = new_pmesh;
3517}
3518
3520 int lsize, const GroupCommunicator &gc_, bool local_)
3521 : gc(gc_), local(local_)
3522{
3523 const Table &group_ldof = gc.GroupLDofTable();
3524
3525 int n_external = 0;
3526 for (int g=1; g<group_ldof.Size(); ++g)
3527 {
3528 if (!gc.GetGroupTopology().IAmMaster(g))
3529 {
3530 n_external += group_ldof.RowSize(g);
3531 }
3532 }
3533 int tsize = lsize - n_external;
3534
3535 height = lsize;
3536 width = tsize;
3537
3538 external_ldofs.Reserve(n_external);
3539 for (int gr = 1; gr < group_ldof.Size(); gr++)
3540 {
3541 if (!gc.GetGroupTopology().IAmMaster(gr))
3542 {
3543 external_ldofs.Append(group_ldof.GetRow(gr), group_ldof.RowSize(gr));
3544 }
3545 }
3547}
3548
3554
3556 const ParFiniteElementSpace &pfes, bool local_)
3557 : Operator(pfes.GetVSize(), pfes.GetTrueVSize()),
3558 external_ldofs(),
3559 gc(pfes.GroupComm()),
3560 local(local_)
3561{
3562 MFEM_VERIFY(pfes.Conforming(), "");
3563 const Table &group_ldof = gc.GroupLDofTable();
3565 for (int gr = 1; gr < group_ldof.Size(); gr++)
3566 {
3567 if (!gc.GetGroupTopology().IAmMaster(gr))
3568 {
3569 external_ldofs.Append(group_ldof.GetRow(gr), group_ldof.RowSize(gr));
3570 }
3571 }
3573 MFEM_ASSERT(external_ldofs.Size() == Height()-Width(), "");
3574#ifdef MFEM_DEBUG
3575 for (int j = 1; j < external_ldofs.Size(); j++)
3576 {
3577 // Check for repeated ldofs.
3578 MFEM_VERIFY(external_ldofs[j-1] < external_ldofs[j], "");
3579 }
3580 int j = 0;
3581 for (int i = 0; i < external_ldofs.Size(); i++)
3582 {
3583 const int end = external_ldofs[i];
3584 for ( ; j < end; j++)
3585 {
3586 MFEM_VERIFY(j-i == pfes.GetLocalTDofNumber(j), "");
3587 }
3588 j = end+1;
3589 }
3590 for ( ; j < Height(); j++)
3591 {
3592 MFEM_VERIFY(j-external_ldofs.Size() == pfes.GetLocalTDofNumber(j), "");
3593 }
3594 // gc.PrintInfo();
3595 // pfes.Dof_TrueDof_Matrix()->PrintCommPkg();
3596#endif
3597}
3598
3600{
3601 MFEM_ASSERT(x.Size() == Width(), "");
3602 MFEM_ASSERT(y.Size() == Height(), "");
3603
3604 const real_t *xdata = x.HostRead();
3605 real_t *ydata = y.HostWrite();
3606 const int m = external_ldofs.Size();
3607
3608 const int in_layout = 2; // 2 - input is ltdofs array
3609 if (local)
3610 {
3611 y = 0.0;
3612 }
3613 else
3614 {
3615 gc.BcastBegin(const_cast<real_t*>(xdata), in_layout);
3616 }
3617
3618 int j = 0;
3619 for (int i = 0; i < m; i++)
3620 {
3621 const int end = external_ldofs[i];
3622 std::copy(xdata+j-i, xdata+end-i, ydata+j);
3623 j = end+1;
3624 }
3625 std::copy(xdata+j-m, xdata+Width(), ydata+j);
3626
3627 const int out_layout = 0; // 0 - output is ldofs array
3628 if (!local)
3629 {
3630 gc.BcastEnd(ydata, out_layout);
3631 }
3632}
3633
3635 const Vector &x, Vector &y) const
3636{
3637 MFEM_ASSERT(x.Size() == Height(), "");
3638 MFEM_ASSERT(y.Size() == Width(), "");
3639
3640 const real_t *xdata = x.HostRead();
3641 real_t *ydata = y.HostWrite();
3642 const int m = external_ldofs.Size();
3643
3644 if (!local)
3645 {
3646 gc.ReduceBegin(xdata);
3647 }
3648
3649 int j = 0;
3650 for (int i = 0; i < m; i++)
3651 {
3652 const int end = external_ldofs[i];
3653 std::copy(xdata+j, xdata+end, ydata+j-i);
3654 j = end+1;
3655 }
3656 std::copy(xdata+j, xdata+Height(), ydata+j-m);
3657
3658 const int out_layout = 2; // 2 - output is an array on all ltdofs
3659 if (!local)
3660 {
3661 gc.ReduceEnd<real_t>(ydata, out_layout, GroupCommunicator::Sum);
3662 }
3663}
3664
3666 const GroupCommunicator &gc_, const SparseMatrix *R, bool local_)
3667 : ConformingProlongationOperator(R->Width(), gc_, local_),
3668 mpi_gpu_aware(Device::GetGPUAwareMPI())
3669{
3670 MFEM_ASSERT(R->Finalized(), "");
3671 const int tdofs = R->Height();
3672 MFEM_ASSERT(tdofs == R->HostReadI()[tdofs], "");
3673 ltdof_ldof = Array<int>(const_cast<int*>(R->HostReadJ()), tdofs);
3674 {
3675 Table nbr_ltdof;
3676 gc.GetNeighborLTDofTable(nbr_ltdof);
3677 const int nb_connections = nbr_ltdof.Size_of_connections();
3678 shr_ltdof.SetSize(nb_connections);
3679 shr_ltdof.CopyFrom(nbr_ltdof.GetJ());
3680 shr_buf.SetSize(nb_connections);
3681 shr_buf.UseDevice(true);
3682 shr_buf_offsets = nbr_ltdof.GetIMemory();
3683 {
3684 Array<int> shared_ltdof(nbr_ltdof.GetJ(), nb_connections);
3685 Array<int> unique_ltdof(shared_ltdof);
3686 unique_ltdof.Sort();
3687 unique_ltdof.Unique();
3688 // Note: the next loop modifies the J array of nbr_ltdof
3689 for (int i = 0; i < shared_ltdof.Size(); i++)
3690 {
3691 shared_ltdof[i] = unique_ltdof.FindSorted(shared_ltdof[i]);
3692 MFEM_ASSERT(shared_ltdof[i] != -1, "internal error");
3693 }
3694 Table unique_shr;
3695 Transpose(shared_ltdof, unique_shr, unique_ltdof.Size());
3696 unq_ltdof = Array<int>(unique_ltdof, unique_ltdof.Size());
3697 unq_shr_i = Array<int>(unique_shr.GetI(), unique_shr.Size()+1);
3698 unq_shr_j = Array<int>(unique_shr.GetJ(), unique_shr.Size_of_connections());
3699 }
3700 nbr_ltdof.GetJMemory().Delete();
3701 nbr_ltdof.LoseData();
3702 }
3703 {
3704 Table nbr_ldof;
3705 gc.GetNeighborLDofTable(nbr_ldof);
3706 const int nb_connections = nbr_ldof.Size_of_connections();
3707 ext_ldof.SetSize(nb_connections);
3708 ext_ldof.CopyFrom(nbr_ldof.GetJ());
3710 ext_buf.SetSize(nb_connections);
3711 ext_buf.UseDevice(true);
3712 ext_buf_offsets = nbr_ldof.GetIMemory();
3713 nbr_ldof.GetJMemory().Delete();
3714 nbr_ldof.LoseData();
3715 }
3716 const GroupTopology &gtopo = gc.GetGroupTopology();
3717 int req_counter = 0;
3718 for (int nbr = 1; nbr < gtopo.GetNumNeighbors(); nbr++)
3719 {
3720 const int send_offset = shr_buf_offsets[nbr];
3721 const int send_size = shr_buf_offsets[nbr+1] - send_offset;
3722 if (send_size > 0) { req_counter++; }
3723
3724 const int recv_offset = ext_buf_offsets[nbr];
3725 const int recv_size = ext_buf_offsets[nbr+1] - recv_offset;
3726 if (recv_size > 0) { req_counter++; }
3727 }
3728 requests = new MPI_Request[req_counter];
3729}
3730
3732 const ParFiniteElementSpace &pfes, bool local_)
3733 : DeviceConformingProlongationOperator(pfes.GroupComm(),
3734 pfes.GetRestrictionMatrix(),
3735 local_)
3736{
3737 MFEM_ASSERT(pfes.Conforming(), "internal error");
3738 MFEM_ASSERT(pfes.GetRestrictionMatrix()->Height() == pfes.GetTrueVSize(), "");
3739}
3740
3741static void ExtractSubVector(const Array<int> &indices,
3742 const Vector &vin, Vector &vout)
3743{
3744 MFEM_ASSERT(indices.Size() == vout.Size(), "incompatible sizes!");
3745 auto y = vout.Write();
3746 const auto x = vin.Read();
3747 const auto I = indices.Read();
3748 mfem::forall(indices.Size(), [=] MFEM_HOST_DEVICE (int i)
3749 {
3750 y[i] = x[I[i]];
3751 }); // indices can be repeated
3752}
3753
3755 const Vector &x) const
3756{
3757 // shr_buf[i] = src[shr_ltdof[i]]
3758 if (shr_ltdof.Size() == 0) { return; }
3759 ExtractSubVector(shr_ltdof, x, shr_buf);
3760 // If the above kernel is executed asynchronously, we should wait for it to
3761 // complete
3762 if (mpi_gpu_aware) { MFEM_STREAM_SYNC; }
3763}
3764
3765static void SetSubVector(const Array<int> &indices,
3766 const Vector &vin, Vector &vout)
3767{
3768 MFEM_ASSERT(indices.Size() == vin.Size(), "incompatible sizes!");
3769 // Use ReadWrite() since we modify only a subset of the indices:
3770 auto y = vout.ReadWrite();
3771 const auto x = vin.Read();
3772 const auto I = indices.Read();
3773 mfem::forall(indices.Size(), [=] MFEM_HOST_DEVICE (int i)
3774 {
3775 y[I[i]] = x[i];
3776 });
3777}
3778
3780 const Vector &x, Vector &y) const
3781{
3782 // dst[ltdof_ldof[i]] = src[i]
3783 if (ltdof_ldof.Size() == 0) { return; }
3784 SetSubVector(ltdof_ldof, x, y);
3785}
3786
3788 Vector &y) const
3789{
3790 // dst[ext_ldof[i]] = ext_buf[i]
3791 if (ext_ldof.Size() == 0) { return; }
3792 SetSubVector(ext_ldof, ext_buf, y);
3793}
3794
3796 Vector &y) const
3797{
3798 const GroupTopology &gtopo = gc.GetGroupTopology();
3799 int req_counter = 0;
3800 // Make sure 'y' is marked as valid on device and for use on device.
3801 // This ensures that there is no unnecessary host to device copy when the
3802 // input 'y' is valid on host (in 'y.SetSubVector(ext_ldof, 0.0)' when local
3803 // is true) or BcastLocalCopy (when local is false).
3804 y.Write();
3805 if (local)
3806 {
3807 // done on device since we've marked ext_ldof for use on device:
3808 y.SetSubVector(ext_ldof, 0.0);
3809 }
3810 else
3811 {
3812 BcastBeginCopy(x); // copy to 'shr_buf'
3813 for (int nbr = 1; nbr < gtopo.GetNumNeighbors(); nbr++)
3814 {
3815 const int send_offset = shr_buf_offsets[nbr];
3816 const int send_size = shr_buf_offsets[nbr+1] - send_offset;
3817 if (send_size > 0)
3818 {
3819 auto send_buf = mpi_gpu_aware ? shr_buf.Read() : shr_buf.HostRead();
3820 MPI_Isend(send_buf + send_offset, send_size, MPITypeMap<real_t>::mpi_type,
3821 gtopo.GetNeighborRank(nbr), 41822,
3822 gtopo.GetComm(), &requests[req_counter++]);
3823 }
3824 const int recv_offset = ext_buf_offsets[nbr];
3825 const int recv_size = ext_buf_offsets[nbr+1] - recv_offset;
3826 if (recv_size > 0)
3827 {
3828 auto recv_buf = mpi_gpu_aware ? ext_buf.Write() : ext_buf.HostWrite();
3829 MPI_Irecv(recv_buf + recv_offset, recv_size, MPITypeMap<real_t>::mpi_type,
3830 gtopo.GetNeighborRank(nbr), 41822,
3831 gtopo.GetComm(), &requests[req_counter++]);
3832 }
3833 }
3834 }
3835 BcastLocalCopy(x, y);
3836 if (!local)
3837 {
3838 MPI_Waitall(req_counter, requests, MPI_STATUSES_IGNORE);
3839 BcastEndCopy(y); // copy from 'ext_buf'
3840 }
3841}
3842
3849
3851 const Vector &x) const
3852{
3853 // ext_buf[i] = src[ext_ldof[i]]
3854 if (ext_ldof.Size() == 0) { return; }
3855 ExtractSubVector(ext_ldof, x, ext_buf);
3856 // If the above kernel is executed asynchronously, we should wait for it to
3857 // complete
3858 if (mpi_gpu_aware) { MFEM_STREAM_SYNC; }
3859}
3860
3862 const Vector &x, Vector &y) const
3863{
3864 // dst[i] = src[ltdof_ldof[i]]
3865 if (ltdof_ldof.Size() == 0) { return; }
3866 ExtractSubVector(ltdof_ldof, x, y);
3867}
3868
3869static void AddSubVector(const Array<int> &unique_dst_indices,
3870 const Array<int> &unique_to_src_offsets,
3871 const Array<int> &unique_to_src_indices,
3872 const Vector &src,
3873 Vector &dst)
3874{
3875 auto y = dst.ReadWrite();
3876 const auto x = src.Read();
3877 const auto DST_I = unique_dst_indices.Read();
3878 const auto SRC_O = unique_to_src_offsets.Read();
3879 const auto SRC_I = unique_to_src_indices.Read();
3880 mfem::forall(unique_dst_indices.Size(), [=] MFEM_HOST_DEVICE (int i)
3881 {
3882 const int dst_idx = DST_I[i];
3883 real_t sum = y[dst_idx];
3884 const int end = SRC_O[i+1];
3885 for (int j = SRC_O[i]; j != end; ++j) { sum += x[SRC_I[j]]; }
3886 y[dst_idx] = sum;
3887 });
3888}
3889
3891{
3892 // dst[shr_ltdof[i]] += shr_buf[i]
3893 if (unq_ltdof.Size() == 0) { return; }
3894 AddSubVector(unq_ltdof, unq_shr_i, unq_shr_j, shr_buf, y);
3895}
3896
3898 Vector &y) const
3899{
3900 const GroupTopology &gtopo = gc.GetGroupTopology();
3901 int req_counter = 0;
3902 if (!local)
3903 {
3904 ReduceBeginCopy(x); // copy to 'ext_buf'
3905 for (int nbr = 1; nbr < gtopo.GetNumNeighbors(); nbr++)
3906 {
3907 const int send_offset = ext_buf_offsets[nbr];
3908 const int send_size = ext_buf_offsets[nbr+1] - send_offset;
3909 if (send_size > 0)
3910 {
3911 auto send_buf = mpi_gpu_aware ? ext_buf.Read() : ext_buf.HostRead();
3912 MPI_Isend(send_buf + send_offset, send_size, MPITypeMap<real_t>::mpi_type,
3913 gtopo.GetNeighborRank(nbr), 41823,
3914 gtopo.GetComm(), &requests[req_counter++]);
3915 }
3916 const int recv_offset = shr_buf_offsets[nbr];
3917 const int recv_size = shr_buf_offsets[nbr+1] - recv_offset;
3918 if (recv_size > 0)
3919 {
3920 auto recv_buf = mpi_gpu_aware ? shr_buf.Write() : shr_buf.HostWrite();
3921 MPI_Irecv(recv_buf + recv_offset, recv_size, MPITypeMap<real_t>::mpi_type,
3922 gtopo.GetNeighborRank(nbr), 41823,
3923 gtopo.GetComm(), &requests[req_counter++]);
3924 }
3925 }
3926 }
3927 ReduceLocalCopy(x, y);
3928 if (!local)
3929 {
3930 MPI_Waitall(req_counter, requests, MPI_STATUSES_IGNORE);
3931 ReduceEndAssemble(y); // assemble from 'shr_buf'
3932 }
3933}
3934
3935} // namespace mfem
3936
3937#endif
Memory< T > & GetMemory()
Return a reference to the Memory object used by the Array.
Definition array.hpp:123
int FindSorted(const T &el) const
Do bisection search for 'el' in a sorted array; return -1 if not found.
Definition array.hpp:838
const T * HostRead() const
Shortcut for mfem::Read(a.GetMemory(), a.Size(), false).
Definition array.hpp:321
void Sort()
Sorts the array in ascending order. This requires operator< to be defined for T.
Definition array.hpp:261
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:298
void Reserve(int capacity)
Ensures that the allocated size is at least the given size.
Definition array.hpp:162
void SetSize(int nsize)
Change the logical size of the array, keep existing entries.
Definition array.hpp:697
void LoseData()
NULL-ifies the data.
Definition array.hpp:138
int Size() const
Return the logical size of the array.
Definition array.hpp:144
void MakeRef(T *data_, int size_, bool own_data=false)
Make this Array a reference to a pointer.
Definition array.hpp:882
void DeleteAll()
Delete the whole array.
Definition array.hpp:864
int Append(const T &el)
Append element 'el' to array, resize if necessary.
Definition array.hpp:769
void Unique()
Removes duplicities from a sorted array. This requires operator== to be defined for T.
Definition array.hpp:269
const T * Read(bool on_dev=true) const
Shortcut for mfem::Read(a.GetMemory(), a.Size(), on_dev).
Definition array.hpp:317
Operator that extracts face degrees of freedom for H1, ND, or RT FiniteElementSpaces.
Auxiliary class used by ParFiniteElementSpace.
Definition pfespace.hpp:442
void MultTranspose(const Vector &x, Vector &y) const override
Action of the transpose operator: y=A^t(x). The default behavior in class Operator is to generate an ...
const GroupCommunicator & GetGroupCommunicator() const
ConformingProlongationOperator(int lsize, const GroupCommunicator &gc_, bool local_=false)
void Mult(const Vector &x, Vector &y) const override
Operator application: y=A(x).
const GroupCommunicator & gc
Definition pfespace.hpp:445
real_t * GetData() const
Returns the matrix data array.
Definition densemat.hpp:115
Auxiliary device class used by ParFiniteElementSpace.
Definition pfespace.hpp:465
void Mult(const Vector &x, Vector &y) const override
Operator application: y=A(x).
void MultTranspose(const Vector &x, Vector &y) const override
Action of the transpose operator: y=A^t(x). The default behavior in class Operator is to generate an ...
void ReduceEndAssemble(Vector &dst) const
void BcastBeginCopy(const Vector &src) const
void ReduceLocalCopy(const Vector &src, Vector &dst) const
void ReduceBeginCopy(const Vector &src) const
DeviceConformingProlongationOperator(const GroupCommunicator &gc_, const SparseMatrix *R, bool local_=false)
void BcastLocalCopy(const Vector &src, Vector &dst) const
The MFEM Device class abstracts hardware devices such as GPUs, as well as programming models such as ...
Definition device.hpp:123
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:259
void SetFaceOrientations(const Array< int > &Fo)
Configure the transformation using face orientations for the current element.
Definition doftrans.hpp:169
void SetDofTransformation(const StatelessDofTransformation &dof_trans)
Set or change the nested StatelessDofTransformation object.
Definition doftrans.hpp:176
const StatelessDofTransformation * GetDofTransformation() const
Return the nested StatelessDofTransformation object.
Definition doftrans.hpp:186
void SetVDim(int vdim=1, int ordering=0)
Set or change the vdim and ordering parameter.
Definition doftrans.hpp:190
Abstract data type element.
Definition element.hpp:29
Geometry::Type GetGeometryType() const
Definition element.hpp:52
Base class for operators that extracts Face degrees of freedom.
Collection of finite elements from the same family in multiple dimensions. This class is used to matc...
Definition fe_coll.hpp:27
virtual const int * DofOrderForOrientation(Geometry::Type GeomType, int Or) const =0
Returns an array, say p, that maps a local permuted index i to a local base index: base_i = p[i].
@ DISCONTINUOUS
Field is discontinuous across element interfaces.
Definition fe_coll.hpp:48
@ TANGENTIAL
Tangential components of vector field.
Definition fe_coll.hpp:46
int GetOrder() const
Return the order (polynomial degree) of the FE collection, corresponding to the order/degree returned...
Definition fe_coll.hpp:225
virtual int GetContType() const =0
int HasFaceDofs(Geometry::Type geom, int p) const
Definition fe_coll.cpp:100
virtual int DofForGeometry(Geometry::Type GeomType) const =0
virtual const FiniteElement * FiniteElementForGeometry(Geometry::Type GeomType) const =0
void SubDofOrder(Geometry::Type Geom, int SDim, int Info, Array< int > &dofs) const
Get the local dofs for a given sub-manifold.
Definition fe_coll.cpp:472
GridFunction interpolation operator applicable after mesh refinement.
Definition fespace.hpp:417
Class FiniteElementSpace - responsible for providing FEM view of the mesh, mainly managing the set of...
Definition fespace.hpp:220
friend void Mesh::Swap(Mesh &, bool)
DofTransformation DoFTrans
Definition fespace.hpp:275
bool IsVariableOrder() const
Returns true if the space contains elements of varying polynomial orders.
Definition fespace.hpp:581
Array< StatelessDofTransformation * > DoFTransArray
Definition fespace.hpp:274
void DofsToVDofs(Array< int > &dofs, int ndofs=-1) const
Compute the full set of vdofs corresponding to each entry in dofs.
Definition fespace.cpp:213
DofTransformation * GetElementDofs(int elem, Array< int > &dofs) const
Returns indices of degrees of freedom of element 'elem'. The returned indices are offsets into an ldo...
Definition fespace.cpp:2846
NURBSExtension * NURBSext
Definition fespace.hpp:270
virtual int GetFaceDofs(int face, Array< int > &dofs, int variant=0) const
Returns the indices of the degrees of freedom for the specified face, including the DOFs for the edge...
Definition fespace.cpp:2958
int GetNDofs() const
Returns number of degrees of freedom. This is the number of Local Degrees of Freedom.
Definition fespace.hpp:710
void GetTransferOperator(const FiniteElementSpace &coarse_fes, OperatorHandle &T) const
Construct and return an Operator that can be used to transfer GridFunction data from coarse_fes,...
Definition fespace.cpp:3349
virtual const Operator * GetProlongationMatrix() const
The returned Operator is owned by the FiniteElementSpace.
Definition fespace.hpp:597
std::unique_ptr< Operator > R_transpose
Operator computing the action of the transpose of the restriction.
Definition fespace.hpp:287
const FiniteElementCollection * fec
Associated FE collection (not owned).
Definition fespace.hpp:231
int VDofToDof(int vdof) const
Compute the inverse of the Dof to VDof mapping for a single index vdof.
Definition fespace.hpp:995
DofTransformation * GetElementVDofs(int i, Array< int > &vdofs) const
Returns indices of degrees of freedom for the i'th element. The returned indices are offsets into an ...
Definition fespace.cpp:287
virtual const FiniteElement * GetFE(int i) const
Returns pointer to the FiniteElement in the FiniteElementCollection associated with i'th element in t...
Definition fespace.cpp:3168
int FirstFaceDof(int face, int variant=0) const
Definition fespace.hpp:376
void GetLocalDerefinementMatrices(Geometry::Type geom, DenseTensor &localR) const
Definition fespace.cpp:2071
int vdim
Vector dimension (number of unknowns per degree of freedom).
Definition fespace.hpp:234
Table var_face_dofs
NOTE: also used for spaces with mixed faces.
Definition fespace.hpp:255
int GetEdgeDofs(int edge, Array< int > &dofs, int variant=0) const
Returns the indices of the degrees of freedom for the specified edge, including the DOFs for the vert...
Definition fespace.cpp:3046
OperatorHandle Th
Transformation to apply to GridFunctions after space Update().
Definition fespace.hpp:290
SparseMatrix * RefinementMatrix(int old_ndofs, const Table *old_elem_dof, const Table *old_elem_fos)
Definition fespace.cpp:1551
int ndofs
Number of degrees of freedom. Number of unknowns is ndofs * vdim.
Definition fespace.hpp:242
static void MarkerToList(const Array< int > &marker, Array< int > &list)
Convert a Boolean marker array to a list containing all marked indices.
Definition fespace.cpp:648
Mesh * mesh
The mesh that FE space lives on (not owned).
Definition fespace.hpp:228
Ordering::Type ordering
Definition fespace.hpp:239
const FiniteElementCollection * FEColl() const
Definition fespace.hpp:727
int GetMaxElementOrder() const
Return the maximum polynomial order.
Definition fespace.hpp:577
int GetVSize() const
Return the number of vector dofs, i.e. GetNDofs() x GetVDim().
Definition fespace.hpp:713
DofTransformation * GetBdrElementDofs(int bel, Array< int > &dofs) const
Returns indices of degrees of freedom for boundary element 'bel'. The returned indices are offsets in...
Definition fespace.cpp:2950
bool IsDGSpace() const
Return whether or not the space is discontinuous (L2)
Definition fespace.hpp:1313
static int DecodeDof(int dof)
Helper to return the DOF associated with a sign encoded DOF.
Definition fespace.hpp:1017
virtual void GetEssentialVDofs(const Array< int > &bdr_attr_is_ess, Array< int > &ess_vdofs, int component=-1) const
Mark degrees of freedom associated with boundary elements with the specified boundary attributes (mar...
Definition fespace.cpp:514
int DofToVDof(int dof, int vd, int ndofs=-1) const
Compute a single vdof corresponding to the index dof and the vector index vd.
Definition fespace.cpp:249
void BuildElementToDofTable() const
Definition fespace.cpp:346
Abstract class for all finite elements.
Definition fe_base.hpp:239
int GetDof() const
Returns the number of degrees of freedom in the finite element.
Definition fe_base.hpp:329
static const int NumGeom
Definition geom.hpp:42
static const int Dimension[NumGeom]
Definition geom.hpp:47
static bool IsTensorProduct(Type geom)
Definition geom.hpp:108
Communicator performing operations within groups defined by a GroupTopology with arbitrary-size data ...
void GetNeighborLTDofTable(Table &nbr_ltdof) const
Dofs to be sent to communication neighbors.
Table & GroupLDofTable()
Fill-in the returned Table reference to initialize the GroupCommunicator then call Finalize().
void GetNeighborLDofTable(Table &nbr_ldof) const
Dofs to be received from communication neighbors.
void BcastBegin(T *ldata, int layout) const
Begin a broadcast within each group where the master is the root.
void BcastEnd(T *ldata, int layout) const
Finalize a broadcast started with BcastBegin().
void ReduceBegin(const T *ldata) const
Begin reduction operation within each group where the master is the root.
void ReduceEnd(T *ldata, int layout, void(*Op)(OpData< T >)) const
Finalize reduction operation started with ReduceBegin().
void Reduce(T *ldata, void(*Op)(OpData< T >)) const
Reduce within each group where the master is the root.
const GroupTopology & GetGroupTopology()
Get a reference to the associated GroupTopology object.
void Create(const Array< int > &ldof_group)
Initialize the communicator from a local-dof to group map. Finalize() is called internally.
static void Sum(OpData< T >)
Reduce operation Sum, instantiated for int and double.
void Bcast(T *ldata, int layout) const
Broadcast within each group where the master is the root.
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.
void Finalize()
Allocate internal buffers after the GroupLDofTable is defined.
int GetNeighborRank(int i) const
Return the MPI rank of neighbor 'i'.
bool IAmMaster(int g) const
Return true if I am master for group 'g'.
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.
int GetGroupMasterRank(int g) const
Return the rank of the group master for group 'g'.
MPI_Comm GetComm() const
Return the MPI communicator.
int GetNumNeighbors() const
Return the number of neighbors including the local processor.
Wrapper for hypre's ParCSR matrix class.
Definition hypre.hpp:388
void GetDiag(Vector &diag) const
Get the local diagonal of the matrix.
Definition hypre.cpp:1557
HypreParMatrix * LeftDiagMult(const SparseMatrix &D, HYPRE_BigInt *row_starts=NULL) const
Multiply the HypreParMatrix on the left by a block-diagonal parallel matrix D and return the result a...
Definition hypre.cpp:1994
Identity Operator I: x -> x.
Definition operator.hpp:706
bool UseDevice() const
Read the internal device flag.
void Delete()
Delete the owned pointers and reset the Memory object.
Mesh data type.
Definition mesh.hpp:56
Operation GetLastOperation() const
Return type of last modification of the mesh.
Definition mesh.hpp:2236
NURBSExtension * NURBSext
Optional NURBS mesh extension.
Definition mesh.hpp:290
void GetFaceInfos(int Face, int *Inf1, int *Inf2) const
Definition mesh.cpp:1439
int GetNumFaces() const
Return the number of faces (3D), edges (2D) or vertices (1D).
Definition mesh.cpp:6250
Geometry::Type GetFaceGeometry(int i) const
Return the Geometry::Type associated with face i.
Definition mesh.cpp:1452
Geometry::Type GetBdrElementGeometry(int i) const
Definition mesh.hpp:1376
@ REBALANCE
Definition mesh.hpp:277
long GetSequence() const
Definition mesh.hpp:2242
int GetNE() const
Returns number of elements.
Definition mesh.hpp:1226
int Dimension() const
Dimension of the reference space used within the elements.
Definition mesh.hpp:1160
void GetFaceElements(int Face, int *Elem1, int *Elem2) const
Definition mesh.cpp:1433
bool HasGeometry(Geometry::Type geom) const
Return true iff the given geom is encountered in the mesh. Geometries of dimensions lower than Dimens...
Definition mesh.hpp:1194
Geometry::Type GetElementBaseGeometry(int i) const
Definition mesh.hpp:1385
static int WorldRank()
Return the MPI rank in MPI_COMM_WORLD.
void GetEdgeVertices(const MeshId &edge_id, int vert_index[2], bool oriented=true) const
Return Mesh vertex indices of an edge identified by 'edge_id'.
Definition ncmesh.cpp:5109
const NCList & GetNCList(int entity)
Return vertex/edge/face list (entity = 0/1/2, respectively).
Definition ncmesh.hpp:320
int GetEdgeNCOrientation(const MeshId &edge_id) const
Definition ncmesh.cpp:5128
int GetFaceVerticesEdges(const MeshId &face_id, int vert_index[4], int edge_index[4], int edge_orientation[4]) const
Definition ncmesh.cpp:5140
Geometry::Type GetFaceGeometry(int index) const
Return face geometry type. index is the Mesh face number.
Definition ncmesh.hpp:425
int GetNVertices() const
Return the number of vertices in the NCMesh.
Definition ncmesh.hpp:150
int MyRank
used in parallel, or when loading a parallel file in serial
Definition ncmesh.hpp:483
int GetNFaces() const
Return the number of (2D) faces in the NCMesh.
Definition ncmesh.hpp:154
int GetNEdges() const
Return the number of edges in the NCMesh.
Definition ncmesh.hpp:152
static const DenseMatrix & GetFaceTransform(int ori)
Definition doftrans.hpp:319
Pointer to an Operator of a specified type.
Definition handle.hpp:34
void SetOperatorOwner(bool own=true)
Set the ownership flag for the held Operator.
Definition handle.hpp:120
Operator * Ptr() const
Access the underlying Operator pointer.
Definition handle.hpp:87
void Reset(OpType *A, bool own_A=true)
Reset the OperatorHandle to the given OpType pointer, A.
Definition handle.hpp:145
Operator::Type Type() const
Get the currently set operator type id.
Definition handle.hpp:99
Abstract operator.
Definition operator.hpp:25
int width
Dimension of the input / number of columns in the matrix.
Definition operator.hpp:28
int Height() const
Get the height (size of output) of the Operator. Synonym with NumRows().
Definition operator.hpp:66
int height
Dimension of the output / number of rows in the matrix.
Definition operator.hpp:27
int Width() const
Get the width (size of input) of the Operator. Synonym with NumCols().
Definition operator.hpp:72
@ ANY_TYPE
ID for the base class Operator, i.e. any type.
Definition operator.hpp:285
@ MFEM_SPARSEMAT
ID for class SparseMatrix.
Definition operator.hpp:286
@ Hypre_ParCSR
ID for class HypreParMatrix.
Definition operator.hpp:287
Abstract parallel finite element space.
Definition pfespace.hpp:29
void GetTrueTransferOperator(const FiniteElementSpace &coarse_fes, OperatorHandle &T) const override
Construct and return an Operator that can be used to transfer true-dof data from coarse_fes,...
HYPRE_BigInt GetGlobalScalarTDofNumber(int sldof)
void GetSharedTriangleDofs(int group, int fi, Array< int > &dofs) const
Definition pfespace.cpp:606
void GetSharedEdgeDofs(int group, int ei, Array< int > &dofs) const
Definition pfespace.cpp:583
const FaceRestriction * GetFaceRestriction(ElementDofOrdering f_ordering, FaceType type, L2FaceValues mul=L2FaceValues::DoubleValued) const override
Definition pfespace.cpp:541
HYPRE_BigInt * GetTrueDofOffsets() const
Definition pfespace.hpp:282
void GetEssentialTrueDofs(const Array< int > &bdr_attr_is_ess, Array< int > &ess_tdof_list, int component=-1) const override
HYPRE_BigInt GlobalVSize() const
Definition pfespace.hpp:283
const Operator * GetRestrictionOperator() const override
int GetLocalTDofNumber(int ldof) const
void Synchronize(Array< int > &ldof_marker) const
Given an integer array on the local degrees of freedom, perform a bitwise OR between the shared dofs.
ParFiniteElementSpace(const ParFiniteElementSpace &orig, ParMesh *pmesh=NULL, const FiniteElementCollection *fec=NULL)
Copy constructor: deep copy all data from orig except the ParMesh, the FiniteElementCollection,...
Definition pfespace.cpp:29
void DivideByGroupSize(real_t *vec)
Scale a vector of true dofs.
Definition pfespace.cpp:974
HYPRE_BigInt GlobalTrueVSize() const
Definition pfespace.hpp:285
int GetTrueVSize() const override
Return the number of local vector true dofs.
Definition pfespace.hpp:289
HYPRE_BigInt GetMyDofOffset() const
HYPRE_BigInt * GetDofOffsets() const
Definition pfespace.hpp:281
Array< HYPRE_BigInt > face_nbr_glob_dof_map
Definition pfespace.hpp:214
void GetFaceNbrFaceVDofs(int i, Array< int > &vdofs) const
const Operator * GetProlongationMatrix() const override
The returned Operator is owned by the FiniteElementSpace.
GroupCommunicator * ScalarGroupComm()
Return a new GroupCommunicator on scalar dofs, i.e. for VDim = 1.
Definition pfespace.cpp:994
void GetEssentialVDofs(const Array< int > &bdr_attr_is_ess, Array< int > &ess_dofs, int component=-1) const override
Determine the boundary degrees of freedom.
HypreParMatrix * Dof_TrueDof_Matrix() const
The true dof-to-dof interpolation matrix.
Definition pfespace.hpp:327
void GetFaceNbrElementVDofs(int i, Array< int > &vdofs, DofTransformation &doftrans) const
int GetFaceDofs(int i, Array< int > &dofs, int variant=0) const override
Definition pfespace.cpp:518
HYPRE_BigInt GetMyTDofOffset() const
void GetSharedQuadrilateralDofs(int group, int fi, Array< int > &dofs) const
Definition pfespace.cpp:630
void GetElementDofs(int i, Array< int > &dofs, DofTransformation &doftrans) const override
The same as GetElementDofs(), but with a user-allocated DofTransformation object. doftrans must be al...
Definition pfespace.cpp:468
const SparseMatrix * GetRestrictionMatrix() const override
Get the R matrix which restricts a local dof vector to true dof vector.
Definition pfespace.hpp:389
HYPRE_BigInt GetGlobalTDofNumber(int ldof) const
Returns the global tdof number of the given local degree of freedom.
void Update(bool want_transform=true) override
HypreParMatrix * GetPartialConformingInterpolation()
For a non-conforming mesh, construct and return the interpolation matrix from the partially conformin...
Definition pfespace.cpp:963
int TrueVSize() const
Obsolete, kept for backward compatibility.
Definition pfespace.hpp:436
const FiniteElement * GetFaceNbrFaceFE(int i) const
const FiniteElement * GetFaceNbrFE(int i) const
const FiniteElement * GetFE(int i) const override
Definition pfespace.cpp:534
void GetBdrElementDofs(int i, Array< int > &dofs, DofTransformation &doftrans) const override
The same as GetBdrElementDofs(), but with a user-allocated DofTransformation object....
Definition pfespace.cpp:493
Operator that extracts Face degrees of freedom in parallel.
Class for parallel meshes.
Definition pmesh.hpp:34
int GroupNQuadrilaterals(int group) const
Definition pmesh.hpp:449
Table send_face_nbr_elements
Definition pmesh.hpp:436
MPI_Comm GetComm() const
Definition pmesh.hpp:402
int GetMyRank() const
Definition pmesh.hpp:404
void ExchangeFaceNbrData(Table *gr_sface, int *s2l_face)
Definition pmesh.cpp:2159
int GetNRanks() const
Definition pmesh.hpp:403
int GroupVertex(int group, int i) const
Definition pmesh.hpp:451
void GetFaceNbrElementFaces(int i, Array< int > &faces, Array< int > &orientation) const
Definition pmesh.cpp:2814
Array< Element * > face_nbr_elements
Definition pmesh.hpp:433
GroupTopology gtopo
Definition pmesh.hpp:426
int GroupNTriangles(int group) const
Definition pmesh.hpp:448
int GroupNEdges(int group) const
Definition pmesh.hpp:447
void GenerateOffsets(int N, HYPRE_BigInt loc_sizes[], Array< HYPRE_BigInt > *offsets[]) const
Definition pmesh.cpp:1943
Array< int > face_nbr_elements_offset
Definition pmesh.hpp:431
int GetNFaceNeighbors() const
Definition pmesh.hpp:517
void GroupQuadrilateral(int group, int i, int &face, int &o) const
Definition pmesh.cpp:1635
int GetNGroups() const
Definition pmesh.hpp:443
ParNCMesh * pncmesh
Definition pmesh.hpp:439
int GetFaceNbrRank(int fn) const
Definition pmesh.cpp:2796
void GroupTriangle(int group, int i, int &face, int &o) const
Definition pmesh.cpp:1624
void GroupEdge(int group, int i, int &edge, int &o) const
Definition pmesh.cpp:1616
int GroupNVertices(int group) const
Definition pmesh.hpp:446
Operator that extracts Face degrees of freedom for NCMesh in parallel.
Operator that extracts Face degrees of freedom for NCMesh in parallel.
void SendRebalanceDofs(int old_ndofs, const Table &old_element_dofs, long old_global_offset, FiniteElementSpace *space)
Use the communication pattern from last Rebalance() to send element DOFs.
Definition pncmesh.cpp:2218
int GetFaceOrientation(int index) const
Return (shared) face orientation relative to its owner element.
Definition pncmesh.hpp:137
int GetNGhostEdges() const
Definition pncmesh.hpp:115
void DecodeGroups(std::istream &is, Array< GroupId > &ids)
Definition pncmesh.cpp:2752
const CommGroup & GetGroup(GroupId id) const
Return a list of ranks contained in the group of the given ID.
Definition pncmesh.hpp:172
int GetNGhostFaces() const
Definition pncmesh.hpp:116
void EncodeGroups(std::ostream &os, const Array< GroupId > &ids)
Definition pncmesh.cpp:2710
void AdjustMeshIds(Array< MeshId > ids[], int rank)
Definition pncmesh.cpp:2472
std::vector< int > CommGroup
Definition pncmesh.hpp:143
void DecodeMeshIds(std::istream &is, Array< MeshId > ids[])
Definition pncmesh.cpp:2653
void EncodeMeshIds(std::ostream &os, Array< MeshId > ids[])
Definition pncmesh.cpp:2610
int GetMyRank() const
Definition pncmesh.hpp:208
int GetNGhostVertices() const
Definition pncmesh.hpp:114
bool GroupContains(GroupId id, int rank) const
Return true if group 'id' contains the given rank.
Definition pncmesh.cpp:421
GroupTopology gtopo
Definition nurbs.hpp:621
Array< int > ldof_group
Definition nurbs.hpp:623
Data type sparse matrix.
Definition sparsemat.hpp:51
const int * HostReadJ() const
bool Finalized() const
Returns whether or not CSR format has been finalized.
void BooleanMult(const Array< int > &x, Array< int > &y) const
y = A * x, treating all entries as booleans (zero=false, nonzero=true).
const int * HostReadI() const
void LoseData()
Call this if data has been stolen.
Definition table.hpp:180
int * GetJ()
Definition table.hpp:114
void AddConnections(int r, const int *c, int nc)
Definition table.cpp:104
int RowSize(int i) const
Definition table.hpp:108
void ShiftUpI()
Definition table.cpp:115
void Clear()
Definition table.cpp:381
void GetRow(int i, Array< int > &row) const
Return row i in array row (the Table must be finalized)
Definition table.cpp:187
void AddConnection(int r, int c)
Definition table.hpp:80
void MakeI(int nrows)
Next 7 methods are used together with the default constructor.
Definition table.cpp:81
int Size() const
Returns the number of TYPE I elements.
Definition table.hpp:92
int Size_of_connections() const
Definition table.hpp:98
void AddColumnsInRow(int r, int ncol)
Definition table.hpp:78
void MakeJ()
Definition table.cpp:91
Memory< int > & GetJMemory()
Definition table.hpp:119
int * GetI()
Definition table.hpp:113
void AddAColumnInRow(int r)
Definition table.hpp:77
void SetDims(int rows, int nnz)
Definition table.cpp:140
Memory< int > & GetIMemory()
Definition table.hpp:118
The transpose of a given operator. Switches the roles of the methods Mult() and MultTranspose().
Definition operator.hpp:750
General triple product operator x -> A*B*C*x, with ownership of the factors.
Definition operator.hpp:861
Vector data type.
Definition vector.hpp:80
virtual const real_t * HostRead() const
Shortcut for mfem::Read(vec.GetMemory(), vec.Size(), false).
Definition vector.hpp:478
virtual const real_t * Read(bool on_dev=true) const
Shortcut for mfem::Read(vec.GetMemory(), vec.Size(), on_dev).
Definition vector.hpp:474
virtual real_t * ReadWrite(bool on_dev=true)
Shortcut for mfem::ReadWrite(vec.GetMemory(), vec.Size(), on_dev).
Definition vector.hpp:490
void SetSubVector(const Array< int > &dofs, const real_t value)
Set the entries listed in dofs to the given value.
Definition vector.cpp:604
int Size() const
Returns the size of the vector.
Definition vector.hpp:218
virtual void UseDevice(bool use_dev) const
Enable execution of Vector operations using the mfem::Device.
Definition vector.hpp:136
void SetSize(int s)
Resize the vector to size s.
Definition vector.hpp:538
virtual real_t * HostWrite()
Shortcut for mfem::Write(vec.GetMemory(), vec.Size(), false).
Definition vector.hpp:486
virtual real_t * Write(bool on_dev=true)
Shortcut for mfem::Write(vec.GetMemory(), vec.Size(), on_dev).
Definition vector.hpp:482
int dim
Definition ex24.cpp:53
HYPRE_Int HYPRE_BigInt
int index(int i, int j, int nx, int ny)
Definition life.cpp:235
void write(std::ostream &os, T value)
Write 'value' to stream.
Definition binaryio.hpp:30
T read(std::istream &is)
Read a value from the stream and return it.
Definition binaryio.hpp:37
Linear1DFiniteElement SegmentFE
Definition segment.cpp:52
void mfem_error(const char *msg)
Definition error.cpp:154
void Mult(const Table &A, const Table &B, Table &C)
C = A * B (as boolean matrices)
Definition table.cpp:476
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:414
BiLinear2DFiniteElement QuadrilateralFE
float real_t
Definition config.hpp:43
void SortPairs(Pair< A, B > *pairs, int size)
Sort an array of Pairs with respect to the first element.
ElementDofOrdering
Constants describing the possible orderings of the DOFs in one element.
Definition fespace.hpp:75
std::function< real_t(const Vector &)> f(real_t mass_coeff)
Definition lor_mms.hpp:30
MFEM_EXPORT Linear2DFiniteElement TriangleFE
Definition fe.cpp:32
void forall(int N, lambda &&body)
Definition forall.hpp:754
FaceType
Definition mesh.hpp:47
real_t p(const Vector &x, real_t t)
RefCoord s[3]
@ DEVICE_MASK
Biwise-OR of all device backends.
Definition device.hpp:97
Helper struct to convert a C++ type to an MPI type.
MeshIdAndType GetMeshIdAndType(int index) const
Return a mesh id and type for a given nc index.
Definition ncmesh.cpp:3509
static void WaitAllSent(MapT &rank_msg)
static void IsendAll(MapT &rank_msg, MPI_Comm comm)
static bool IProbe(int &rank, int &size, MPI_Comm comm)