MFEM v4.10.0
Finite element discretization library
Loading...
Searching...
No Matches
pfespace.cpp
Go to the documentation of this file.
1// Copyright (c) 2010-2026, Lawrence Livermore National Security, LLC. Produced
2// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
3// LICENSE and NOTICE for details. LLNL-CODE-806117.
4//
5// This file is part of the MFEM library. For more information and source code
6// availability visit https://mfem.org.
7//
8// MFEM is free software; you can redistribute it and/or modify it under the
9// terms of the BSD-3 license. We welcome feedback and contributions, see file
10// CONTRIBUTING.md for details.
11
12#include "../config/config.hpp"
13
14#ifdef MFEM_USE_MPI
15
16#include "pfespace.hpp"
17#include "prestriction.hpp"
18#include "transfer.hpp"
19
20#include "../general/forall.hpp"
24
25#include "pderefmat_op.hpp"
26
27#include <limits>
28#include <list>
29#include <unordered_map>
30#include <unordered_set>
31
32namespace mfem
33{
35 const ParFiniteElementSpace &orig, ParMesh *pmesh,
36 const FiniteElementCollection *fec)
37 : FiniteElementSpace(orig, pmesh, fec)
38{
39 ParInit(pmesh ? pmesh : orig.pmesh);
40}
41
43 const FiniteElementSpace &orig, ParMesh &pmesh,
44 const FiniteElementCollection *fec)
45 : FiniteElementSpace(orig, &pmesh, fec)
46{
47 ParInit(&pmesh);
48}
49
51 ParMesh *pm, const FiniteElementSpace *global_fes, const int *partitioning,
53 : FiniteElementSpace(pm, MakeLocalNURBSext(global_fes->GetNURBSext(),
54 pm->NURBSext),
55 f ? f : global_fes->FEColl(),
56 global_fes->GetVDim(), global_fes->GetOrdering())
57{
58 ParInit(pm);
59 // For NURBS spaces, the variable-order data is contained in the
60 // NURBSExtension of 'global_fes' and inside the ParNURBSExtension of 'pm'.
61
62 // TODO: when general variable-order support is added, copy the local portion
63 // of the variable-order data from 'global_fes' to 'this'.
64}
65
67 ParMesh *pm, const FiniteElementCollection *f, int dim, int ordering)
68 : FiniteElementSpace(pm, f, dim, ordering)
69{
70 ParInit(pm);
71}
72
75 int dim, int ordering)
76 : FiniteElementSpace(pm, ext, f, dim, ordering)
77{
78 ParInit(pm);
79}
80
81// static method
82ParNURBSExtension *ParFiniteElementSpace::MakeLocalNURBSext(
83 const NURBSExtension *globNURBSext, const NURBSExtension *parNURBSext)
84{
85 if (globNURBSext == NULL) { return NULL; }
86 const ParNURBSExtension *pNURBSext =
87 dynamic_cast<const ParNURBSExtension*>(parNURBSext);
88 MFEM_ASSERT(pNURBSext, "need a ParNURBSExtension");
89 // make a copy of globNURBSext:
90 NURBSExtension *tmp_globNURBSext = new NURBSExtension(*globNURBSext);
91 // tmp_globNURBSext will be deleted by the following ParNURBSExtension ctor:
92 return new ParNURBSExtension(tmp_globNURBSext, pNURBSext);
93}
94
95void ParFiniteElementSpace::ParInit(ParMesh *pm)
96{
97 pmesh = pm;
98 pncmesh = nullptr;
99
100 MyComm = pmesh->GetComm();
101 NRanks = pmesh->GetNRanks();
102 MyRank = pmesh->GetMyRank();
103
104 gcomm = nullptr;
105
106 P = nullptr;
107 Pconf = nullptr;
108 nonconf_P = false;
109 Rconf = nullptr;
110 R = nullptr;
112
113 if (NURBSext && !pNURBSext())
114 {
115 // This is necessary in some cases: e.g. when the FiniteElementSpace
116 // constructor creates a serial NURBSExtension of higher order than the
117 // mesh NURBSExtension.
118 MFEM_ASSERT(own_ext, "internal error");
119
120 ParNURBSExtension *pNe = new ParNURBSExtension(
121 NURBSext, dynamic_cast<ParNURBSExtension *>(pmesh->NURBSext));
122 // serial NURBSext is destroyed by the above constructor
123 NURBSext = pNe;
124 UpdateNURBS();
125 }
126
127 Construct(); // parallel version of Construct().
128
129 // Apply the ldof_signs to the elem_dof Table
130 if (Conforming() && !NURBSext)
131 {
132 ApplyLDofSigns(*elem_dof);
133 }
134}
135
136void ParFiniteElementSpace::CommunicateGhostOrder()
137{
138 // Variable-order space needs a nontrivial P matrix + also ghost elements
139 // in parallel, we thus require the mesh to be NC.
140 MFEM_VERIFY(variableOrder && Nonconforming(),
141 "Variable-order space requires a nonconforming mesh.");
142
143 // Check whether h-refinement was done.
144 const bool href = mesh->GetLastOperation() == Mesh::REFINE &&
146 if (href && mesh->GetSequence() != mesh_sequence + 1)
147 {
148 MFEM_ABORT("Error in update sequence. Space needs to be updated after "
149 "each mesh modification.");
150 }
151
152 if (href)
153 {
154 // Update elems_pref and elem_orders
156 }
157
158 int local_orders_changed = orders_changed;
159 int global_orders_changed = 0;
160
161 MPI_Allreduce(&local_orders_changed, &global_orders_changed, 1, MPI_INT,
162 MPI_MAX, MyComm);
163
164 if ((global_orders_changed == 0 && !href) || NRanks == 1)
165 {
166 return;
167 }
168
169 MFEM_ASSERT(mesh->GetNE() == pncmesh->GetNElements(), "");
170
171 Array<ParNCMesh::VarOrderElemInfo> localOrders(mesh->GetNE());
172 for (int i=0; i<mesh->GetNE(); ++i)
173 {
174 ParNCMesh::VarOrderElemInfo order_i{(unsigned int) i, elem_order[i]};
175 localOrders[i] = order_i;
176 }
177
178 pncmesh->CommunicateGhostData(localOrders, ghost_orders);
179}
180
181void ParFiniteElementSpace::Construct()
182{
183 if (NURBSext)
184 {
185 ConstructTrueNURBSDofs();
186 GenerateGlobalOffsets();
187 }
188 else if (Conforming())
189 {
190 ConstructTrueDofs();
191 GenerateGlobalOffsets();
192 }
193 else // Nonconforming()
194 {
195 pncmesh = pmesh->pncmesh;
196
197 // Initialize 'gcomm' for the cut (aka "partially conforming") space.
198 // In the process, the array 'ldof_ltdof' is also initialized (for the cut
199 // space) and used; however, it will be overwritten below with the real
200 // true dofs. Also, 'ldof_sign' and 'ldof_group' are constructed for the
201 // cut space.
202 ConstructTrueDofs();
203
204 ngedofs = ngfdofs = 0;
205
206 // calculate number of ghost DOFs
207 ngvdofs = pncmesh->GetNGhostVertices()
209
210 if (pmesh->Dimension() > 1)
211 {
212 if (IsVariableOrder())
213 {
214 // Note that this requires fespace to have edge order and DOF info
215 // for ghost edges, so var_edge_dofs must include ghost edges.
216 // These are set by ApplyGhostElementOrdersToEdgesAndFaces, which is
217 // called by CalcEdgeFaceVarOrders.
218 for (int i = 0; i < pncmesh->GetNGhostEdges(); ++i)
219 {
220 const int ghostEdge = pncmesh->GetNEdges() + i;
221 const int nvar = GetNVariants(1, ghostEdge);
222 for (int var=0; var<nvar; ++var)
223 {
224 const int eo = GetEdgeOrder(ghostEdge, var);
225 const int dofs = fec->GetNumDof(Geometry::SEGMENT, eo);
226 ngedofs += dofs;
227 }
228 }
229 }
230 else
231 {
232 ngedofs = pncmesh->GetNGhostEdges()
234 }
235 }
236
237 if (pmesh->Dimension() > 2)
238 {
239 if (IsVariableOrder())
240 {
241 // Note that this requires fespace to have face order and DOF info
242 // for ghost faces, so var_face_dofs must include ghost faces.
243 // These are set by ApplyGhostElementOrdersToEdgesAndFaces, which is
244 // called by CalcEdgeFaceVarOrders.
245 for (int i = 0; i < pncmesh->GetNGhostFaces(); ++i)
246 {
247 const int ghostFace = pncmesh->GetNFaces() + i;
248 const int nvar = GetNVariants(2, ghostFace);
249 for (int var=0; var<nvar; ++var)
250 {
251 const int fo = GetFaceOrder(ghostFace, var);
252 const int dofs = fec->GetNumDof(Geometry::SQUARE, fo);
253 ngfdofs += dofs;
254 }
255 }
256 }
257 else
258 {
259 ngfdofs = pncmesh->GetNGhostFaces()
261 }
262 }
263
264 // Total number of ghost DOFs. Ghost DOFs start at index 'ndofs', i.e.,
265 // after all regular DOFs. Ghost element internal ("bubble") DOFs are not
266 // included.
267 ngdofs = ngvdofs + ngedofs + ngfdofs;
268
269 if (IsVariableOrder())
270 {
271 SetVarDofMap(var_edge_dofs, var_edge_dofmap);
272 SetVarDofMap(var_face_dofs, var_face_dofmap);
273 }
274
275 // get P and R matrices, initialize DOF offsets, etc. NOTE: in the NC
276 // case this needs to be done here to get the number of true DOFs
277 ltdof_size = BuildParallelConformingInterpolation(
278 &P, &R, dof_offsets, tdof_offsets, &ldof_ltdof, false);
279
280 // TODO future: split BuildParallelConformingInterpolation into two parts
281 // to overlap its communication with processing between this constructor
282 // and the point where the P matrix is actually needed.
283 }
284
285 // Check for shared triangular faces with interior Nedelec DoFs:
286 // initializes 'nd_strias'.
287 CheckNDSTriaDofs();
288}
289
291{
292 long long ltdofs = ltdof_size;
293 long long min_ltdofs, max_ltdofs, sum_ltdofs;
294
295 MPI_Reduce(&ltdofs, &min_ltdofs, 1, MPI_LONG_LONG, MPI_MIN, 0, MyComm);
296 MPI_Reduce(&ltdofs, &max_ltdofs, 1, MPI_LONG_LONG, MPI_MAX, 0, MyComm);
297 MPI_Reduce(&ltdofs, &sum_ltdofs, 1, MPI_LONG_LONG, MPI_SUM, 0, MyComm);
298
299 if (MyRank == 0)
300 {
301 real_t avg = real_t(sum_ltdofs) / NRanks;
302 mfem::out << "True DOF partitioning: min " << min_ltdofs
303 << ", avg " << std::fixed << std::setprecision(1) << avg
304 << ", max " << max_ltdofs
305 << ", (max-avg)/avg " << 100.0*(max_ltdofs - avg)/avg
306 << "%" << std::endl;
307 }
308
309 if (NRanks <= 32)
310 {
311 if (MyRank == 0)
312 {
313 mfem::out << "True DOFs by rank: " << ltdofs;
314 for (int i = 1; i < NRanks; i++)
315 {
316 MPI_Status status;
317 MPI_Recv(&ltdofs, 1, MPI_LONG_LONG, i, 123, MyComm, &status);
318 mfem::out << " " << ltdofs;
319 }
320 mfem::out << "\n";
321 }
322 else
323 {
324 MPI_Send(&ltdofs, 1, MPI_LONG_LONG, 0, 123, MyComm);
325 }
326 }
327}
328
329void ParFiniteElementSpace::GetGroupComm(
330 GroupCommunicator &gc, int ldof_type, Array<int> *g_ldof_sign)
331{
332 int gr;
333 int ng = pmesh->GetNGroups();
334 int nvd, ned, ntd = 0, nqd = 0;
335 Array<int> dofs;
336
337 int group_ldof_counter;
338 Table &group_ldof = gc.GroupLDofTable();
339
342
343 if (mesh->Dimension() >= 3)
344 {
346 {
348 }
350 {
352 }
353 }
354
355 bool have_sign_flips = false;
356 if (g_ldof_sign)
357 {
358 g_ldof_sign->SetSize(GetNDofs());
359 *g_ldof_sign = 1;
360 }
361
362 // count the number of ldofs in all groups (excluding the local group 0)
363 group_ldof_counter = 0;
364 for (gr = 1; gr < ng; gr++)
365 {
366 group_ldof_counter += nvd * pmesh->GroupNVertices(gr);
367 group_ldof_counter += ned * pmesh->GroupNEdges(gr);
368 group_ldof_counter += ntd * pmesh->GroupNTriangles(gr);
369 group_ldof_counter += nqd * pmesh->GroupNQuadrilaterals(gr);
370 }
371 if (ldof_type)
372 {
373 group_ldof_counter *= vdim;
374 }
375 // allocate the I and J arrays in group_ldof
376 group_ldof.SetDims(ng, group_ldof_counter);
377
378 // build the full group_ldof table
379 group_ldof_counter = 0;
380 group_ldof.GetI()[0] = group_ldof.GetI()[1] = 0;
381 for (gr = 1; gr < ng; gr++)
382 {
383 int j, k, l, m, o, nv, ne, nt, nq;
384 const int *ind;
385
386 nv = pmesh->GroupNVertices(gr);
387 ne = pmesh->GroupNEdges(gr);
388 nt = pmesh->GroupNTriangles(gr);
389 nq = pmesh->GroupNQuadrilaterals(gr);
390
391 // vertices
392 if (nvd > 0)
393 {
394 for (j = 0; j < nv; j++)
395 {
396 k = pmesh->GroupVertex(gr, j);
397
398 dofs.SetSize(nvd);
399 m = nvd * k;
400 for (l = 0; l < nvd; l++, m++)
401 {
402 dofs[l] = m;
403 }
404
405 if (ldof_type)
406 {
407 DofsToVDofs(dofs);
408 }
409
410 for (l = 0; l < dofs.Size(); l++)
411 {
412 group_ldof.GetJ()[group_ldof_counter++] = dofs[l];
413 }
414 }
415 }
416
417 // edges
418 if (ned > 0)
419 {
420 for (j = 0; j < ne; j++)
421 {
422 pmesh->GroupEdge(gr, j, k, o);
423
424 dofs.SetSize(ned);
425 m = nvdofs+k*ned;
427 for (l = 0; l < ned; l++)
428 {
429 if (ind[l] < 0)
430 {
431 dofs[l] = m + FlipIndexSign(ind[l]);
432 if (g_ldof_sign)
433 {
434 (*g_ldof_sign)[dofs[l]] = -1;
435 have_sign_flips = true;
436 }
437 }
438 else
439 {
440 dofs[l] = m + ind[l];
441 }
442 }
443
444 if (ldof_type)
445 {
446 DofsToVDofs(dofs);
447 }
448
449 for (l = 0; l < dofs.Size(); l++)
450 {
451 group_ldof.GetJ()[group_ldof_counter++] = dofs[l];
452 }
453 }
454 }
455
456 // triangles
457 if (ntd > 0)
458 {
459 for (j = 0; j < nt; j++)
460 {
461 pmesh->GroupTriangle(gr, j, k, o);
462
463 dofs.SetSize(ntd);
464 m = nvdofs + nedofs + FirstFaceDof(k);
466 for (l = 0; l < ntd; l++)
467 {
468 if (ind[l] < 0)
469 {
470 dofs[l] = m + FlipIndexSign(ind[l]);
471 if (g_ldof_sign)
472 {
473 (*g_ldof_sign)[dofs[l]] = -1;
474 have_sign_flips = true;
475 }
476 }
477 else
478 {
479 dofs[l] = m + ind[l];
480 }
481 }
482
483 if (ldof_type)
484 {
485 DofsToVDofs(dofs);
486 }
487
488 for (l = 0; l < dofs.Size(); l++)
489 {
490 group_ldof.GetJ()[group_ldof_counter++] = dofs[l];
491 }
492 }
493 }
494
495 // quadrilaterals
496 if (nqd > 0)
497 {
498 for (j = 0; j < nq; j++)
499 {
500 pmesh->GroupQuadrilateral(gr, j, k, o);
501
502 dofs.SetSize(nqd);
503 m = nvdofs + nedofs + FirstFaceDof(k);
505 for (l = 0; l < nqd; l++)
506 {
507 if (ind[l] < 0)
508 {
509 dofs[l] = m + FlipIndexSign(ind[l]);
510 if (g_ldof_sign)
511 {
512 (*g_ldof_sign)[dofs[l]] = -1;
513 have_sign_flips = true;
514 }
515 }
516 else
517 {
518 dofs[l] = m + ind[l];
519 }
520 }
521
522 if (ldof_type)
523 {
524 DofsToVDofs(dofs);
525 }
526
527 for (l = 0; l < dofs.Size(); l++)
528 {
529 group_ldof.GetJ()[group_ldof_counter++] = dofs[l];
530 }
531 }
532 }
533
534 group_ldof.GetI()[gr+1] = group_ldof_counter;
535 }
536
537 if (g_ldof_sign && have_sign_flips == false)
538 {
539 g_ldof_sign->DeleteAll();
540 }
541
542 gc.Finalize();
543}
544
545void ParFiniteElementSpace::ApplyLDofSigns(Array<int> &dofs) const
546{
547 MFEM_ASSERT(Conforming(), "wrong code path");
548 if (!HaveDofSigns()) { return; }
549
550 for (int i = 0; i < dofs.Size(); i++)
551 {
552 if (dofs[i] < 0)
553 {
554 if (ldof_sign[FlipIndexSign(dofs[i])] < 0)
555 {
556 dofs[i] = FlipIndexSign(dofs[i]);
557 }
558 }
559 else
560 {
561 if (ldof_sign[dofs[i]] < 0)
562 {
563 dofs[i] = FlipIndexSign(dofs[i]);
564 }
565 }
566 }
567}
568
569void ParFiniteElementSpace::ApplyLDofSigns(Table &el_dof) const
570{
571 Array<int> all_dofs(el_dof.GetJ(), el_dof.Size_of_connections());
572 ApplyLDofSigns(all_dofs);
573}
574
576{
577 if (!HaveDofSigns()) { return; }
578
579 const bool byvdim = (ordering == Ordering::byVDIM);
580 for (int i = 0; i < ndofs; i++)
581 {
582 if (ldof_sign[i] < 0)
583 {
584 for (int d = 0; d < vdim; d++)
585 {
586 const int idx = byvdim ? d+vdim*i : i+ndofs*d;
587 h_data[idx] = -h_data[idx];
588 }
589 }
590 }
591}
592
594 DofTransformation &doftrans) const
595{
596 doftrans.SetDofTransformation(nullptr);
597
598 if (elem_dof)
599 {
600 elem_dof->GetRow(i, dofs);
601
603 {
604 Array<int> Fo;
605 elem_fos->GetRow(i, Fo);
606 doftrans.SetDofTransformation(
608 doftrans.SetFaceOrientations(Fo);
609 doftrans.SetVDim();
610 }
611 return;
612 }
613 FiniteElementSpace::GetElementDofs(i, dofs, doftrans);
614 if (Conforming())
615 {
616 ApplyLDofSigns(dofs);
617 }
618}
619
621 DofTransformation &doftrans) const
622{
623 doftrans.SetDofTransformation(nullptr);
624
625 if (bdr_elem_dof)
626 {
627 bdr_elem_dof->GetRow(i, dofs);
628
630 {
631 Array<int> Fo;
632 bdr_elem_fos->GetRow(i, Fo);
633 doftrans.SetDofTransformation(
635 doftrans.SetFaceOrientations(Fo);
636 doftrans.SetVDim();
637 }
638 return;
639 }
640 FiniteElementSpace::GetBdrElementDofs(i, dofs, doftrans);
641 if (Conforming())
642 {
643 ApplyLDofSigns(dofs);
644 }
645}
646
648 int variant) const
649{
650 if (face_dof != nullptr && variant == 0)
651 {
652 face_dof->GetRow(i, dofs);
653 return fec->GetOrder();
654 }
655 int p = FiniteElementSpace::GetFaceDofs(i, dofs, variant);
656 if (Conforming())
657 {
658 ApplyLDofSigns(dofs);
659 }
660 return p;
661}
662
664{
665 int ne = mesh->GetNE();
666 if (i >= ne) { return GetFaceNbrFE(i - ne); }
667 else { return FiniteElementSpace::GetFE(i); }
668}
669
671 ElementDofOrdering f_ordering, FaceType type, L2FaceValues mul) const
672{
673 const bool is_dg_space = IsDGSpace();
674 const L2FaceValues m = (is_dg_space && mul==L2FaceValues::DoubleValued) ?
676 auto key = std::make_tuple(is_dg_space, f_ordering, type, m);
677 auto itr = L2F.find(key);
678 if (itr != L2F.end())
679 {
680 return itr->second.get();
681 }
682 else
683 {
684 std::unique_ptr<FaceRestriction> res;
685 if (is_dg_space)
686 {
687 if (Conforming())
688 {
689 res.reset(new ParL2FaceRestriction(*this, f_ordering, type, m));
690 }
691 else
692 {
693 res.reset(new ParNCL2FaceRestriction(*this, f_ordering, type, m));
694 }
695 }
696 else if (dynamic_cast<const DG_Interface_FECollection*>(fec))
697 {
698 res.reset(new L2InterfaceFaceRestriction(*this, f_ordering, type));
699 }
700 else
701 {
702 if (Conforming())
703 {
704 res.reset(new ConformingFaceRestriction(*this, f_ordering, type));
705 }
706 else
707 {
708 res.reset(new ParNCH1FaceRestriction(*this, f_ordering, type));
709 }
710 }
711 return L2F.emplace(key, std::move(res)).first->second.get();
712 }
713}
714
716 int group, int ei, Array<int> &dofs) const
717{
718 int l_edge, ori;
719 MFEM_ASSERT(0 <= ei && ei < pmesh->GroupNEdges(group), "invalid edge index");
720 pmesh->GroupEdge(group, ei, l_edge, ori);
721 if (ori > 0) // ori = +1 or -1
722 {
723 GetEdgeDofs(l_edge, dofs);
724 }
725 else
726 {
727 Array<int> rdofs;
728 fec->SubDofOrder(Geometry::SEGMENT, 1, 1, dofs);
729 GetEdgeDofs(l_edge, rdofs);
730 for (int i = 0; i < dofs.Size(); i++)
731 {
732 const int di = dofs[i];
733 dofs[i] = di >= 0 ? rdofs[di] :
734 FlipIndexSign(rdofs[FlipIndexSign(di)]);
735 }
736 }
737}
738
740 int group, int fi, Array<int> &dofs) const
741{
742 int l_face, ori;
743 MFEM_ASSERT(0 <= fi && fi < pmesh->GroupNTriangles(group),
744 "invalid triangular face index");
745 pmesh->GroupTriangle(group, fi, l_face, ori);
746 if (ori == 0)
747 {
748 GetFaceDofs(l_face, dofs);
749 }
750 else
751 {
752 Array<int> rdofs;
753 fec->SubDofOrder(Geometry::TRIANGLE, 2, ori, dofs);
754 GetFaceDofs(l_face, rdofs);
755 for (int i = 0; i < dofs.Size(); i++)
756 {
757 const int di = dofs[i];
758 dofs[i] = di >= 0 ? rdofs[di] :
759 FlipIndexSign(rdofs[FlipIndexSign(di)]);
760 }
761 }
762}
763
765 int group, int fi, Array<int> &dofs) const
766{
767 int l_face, ori;
768 MFEM_ASSERT(0 <= fi && fi < pmesh->GroupNQuadrilaterals(group),
769 "invalid quadrilateral face index");
770 pmesh->GroupQuadrilateral(group, fi, l_face, ori);
771 if (ori == 0)
772 {
773 GetFaceDofs(l_face, dofs);
774 }
775 else
776 {
777 Array<int> rdofs;
778 fec->SubDofOrder(Geometry::SQUARE, 2, ori, dofs);
779 GetFaceDofs(l_face, rdofs);
780 for (int i = 0; i < dofs.Size(); i++)
781 {
782 const int di = dofs[i];
783 dofs[i] = (di >= 0) ? rdofs[di] :
784 FlipIndexSign(rdofs[FlipIndexSign(di)]);
785 }
786 }
787}
788
789void ParFiniteElementSpace::GenerateGlobalOffsets() const
790{
791 MFEM_ASSERT(Conforming(), "wrong code path");
792
793 HYPRE_BigInt ldof[2];
794 Array<HYPRE_BigInt> *offsets[2] = { &dof_offsets, &tdof_offsets };
795
796 ldof[0] = GetVSize();
797 ldof[1] = TrueVSize();
798
799 pmesh->GenerateOffsets(2, ldof, offsets);
800
801 if (HYPRE_AssumedPartitionCheck())
802 {
803 // communicate the neighbor offsets in tdof_nb_offsets
804 GroupTopology &gt = GetGroupTopo();
805 int nsize = gt.GetNumNeighbors()-1;
806 MPI_Request *requests = new MPI_Request[2*nsize];
807 MPI_Status *statuses = new MPI_Status[2*nsize];
808 tdof_nb_offsets.SetSize(nsize+1);
809 tdof_nb_offsets[0] = tdof_offsets[0];
810
811 // send and receive neighbors' local tdof offsets
812 int request_counter = 0;
813 for (int i = 1; i <= nsize; i++)
814 {
815 MPI_Irecv(&tdof_nb_offsets[i], 1, HYPRE_MPI_BIG_INT,
816 gt.GetNeighborRank(i), 5365, MyComm,
817 &requests[request_counter++]);
818 }
819 for (int i = 1; i <= nsize; i++)
820 {
821 MPI_Isend(&tdof_nb_offsets[0], 1, HYPRE_MPI_BIG_INT,
822 gt.GetNeighborRank(i), 5365, MyComm,
823 &requests[request_counter++]);
824 }
825 MPI_Waitall(request_counter, requests, statuses);
826
827 delete [] statuses;
828 delete [] requests;
829 }
830}
831
832void ParFiniteElementSpace::CheckNDSTriaDofs()
833{
834 // Check for Nedelec basis
835 bool nd_basis = dynamic_cast<const ND_FECollection*>(fec);
836 if (!nd_basis)
837 {
838 nd_strias = false;
839 return;
840 }
841
842 // Check for interior face dofs on triangles (the use of TETRAHEDRON
843 // is not an error)
844 bool nd_fdof = fec->HasFaceDofs(Geometry::TETRAHEDRON,
846 if (!nd_fdof)
847 {
848 nd_strias = false;
849 return;
850 }
851
852 // Check for shared triangle faces
853 bool strias = false;
854 {
855 int ngrps = pmesh->GetNGroups();
856 for (int g = 1; g < ngrps; g++)
857 {
858 strias |= pmesh->GroupNTriangles(g);
859 }
860 }
861
862 // Combine results
863 int loc_nd_strias = strias ? 1 : 0;
864 int glb_nd_strias = 0;
865 MPI_Allreduce(&loc_nd_strias, &glb_nd_strias, 1, MPI_INT, MPI_SUM, MyComm);
866 nd_strias = glb_nd_strias > 0;
867}
868
869void ParFiniteElementSpace::Build_Dof_TrueDof_Matrix() const // matrix P
870{
871 MFEM_ASSERT(Conforming(), "wrong code path");
872
873 if (P) { return; }
874
875 if (!nd_strias)
876 {
877 // Safe to assume 1-1 correspondence between shared dofs
878 int ldof = GetVSize();
879 int ltdof = TrueVSize();
880
881 HYPRE_Int *i_diag = Memory<HYPRE_Int>(ldof+1);
882 HYPRE_Int *j_diag = Memory<HYPRE_Int>(ltdof);
883 int diag_counter;
884
885 HYPRE_Int *i_offd = Memory<HYPRE_Int>(ldof+1);
886 HYPRE_Int *j_offd = Memory<HYPRE_Int>(ldof-ltdof);
887 int offd_counter;
888
889 HYPRE_BigInt *cmap = Memory<HYPRE_BigInt>(ldof-ltdof);
890
891 HYPRE_BigInt *col_starts = GetTrueDofOffsets();
892 HYPRE_BigInt *row_starts = GetDofOffsets();
893
894 Array<Pair<HYPRE_BigInt, int> > cmap_j_offd(ldof-ltdof);
895
896 i_diag[0] = i_offd[0] = 0;
897 diag_counter = offd_counter = 0;
898 for (int i = 0; i < ldof; i++)
899 {
900 int ltdof_i = GetLocalTDofNumber(i);
901 if (ltdof_i >= 0)
902 {
903 j_diag[diag_counter++] = ltdof_i;
904 }
905 else
906 {
907 cmap_j_offd[offd_counter].one = GetGlobalTDofNumber(i);
908 cmap_j_offd[offd_counter].two = offd_counter;
909 offd_counter++;
910 }
911 i_diag[i+1] = diag_counter;
912 i_offd[i+1] = offd_counter;
913 }
914
915 SortPairs<HYPRE_BigInt, int>(cmap_j_offd, offd_counter);
916
917 for (int i = 0; i < offd_counter; i++)
918 {
919 cmap[i] = cmap_j_offd[i].one;
920 j_offd[cmap_j_offd[i].two] = i;
921 }
922
923 P = new HypreParMatrix(MyComm, MyRank, NRanks, row_starts, col_starts,
924 i_diag, j_diag, i_offd, j_offd,
925 cmap, offd_counter);
926 }
927 else
928 {
929 // Some shared dofs will be linear combinations of others
930 HYPRE_BigInt ldof = GetVSize();
931 HYPRE_BigInt ltdof = TrueVSize();
932
933 HYPRE_BigInt gdof = -1;
934 HYPRE_BigInt gtdof = -1;
935
936 MPI_Allreduce(&ldof, &gdof, 1, HYPRE_MPI_BIG_INT, MPI_SUM, MyComm);
937 MPI_Allreduce(&ltdof, &gtdof, 1, HYPRE_MPI_BIG_INT, MPI_SUM, MyComm);
938
939 // Ensure face orientations have been communicated
940 pmesh->ExchangeFaceNbrData();
941
942 // Locate and count non-zeros in off-diagonal portion of P
943 int nnz_offd = 0;
944 Array<int> ldsize(ldof); ldsize = 0;
945 Array<int> ltori(ldof); ltori = 0; // Local triangle orientations
946 {
947 int ngrps = pmesh->GetNGroups();
949 Array<int> sdofs;
950 for (int g = 1; g < ngrps; g++)
951 {
952 if (pmesh->gtopo.IAmMaster(g))
953 {
954 continue;
955 }
956 for (int ei=0; ei<pmesh->GroupNEdges(g); ei++)
957 {
958 this->GetSharedEdgeDofs(g, ei, sdofs);
959 for (int i=0; i<sdofs.Size(); i++)
960 {
961 int ind = (sdofs[i]>=0) ? sdofs[i] : (-sdofs[i]-1);
962 if (ldsize[ind] == 0) { nnz_offd++; }
963 ldsize[ind] = 1;
964 }
965 }
966 for (int fi=0; fi<pmesh->GroupNTriangles(g); fi++)
967 {
968 int face, ori, info1, info2;
969 pmesh->GroupTriangle(g, fi, face, ori);
970 pmesh->GetFaceInfos(face, &info1, &info2);
971 this->GetSharedTriangleDofs(g, fi, sdofs);
972 for (int i=0; i<3*nedofs; i++)
973 {
974 int ind = (sdofs[i]>=0) ? sdofs[i] : (-sdofs[i]-1);
975 if (ldsize[ind] == 0) { nnz_offd++; }
976 ldsize[ind] = 1;
977 }
978 for (int i=3*nedofs; i<sdofs.Size(); i++)
979 {
980 if (ldsize[sdofs[i]] == 0) { nnz_offd += 2; }
981 ldsize[sdofs[i]] = 2;
982 ltori[sdofs[i]] = info2 % 64;
983 }
984 }
985 for (int fi=0; fi<pmesh->GroupNQuadrilaterals(g); fi++)
986 {
987 this->GetSharedQuadrilateralDofs(g, fi, sdofs);
988 for (int i=0; i<sdofs.Size(); i++)
989 {
990 int ind = (sdofs[i]>=0) ? sdofs[i] : (-sdofs[i]-1);
991 if (ldsize[ind] == 0) { nnz_offd++; }
992 ldsize[ind] = 1;
993 }
994 }
995 }
996 }
997
998 HYPRE_Int *i_diag = Memory<HYPRE_Int>(ldof+1);
999 HYPRE_Int *j_diag = Memory<HYPRE_Int>(ltdof);
1000 real_t *d_diag = Memory<real_t>(ltdof);
1001 int diag_counter;
1002
1003 HYPRE_Int *i_offd = Memory<HYPRE_Int>(ldof+1);
1004 HYPRE_Int *j_offd = Memory<HYPRE_Int>(nnz_offd);
1005 real_t *d_offd = Memory<real_t>(nnz_offd);
1006 int offd_counter;
1007
1008 HYPRE_BigInt *cmap = Memory<HYPRE_BigInt>(ldof-ltdof);
1009
1010 HYPRE_BigInt *col_starts = GetTrueDofOffsets();
1011 HYPRE_BigInt *row_starts = GetDofOffsets();
1012
1013 Array<Pair<HYPRE_BigInt, int> > cmap_j_offd(ldof-ltdof);
1014
1015 i_diag[0] = i_offd[0] = 0;
1016 diag_counter = offd_counter = 0;
1017 int offd_col_counter = 0;
1018 for (int i = 0; i < ldof; i++)
1019 {
1020 int ltdofi = GetLocalTDofNumber(i);
1021 if (ltdofi >= 0)
1022 {
1023 j_diag[diag_counter] = ltdofi;
1024 d_diag[diag_counter++] = 1.0;
1025 }
1026 else
1027 {
1028 if (ldsize[i] == 1)
1029 {
1030 cmap_j_offd[offd_col_counter].one = GetGlobalTDofNumber(i);
1031 cmap_j_offd[offd_col_counter].two = offd_counter;
1032 offd_counter++;
1033 offd_col_counter++;
1034 }
1035 else
1036 {
1037 cmap_j_offd[offd_col_counter].one = GetGlobalTDofNumber(i);
1038 cmap_j_offd[offd_col_counter].two = offd_counter;
1039 offd_counter += 2;
1040 offd_col_counter++;
1041 i_diag[i+1] = diag_counter;
1042 i_offd[i+1] = offd_counter;
1043 i++;
1044 cmap_j_offd[offd_col_counter].one = GetGlobalTDofNumber(i);
1045 cmap_j_offd[offd_col_counter].two = offd_counter;
1046 offd_counter += 2;
1047 offd_col_counter++;
1048 }
1049 }
1050 i_diag[i+1] = diag_counter;
1051 i_offd[i+1] = offd_counter;
1052 }
1053
1054 SortPairs<HYPRE_BigInt, int>(cmap_j_offd, offd_col_counter);
1055
1056 for (int i = 0; i < nnz_offd; i++)
1057 {
1058 j_offd[i] = -1;
1059 d_offd[i] = 0.0;
1060 }
1061
1062 for (int i = 0; i < offd_col_counter; i++)
1063 {
1064 cmap[i] = cmap_j_offd[i].one;
1065 j_offd[cmap_j_offd[i].two] = i;
1066 }
1067
1068 for (int i = 0; i < ldof; i++)
1069 {
1070 if (i_offd[i+1] == i_offd[i] + 1)
1071 {
1072 d_offd[i_offd[i]] = 1.0;
1073 }
1074 else if (i_offd[i+1] == i_offd[i] + 2)
1075 {
1076 const real_t *T =
1078 j_offd[i_offd[i] + 1] = j_offd[i_offd[i]] + 1;
1079 d_offd[i_offd[i]] = T[0]; d_offd[i_offd[i] + 1] = T[2];
1080 i++;
1081 j_offd[i_offd[i] + 1] = j_offd[i_offd[i]];
1082 j_offd[i_offd[i]] = j_offd[i_offd[i] + 1] - 1;
1083 d_offd[i_offd[i]] = T[1]; d_offd[i_offd[i] + 1] = T[3];
1084 }
1085 }
1086
1087 P = new HypreParMatrix(MyComm, gdof, gtdof, row_starts, col_starts,
1088 i_diag, j_diag, d_diag, i_offd, j_offd, d_offd,
1089 offd_col_counter, cmap);
1090 }
1091
1092 SparseMatrix Pdiag;
1093 P->GetDiag(Pdiag);
1094 R = Transpose(Pdiag);
1095}
1096
1098{
1099 HypreParMatrix *P_pc;
1100 Array<HYPRE_BigInt> P_pc_row_starts, P_pc_col_starts;
1101 BuildParallelConformingInterpolation(&P_pc, NULL, P_pc_row_starts,
1102 P_pc_col_starts, NULL, true);
1103 P_pc->CopyRowStarts();
1104 P_pc->CopyColStarts();
1105 return P_pc;
1106}
1107
1109{
1110 GroupTopology &gt = GetGroupTopo();
1111 for (int i = 0; i < ldof_group.Size(); i++)
1112 {
1113 if (gt.IAmMaster(ldof_group[i])) // we are the master
1114 {
1115 if (ldof_ltdof[i] >= 0) // see note below
1116 {
1117 vec[ldof_ltdof[i]] /= gt.GetGroupSize(ldof_group[i]);
1118 }
1119 // NOTE: in NC meshes, ldof_ltdof generated for the gtopo
1120 // groups by ConstructTrueDofs gets overwritten by
1121 // BuildParallelConformingInterpolation. Some DOFs that are
1122 // seen as true by the conforming code are actually slaves and
1123 // end up with a -1 in ldof_ltdof.
1124 }
1125 }
1126}
1127
1129{
1130 GroupCommunicator *gc = new GroupCommunicator(GetGroupTopo());
1131 if (NURBSext)
1132 {
1133 gc->Create(pNURBSext()->ldof_group);
1134 }
1135 else
1136 {
1137 GetGroupComm(*gc, 0);
1138 }
1139 return gc;
1140}
1141
1143{
1144 // For non-conforming mesh, synchronization is performed on the cut (aka
1145 // "partially conforming") space.
1146
1147 MFEM_VERIFY(ldof_marker.Size() == GetVSize(), "invalid in/out array");
1148
1149 // implement allreduce(|) as reduce(|) + broadcast
1150 gcomm->Reduce<int>(ldof_marker, GroupCommunicator::BitOR);
1151 gcomm->Bcast(ldof_marker);
1152}
1153
1155 Array<int> &ess_dofs,
1156 int component) const
1157{
1158 FiniteElementSpace::GetEssentialVDofs(bdr_attr_is_ess, ess_dofs, component);
1159
1160 // Make sure that processors without boundary elements mark
1161 // their boundary dofs (if they have any).
1162 Synchronize(ess_dofs);
1163}
1164
1166 &bdr_attr_is_ess,
1168 int component) const
1169{
1170 Array<int> ess_dofs, true_ess_dofs;
1171
1172 GetEssentialVDofs(bdr_attr_is_ess, ess_dofs, component);
1173
1174 if (IsVariableOrderH1())
1175 {
1176 GetEssentialTrueDofsVar(bdr_attr_is_ess, ess_dofs, true_ess_dofs,
1177 component);
1178 }
1179 else
1180 {
1181 GetRestrictionMatrix()->BooleanMult(ess_dofs, true_ess_dofs);
1182 }
1183
1184#ifdef MFEM_DEBUG
1185 // Verify that in boolean arithmetic: P^T ess_dofs = R ess_dofs.
1186 Array<int> true_ess_dofs2(true_ess_dofs.Size());
1187 auto Pt = std::unique_ptr<HypreParMatrix>(Dof_TrueDof_Matrix()->Transpose());
1188
1189 const int *ess_dofs_data = ess_dofs.HostRead();
1190 Pt->BooleanMult(1, ess_dofs_data, 0, true_ess_dofs2);
1191 int counter = 0;
1192 const int *ted = true_ess_dofs.HostRead();
1193 std::string error_msg = "failed dof: ";
1194 for (int i = 0; i < true_ess_dofs.Size(); i++)
1195 {
1196 if (bool(ted[i]) != bool(true_ess_dofs2[i]))
1197 {
1198 error_msg += std::to_string(i) += "(R ";
1199 error_msg += std::to_string(bool(ted[i])) += " P^T ";
1200 error_msg += std::to_string(bool(true_ess_dofs2[i])) += ") ";
1201 ++counter;
1202 }
1203 }
1204 MFEM_ASSERT(R->Height() == P->Width(), "!");
1205
1206 if (!IsVariableOrder())
1207 {
1208 MFEM_ASSERT(R->Width() == P->Height(), "!");
1209 MFEM_ASSERT(R->Width() == ess_dofs.Size(), "!");
1210 MFEM_VERIFY(counter == 0, "internal MFEM error: counter = " << counter
1211 << ", rank = " << MyRank << ", " << error_msg);
1212 }
1213#endif
1214
1215 MarkerToList(true_ess_dofs, ess_tdof_list);
1216}
1217
1219 &bdr_attr_is_ess,
1220 const Array<int> &ess_dofs,
1221 Array<int> &true_ess_dofs,
1222 int component) const
1223{
1224 MFEM_VERIFY(IsVariableOrder() && R,
1225 "GetEssentialTrueDofsVar is only for variable-order spaces");
1226
1227 true_ess_dofs.SetSize(R->Height());
1228 true_ess_dofs.HostWrite();
1229 true_ess_dofs = 0;
1230
1231 const int ntdofs = tdof2ldof.Size();
1232 MFEM_VERIFY(vdim * ntdofs == R->NumRows() &&
1233 vdim * ntdofs == true_ess_dofs.Size(), "");
1234 MFEM_VERIFY(ldof_ltdof.Size() == ndofs && ess_dofs.Size() == vdim * ndofs, "");
1235
1236 const bool bynodes = (ordering == Ordering::byNODES);
1237 const int vdim_factor = bynodes ? 1 : vdim;
1238 const int num_true_dofs = R->NumRows() / vdim;
1239 const int tdof_stride = bynodes ? num_true_dofs : 1;
1240
1241 // Use ldof_ltdof for vertex and element T-dofs
1242 for (int l=0; l<ndofs; ++l)
1243 {
1244 const int tdof = ldof_ltdof[l];
1245 if (tdof >= 0 && ess_dofs[l])
1246 {
1247 for (int vd = 0; vd < vdim; vd++)
1248 {
1249 if (component >= 0 && vd != component) { continue; }
1250 const int vtdof = tdof*vdim_factor + vd*tdof_stride;
1251 true_ess_dofs[vtdof] = 1;
1252 }
1253 }
1254 }
1255
1256 // Find all essential boundary edges and faces.
1257 std::set<int> edges, faces;
1258 GetEssentialBdrEdgesFaces(bdr_attr_is_ess, edges, faces);
1259
1260 // Use tdof2ldof for edge and face T-dofs
1261 for (int tdof=0; tdof<ntdofs; ++tdof)
1262 {
1263 // Not set for vertex and element T-dofs
1264 if (!tdof2ldof[tdof].set) { continue; }
1265
1266 const bool edge = tdof2ldof[tdof].isEdge;
1267 const int index = tdof2ldof[tdof].idx;
1268
1269 const bool bdry = edge ? edges.count(index) > 0 : faces.count(index) > 0;
1270 if (!bdry) { continue; }
1271
1272 for (int vd = 0; vd < vdim; vd++)
1273 {
1274 if (component >= 0 && vd != component) { continue; }
1275 const int vtdof = tdof*vdim_factor + vd*tdof_stride;
1276 true_ess_dofs[vtdof] = 1;
1277 }
1278 }
1279}
1280
1282 int component) const
1283{
1284 FiniteElementSpace::GetExteriorVDofs(ext_dofs, component);
1285
1286 // Make sure that processors without boundary elements mark
1287 // their boundary dofs (if they have any).
1288 Synchronize(ext_dofs);
1289}
1290
1292 const Array<int> &boundary_element_indices,
1294 Array<int> &boundary_edge_dofs_out,
1295 Array<int> *ldof_marker,
1296 Array<int> *dof_edges,
1297 Array<int> *dof_boundary_elements,
1298 Array<int> *ess_edge_list)
1299{
1300 MFEM_VERIFY(!pmesh->Nonconforming(),
1301 "GetBoundaryLoopEdgeDofs does not support nonconforming meshes");
1302 MFEM_VERIFY(pmesh->Dimension() >= 2,
1303 "GetBoundaryLoopEdgeDofs requires 2D or 3D meshes to find 1D edge objects");
1304
1305 // Call the serial version, then rebuild scratch maps/set from the returned
1306 // arrays for the O(1) lookups the parallel reconciliation below needs.
1307 Array<int> loc_dofs, loc_edges, loc_belems;
1308 FiniteElementSpace::GetBoundaryLoopEdgeDofs(boundary_element_indices, loc_dofs,
1309 &loc_edges, &loc_belems);
1310
1311 std::unordered_set<int> boundary_edge_dofs;
1312 std::unordered_map<int, int> dof_to_edge_map;
1313 std::unordered_map<int, int> dof_to_boundary_element;
1314 boundary_edge_dofs.reserve(loc_dofs.Size());
1315 dof_to_edge_map.reserve(loc_dofs.Size());
1316 dof_to_boundary_element.reserve(loc_dofs.Size());
1317 for (int i = 0; i < loc_dofs.Size(); i++)
1318 {
1319 const int dof = loc_dofs[i];
1320 boundary_edge_dofs.insert(dof);
1321 dof_to_edge_map[dof] = loc_edges[i];
1322 dof_to_boundary_element[dof] = loc_belems[i];
1323 }
1324
1325 // Parallel processing: Build edge sharing lookup table
1326 std::unordered_map<int, int> edge_to_group_size;
1327 int num_groups = pmesh->GetNGroups();
1328
1329 int total_shared_edges = 0;
1330 for (int group = 1; group < num_groups; group++)
1331 {
1332 total_shared_edges += pmesh->GroupNEdges(group);
1333 }
1334 edge_to_group_size.reserve(total_shared_edges);
1335
1336 for (int group = 1; group < num_groups; group++)
1337 {
1338 int group_size = pmesh->gtopo.GetGroupSize(group);
1339 int num_edges_in_group = pmesh->GroupNEdges(group);
1340
1341 for (int i = 0; i < num_edges_in_group; i++)
1342 {
1343 edge_to_group_size.emplace(pmesh->GroupEdge(group, i), group_size);
1344 }
1345 }
1346
1347 // Get global indices
1348 Array<HYPRE_BigInt> global_edge_indices;
1349 pmesh->GetGlobalEdgeIndices(global_edge_indices);
1350
1351 // Handle dimension-specific boundary element relationships
1352 Array<HYPRE_BigInt> global_face_indices;
1353 std::unordered_map<int, int> boundary_element_to_companion;
1354 std::unordered_set<int> dofs_to_remove;
1355
1356 const int dim = pmesh->Dimension();
1357 if (dim == 3)
1358 {
1359 // In 3D: boundary elements are faces, we track which face each boundary element is
1360 pmesh->GetGlobalFaceIndices(global_face_indices);
1361 for (int boundary_element_idx : boundary_element_indices)
1362 {
1363 int face_index, face_orientation;
1364 pmesh->GetBdrElementFace(boundary_element_idx, &face_index, &face_orientation);
1365 boundary_element_to_companion[boundary_element_idx] = face_index;
1366 }
1367
1368 std::vector<HYPRE_BigInt> local_data;
1369 local_data.reserve(boundary_edge_dofs.size() * 2);
1370
1371 std::unordered_set<int> processed_edges;
1372 processed_edges.reserve(boundary_edge_dofs.size());
1373
1374 for (const auto& [dof, local_edge] : dof_to_edge_map)
1375 {
1376 // Skip if already processed this edge
1377 if (!processed_edges.insert(local_edge).second) { continue; }
1378
1379 // Check if edge is shared (fast lookup)
1380 auto it = edge_to_group_size.find(local_edge);
1381 if (it != edge_to_group_size.end() && it->second > 1)
1382 {
1383 // Get boundary element and companion index directly from pre-computed map
1384 int boundary_element_idx = dof_to_boundary_element[dof];
1385 int companion_index = boundary_element_to_companion[boundary_element_idx];
1386
1387 // Store edge-face pair for 3D artificial boundary detection
1388 local_data.push_back(global_edge_indices[local_edge]);
1389 local_data.push_back(global_face_indices[companion_index]);
1390 }
1391 }
1392
1393 // MPI communication for 3D artificial boundary detection
1394 int num_procs = pmesh->GetNRanks();
1395 int local_size = local_data.size();
1396
1397 std::vector<int> mpi_arrays(num_procs * 4);
1398 int* all_sizes = mpi_arrays.data();
1399 int* displs = all_sizes + num_procs;
1400 int* byte_sizes = displs + num_procs;
1401 int* byte_displs = byte_sizes + num_procs;
1402
1403 MPI_Allgather(&local_size, 1, MPI_INT, all_sizes, 1, MPI_INT, pmesh->GetComm());
1404
1405 int total_size = 0;
1406 constexpr int hypre_size = sizeof(HYPRE_BigInt);
1407 for (int i = 0; i < num_procs; i++)
1408 {
1409 displs[i] = total_size;
1410 byte_displs[i] = total_size * hypre_size;
1411 total_size += all_sizes[i];
1412 byte_sizes[i] = all_sizes[i] * hypre_size;
1413 }
1414
1415 if (total_size > 0)
1416 {
1417 std::vector<HYPRE_BigInt> all_data(total_size);
1418 MPI_Allgatherv(local_data.data(), local_size * hypre_size, MPI_BYTE,
1419 all_data.data(), byte_sizes, byte_displs, MPI_BYTE, pmesh->GetComm());
1420
1421 // Build global-to-local edge mapping
1422 std::unordered_map<HYPRE_BigInt, int> global_to_local_edge;
1423 global_to_local_edge.reserve(global_edge_indices.Size());
1424 for (int i = 0; i < global_edge_indices.Size(); ++i)
1425 {
1426 global_to_local_edge[global_edge_indices[i]] = i;
1427 }
1428
1429 // Process collected data to find edges in multiple faces (artificial boundaries)
1430 std::unordered_map<HYPRE_BigInt, std::unordered_set<HYPRE_BigInt>>edge_to_faces;
1431 edge_to_faces.reserve(total_size / 2);
1432
1433 for (size_t i = 0; i < all_data.size(); i += 2)
1434 {
1435 edge_to_faces[all_data[i]].insert(all_data[i + 1]);
1436 }
1437
1438 // Mark DOFs from artificial edges for removal
1439 dofs_to_remove.reserve(local_data.size() / 4);
1440
1441 for (size_t i = 0; i < local_data.size(); i += 2)
1442 {
1443 HYPRE_BigInt global_edge_id = local_data[i];
1444
1445 // If this edge appears in 2+ distinct faces, it's artificial
1446 if (edge_to_faces[global_edge_id].size() >= 2)
1447 {
1448 int local_edge = global_to_local_edge[global_edge_id];
1449 Array<int> local_edge_dofs;
1450 GetEdgeDofs(local_edge, local_edge_dofs);
1451
1452 // Mark boundary DOFs of this edge for removal
1453 for (int k = 0; k < local_edge_dofs.Size(); ++k)
1454 {
1455 int dof = local_edge_dofs[k];
1456 if (boundary_edge_dofs.count(dof))
1457 {
1458 dofs_to_remove.insert(dof);
1459 }
1460 }
1461 }
1462 }
1463 }
1464 }
1465 else if (dim == 2)
1466 {
1467 // In 2D the boundary elements are themselves the edges, so there are no
1468 // artificial boundary edges to detect. However, for collections with
1469 // vertex DOFs (e.g. ND_R2D), a vertex shared by two boundary segments is
1470 // interior to the boundary curve and must be dropped. The serial code
1471 // does this by erasing a DOF on its second occurrence, which only sees
1472 // the occurrences local to this rank. When the two segments meeting at a
1473 // vertex live on different ranks, each rank sees a single occurrence and
1474 // wrongly keeps the DOF. Reconcile the occurrence parity across each
1475 // sharing group: membership in boundary_edge_dofs is the local parity,
1476 // and the parities sum (mod 2) to the global occurrence parity.
1477 Array<int> boundary_dof_count(GetVSize());
1478 boundary_dof_count = 0;
1479 for (const int dof : boundary_edge_dofs)
1480 {
1481 boundary_dof_count[dof] = 1;
1482 }
1483
1484 // implement allreduce(+) as reduce(+) + broadcast
1485 gcomm->Reduce<int>(boundary_dof_count, GroupCommunicator::Sum);
1486 gcomm->Bcast(boundary_dof_count);
1487
1488 for (const int dof : boundary_edge_dofs)
1489 {
1490 if (boundary_dof_count[dof] % 2 == 0)
1491 {
1492 dofs_to_remove.insert(dof);
1493 }
1494 }
1495 }
1496
1497 // Remove artificial DOFs
1498 for (int dof : dofs_to_remove)
1499 {
1500 boundary_edge_dofs.erase(dof);
1501 dof_to_edge_map.erase(dof);
1502 dof_to_boundary_element.erase(dof);
1503 }
1504
1505 // Convert to true DOFs and output
1506 ess_tdof_list.SetSize(0);
1507 ess_tdof_list.Reserve(boundary_edge_dofs.size());
1508 if (ess_edge_list)
1509 {
1510 // Reset as well, so that it stays in correspondence with ess_tdof_list
1511 // when the same output array is reused across calls.
1512 ess_edge_list->SetSize(0);
1513 ess_edge_list->Reserve(boundary_edge_dofs.size());
1514 }
1515 // Marker of the boundary edge DOFs. Always computed locally because the
1516 // parallel reconciliation below needs it; only copied to the caller's output
1517 // if requested (see the ldof_marker parameter).
1518 Array<int> local_ldof_marker(GetVSize());
1519 local_ldof_marker = 0;
1520
1521 for (int dof : boundary_edge_dofs)
1522 {
1523 local_ldof_marker[dof] = 1; // Mark all boundary edge dofs
1524 }
1525
1526 // Make sure that a selected shared DOF is marked on every rank of its
1527 // sharing group, including ranks holding none of the selected boundary
1528 // elements. Only the group master owns the corresponding true DOF, so
1529 // without this the true DOF would be emitted by no rank at all: the
1530 // non-master ranks get -1 from GetLocalTDofNumber(), while the master may
1531 // not have selected the DOF locally.
1532 Synchronize(local_ldof_marker);
1533
1534 // A DOF marked only through the synchronization above has no local
1535 // dof_to_edge_map entry, but the shared edge carrying it is still present in
1536 // the local mesh. Build the missing DOF -> edge entries from the shared
1537 // edges of the groups, so that ess_edge_list stays in correspondence with
1538 // ess_tdof_list. Note that a vertex DOF is not associated with a unique
1539 // edge, so it is only resolved when it is an interior DOF of an edge.
1540 std::unordered_map<int, int> shared_dof_to_edge;
1541 Array<int> shared_edge_dofs;
1542 for (int group = 1; group < num_groups; group++)
1543 {
1544 const int num_edges_in_group = pmesh->GroupNEdges(group);
1545 for (int i = 0; i < num_edges_in_group; i++)
1546 {
1547 const int edge = pmesh->GroupEdge(group, i);
1548 GetEdgeInteriorDofs(edge, shared_edge_dofs);
1549 for (int k = 0; k < shared_edge_dofs.Size(); k++)
1550 {
1551 shared_dof_to_edge.emplace(shared_edge_dofs[k], edge);
1552 }
1553 }
1554 }
1555
1556 // Build parallel arrays for DOFs and corresponding edges
1557 std::vector<std::pair<int, int>> tdof_edge_pairs;
1558 tdof_edge_pairs.reserve(boundary_edge_dofs.size());
1559
1560 for (int dof = 0; dof < local_ldof_marker.Size(); dof++)
1561 {
1562 if (!local_ldof_marker[dof]) { continue; }
1563
1564 const int tdof = GetLocalTDofNumber(dof);
1565 if (tdof < 0) { continue; } // tdof == -1 means not owned by this rank
1566
1567 int edge = -1;
1568 auto it = dof_to_edge_map.find(dof);
1569 if (it != dof_to_edge_map.end())
1570 {
1571 edge = it->second;
1572 }
1573 else
1574 {
1575 auto shared_it = shared_dof_to_edge.find(dof);
1576 if (shared_it != shared_dof_to_edge.end())
1577 {
1578 edge = shared_it->second;
1579 }
1580 }
1581 tdof_edge_pairs.push_back({tdof, edge});
1582 }
1583
1584 // Sort by true DOF index to maintain consistent ordering
1585 std::sort(tdof_edge_pairs.begin(), tdof_edge_pairs.end());
1586
1587 // Extract sorted true DOFs and edges
1588 for (const auto& pair : tdof_edge_pairs)
1589 {
1590 ess_tdof_list.Append(pair.first);
1591 if (ess_edge_list)
1592 {
1593 ess_edge_list->Append(pair.second);
1594 }
1595 }
1596
1597 // Emit the local boundary-loop DOFs in a deterministic (increasing DOF
1598 // index) order shared by all output arrays.
1599 std::vector<int> kept(boundary_edge_dofs.begin(), boundary_edge_dofs.end());
1600 std::sort(kept.begin(), kept.end());
1601
1602 boundary_edge_dofs_out.SetSize(0);
1603 boundary_edge_dofs_out.Reserve(static_cast<int>(kept.size()));
1604 if (dof_edges)
1605 {
1606 dof_edges->SetSize(0);
1607 dof_edges->Reserve(static_cast<int>(kept.size()));
1608 }
1609 if (dof_boundary_elements)
1610 {
1611 dof_boundary_elements->SetSize(0);
1612 dof_boundary_elements->Reserve(static_cast<int>(kept.size()));
1613 }
1614 for (int dof : kept)
1615 {
1616 boundary_edge_dofs_out.Append(dof);
1617 if (dof_edges) { dof_edges->Append(dof_to_edge_map[dof]); }
1618 if (dof_boundary_elements)
1619 {
1620 dof_boundary_elements->Append(dof_to_boundary_element[dof]);
1621 }
1622 }
1623
1624 if (ldof_marker) { ldof_marker->Swap(local_ldof_marker); }
1625}
1626
1628 int component) const
1629{
1630 Array<int> ext_dofs, true_ext_dofs;
1631
1632 GetExteriorVDofs(ext_dofs, component);
1633 GetRestrictionMatrix()->BooleanMult(ext_dofs, true_ext_dofs);
1634
1635#ifdef MFEM_DEBUG
1636 // Verify that in boolean arithmetic: P^T ext_dofs = R ext_dofs.
1637 Array<int> true_ext_dofs2(true_ext_dofs.Size());
1638 auto Pt = std::unique_ptr<HypreParMatrix>(Dof_TrueDof_Matrix()->Transpose());
1639
1640 const int *ext_dofs_data = ext_dofs.HostRead();
1641 Pt->BooleanMult(1, ext_dofs_data, 0, true_ext_dofs2);
1642 int counter = 0;
1643 const int *ted = true_ext_dofs.HostRead();
1644 std::string error_msg = "failed dof: ";
1645 for (int i = 0; i < true_ext_dofs.Size(); i++)
1646 {
1647 if (bool(ted[i]) != bool(true_ext_dofs2[i]))
1648 {
1649 error_msg += std::to_string(i) += "(R ";
1650 error_msg += std::to_string(bool(ted[i])) += " P^T ";
1651 error_msg += std::to_string(bool(true_ext_dofs2[i])) += ") ";
1652 ++counter;
1653 }
1654 }
1655 MFEM_ASSERT(R->Height() == P->Width(), "!");
1656 MFEM_ASSERT(R->Width() == P->Height(), "!");
1657 MFEM_ASSERT(R->Width() == ext_dofs.Size(), "!");
1658 MFEM_VERIFY(counter == 0, "internal MFEM error: counter = " << counter
1659 << ", rank = " << MyRank << ", " << error_msg);
1660#endif
1661
1662 MarkerToList(true_ext_dofs, ext_tdof_list);
1663}
1664
1666{
1667 if (Nonconforming())
1668 {
1669 Dof_TrueDof_Matrix(); // make sure P has been built
1670
1671 return ldof_ltdof[ldof]; // NOTE: contains -1 for slaves/DOFs we don't own
1672 }
1673 else
1674 {
1675 if (GetGroupTopo().IAmMaster(ldof_group[ldof]))
1676 {
1677 return ldof_ltdof[ldof];
1678 }
1679 else
1680 {
1681 return -1;
1682 }
1683 }
1684}
1685
1687{
1688 if (Nonconforming())
1689 {
1690 MFEM_VERIFY(ldof_ltdof[ldof] >= 0, "ldof " << ldof << " not a true DOF.");
1691
1692 return GetMyTDofOffset() + ldof_ltdof[ldof];
1693 }
1694 else
1695 {
1696 if (HYPRE_AssumedPartitionCheck())
1697 {
1698 return ldof_ltdof[ldof] +
1699 tdof_nb_offsets[GetGroupTopo().GetGroupMaster(ldof_group[ldof])];
1700 }
1701 else
1702 {
1703 return ldof_ltdof[ldof] +
1704 tdof_offsets[GetGroupTopo().GetGroupMasterRank(ldof_group[ldof])];
1705 }
1706 }
1707}
1708
1710{
1711 if (Nonconforming())
1712 {
1713 MFEM_ABORT("Not implemented for NC mesh.");
1714 }
1715
1716 if (HYPRE_AssumedPartitionCheck())
1717 {
1719 {
1720 return ldof_ltdof[sldof] +
1721 tdof_nb_offsets[GetGroupTopo().GetGroupMaster(
1722 ldof_group[sldof])] / vdim;
1723 }
1724 else
1725 {
1726 return (ldof_ltdof[sldof*vdim] +
1727 tdof_nb_offsets[GetGroupTopo().GetGroupMaster(
1728 ldof_group[sldof*vdim])]) / vdim;
1729 }
1730 }
1731
1733 {
1734 return ldof_ltdof[sldof] +
1735 tdof_offsets[GetGroupTopo().GetGroupMasterRank(
1736 ldof_group[sldof])] / vdim;
1737 }
1738 else
1739 {
1740 return (ldof_ltdof[sldof*vdim] +
1741 tdof_offsets[GetGroupTopo().GetGroupMasterRank(
1742 ldof_group[sldof*vdim])]) / vdim;
1743 }
1744}
1745
1747{
1748 return HYPRE_AssumedPartitionCheck() ? dof_offsets[0] : dof_offsets[MyRank];
1749}
1750
1752{
1753 return HYPRE_AssumedPartitionCheck()? tdof_offsets[0] : tdof_offsets[MyRank];
1754}
1755
1757{
1758 if (Conforming())
1759 {
1760 if (Pconf) { return Pconf; }
1761
1762 if (nd_strias) { return Dof_TrueDof_Matrix(); }
1763
1764 if (NRanks == 1)
1765 {
1766 Pconf = new IdentityOperator(GetTrueVSize());
1767 }
1768 else
1769 {
1771 {
1772 Pconf = new ConformingProlongationOperator(*this);
1773 }
1774 else
1775 {
1776 Pconf = new DeviceConformingProlongationOperator(*this);
1777 }
1778 }
1779 return Pconf;
1780 }
1781 else
1782 {
1783 return Dof_TrueDof_Matrix();
1784 }
1785}
1786
1788{
1789 if (Conforming())
1790 {
1791 if (Rconf) { return Rconf; }
1792
1793 if (NRanks == 1)
1794 {
1796 }
1797 else
1798 {
1800 {
1801 R_transpose.reset(new ConformingProlongationOperator(*this, true));
1802 }
1803 else
1804 {
1805 R_transpose.reset(
1806 new DeviceConformingProlongationOperator(*this, true));
1807 }
1808 }
1809 Rconf = new TransposeOperator(*R_transpose);
1810 return Rconf;
1811 }
1812 else
1813 {
1815 if (!R_transpose) { R_transpose.reset(new TransposeOperator(R)); }
1816 return R;
1817 }
1818}
1819
1821{
1822 if (num_face_nbr_dofs >= 0) { return; }
1823
1824 pmesh->ExchangeFaceNbrData();
1825
1826 int num_face_nbrs = pmesh->GetNFaceNeighbors();
1827
1828 if (num_face_nbrs == 0)
1829 {
1831 return;
1832 }
1833
1834 MPI_Request *requests = new MPI_Request[2*num_face_nbrs];
1835 MPI_Request *send_requests = requests;
1836 MPI_Request *recv_requests = requests + num_face_nbrs;
1837 MPI_Status *statuses = new MPI_Status[num_face_nbrs];
1838
1839 Array<int> ldofs;
1840 Array<int> ldof_marker(GetVSize());
1841 ldof_marker = -1;
1842
1843 Table send_nbr_elem_dof;
1844
1845 send_nbr_elem_dof.MakeI(pmesh->send_face_nbr_elements.Size_of_connections());
1846 send_face_nbr_ldof.MakeI(num_face_nbrs);
1847 face_nbr_ldof.MakeI(num_face_nbrs);
1848 int *send_el_off = pmesh->send_face_nbr_elements.GetI();
1849 int *recv_el_off = pmesh->face_nbr_elements_offset;
1850 for (int fn = 0; fn < num_face_nbrs; fn++)
1851 {
1852 int *my_elems = pmesh->send_face_nbr_elements.GetRow(fn);
1853 int num_my_elems = pmesh->send_face_nbr_elements.RowSize(fn);
1854
1855 for (int i = 0; i < num_my_elems; i++)
1856 {
1857 GetElementVDofs(my_elems[i], ldofs);
1858 for (int j = 0; j < ldofs.Size(); j++)
1859 {
1860 int ldof = UnsignIndex(ldofs[j]);
1861
1862 if (ldof_marker[ldof] != fn)
1863 {
1864 ldof_marker[ldof] = fn;
1866 }
1867 }
1868 send_nbr_elem_dof.AddColumnsInRow(send_el_off[fn] + i, ldofs.Size());
1869 }
1870
1871 int nbr_rank = pmesh->GetFaceNbrRank(fn);
1872 int tag = 0;
1873 MPI_Isend(&send_face_nbr_ldof.GetI()[fn], 1, MPI_INT, nbr_rank, tag,
1874 MyComm, &send_requests[fn]);
1875
1876 MPI_Irecv(&face_nbr_ldof.GetI()[fn], 1, MPI_INT, nbr_rank, tag,
1877 MyComm, &recv_requests[fn]);
1878 }
1879
1880 MPI_Waitall(num_face_nbrs, recv_requests, statuses);
1882
1884
1885 MPI_Waitall(num_face_nbrs, send_requests, statuses);
1887
1888 // send/receive the I arrays of send_nbr_elem_dof/face_nbr_element_dof,
1889 // respectively (they contain the number of dofs for each face-neighbor
1890 // element)
1891 face_nbr_element_dof.MakeI(recv_el_off[num_face_nbrs]);
1892
1893 int *send_I = send_nbr_elem_dof.GetI();
1894 int *recv_I = face_nbr_element_dof.GetI();
1895 for (int fn = 0; fn < num_face_nbrs; fn++)
1896 {
1897 int nbr_rank = pmesh->GetFaceNbrRank(fn);
1898 int tag = 0;
1899 MPI_Isend(send_I + send_el_off[fn], send_el_off[fn+1] - send_el_off[fn],
1900 MPI_INT, nbr_rank, tag, MyComm, &send_requests[fn]);
1901
1902 MPI_Irecv(recv_I + recv_el_off[fn], recv_el_off[fn+1] - recv_el_off[fn],
1903 MPI_INT, nbr_rank, tag, MyComm, &recv_requests[fn]);
1904 }
1905
1906 MPI_Waitall(num_face_nbrs, send_requests, statuses);
1907 send_nbr_elem_dof.MakeJ();
1908
1909 ldof_marker = -1;
1910
1911 for (int fn = 0; fn < num_face_nbrs; fn++)
1912 {
1913 int *my_elems = pmesh->send_face_nbr_elements.GetRow(fn);
1914 int num_my_elems = pmesh->send_face_nbr_elements.RowSize(fn);
1915
1916 for (int i = 0; i < num_my_elems; i++)
1917 {
1918 GetElementVDofs(my_elems[i], ldofs);
1919 for (int j = 0; j < ldofs.Size(); j++)
1920 {
1921 int ldof = UnsignIndex(ldofs[j]);
1922
1923 if (ldof_marker[ldof] != fn)
1924 {
1925 ldof_marker[ldof] = fn;
1926 send_face_nbr_ldof.AddConnection(fn, ldofs[j]);
1927 }
1928 }
1929 send_nbr_elem_dof.AddConnections(
1930 send_el_off[fn] + i, ldofs, ldofs.Size());
1931 }
1932 }
1934 send_nbr_elem_dof.ShiftUpI();
1935
1936 // convert the ldof indices in send_nbr_elem_dof
1937 int *send_J = send_nbr_elem_dof.GetJ();
1938 for (int fn = 0, j = 0; fn < num_face_nbrs; fn++)
1939 {
1940 int num_ldofs = send_face_nbr_ldof.RowSize(fn);
1941 int *ldofs_fn = send_face_nbr_ldof.GetRow(fn);
1942 int j_end = send_I[send_el_off[fn+1]];
1943
1944 for (int i = 0; i < num_ldofs; i++)
1945 {
1946 int ldof = UnsignIndex(ldofs_fn[i]);
1947 ldof_marker[ldof] = i;
1948 }
1949
1950 for ( ; j < j_end; j++)
1951 {
1952 const int ldof = UnsignIndex(send_J[j]);
1953 send_J[j] = (send_J[j] >= 0 ? ldof_marker[ldof] :
1954 FlipIndexSign(ldof_marker[ldof]));
1955 }
1956 }
1957
1958 MPI_Waitall(num_face_nbrs, recv_requests, statuses);
1960
1961 // send/receive the J arrays of send_nbr_elem_dof/face_nbr_element_dof,
1962 // respectively (they contain the element dofs in enumeration local for
1963 // the face-neighbor pair)
1964 int *recv_J = face_nbr_element_dof.GetJ();
1965 for (int fn = 0; fn < num_face_nbrs; fn++)
1966 {
1967 int nbr_rank = pmesh->GetFaceNbrRank(fn);
1968 int tag = 0;
1969
1970 MPI_Isend(send_J + send_I[send_el_off[fn]],
1971 send_I[send_el_off[fn+1]] - send_I[send_el_off[fn]],
1972 MPI_INT, nbr_rank, tag, MyComm, &send_requests[fn]);
1973
1974 MPI_Irecv(recv_J + recv_I[recv_el_off[fn]],
1975 recv_I[recv_el_off[fn+1]] - recv_I[recv_el_off[fn]],
1976 MPI_INT, nbr_rank, tag, MyComm, &recv_requests[fn]);
1977 }
1978
1979 MPI_Waitall(num_face_nbrs, recv_requests, statuses);
1980
1981 // shift the J array of face_nbr_element_dof
1982 for (int fn = 0, j = 0; fn < num_face_nbrs; fn++)
1983 {
1984 int shift = face_nbr_ldof.GetI()[fn];
1985 int j_end = recv_I[recv_el_off[fn+1]];
1986
1987 for ( ; j < j_end; j++)
1988 {
1989 if (recv_J[j] >= 0)
1990 {
1991 recv_J[j] += shift;
1992 }
1993 else
1994 {
1995 recv_J[j] -= shift;
1996 }
1997 }
1998 }
1999
2000 MPI_Waitall(num_face_nbrs, send_requests, statuses);
2001
2002 // send/receive the J arrays of send_face_nbr_ldof/face_nbr_ldof,
2003 // respectively
2004 for (int fn = 0; fn < num_face_nbrs; fn++)
2005 {
2006 int nbr_rank = pmesh->GetFaceNbrRank(fn);
2007 int tag = 0;
2008
2009 MPI_Isend(send_face_nbr_ldof.GetRow(fn),
2011 MPI_INT, nbr_rank, tag, MyComm, &send_requests[fn]);
2012
2013 MPI_Irecv(face_nbr_ldof.GetRow(fn),
2015 MPI_INT, nbr_rank, tag, MyComm, &recv_requests[fn]);
2016 }
2017
2018 MPI_Waitall(num_face_nbrs, recv_requests, statuses);
2019 MPI_Waitall(num_face_nbrs, send_requests, statuses);
2020
2021 // send my_dof_offset (i.e. my_ldof_offset) to face neighbors and receive
2022 // their offset in dof_face_nbr_offsets, used to define face_nbr_glob_dof_map
2024 Array<HYPRE_BigInt> dof_face_nbr_offsets(num_face_nbrs);
2025 HYPRE_BigInt my_dof_offset = GetMyDofOffset();
2026 for (int fn = 0; fn < num_face_nbrs; fn++)
2027 {
2028 int nbr_rank = pmesh->GetFaceNbrRank(fn);
2029 int tag = 0;
2030
2031 MPI_Isend(&my_dof_offset, 1, HYPRE_MPI_BIG_INT, nbr_rank, tag,
2032 MyComm, &send_requests[fn]);
2033
2034 MPI_Irecv(&dof_face_nbr_offsets[fn], 1, HYPRE_MPI_BIG_INT, nbr_rank, tag,
2035 MyComm, &recv_requests[fn]);
2036 }
2037
2038 MPI_Waitall(num_face_nbrs, recv_requests, statuses);
2039
2040 // set the array face_nbr_glob_dof_map which holds the global ldof indices of
2041 // the face-neighbor dofs
2042 for (int fn = 0, j = 0; fn < num_face_nbrs; fn++)
2043 {
2044 for (int j_end = face_nbr_ldof.GetI()[fn+1]; j < j_end; j++)
2045 {
2046 const int ldof = UnsignIndex(face_nbr_ldof.GetJ()[j]);
2047 face_nbr_glob_dof_map[j] = dof_face_nbr_offsets[fn] + ldof;
2048 }
2049 }
2050
2051 MPI_Waitall(num_face_nbrs, send_requests, statuses);
2052
2053 delete [] statuses;
2054 delete [] requests;
2055}
2056
2058 int i, Array<int> &vdofs, DofTransformation &doftrans) const
2059{
2060 doftrans.SetDofTransformation(nullptr);
2061
2062 face_nbr_element_dof.GetRow(i, vdofs);
2063
2064 if (DoFTransArray[GetFaceNbrFE(i)->GetGeomType()])
2065 {
2066 Array<int> F, Fo;
2067 pmesh->GetFaceNbrElementFaces(pmesh->GetNE() + i, F, Fo);
2068 doftrans.SetDofTransformation(
2069 *DoFTransArray[GetFaceNbrFE(i)->GetGeomType()]);
2070 doftrans.SetFaceOrientations(Fo);
2071 doftrans.SetVDim(vdim, ordering);
2072 }
2073}
2074
2081
2083{
2084 // Works for NC mesh where 'i' is an index returned by
2085 // ParMesh::GetSharedFace() such that i >= Mesh::GetNumFaces(), i.e. 'i' is
2086 // the index of a ghost face.
2087 MFEM_ASSERT(Nonconforming() && i >= pmesh->GetNumFaces(), "");
2088 int el1, el2, inf1, inf2;
2089 pmesh->GetFaceElements(i, &el1, &el2);
2090 el2 = FlipIndexSign(el2);
2091 pmesh->GetFaceInfos(i, &inf1, &inf2);
2092 MFEM_ASSERT(0 <= el2 && el2 < face_nbr_element_dof.Size(), "");
2093 const int nd = face_nbr_element_dof.RowSize(el2);
2094 const int *vol_vdofs = face_nbr_element_dof.GetRow(el2);
2095 const Element *face_nbr_el = pmesh->face_nbr_elements[el2];
2096 Geometry::Type geom = face_nbr_el->GetGeometryType();
2097 const int face_dim = Geometry::Dimension[geom]-1;
2098
2099 fec->SubDofOrder(geom, face_dim, inf2, vdofs);
2100 // Convert local dofs to local vdofs.
2101 Ordering::DofsToVDofs<Ordering::byNODES>(nd/vdim, vdim, vdofs);
2102 // Convert local vdofs to global vdofs.
2103 for (int j = 0; j < vdofs.Size(); j++)
2104 {
2105 const int ldof = vdofs[j];
2106 vdofs[j] = (ldof >= 0) ? vol_vdofs[ldof] :
2107 FlipIndexSign(vol_vdofs[FlipIndexSign(ldof)]);
2108 }
2109}
2110
2112{
2113 if (NURBSext)
2114 {
2115 mfem_error("ParFiniteElementSpace::GetFaceNbrFE"
2116 " does not support NURBS!");
2117 }
2118
2119 if (ndofs > 0)
2120 {
2121 for (int order = fec->GetOrder(); ; ++order)
2122 {
2123 const FiniteElement *FE =
2124 fec->GetFE(pmesh->face_nbr_elements[i]->GetGeometryType(), order);
2125 const int ndofs_order = FE->GetDof();
2126 if (ndofs_order == ndofs)
2127 {
2128 return FE;
2129 }
2130 else if (ndofs_order > ndofs)
2131 {
2132 MFEM_ABORT("Finite element order not found in GetFaceNbrFE");
2133 }
2134 }
2135 }
2136 else
2137 {
2139 pmesh->face_nbr_elements[i]->GetGeometryType());
2140 }
2141}
2142
2144{
2145 // Works for NC mesh where 'i' is an index returned by
2146 // ParMesh::GetSharedFace() such that i >= Mesh::GetNumFaces(), i.e. 'i' is
2147 // the index of a ghost face.
2148 // Works in tandem with GetFaceNbrFaceVDofs() defined above.
2149
2150 MFEM_ASSERT(Nonconforming() && !NURBSext, "");
2151 Geometry::Type face_geom = pmesh->GetFaceGeometry(i);
2152 return fec->FiniteElementForGeometry(face_geom);
2153}
2154
2156{
2157 P -> StealData();
2158#if MFEM_HYPRE_VERSION <= 22200
2159 hypre_ParCSRMatrix *csrP = (hypre_ParCSRMatrix*)(*P);
2160 hypre_ParCSRMatrixOwnsRowStarts(csrP) = 1;
2161 hypre_ParCSRMatrixOwnsColStarts(csrP) = 1;
2162 dof_offsets.LoseData();
2163 tdof_offsets.LoseData();
2164#else
2165 dof_offsets.DeleteAll();
2166 tdof_offsets.DeleteAll();
2167#endif
2168}
2169
2170void ParFiniteElementSpace::ConstructTrueDofs()
2171{
2172 int i, gr, n = GetVSize();
2173 GroupTopology &gt = pmesh->gtopo;
2174 gcomm = new GroupCommunicator(gt);
2175 Table &group_ldof = gcomm->GroupLDofTable();
2176
2177 GetGroupComm(*gcomm, 1, &ldof_sign);
2178
2179 // Define ldof_group and mark ldof_ltdof with
2180 // -1 for ldof that is ours
2181 // -2 for ldof that is in a group with another master
2182 ldof_group.SetSize(n);
2183 ldof_ltdof.SetSize(n);
2184 ldof_group = 0;
2185 ldof_ltdof = -1;
2186
2187 for (gr = 1; gr < group_ldof.Size(); gr++)
2188 {
2189 const int *ldofs = group_ldof.GetRow(gr);
2190 const int nldofs = group_ldof.RowSize(gr);
2191 for (i = 0; i < nldofs; i++)
2192 {
2193 ldof_group[ldofs[i]] = gr;
2194 }
2195
2196 if (!gt.IAmMaster(gr)) // we are not the master
2197 {
2198 for (i = 0; i < nldofs; i++)
2199 {
2200 ldof_ltdof[ldofs[i]] = -2;
2201 }
2202 }
2203 }
2204
2205 // count ltdof_size
2206 ltdof_size = 0;
2207 for (i = 0; i < n; i++)
2208 {
2209 if (ldof_ltdof[i] == -1)
2210 {
2211 ldof_ltdof[i] = ltdof_size++;
2212 }
2213 }
2214 gcomm->SetLTDofTable(ldof_ltdof);
2215
2216 // have the group masters broadcast their ltdofs to the rest of the group
2217 gcomm->Bcast(ldof_ltdof);
2218}
2219
2220void ParFiniteElementSpace::ConstructTrueNURBSDofs()
2221{
2222 int n = GetVSize();
2223 GroupTopology &gt = pNURBSext()->gtopo;
2224 gcomm = new GroupCommunicator(gt);
2225
2226 // pNURBSext()->ldof_group is for scalar space!
2227 if (vdim == 1)
2228 {
2229 ldof_group.MakeRef(pNURBSext()->ldof_group);
2230 }
2231 else
2232 {
2233 const int *scalar_ldof_group = pNURBSext()->ldof_group;
2234 ldof_group.SetSize(n);
2235 for (int i = 0; i < n; i++)
2236 {
2237 ldof_group[i] = scalar_ldof_group[VDofToDof(i)];
2238 }
2239 }
2240
2241 gcomm->Create(ldof_group);
2242
2243 // ldof_sign.SetSize(n);
2244 // ldof_sign = 1;
2245 ldof_sign.DeleteAll();
2246
2247 ltdof_size = 0;
2248 ldof_ltdof.SetSize(n);
2249 for (int i = 0; i < n; i++)
2250 {
2251 if (gt.IAmMaster(ldof_group[i]))
2252 {
2253 ldof_ltdof[i] = ltdof_size;
2254 ltdof_size++;
2255 }
2256 else
2257 {
2258 ldof_ltdof[i] = -2;
2259 }
2260 }
2261 gcomm->SetLTDofTable(ldof_ltdof);
2262
2263 // have the group masters broadcast their ltdofs to the rest of the group
2264 gcomm->Bcast(ldof_ltdof);
2265}
2266
2267void ParFiniteElementSpace::GetGhostVertexDofs(const MeshId &id,
2268 Array<int> &dofs) const
2269{
2271 dofs.SetSize(nv);
2272 for (int j = 0; j < nv; j++)
2273 {
2274 dofs[j] = ndofs + nv*id.index + j;
2275 }
2276}
2277
2278static const char* msg_orders_changed =
2279 "Element orders changed, you need to Update() the space first.";
2280
2281void ParFiniteElementSpace::GetGhostEdgeDofs(const MeshId &edge_id,
2282 Array<int> &dofs, int variant) const
2283{
2284 MFEM_VERIFY(!orders_changed, msg_orders_changed);
2285
2286 int order, ne, base;
2287 if (IsVariableOrder())
2288 {
2289 const int edge = edge_id.index;
2290 const int* beg = var_edge_dofs.GetRow(edge);
2291
2292 base = beg[variant];
2293 ne = beg[variant+1] - base;
2294
2295 base -= nedofs;
2296
2297 order = var_edge_orders[var_edge_dofs.GetI()[edge] + variant];
2298 MFEM_ASSERT(fec->GetNumDof(Geometry::SEGMENT, order) == ne, "");
2299 }
2300 else
2301 {
2302 order = fec->GetOrder();
2303 ne = fec->GetNumDof(Geometry::SEGMENT, order);
2304 base = (edge_id.index - pncmesh->GetNEdges())*ne;
2305 }
2306
2307 int nv = fec->GetNumDof(Geometry::POINT, order);
2308
2309 dofs.SetSize(2*nv + ne);
2310
2311 int V[2], ghost = pncmesh->GetNVertices();
2312 pmesh->pncmesh->GetEdgeVertices(edge_id, V);
2313
2314 for (int i = 0; i < 2; i++)
2315 {
2316 int k = (V[i] < ghost) ? V[i]*nv : (ndofs + (V[i] - ghost)*nv);
2317 for (int j = 0; j < nv; j++)
2318 {
2319 dofs[i*nv + j] = k++;
2320 }
2321 }
2322
2323 int k = ndofs + ngvdofs + base;
2324 for (int j = 0; j < ne; j++)
2325 {
2326 dofs[2*nv + j] = k++;
2327 }
2328}
2329
2330void ParFiniteElementSpace::GetGhostFaceDofs(const MeshId &face_id,
2331 Array<int> &dofs) const
2332{
2333 MFEM_VERIFY(!orders_changed, msg_orders_changed);
2334
2335 int nfv, V[4], E[4], Eo[4];
2336 nfv = pmesh->pncmesh->GetFaceVerticesEdges(face_id, V, E, Eo);
2337
2340 int nf_tri = fec->DofForGeometry(Geometry::TRIANGLE);
2341 int nf_quad = fec->DofForGeometry(Geometry::SQUARE);
2342 int nf = (nfv == 3) ? nf_tri : nf_quad;
2343
2344 const int ghost_face_index = face_id.index - pncmesh->GetNFaces();
2345
2346 Array<int> evar(nfv);
2347
2348 int base;
2349 if (IsVariableOrder())
2350 {
2351 const int face = face_id.index;
2352 const int* beg = var_face_dofs.GetRow(face);
2353 constexpr int variant = 0; // Face variant
2354
2355 base = beg[variant];
2356 nf = beg[variant+1] - base;
2357
2358 base -= nfdofs;
2359
2360 int allne = 0;
2361
2362 const int fo = GetFaceOrder(face, variant);
2363 for (int i = 0; i < nfv; i++)
2364 {
2365 // Find the edge variant matching the face order
2366 evar[i] = 0;
2367 int eo = 0;
2368 while (eo != -1)
2369 {
2370 eo = GetEdgeOrder(E[i], evar[i]);
2371 if (eo == fo)
2372 {
2373 break;
2374 }
2375
2376 evar[i]++;
2377 }
2378
2379 MFEM_VERIFY(eo == fo, "Edge must have same order as face");
2380
2381 const int* ebeg = var_edge_dofs.GetRow(E[i]);
2382 const int ne_i = ebeg[evar[i] + 1] - ebeg[evar[i]];
2383 allne += ne_i;
2384 }
2385
2386 dofs.SetSize((nfv * nv) + allne + nf);
2387 }
2388 else
2389 {
2390 base = nf_quad * ghost_face_index;
2391 // TODO: why nf_quad and never nf_tri? Is it because only quad faces are
2392 // supported for NCMesh? If so, why even have nf_tri?
2393
2394 dofs.SetSize(nfv*(nv + ne) + nf);
2395 }
2396
2397 int offset = 0;
2398 for (int i = 0; i < nfv; i++)
2399 {
2400 const int ghost = pncmesh->GetNVertices();
2401 const int first = (V[i] < ghost) ? V[i]*nv : (ndofs + (V[i] - ghost)*nv);
2402 for (int j = 0; j < nv; j++)
2403 {
2404 dofs[offset++] = first + j;
2405 }
2406 }
2407
2408 for (int i = 0; i < nfv; i++)
2409 {
2410 const int ghost = pncmesh->GetNEdges();
2411 if (IsVariableOrder())
2412 {
2413 const int variant = evar[i]; // Edge variant
2414
2415 const int* beg = var_edge_dofs.GetRow(E[i]);
2416 int ebase = beg[variant];
2417 ne = beg[variant+1] - ebase;
2418
2419 MFEM_ASSERT(ebase == FindEdgeDof(E[i], ne), "sanity check?");
2420
2421 const int first = (E[i] < ghost) ? nvdofs + ebase
2422 /* */ : ndofs + ngvdofs + ebase - nedofs;
2423
2424 const int edge_order = var_edge_orders[var_edge_dofs.GetI()[E[i]] + variant];
2425 const int *ind = fec->GetDofOrdering(Geometry::SEGMENT, edge_order, Eo[i]);
2426
2427 MFEM_ASSERT(fec->GetNumDof(Geometry::SEGMENT, edge_order) == ne, "");
2428
2429 for (int j = 0; j < ne; j++)
2430 {
2431 dofs[offset++] = (ind[j] >= 0) ? (first + ind[j]) :
2432 FlipIndexSign(first + FlipIndexSign(ind[j]));
2433 }
2434 }
2435 else
2436 {
2437 const int first = (E[i] < ghost) ? nvdofs + E[i]*ne
2438 /* */ : ndofs + ngvdofs + (E[i] - ghost)*ne;
2439 const int *ind = fec->DofOrderForOrientation(Geometry::SEGMENT, Eo[i]);
2440 for (int j = 0; j < ne; j++)
2441 {
2442 dofs[offset++] = (ind[j] >= 0) ? (first + ind[j]) :
2443 FlipIndexSign(first + FlipIndexSign(ind[j]));
2444 }
2445 }
2446 }
2447
2448 const int first = ndofs + ngvdofs + ngedofs + base;
2449 for (int j = 0; j < nf; j++)
2450 {
2451 dofs[offset++] = first + j;
2452 }
2453}
2454
2455void ParFiniteElementSpace::GetGhostDofs(int entity, const MeshId &id,
2456 Array<int> &dofs, int var) const
2457{
2458 // helper to get ghost vertex, ghost edge or ghost face DOFs
2459 switch (entity)
2460 {
2461 case 0: GetGhostVertexDofs(id, dofs); break;
2462 case 1: GetGhostEdgeDofs(id, dofs, var); break;
2463 case 2: GetGhostFaceDofs(id, dofs); break;
2464 }
2465}
2466
2467void ParFiniteElementSpace::GetBareDofsVar(int entity, int index,
2468 Array<int> &dofs) const
2469{
2470 int ned, ghost, first;
2471 switch (entity)
2472 {
2473 case 0:
2475 ghost = pncmesh->GetNVertices();
2476 first = (index < ghost)
2477 ? index*ned // regular vertex
2478 : ndofs + (index - ghost)*ned; // ghost vertex
2479 break;
2480 case 1:
2481 ghost = pncmesh->GetNEdges();
2482 {
2483 const int* row = var_edge_dofs.GetRow(index);
2484 const int* rowNext = var_edge_dofs.GetRow(index + 1);
2485 ned = rowNext[0] - row[0];
2486 first = (index < ghost)
2487 ? nvdofs + row[0] // regular edge
2488 : ndofs + ngvdofs + row[0] - nedofs; // ghost edge
2489 }
2490 break;
2491 default:
2492 ghost = pncmesh->GetNFaces();
2493 {
2494 const int row0 = FirstFaceDof(index);
2495 ned = FirstFaceDof(index + 1) - row0;
2496 if (index < ghost) // regular face
2497 {
2498 first = nvdofs + nedofs + row0;
2499 }
2500 else // ghost face
2501 {
2502 first = ndofs + ngvdofs + ngedofs + row0 - nfdofs;
2503 }
2504 }
2505 break;
2506 }
2507
2508 dofs.SetSize(ned);
2509 for (int i = 0; i < ned; i++)
2510 {
2511 dofs[i] = first + i;
2512 }
2513}
2514
2515void ParFiniteElementSpace::GetBareDofs(int entity, int index,
2516 Array<int> &dofs) const
2517{
2518 if (IsVariableOrder())
2519 {
2520 GetBareDofsVar(entity, index, dofs);
2521 return;
2522 }
2523
2524 int ned, ghost, first;
2525 switch (entity)
2526 {
2527 case 0:
2529 ghost = pncmesh->GetNVertices();
2530 first = (index < ghost)
2531 ? index*ned // regular vertex
2532 : ndofs + (index - ghost)*ned; // ghost vertex
2533 break;
2534
2535 case 1:
2537 ghost = pncmesh->GetNEdges();
2538 first = (index < ghost)
2539 ? nvdofs + index*ned // regular edge
2540 : ndofs + ngvdofs + (index - ghost)*ned; // ghost edge
2541 break;
2542
2543 default:
2544 Geometry::Type geom = pncmesh->GetFaceGeometry(index);
2545 MFEM_ASSERT(geom == Geometry::SQUARE ||
2546 geom == Geometry::TRIANGLE, "");
2547
2548 ned = fec->DofForGeometry(geom);
2549 ghost = pncmesh->GetNFaces();
2550
2551 if (index < ghost) // regular face
2552 {
2553 first = nvdofs + nedofs + FirstFaceDof(index);
2554 }
2555 else // ghost face
2556 {
2557 index -= ghost;
2558 int stride = fec->DofForGeometry(Geometry::SQUARE);
2559 first = ndofs + ngvdofs + ngedofs + index*stride;
2560 }
2561 break;
2562 }
2563
2564 dofs.SetSize(ned);
2565 for (int i = 0; i < ned; i++)
2566 {
2567 dofs[i] = first + i;
2568 }
2569}
2570
2571int ParFiniteElementSpace::PackDofVar(int entity, int index, int edof,
2572 int var) const
2573{
2574 int ghost, ned;
2575 switch (entity)
2576 {
2577 case 0:
2578 // Vertices have 0 or 1 DOFs, regardless of order.
2579 ghost = pncmesh->GetNVertices();
2581
2582 return (index < ghost)
2583 ? index*ned + edof // regular vertex
2584 : ndofs + (index - ghost)*ned + edof; // ghost vertex
2585
2586 case 1:
2587 ghost = pncmesh->GetNEdges();
2588 {
2589 const int* row = var_edge_dofs.GetRow(index);
2590 MFEM_ASSERT(0 <= var && var < var_edge_dofs.RowSize(index), "");
2591 const int d = row[var] + edof;
2592 if (index < ghost) // regular edge
2593 {
2594 return nvdofs + d;
2595 }
2596 else // ghost edge
2597 {
2598 return ndofs + ngvdofs + d - nedofs;
2599 }
2600 }
2601 default:
2602 ghost = pncmesh->GetNFaces();
2603 if (index < ghost) // regular face
2604 {
2605 MFEM_ASSERT(0 <= var && var < var_face_dofs.RowSize(index), "");
2606 return nvdofs + nedofs + FirstFaceDof(index, var) + edof;
2607 }
2608 else // ghost face
2609 {
2610 return ndofs + ngvdofs + ngedofs + FirstFaceDof(index, var) - nfdofs + edof;
2611 }
2612 }
2613}
2614
2615static int bisect(const int* array, int size, int value)
2616{
2617 const int* end = array + size;
2618 const int* pos = std::upper_bound(array, end, value);
2619 MFEM_VERIFY(pos != array, "value not found");
2620 if (pos == end)
2621 {
2622 MFEM_VERIFY(*(array+size - 1) == value, "Last entry must be exact")
2623 }
2624 return pos - array - 1;
2625}
2626
2627void ParFiniteElementSpace::UnpackDofVar(int dof, int &entity, int &index,
2628 int &edof, int &order) const
2629{
2630 order = -1;
2631 MFEM_ASSERT(dof >= 0, "");
2632 if (dof < ndofs)
2633 {
2634 if (dof < nvdofs) // regular vertex
2635 {
2637 entity = 0, index = dof / nv, edof = dof % nv;
2638 return;
2639 }
2640 dof -= nvdofs;
2641 if (dof < nedofs) // regular edge
2642 {
2643 entity = 1;
2644 index = var_edge_dofmap[dof].index;
2645 edof = var_edge_dofmap[dof].edof;
2646
2647 // Convert from local to global offset.
2648 int os = 0;
2649 order = -1;
2650 const int edge = index;
2651 const int nvar = this->GetNVariants(1, edge);
2652 for (int v=0; v<nvar; ++v)
2653 {
2654 const int eo = this->GetEdgeOrder(edge, v);
2655 const int dofs = fec->GetNumDof(Geometry::SEGMENT, eo);
2656 if (edof < os + dofs)
2657 {
2658 order = eo;
2659 break;
2660 }
2661
2662 os += dofs;
2663 }
2664
2665 MFEM_ASSERT(order >= 0, "");
2666
2667 edof -= os; // Local offset
2668 return;
2669 }
2670 dof -= nedofs;
2671 if (dof < nfdofs) // regular face
2672 {
2673 entity = 2;
2674 index = var_face_dofmap[dof].index;
2675 edof = var_face_dofmap[dof].edof;
2676
2677 // Convert from local to global offset.
2678 int os = 0;
2679 order = -1;
2680 const int face = index;
2681 const Geometry::Type geom = pncmesh->GetFaceGeometry(face);
2682 const int nvar = this->GetNVariants(2, face);
2683 for (int v=0; v<nvar; ++v)
2684 {
2685 const int fo = this->GetFaceOrder(face, v);
2686 const int dofs = fec->GetNumDof(geom, fo);
2687 if (edof < os + dofs)
2688 {
2689 order = fo;
2690 break;
2691 }
2692
2693 os += dofs;
2694 }
2695
2696 MFEM_ASSERT(order >= 0, "");
2697
2698 edof -= os; // Local offset
2699 return;
2700 }
2701 MFEM_ABORT("Cannot unpack internal DOF");
2702 }
2703 else
2704 {
2705 dof -= ndofs;
2706 if (dof < ngvdofs) // ghost vertex
2707 {
2709 entity = 0, index = pncmesh->GetNVertices() + dof / nv, edof = dof % nv;
2710 return;
2711 }
2712
2713 dof -= ngvdofs;
2714 if (dof < ngedofs) // ghost edge
2715 {
2716 entity = 1;
2717 index = var_edge_dofmap[dof + nedofs].index;
2718 edof = var_edge_dofmap[dof + nedofs].edof;
2719 return;
2720 }
2721
2722 dof -= ngedofs;
2723 if (dof < ngfdofs) // ghost face
2724 {
2725 entity = 2;
2726 index = var_face_dofmap[dof + nfdofs].index;
2727 edof = var_face_dofmap[dof + nfdofs].edof;
2728 return;
2729 }
2730 MFEM_ABORT("Out of range DOF.");
2731 }
2732}
2733
2734int ParFiniteElementSpace::PackDof(int entity, int index, int edof,
2735 int var) const
2736{
2737 if (IsVariableOrder())
2738 {
2739 return PackDofVar(entity, index, edof, var);
2740 }
2741
2742 // DOFs are ordered as follows:
2743 // vertices | edges | faces | internal | ghost vert. | g. edges | g. faces
2744
2745 int ghost, ned;
2746 switch (entity)
2747 {
2748 case 0:
2749 ghost = pncmesh->GetNVertices();
2751
2752 return (index < ghost)
2753 ? index*ned + edof // regular vertex
2754 : ndofs + (index - ghost)*ned + edof; // ghost vertex
2755
2756 case 1:
2757 ghost = pncmesh->GetNEdges();
2759
2760 return (index < ghost)
2761 ? nvdofs + index*ned + edof // regular edge
2762 : ndofs + ngvdofs + (index - ghost)*ned + edof; // ghost edge
2763
2764 default:
2765 ghost = pncmesh->GetNFaces();
2766 ned = fec->DofForGeometry(pncmesh->GetFaceGeometry(index));
2767
2768 if (index < ghost) // regular face
2769 {
2770 return nvdofs + nedofs + FirstFaceDof(index) + edof;
2771 }
2772 else // ghost face
2773 {
2774 index -= ghost;
2775 int stride = fec->DofForGeometry(Geometry::SQUARE);
2776 return ndofs + ngvdofs + ngedofs + index*stride + edof;
2777 }
2778 }
2779}
2780
2781/** Dissect a DOF number to obtain the entity type (0=vertex, 1=edge, 2=face),
2782 * entity index and the DOF number within the entity.
2783 */
2784void ParFiniteElementSpace::UnpackDof(int dof,
2785 int &entity, int &index,
2786 int &edof, int &order) const
2787{
2788 order = -1;
2789
2790 if (IsVariableOrder())
2791 {
2792 UnpackDofVar(dof, entity, index, edof, order);
2793 return;
2794 }
2795
2796 MFEM_ASSERT(dof >= 0, "");
2797 if (dof < ndofs)
2798 {
2799 if (dof < nvdofs) // regular vertex
2800 {
2802 entity = 0, index = dof / nv, edof = dof % nv;
2803 return;
2804 }
2805 dof -= nvdofs;
2806 if (dof < nedofs) // regular edge
2807 {
2809 entity = 1, index = dof / ne, edof = dof % ne;
2810 return;
2811 }
2812 dof -= nedofs;
2813 if (dof < nfdofs) // regular face
2814 {
2815 if (uni_fdof >= 0) // uniform faces
2816 {
2817 int nf = fec->DofForGeometry(pmesh->GetTypicalFaceGeometry());
2818 index = dof / nf, edof = dof % nf;
2819 }
2820 else // mixed faces or var-order space
2821 {
2822 const Table &table = var_face_dofs;
2823
2824 MFEM_ASSERT(table.Size() > 0, "");
2825 int jpos = bisect(table.GetJ(), table.Size_of_connections(), dof);
2826 index = bisect(table.GetI(), table.Size(), jpos);
2827 edof = dof - table.GetRow(index)[0];
2828 }
2829 entity = 2;
2830 return;
2831 }
2832 MFEM_ABORT("Cannot unpack internal DOF");
2833 }
2834 else
2835 {
2836 dof -= ndofs;
2837 if (dof < ngvdofs) // ghost vertex
2838 {
2840 entity = 0, index = pncmesh->GetNVertices() + dof / nv, edof = dof % nv;
2841 return;
2842 }
2843 dof -= ngvdofs;
2844 if (dof < ngedofs) // ghost edge
2845 {
2847 entity = 1, index = pncmesh->GetNEdges() + dof / ne, edof = dof % ne;
2848 return;
2849 }
2850 dof -= ngedofs;
2851 if (dof < ngfdofs) // ghost face
2852 {
2853 int stride = fec->DofForGeometry(Geometry::SQUARE);
2854 index = pncmesh->GetNFaces() + dof / stride, edof = dof % stride;
2855 entity = 2;
2856 return;
2857 }
2858 MFEM_ABORT("Out of range DOF.");
2859 }
2860}
2861
2862/** Represents an element of the P matrix. The column number is global and
2863 * corresponds to vector dimension 0. The other dimension columns are offset
2864 * by 'stride'.
2865 */
2866struct PMatrixElement
2867{
2868 HYPRE_BigInt column;
2869 int stride;
2870 double value;
2871
2872 PMatrixElement(HYPRE_BigInt col = 0, int str = 0, double val = 0)
2873 : column(col), stride(str), value(val) {}
2874
2875 bool operator<(const PMatrixElement &other) const
2876 { return column < other.column; }
2877
2878 typedef std::vector<PMatrixElement> List;
2879};
2880
2881/** Represents one row of the P matrix, for the construction code below. The row
2882 * is complete: diagonal and off-diagonal elements are not distinguished.
2883 */
2884struct PMatrixRow
2885{
2886 PMatrixElement::List elems;
2887
2888 /// Add other row, times 'coef'.
2889 void AddRow(const PMatrixRow &other, real_t coef)
2890 {
2891 elems.reserve(elems.size() + other.elems.size());
2892 for (const PMatrixElement &oei : other.elems)
2893 {
2894 elems.emplace_back(oei.column, oei.stride, coef * oei.value);
2895 }
2896 }
2897
2898 /// Remove duplicate columns and sum their values.
2899 void Collapse()
2900 {
2901 if (!elems.size()) { return; }
2902 std::sort(elems.begin(), elems.end());
2903
2904 int j = 0;
2905 for (unsigned i = 1; i < elems.size(); i++)
2906 {
2907 if (elems[j].column == elems[i].column)
2908 {
2909 elems[j].value += elems[i].value;
2910 }
2911 else
2912 {
2913 elems[++j] = elems[i];
2914 }
2915 }
2916 elems.resize(j+1);
2917 }
2918
2919 void write(std::ostream &os, real_t sign) const
2920 {
2921 bin_io::write<int>(os, static_cast<int>(elems.size()));
2922 for (unsigned i = 0; i < elems.size(); i++)
2923 {
2924 const PMatrixElement &e = elems[i];
2925 bin_io::write<HYPRE_BigInt>(os, e.column);
2926 bin_io::write<int>(os, e.stride);
2927 bin_io::write<real_t>(os, e.value * sign);
2928 }
2929 }
2930
2931 void read(std::istream &is, real_t sign)
2932 {
2933 elems.resize(bin_io::read<int>(is));
2934 for (unsigned i = 0; i < elems.size(); i++)
2935 {
2936 PMatrixElement &e = elems[i];
2937 e.column = bin_io::read<HYPRE_BigInt>(is);
2938 e.stride = bin_io::read<int>(is);
2939 e.value = bin_io::read<real_t>(is) * sign;
2940 }
2941 }
2942};
2943
2944class NeighborOrderMessage : public VarMessage<VarMessageTag::NEIGHBOR_ORDER_VM>
2945{
2946public:
2947 typedef NCMesh::MeshId MeshId;
2948 typedef ParNCMesh::GroupId GroupId;
2949
2950 struct OrderInfo
2951 {
2952 int entity, index, order;
2953 GroupId group;
2954
2955 OrderInfo(int ent, int idx, int p, GroupId grp)
2956 : entity(ent), index(idx), order(p), group(grp) {}
2957 };
2958
2959 NeighborOrderMessage() : pncmesh(NULL) {}
2960
2961 void AddOrder(int ent, int idx, int p, GroupId grp)
2962 {
2963 msgs.emplace_back(ent, idx, p, grp);
2964 }
2965
2966 void SetNCMesh(ParNCMesh* pnc) { pncmesh = pnc; }
2967
2968 const std::vector<OrderInfo>& GetMsgs() const { return msgs; }
2969
2970 typedef std::map<int, NeighborOrderMessage> Map;
2971
2972protected:
2973 std::vector<OrderInfo> msgs;
2974
2975 ParNCMesh *pncmesh;
2976
2977 /// Encode a NeighborOrderMessage for sending via MPI.
2978 void Encode(int rank) override;
2979 /// Decode a NeighborOrderMessage received via MPI.
2980 void Decode(int rank) override;
2981};
2982
2983void NeighborOrderMessage::Encode(int rank)
2984{
2985 std::ostringstream stream;
2986
2987 Array<MeshId> ent_ids[3];
2988 Array<GroupId> group_ids[3];
2989 Array<int> row_idx[3];
2990
2991 // Encode MeshIds and groups
2992 for (unsigned i = 0; i < msgs.size(); i++)
2993 {
2994 const OrderInfo &ri = msgs[i];
2995 const MeshId &id = *pncmesh->GetNCList(ri.entity).GetMeshIdAndType(ri.index).id;
2996 ent_ids[ri.entity].Append(id);
2997 row_idx[ri.entity].Append(i);
2998 group_ids[ri.entity].Append(ri.group);
2999 }
3000
3001 Array<GroupId> all_group_ids;
3002 all_group_ids.Reserve(msgs.size());
3003 for (int i = 0; i < 3; i++)
3004 {
3005 all_group_ids.Append(group_ids[i]);
3006 }
3007
3008 pncmesh->AdjustMeshIds(ent_ids, rank);
3009 pncmesh->EncodeMeshIds(stream, ent_ids);
3010 pncmesh->EncodeGroups(stream, all_group_ids);
3011
3012 // Write all rows to the stream
3013 for (int ent = 0; ent < 3; ent++)
3014 {
3015 for (int i = 0; i < ent_ids[ent].Size(); i++)
3016 {
3017 const OrderInfo &ri = msgs[row_idx[ent][i]];
3018 MFEM_ASSERT(ent == ri.entity, "");
3019
3020 bin_io::write<int>(stream, ri.order);
3021 }
3022 }
3023
3024 msgs.clear();
3025 stream.str().swap(data);
3026}
3027
3028void NeighborOrderMessage::Decode(int rank)
3029{
3030 std::istringstream stream(data);
3031
3032 Array<MeshId> ent_ids[3];
3033 Array<GroupId> group_ids;
3034
3035 // decode vertex/edge/face IDs and groups
3036 pncmesh->DecodeMeshIds(stream, ent_ids);
3037 pncmesh->DecodeGroups(stream, group_ids);
3038
3039 int nrows = ent_ids[0].Size() + ent_ids[1].Size() + ent_ids[2].Size();
3040 MFEM_ASSERT(nrows == group_ids.Size(), "");
3041
3042 msgs.clear();
3043 msgs.reserve(nrows);
3044
3045 // Read messages. ent = {0,1,2} means vertex, edge and face entity
3046 for (int ent = 1, gi = 0; ent < 3; ent++)
3047 {
3048 // extract the vertex list, edge list or face list.
3049 const Array<MeshId> &ids = ent_ids[ent];
3050 for (int i = 0; i < ids.Size(); i++)
3051 {
3052 const MeshId &id = ids[i];
3053 // read the particular value off the stream.
3054 int order_i = bin_io::read<int>(stream);
3055
3056 // Create an entry for this entity, recording the index of the mesh
3057 // element
3058 msgs.emplace_back(ent, id.index, order_i, group_ids[gi++]);
3059 }
3060 }
3061}
3062
3063/** Represents a message to another processor containing P matrix rows.
3064 * Used by ParFiniteElementSpace::BuildParallelConformingInterpolation.
3065 */
3066class NeighborRowMessage : public VarMessage<VarMessageTag::NEIGHBOR_ROW_VM>
3067{
3068public:
3069 typedef NCMesh::MeshId MeshId;
3070 typedef ParNCMesh::GroupId GroupId;
3071 struct RowInfo
3072 {
3073 int entity, index, edof, var;
3074 GroupId group;
3075 PMatrixRow row;
3076
3077 RowInfo(int ent, int idx, int edof, GroupId grp, const PMatrixRow &row,
3078 int v = 0)
3079 : entity(ent), index(idx), edof(edof), var(v), group(grp), row(row) {}
3080
3081 RowInfo(int ent, int idx, int edof, GroupId grp, int v = 0)
3082 : entity(ent), index(idx), edof(edof), var(v), group(grp) {}
3083 };
3084
3085 NeighborRowMessage() : pncmesh(NULL) {}
3086
3087 void AddRow(int entity, int index, int edof, GroupId group,
3088 const PMatrixRow &row, int order)
3089 {
3090 int var = 0;
3091 if (varOrder && entity == 1)
3092 {
3093 bool found = false;
3094 while (!found)
3095 {
3096 const int order_v = fes->GetEdgeOrder(index, var);
3097 MFEM_ASSERT(order_v >= 0, "");
3098 if (order == order_v)
3099 {
3100 found = true;
3101 }
3102 else
3103 {
3104 var++;
3105 }
3106 }
3107 if (!found)
3108 {
3109 var = -1;
3110 }
3111 }
3112 else if (varOrder && entity == 2)
3113 {
3114 bool found = false;
3115 while (!found)
3116 {
3117 const int order_v = fes->GetFaceOrder(index, var);
3118 MFEM_ASSERT(order_v >= 0, "");
3119 if (order == order_v)
3120 {
3121 found = true;
3122 }
3123 else
3124 {
3125 var++;
3126 }
3127 }
3128
3129 if (!found)
3130 {
3131 var = -1;
3132 }
3133 }
3134
3135 rows.emplace_back(entity, index, edof, group, row, var);
3136 }
3137
3138 const std::vector<RowInfo>& GetRows() const { return rows; }
3139
3140 void SetNCMesh(ParNCMesh* pnc) { pncmesh = pnc; }
3141 void SetFEC(const FiniteElementCollection* fec_) { this->fec = fec_; }
3142 void SetSpace(const ParFiniteElementSpace* fes_)
3143 {
3144 this->fes = fes_;
3145 varOrder = fes->IsVariableOrder();
3146 }
3147
3148 typedef std::map<int, NeighborRowMessage> Map;
3149
3150protected:
3151 std::vector<RowInfo> rows;
3152
3153 ParNCMesh *pncmesh;
3154 const FiniteElementCollection* fec;
3155 const ParFiniteElementSpace* fes;
3156
3157 bool varOrder = false;
3158
3159 int GetEdgeVarOffset(int edge, int var);
3160 int GetFaceVarOffset(int face, int var);
3161
3162 /// Encode a NeighborRowMessage for sending via MPI.
3163 void Encode(int rank) override;
3164 /// Decode a NeighborRowMessage received via MPI.
3165 void Decode(int rank) override;
3166};
3167
3168void NeighborRowMessage::Encode(int rank)
3169{
3170 std::ostringstream stream;
3171
3172 Array<MeshId> ent_ids[3];
3173 Array<GroupId> group_ids[3];
3174 Array<int> row_idx[3];
3175
3176 // Encode MeshIds and groups
3177 for (unsigned i = 0; i < rows.size(); i++)
3178 {
3179 const RowInfo &ri = rows[i];
3180 const MeshId &id = *pncmesh->GetNCList(ri.entity).GetMeshIdAndType(ri.index).id;
3181 ent_ids[ri.entity].Append(id);
3182 row_idx[ri.entity].Append(i);
3183 group_ids[ri.entity].Append(ri.group);
3184 }
3185
3186 Array<GroupId> all_group_ids;
3187 all_group_ids.Reserve(static_cast<int>(rows.size()));
3188 for (int i = 0; i < 3; i++)
3189 {
3190 all_group_ids.Append(group_ids[i]);
3191 }
3192
3193 pncmesh->AdjustMeshIds(ent_ids, rank);
3194 pncmesh->EncodeMeshIds(stream, ent_ids);
3195 pncmesh->EncodeGroups(stream, all_group_ids);
3196
3197 // Write all rows to the stream
3198 for (int ent = 0; ent < 3; ent++)
3199 {
3200 const Array<MeshId> &ids = ent_ids[ent];
3201 for (int i = 0; i < ids.Size(); i++)
3202 {
3203 const MeshId &id = ids[i];
3204 const RowInfo &ri = rows[row_idx[ent][i]];
3205 MFEM_ASSERT(ent == ri.entity, "");
3206
3207#ifdef MFEM_DEBUG_PMATRIX
3208 mfem::out << "Rank " << pncmesh->MyRank << " sending to " << rank
3209 << ": ent " << ri.entity << ", index " << ri.index
3210 << ", edof " << ri.edof << " (id " << id.element << "/"
3211 << int(id.local) << ")" << std::endl;
3212#endif
3213
3214 // Handle orientation and sign change
3215 int edof = ri.edof;
3216 int order_i = fec->GetOrder();
3217 real_t s = 1.0;
3218 if (ent == 1)
3219 {
3220 const int eo = pncmesh->GetEdgeNCOrientation(id);
3221
3222 const int *ind = nullptr;
3223 int osvar = 0; // Offset for DOFs in the variable-order case
3224 if (varOrder)
3225 {
3226 order_i = fes->GetEdgeOrder(ri.index, ri.var);
3227 ind = fec->GetDofOrdering(Geometry::SEGMENT, order_i, eo);
3228 }
3229 else
3230 {
3232 }
3233
3234 if (ind && (edof = ind[edof]) < 0)
3235 {
3236 edof = FlipIndexSign(edof);
3237 s = -1;
3238 }
3239
3240 edof += osvar;
3241 }
3242
3243 if (ent == 2 && varOrder)
3244 {
3245 int var = ri.var;
3246 order_i = fes->GetFaceOrder(ri.index, var);
3247 }
3248
3249 bin_io::write<int>(stream, edof);
3250 bin_io::write<int>(stream, order_i);
3251 ri.row.write(stream, s);
3252 }
3253 }
3254
3255 rows.clear();
3256 stream.str().swap(data);
3257}
3258
3259int NeighborRowMessage::GetEdgeVarOffset(int edge, int var)
3260{
3261 int os = 0;
3262 for (int v=0; v<var; ++v)
3263 {
3264 const int eo = fes->GetEdgeOrder(edge, v);
3265 const int dofs = fec->GetNumDof(Geometry::SEGMENT, eo);
3266 os += dofs;
3267 }
3268
3269 return os;
3270}
3271
3272int NeighborRowMessage::GetFaceVarOffset(int face, int var)
3273{
3274 Geometry::Type geom = pncmesh->GetFaceGeometry(face);
3275 int os = 0;
3276 for (int v=0; v<var; ++v)
3277 {
3278 const int fo = fes->GetFaceOrder(face, v);
3279 const int dofs = fec->GetNumDof(geom, fo);
3280 os += dofs;
3281 }
3282
3283 return os;
3284}
3285
3286void NeighborRowMessage::Decode(int rank)
3287{
3288 std::istringstream stream(data);
3289
3290 Array<MeshId> ent_ids[3];
3291 Array<GroupId> group_ids;
3292
3293 // decode vertex/edge/face IDs and groups
3294 pncmesh->DecodeMeshIds(stream, ent_ids);
3295 pncmesh->DecodeGroups(stream, group_ids);
3296
3297 int nrows = ent_ids[0].Size() + ent_ids[1].Size() + ent_ids[2].Size();
3298 MFEM_ASSERT(nrows == group_ids.Size(), "");
3299
3300 rows.clear();
3301 rows.reserve(nrows);
3302
3303 // read rows ent = {0,1,2} means vertex, edge and face entity
3304 for (int ent = 0, gi = 0; ent < 3; ent++)
3305 {
3306 // extract the vertex list, edge list or face list.
3307 const Array<MeshId> &ids = ent_ids[ent];
3308 for (int i = 0; i < ids.Size(); i++)
3309 {
3310 const MeshId &id = ids[i];
3311 // read the particular element dof value off the stream.
3312 int edof = bin_io::read<int>(stream);
3313 int order_i = bin_io::read<int>(stream);
3314 MFEM_ASSERT(order_i >= 0, "");
3315
3316 // Handle orientation and sign change. This flips the sign on dofs
3317 // where necessary, and for edges and faces also reorders if flipped,
3318 // i.e. an edge with 1 -> 2 -> 3 -> 4 might become -4 -> -3 -> -2 -> -1
3319 // This cannot treat all face dofs, as they can have rotations and
3320 // reflections.
3321 const int *ind = nullptr;
3323 int osvar = 0;
3324 int var = 0;
3325 if (ent == 1)
3326 {
3327 // edge NC orientation is element defined.
3328 int eo = pncmesh->GetEdgeNCOrientation(id);
3329
3330 if (varOrder)
3331 {
3332 int order = -1;
3333 bool found = false;
3334 while (!found)
3335 {
3336 order = fes->GetEdgeOrder(id.index, var);
3337 if (order == -1)
3338 {
3339 // Not found
3340 var = -1;
3341 break;
3342 }
3343 if (order == order_i)
3344 {
3345 found = true;
3346 }
3347 else
3348 {
3349 var++;
3350 }
3351 }
3352
3353 if (order < 0)
3354 {
3355 // Read the stream for this row and ignore it. This is an
3356 // invalid row for an intermediate order or ghost edge not
3357 // used on this rank.
3358 RowInfo tmprow(1, 0, 0, 0); // Fake, unused row, just to read stream.
3359 tmprow.row.read(stream, 1.0);
3360 gi++;
3361 continue;
3362 }
3363 ind = fec->GetDofOrdering(Geometry::SEGMENT, order, eo);
3364 }
3365 else
3366 {
3368 }
3369 }
3370 else if (ent == 2)
3371 {
3372 geom = pncmesh->GetFaceGeometry(id.index);
3373 const int fo = pncmesh->GetFaceOrientation(id.index);
3374 if (varOrder)
3375 {
3376 MFEM_ASSERT(geom == Geometry::SQUARE,
3377 "Only quadrilateral faces are supported in "
3378 "variable-order spaces");
3379
3380 int order = -1;
3381 bool found = false;
3382 while (!found)
3383 {
3384 order = fes->GetFaceOrder(id.index, var);
3385 if (order == -1)
3386 {
3387 // Not found
3388 var = -1;
3389 break;
3390 }
3391 if (order == order_i)
3392 {
3393 found = true;
3394 }
3395 else
3396 {
3397 var++;
3398 }
3399 }
3400
3401 if (order < 0)
3402 {
3403 // Read the stream for this row and ignore it. This is an
3404 // invalid row for an intermediate order or ghost face not
3405 // used on this rank.
3406 RowInfo tmprow(1, 0, 0, 0); // Fake, unused row, just to read stream.
3407 tmprow.row.read(stream, 1.0);
3408 gi++;
3409 continue;
3410 }
3411
3412 if (order >= 0)
3413 {
3414 ind = fec->GetDofOrdering(geom, order, fo);
3415 }
3416 }
3417 else
3418 {
3419 ind = fec->DofOrderForOrientation(geom, fo);
3420 }
3421 }
3422 // Tri faces with second order basis have dofs that must be processed
3423 // in pairs, as the doftransformation is not diagonal.
3424 const bool process_dof_pairs = (ent == 2 &&
3426 && !Geometry::IsTensorProduct(geom));
3427
3428#ifdef MFEM_DEBUG_PMATRIX
3429 mfem::out << "Rank " << pncmesh->MyRank << " receiving from " << rank
3430 << ": ent " << ent << ", index " << id.index
3431 << ", edof " << edof << " (id " << id.element << "/"
3432 << int(id.local) << ")" << std::endl;
3433#endif
3434
3435 // If edof arrived with a negative index, flip it, and the scaling.
3436 real_t s = (edof < 0) ? -1.0 : 1.0;
3437 edof = UnsignIndex(edof);
3438 if (ind && (edof = ind[edof]) < 0)
3439 {
3440 edof = FlipIndexSign(edof);
3441 s *= -1.0;
3442 }
3443
3444 edof += osvar;
3445
3446 // Create a row for this entity, recording the index of the mesh
3447 // element
3448 rows.emplace_back(ent, id.index, edof, group_ids[gi++], var);
3449 rows.back().row.read(stream, s);
3450
3451#ifdef MFEM_DEBUG_PMATRIX
3452 mfem::out << "Rank " << pncmesh->MyRank << " receiving from " << rank
3453 << ": ent " << rows.back().entity << ", index "
3454 << rows.back().index << ", edof " << rows.back().edof
3455 << std::endl;
3456#endif
3457
3458 if (process_dof_pairs)
3459 {
3460 // ND face dofs need to be processed together, as the transformation
3461 // is given by a 2x2 matrix, so we manually apply an extra increment
3462 // to the loop counter and add in a new row. Once these rows are
3463 // placed, they represent the Identity transformation. To map across
3464 // the processor boundary, we also need to apply a Primal
3465 // Transformation (see doftrans.hpp) to a notional "global dof"
3466 // orientation. For simplicity we perform the action of these 2x2
3467 // matrices manually using the AddRow capability, followed by a
3468 // Collapse.
3469
3470 // To perform the operations, we add and subtract initial versions
3471 // of the rows, that represent [1 0; 0 1] in row major notation. The
3472 // first row represents the 1 at (0,0) in [1 0; 0 1] The second row
3473 // represents the 1 at (1,1) in [1 0; 0 1]
3474
3475 // We can safely bind this reference as rows was reserved above so
3476 // there is no hidden copying that could result in a dangling
3477 // reference.
3478 auto &first_row = rows.back().row;
3479 // This is the first "fundamental unit" used in the transformation.
3480 const auto initial_first_row = first_row;
3481 // Extract the next dof too, and apply any dof order transformation
3482 // expected.
3483 const MeshId &next_id = ids[++i];
3484 const int fo = pncmesh->GetFaceOrientation(next_id.index);
3485 ind = fec->DofOrderForOrientation(geom, fo);
3486 edof = bin_io::read<int>(stream);
3487 order_i = bin_io::read<int>(stream);
3488
3489 // If edof arrived with a negative index, flip it, and the scaling.
3490 s = (edof < 0) ? -1.0 : 1.0;
3491 edof = UnsignIndex(edof);
3492 if (ind && (edof = ind[edof]) < 0)
3493 {
3494 edof = FlipIndexSign(edof);
3495 s *= -1.0;
3496 }
3497
3498 rows.emplace_back(ent, next_id.index, edof, group_ids[gi++]);
3499 rows.back().row.read(stream, s);
3500 auto &second_row = rows.back().row;
3501
3502 // This is the second "fundamental unit" used in the transformation.
3503 const auto initial_second_row = second_row;
3504
3505 // Transform the received dofs by the primal transform. This is
3506 // because within mfem as a face is visited its orientation is
3507 // assigned to match the element that visited it first. Thus on
3508 // processor boundaries, the transform will always be identity going
3509 // into the element. However, the sending processor also thought the
3510 // face orientation was zero, so it has sent the information in a
3511 // different orientation. To map onto the local orientation
3512 // definition, extract the orientation of the sending rank (the
3513 // lower rank face defines the orientation fo), then apply the
3514 // transform to the dependencies. The action of this transform on
3515 // the dependencies is performed by adding scaled versions of the
3516 // original two rows (which by the mfem assumption of face
3517 // orientation, represent the identity transform).
3518 const real_t *T =
3520
3521 MFEM_ASSERT(fo != 2 &&
3522 fo != 4, "This code branch is ambiguous for face orientations 2 and 4."
3523 " Please report this mesh for further testing.\n");
3524
3525 first_row.AddRow(initial_first_row, T[0] - 1.0); // (0,0)
3526 first_row.AddRow(initial_second_row, T[2]); // (0,1)
3527 second_row.AddRow(initial_first_row, T[1]); // (1,0)
3528 second_row.AddRow(initial_second_row, T[3] - 1.0); // (1,1)
3529
3530 first_row.Collapse();
3531 second_row.Collapse();
3532 }
3533 }
3534 }
3535}
3536
3537void
3538ParFiniteElementSpace::ScheduleSendRow(const PMatrixRow &row, int dof,
3539 GroupId group_id,
3540 NeighborRowMessage::Map &send_msg) const
3541{
3542 int ent, idx, edof, order;
3543 UnpackDof(dof, ent, idx, edof, order);
3544
3545 for (const auto &rank : pncmesh->GetGroup(group_id))
3546 {
3547 if (rank != MyRank)
3548 {
3549 NeighborRowMessage &msg = send_msg[rank];
3550 msg.SetSpace(this);
3551 msg.AddRow(ent, idx, edof, group_id, row, order);
3552 msg.SetNCMesh(pncmesh);
3553 msg.SetFEC(fec);
3554#ifdef MFEM_PMATRIX_STATS
3555 n_rows_sent++;
3556#endif
3557 }
3558 }
3559}
3560
3561void ParFiniteElementSpace::ForwardRow(const PMatrixRow &row, int dof,
3562 GroupId group_sent_id, GroupId group_id,
3563 NeighborRowMessage::Map &send_msg) const
3564{
3565 int ent, idx, edof, order;
3566 UnpackDof(dof, ent, idx, edof, order);
3567
3568 const ParNCMesh::CommGroup &group = pncmesh->GetGroup(group_id);
3569 for (unsigned i = 0; i < group.size(); i++)
3570 {
3571 int rank = group[i];
3572 if (rank != MyRank && !pncmesh->GroupContains(group_sent_id, rank))
3573 {
3574 NeighborRowMessage &msg = send_msg[rank];
3575 GroupId invalid = -1; // to prevent forwarding again
3576 msg.SetSpace(this);
3577 msg.AddRow(ent, idx, edof, invalid, row, order);
3578 msg.SetNCMesh(pncmesh);
3579 msg.SetFEC(fec);
3580#ifdef MFEM_PMATRIX_STATS
3581 n_rows_fwd++;
3582#endif
3583#ifdef MFEM_DEBUG_PMATRIX
3584 mfem::out << "Rank " << pncmesh->GetMyRank() << " forwarding to "
3585 << rank << ": ent " << ent << ", index" << idx
3586 << ", edof " << edof << std::endl;
3587#endif
3588 }
3589 }
3590}
3591
3592#ifdef MFEM_DEBUG_PMATRIX
3593void ParFiniteElementSpace
3594::DebugDumpDOFs(std::ostream &os,
3595 const SparseMatrix &deps,
3596 const Array<GroupId> &dof_group,
3597 const Array<GroupId> &dof_owner,
3598 const Array<bool> &finalized) const
3599{
3600 for (int i = 0; i < dof_group.Size(); i++)
3601 {
3602 os << i << ": ";
3603 if (i < (nvdofs + nedofs + nfdofs) || i >= ndofs)
3604 {
3605 int ent, idx, edof;
3606 UnpackDof(i, ent, idx, edof);
3607
3608 os << edof << " @ ";
3609 if (i > ndofs) { os << "ghost "; }
3610 switch (ent)
3611 {
3612 case 0: os << "vertex "; break;
3613 case 1: os << "edge "; break;
3614 default: os << "face "; break;
3615 }
3616 os << idx << "; ";
3617
3618 if (i < deps.Height() && deps.RowSize(i))
3619 {
3620 os << "depends on ";
3621 for (int j = 0; j < deps.RowSize(i); j++)
3622 {
3623 os << deps.GetRowColumns(i)[j] << " ("
3624 << deps.GetRowEntries(i)[j] << ")";
3625 if (j < deps.RowSize(i)-1) { os << ", "; }
3626 }
3627 os << "; ";
3628 }
3629 else
3630 {
3631 os << "no deps; ";
3632 }
3633
3634 os << "group " << dof_group[i] << " (";
3635 const ParNCMesh::CommGroup &g = pncmesh->GetGroup(dof_group[i]);
3636 for (unsigned j = 0; j < g.size(); j++)
3637 {
3638 if (j) { os << ", "; }
3639 os << g[j];
3640 }
3641
3642 os << "), owner " << dof_owner[i] << " (rank "
3643 << pncmesh->GetGroup(dof_owner[i])[0] << "); "
3644 << (finalized[i] ? "finalized" : "NOT finalized");
3645 }
3646 else
3647 {
3648 os << "internal";
3649 }
3650 os << "\n";
3651 }
3652}
3653#endif
3654
3655void ParFiniteElementSpace::ScheduleSendOrder(
3656 int ent, int idx, int order, GroupId group_id,
3657 NeighborOrderMessage::Map &send_msg) const
3658{
3659 for (const auto &rank : pncmesh->GetGroup(group_id))
3660 {
3661 if (rank != MyRank)
3662 {
3663 NeighborOrderMessage &msg = send_msg[rank];
3664 msg.AddOrder(ent, idx, order, group_id);
3665 msg.SetNCMesh(pncmesh);
3666 }
3667 }
3668}
3669
3671 const std::set<int> &edges, const std::set<int> &faces,
3672 Array<VarOrderBits> &edge_orders, Array<VarOrderBits> &face_orders) const
3673{
3674 // Initialize `changed` flag, based on serial changes to edges and faces.
3675 bool changed = edges.size() > 0 || faces.size() > 0;
3676
3677 // If no rank has changes, exit.
3678 int orders_changed = (int) changed;
3679 MPI_Allreduce(MPI_IN_PLACE, &orders_changed, 1, MPI_INT, MPI_MAX, MyComm);
3680 if (orders_changed == 0)
3681 {
3682 return true;
3683 }
3684
3685 NeighborOrderMessage::Map send_msg;
3686
3687 // Schedule messages
3688 for (int entity = 1; entity <= 2; ++entity)
3689 {
3690 const std::set<int> &indices = entity == 1 ? edges : faces;
3691 const Array<VarOrderBits> &orders = entity == 1 ? edge_orders : face_orders;
3692 for (auto idx : indices)
3693 {
3694 GroupId group = pncmesh->GetEntityGroupId(entity, idx);
3695
3696 if (group != 0)
3697 {
3698 ScheduleSendOrder(entity, idx, MinOrder(orders[idx]),
3699 group, send_msg);
3700 }
3701 }
3702 }
3703
3704 // Send messages
3705 NeighborOrderMessage::IsendAll(send_msg, MyComm);
3706
3707 MPI_Barrier(MyComm); // This barrier is necessary for hp-refinement
3708
3709 NeighborOrderMessage recv_msg;
3710 recv_msg.SetNCMesh(pncmesh);
3711
3712 // Check for and receive incoming messages
3713 int rank, size;
3714 while (NeighborOrderMessage::IProbe(rank, size, MyComm))
3715 {
3716 // Note that Recv calls Decode(rank), setting msgs in recv_msg.
3717 recv_msg.Recv(rank, size, MyComm);
3718
3719 for (const auto &ri : recv_msg.GetMsgs())
3720 {
3721 const VarOrderBits mask = (VarOrderBits(1) << ri.order);
3722 if (ri.entity == 1)
3723 {
3724 const VarOrderBits initOrders = edge_orders[ri.index];
3725 edge_orders[ri.index] |= mask;
3726 if (edge_orders[ri.index] != initOrders)
3727 {
3728 changed = true;
3729 }
3730 }
3731 else if (ri.entity == 2)
3732 {
3733 const VarOrderBits initOrders = face_orders[ri.index];
3734 face_orders[ri.index] |= mask;
3735 if (face_orders[ri.index] != initOrders)
3736 {
3737 changed = true;
3738 }
3739 }
3740 else
3741 {
3742 MFEM_ABORT("Invalid entity type");
3743 }
3744 }
3745 }
3746
3747 // Clean up possible remaining messages in the queue to avoid receiving them
3748 // erroneously in the next run
3749 while (NeighborOrderMessage::IProbe(rank, size, MyComm))
3750 {
3751 recv_msg.RecvDrop(rank, size, MyComm);
3752 }
3753
3754 // Make sure we can discard all send buffers
3756
3757 orders_changed = (int) changed;
3758 MPI_Allreduce(MPI_IN_PLACE, &orders_changed, 1, MPI_INT, MPI_MAX, MyComm);
3759 return (orders_changed == 0);
3760}
3761
3763 int entity, Array<bool> & intermediate) const
3764{
3765 if (!IsVariableOrder()) { return; }
3766
3767 MFEM_VERIFY(intermediate.Size() == ndofs, "");
3768
3769 const int os = entity == 1 ? nvdofs : nvdofs + nedofs;
3770
3771 const int n = entity == 1 ? pmesh->GetNEdges() : pmesh->GetNFaces();
3772 for (int e=0; e<n; ++e)
3773 {
3774 const int nvar = GetNVariants(entity, e);
3775 for (int var = 1; var < nvar - 1; ++var) // Intermediate variants
3776 {
3777 Array<int> dofs;
3778 GetEntityDofs(entity, e, dofs, Geometry::INVALID, // dummy geom
3779 var);
3780 for (auto dof : dofs)
3781 {
3782 if (dof >= os) // Skip dofs for vertices (and edges in face case)
3783 {
3784 intermediate[dof] = true;
3785 }
3786 }
3787 }
3788 }
3789}
3790
3791void ParFiniteElementSpace::SetVarDofMap(const Table & dofs,
3793{
3794 if (dofs.Size() < 1)
3795 {
3796 dmap.SetSize(0);
3797 return;
3798 }
3799
3800 MFEM_ASSERT(dofs.RowSize(dofs.Size() - 1) == 1, "");
3801 const int* rowLast = dofs.GetRow(dofs.Size() - 1);
3802 const int ndofs = rowLast[0];
3803
3804 dmap.SetSize(ndofs);
3805
3806 for (int r = 0; r < dofs.Size() - 1; ++r)
3807 {
3808 const int* row = dofs.GetRow(r);
3809 const int* row1 = dofs.GetRow(r+1);
3810
3811 for (int d=row[0]; d<row1[0]; ++d) // d = dof
3812 {
3813 dmap[d].index = r; // row index
3814 dmap[d].edof = d - row[0]; // entity index
3815 }
3816 }
3817}
3818
3819void ParFiniteElementSpace::SetTDOF2LDOFinfo(int ntdofs, int vdim_factor,
3820 int dof_stride, int allnedofs)
3821{
3822 if (!IsVariableOrder()) { return; }
3823
3824 tdof2ldof.SetSize(ntdofs);
3825 for (int i=0; i<ntdofs; ++i)
3826 {
3827 tdof2ldof[i].set = false;
3828 }
3829
3830 // All T-dofs are on conforming and master edges and faces, and we only need
3831 // data for such entities shared with other MPI ranks.
3832
3833 for (int entity = 1; entity < pmesh->Dimension(); entity++)
3834 {
3835 const Table &ent_dofs = (entity == 1) ? var_edge_dofs : var_face_dofs;
3836 const int num_ent = (entity == 1) ? pmesh->GetNEdges() :
3837 pmesh->GetNFaces();
3838 MFEM_ASSERT(ent_dofs.Size() >= num_ent+1, "");
3839
3840 for (int idx = 0; idx < num_ent; idx++)
3841 {
3842 if (ent_dofs.RowSize(idx) == 0) { continue; }
3843
3844 Geometry::Type geom =
3845 (entity == 1) ? Geometry::SEGMENT : pmesh->GetFaceGeometry(idx);
3846
3847 // Loop over all DOFs to find T-dofs, since some T-dofs may not be
3848 // contained in the L-dofs.
3849
3850 // Get the lowest order variant DOFs and FE
3851 Array<int> dofs;
3852 const int order0 = GetEntityDofs(entity, idx, dofs, geom, 0);
3853
3854 int numVert = 2; // Edge case
3855 if (entity == 2) // Face case
3856 {
3857 Array<int> verts;
3858 pmesh->GetFaceVertices(idx, verts);
3859 numVert = verts.Size();
3860 MFEM_VERIFY(numVert == 4, "Only quadrilateral faces are supported");
3861 }
3862
3863 // Interior DOFs start at index idof0
3864 const int idof0 = GetNumBorderDofs(geom, order0);
3865 const int minOrder = entity == 1 ? edge_min_nghb_order[idx] :
3867
3868 constexpr int vd = 0; // First vector dimension only
3869 for (int i=idof0; i<dofs.Size(); ++i)
3870 {
3871 const int dof_i = dofs[i];
3872 const int vdof_i = dof_i*vdim_factor + vd*dof_stride;
3873 const int tdof = ldof_ltdof[vdof_i];
3874 if (tdof < 0) { continue; }
3875
3876 MFEM_ASSERT(!tdof2ldof[tdof].set, "");
3877
3878 tdof2ldof[tdof].set = true;
3879 tdof2ldof[tdof].minOrder = minOrder;
3880 tdof2ldof[tdof].isEdge = (entity == 1);
3881 tdof2ldof[tdof].idx = idx;
3882 }
3883 }
3884 }
3885}
3886
3887void ParFiniteElementSpace
3888::SetRestrictionMatrixEdgesFaces(int vdim_factor, int dof_stride,
3889 int tdof_stride, const Array<int> &dof_tdof,
3890 const Array<HYPRE_BigInt> &dof_offs)
3891{
3892 MFEM_VERIFY(IsVariableOrder(), "");
3893
3894 const int ntdofs = tdof2ldof.Size();
3895 MFEM_VERIFY(vdim * ntdofs == R->NumRows(), "");
3896
3897 int prevEntity = -1;
3898 int prevIndex = -1;
3899 int tdi = -1;
3900 int idof0 = -1;
3901 Array<int> ldofs, tdofs;
3902 DenseMatrix I;
3903
3904 for (int tdof=0; tdof<ntdofs; ++tdof)
3905 {
3906 if (!tdof2ldof[tdof].set) { continue; } // Skip vertex and element T-dofs
3907
3908 const int minOrder = tdof2ldof[tdof].minOrder;
3909 const bool edge = tdof2ldof[tdof].isEdge;
3910 const int index = tdof2ldof[tdof].idx;
3911 const int entity = edge ? 1 : 2;
3912 MFEM_ASSERT(!pncmesh->IsGhost(entity, index),
3913 "True DOFs are not defined on ghost entities");
3914
3915 if (entity != prevEntity || index != prevIndex)
3916 {
3917 tdi = 0;
3918 }
3919 else
3920 {
3921 tdi++;
3922 }
3923
3924 prevEntity = entity;
3925 prevIndex = index;
3926
3927 if (tdi == 0) // Update I for a new entity
3928 {
3929 // Only square faces are supported currently
3931
3932 const FiniteElement *feT = fec->FiniteElementForGeometry(geom);
3933 const FiniteElement *feL = fec->FiniteElementForGeometry(geom);
3934
3935 int tdofOrder = -1;
3936 if (entity == 1)
3937 {
3938 tdofOrder = GetEdgeOrder(index, 0);
3939 GetEdgeDofs(index, tdofs, 0);
3940 for (int var=0; ; ++var)
3941 {
3942 const int order_var = GetEdgeOrder(index, var);
3943 if (order_var == minOrder)
3944 {
3945 GetEdgeDofs(index, ldofs, var);
3946 break;
3947 }
3948 }
3949 }
3950 else // entity == 2
3951 {
3952 tdofOrder = GetFaceOrder(index, 0);
3953 GetFaceDofs(index, tdofs, 0);
3954 for (int var=0; ; ++var)
3955 {
3956 const int order_var = GetFaceOrder(index, var);
3957 if (order_var == minOrder)
3958 {
3959 GetFaceDofs(index, ldofs, var);
3960 break;
3961 }
3962 }
3963 }
3964
3965 MFEM_VERIFY(tdofs.Size() > 0 && ldofs.Size() > 0, "");
3966
3967 // Interior DOFs start at index idof0
3968 idof0 = GetNumBorderDofs(geom, tdofOrder);
3969
3970 feT = fec->GetFE(geom, tdofOrder);
3971 feL = fec->GetFE(geom, minOrder);
3972
3973 MFEM_VERIFY(feT && feL, "");
3974
3975 IsoparametricTransformation T;
3976
3977 switch (geom)
3978 {
3979 case Geometry::SQUARE: T.SetFE(&QuadrilateralFE); break;
3980 case Geometry::SEGMENT: T.SetFE(&SegmentFE); break;
3981 default: MFEM_ABORT("unsupported geometry");
3982 }
3983
3984 // Interpolate T-dofs of order tdofOrder from L-dofs of order minOrder
3985 T.SetIdentityTransformation(geom);
3986 feT->GetTransferMatrix(*feL, T, I);
3987 }
3988
3989 for (int ldi=0; ldi<ldofs.Size(); ++ldi)
3990 {
3991 const real_t value = I(tdi + idof0, ldi);
3992 if (std::abs(value) > 1e-12)
3993 {
3994 const int ldof = all2local[ldofs[ldi]];
3995 for (int vd = 0; vd < vdim; vd++)
3996 {
3997 const int vdof = ldof*vdim_factor + vd*dof_stride;
3998 const int vtdof = tdof*vdim_factor + vd*tdof_stride;
3999 R->Add(vtdof, vdof, value);
4000 }
4001 }
4002 }
4003 }
4004}
4005
4006int ParFiniteElementSpace
4007::BuildParallelConformingInterpolation(HypreParMatrix **P_, SparseMatrix **R_,
4008 Array<HYPRE_BigInt> &dof_offs,
4009 Array<HYPRE_BigInt> &tdof_offs,
4010 Array<int> *dof_tdof,
4011 bool partial)
4012{
4013 const bool dg = (nvdofs == 0 && nedofs == 0 && nfdofs == 0);
4014 const bool H1var = IsVariableOrderH1();
4015
4016#ifdef MFEM_PMATRIX_STATS
4017 n_msgs_sent = n_msgs_recv = 0;
4018 n_rows_sent = n_rows_recv = n_rows_fwd = 0;
4019#endif
4020
4021 // *** STEP 1: build master-slave dependency lists ***
4022
4023 const int total_dofs = ndofs + ngdofs;
4024 SparseMatrix deps(ndofs, total_dofs);
4025
4026 if (!dg && !partial)
4027 {
4028 VariableOrderMinimumRule(deps);
4029
4030 Array<int> master_dofs, slave_dofs;
4031
4032 // loop through *all* master edges/faces, constrain their slaves
4033 for (int entity = 0; entity <= 2; entity++)
4034 {
4035 const NCMesh::NCList &list = pncmesh->GetNCList(entity);
4036 if (list.masters.Size() == 0) { continue; }
4037
4038 IsoparametricTransformation T;
4039 DenseMatrix I;
4040
4041 // process masters that we own or that affect our edges/faces
4042 for (const auto &mf : list.masters)
4043 {
4044 // get master DOFs
4045 if (entity == 1 && skip_edge.Size() > 0)
4046 {
4047 if (skip_edge[mf.index])
4048 {
4049 continue;
4050 }
4051 }
4052 else if (entity == 2 && skip_face.Size() > 0)
4053 {
4054 if (skip_face[mf.index])
4055 {
4056 continue;
4057 }
4058 }
4059
4060 if (pncmesh->IsGhost(entity, mf.index))
4061 {
4062 GetGhostDofs(entity, mf, master_dofs, 0);
4063 }
4064 else
4065 {
4066 GetEntityDofs(entity, mf.index, master_dofs, mf.Geom(), 0);
4067 }
4068
4069 if (master_dofs.Size() == 0) { continue; }
4070
4071 const FiniteElement *fe = fec->FiniteElementForGeometry(mf.Geom());
4072
4073 if (IsVariableOrder())
4074 {
4075 int mfOrder = -1;
4076 if (entity == 1) { mfOrder = GetEdgeOrder(mf.index, 0); }
4077 else if (entity == 2) { mfOrder = GetFaceOrder(mf.index, 0); }
4078
4079 if (entity != 0) { fe = fec->GetFE(mf.Geom(), mfOrder); }
4080 }
4081
4082 if (fe == nullptr) { continue; }
4083
4084 switch (mf.Geom())
4085 {
4086 case Geometry::SQUARE: T.SetFE(&QuadrilateralFE); break;
4087 case Geometry::TRIANGLE: T.SetFE(&TriangleFE); break;
4088 case Geometry::SEGMENT: T.SetFE(&SegmentFE); break;
4089 default: MFEM_ABORT("unsupported geometry");
4090 }
4091
4092 // constrain slaves that exist in our mesh
4093 for (int si = mf.slaves_begin; si < mf.slaves_end; si++)
4094 {
4095 const NCMesh::Slave &sf = list.slaves[si];
4096 if (pncmesh->IsGhost(entity, sf.index)) { continue; }
4097
4098 constexpr int variant = 0;
4099 const int q = GetEntityDofs(entity, sf.index, slave_dofs, mf.Geom(), variant);
4100 if (q < 0) { break; }
4101
4102 list.OrientedPointMatrix(sf, T.GetPointMat());
4103
4104 const auto *slave_fe = fec->GetFE(mf.Geom(), q);
4105 slave_fe->GetTransferMatrix(*fe, T, I);
4106
4107 // make each slave DOF dependent on all master DOFs
4108 AddDependencies(deps, master_dofs, slave_dofs, I);
4109 }
4110 }
4111 }
4112
4113 deps.Finalize();
4114 }
4115
4116 // *** STEP 2: initialize group and owner ID for each DOF ***
4117
4118 Array<GroupId> dof_group(total_dofs);
4119 Array<GroupId> dof_owner(total_dofs);
4120 dof_group = 0;
4121 dof_owner = 0;
4122
4123 if (!dg)
4124 {
4125 Array<int> dofs;
4126
4127 auto initialize_group_and_owner = [&dof_group, &dof_owner, &dofs,
4128 this](int entity, const MeshId &id)
4129 {
4130 if (id.index < 0) { return; }
4131
4132 GroupId owner = pncmesh->GetEntityOwnerId(entity, id.index);
4133 GroupId group = pncmesh->GetEntityGroupId(entity, id.index);
4134
4135 GetBareDofs(entity, id.index, dofs);
4136
4137 for (auto dof : dofs)
4138 {
4139 dof_owner[dof] = owner;
4140 dof_group[dof] = group;
4141 }
4142 };
4143
4144 // initialize dof_group[], dof_owner[] in sequence
4145 for (int entity : {0,1,2})
4146 {
4147 for (const auto &id : pncmesh->GetNCList(entity).conforming)
4148 {
4149 initialize_group_and_owner(entity, id);
4150 }
4151 for (const auto &id : pncmesh->GetNCList(entity).masters)
4152 {
4153 initialize_group_and_owner(entity, id);
4154 }
4155 for (const auto &id : pncmesh->GetNCList(entity).slaves)
4156 {
4157 initialize_group_and_owner(entity, id);
4158 }
4159 }
4160 }
4161
4162 // *** STEP 3: count true DOFs and calculate P row/column partitions ***
4163
4164 Array<bool> finalized(total_dofs);
4165 finalized = false;
4166
4167 // DOFs that stayed independent and are ours are true DOFs
4168 int num_true_dofs = 0;
4169 for (int i = 0; i < ndofs; ++i)
4170 {
4171 if (dof_owner[i] == 0 && deps.RowSize(i) == 0)
4172 {
4173 ++num_true_dofs;
4174 finalized[i] = true;
4175 }
4176 }
4177
4178#ifdef MFEM_DEBUG_PMATRIX
4179 // Helper for dumping diagnostics on one dof
4180 auto dof_diagnostics = [&](int dof, bool print_diagnostic)
4181 {
4182 const auto &comm_group = pncmesh->GetGroup(dof_group[dof]);
4183 std::stringstream msg;
4184 msg << std::boolalpha;
4185 msg << "R" << Mpi::WorldRank() << " dof " << dof
4186 << " owner_rank " << pncmesh->GetGroup(dof_owner[dof])[0] << " CommGroup {";
4187 for (const auto &x : comm_group)
4188 {
4189 msg << x << ' ';
4190 }
4191 msg << "} finalized " << finalized[dof];
4192
4193 Array<int> cols;
4194 if (dof < ndofs)
4195 {
4196 Vector row;
4197 deps.GetRow(dof, cols, row);
4198 msg << " deps cols {";
4199 for (const auto &x : cols)
4200 {
4201 msg << x << ' ';
4202 }
4203 msg << '}';
4204 }
4205
4206 int entity, index, edof;
4207 UnpackDof(dof, entity, index, edof);
4208 msg << " entity " << entity << " index " << index << " edof " << edof;
4209 return msg.str();
4210 };
4211#endif
4212
4213 // calculate global offsets
4214 {
4215 HYPRE_BigInt loc_sizes[2] = { ndofs*vdim, num_true_dofs*vdim };
4216 Array<HYPRE_BigInt>* offsets[2] = { &dof_offs, &tdof_offs };
4217 pmesh->GenerateOffsets(2, loc_sizes, offsets); // calls MPI_Scan, MPI_Bcast
4218 }
4219
4220 HYPRE_BigInt my_tdof_offset =
4221 tdof_offs[HYPRE_AssumedPartitionCheck() ? 0 : MyRank];
4222
4223 if (R_ && !H1var)
4224 {
4225 // initialize the restriction matrix (also parallel but block-diagonal)
4226 *R_ = new SparseMatrix(num_true_dofs*vdim, ndofs*vdim);
4227 }
4228 if (dof_tdof)
4229 {
4230 dof_tdof->SetSize(ndofs*vdim);
4231 *dof_tdof = -1;
4232 }
4233
4234 std::vector<PMatrixRow> pmatrix(total_dofs);
4235
4236 const bool bynodes = (ordering == Ordering::byNODES);
4237 const int vdim_factor = bynodes ? 1 : vdim;
4238 const int dof_stride = bynodes ? ndofs : 1;
4239 const int tdof_stride = bynodes ? num_true_dofs : 1;
4240
4241 // big container for all messages we send (the list is for iterations)
4242 std::list<NeighborRowMessage::Map> send_msg;
4243 send_msg.emplace_back();
4244
4245 // put identity in P and R for true DOFs, set ldof_ltdof (dof_tdof)
4246 for (int dof = 0, tdof = 0; dof < ndofs; dof++)
4247 {
4248 if (finalized[dof])
4249 {
4250 pmatrix[dof].elems.emplace_back(
4251 my_tdof_offset + vdim_factor*tdof, tdof_stride, 1.);
4252
4253 // prepare messages to neighbors with identity rows
4254 if (dof_group[dof] != 0)
4255 {
4256 MFEM_VERIFY(!send_msg.empty(), "");
4257 ScheduleSendRow(pmatrix[dof], dof, dof_group[dof], send_msg.back());
4258 }
4259
4260 for (int vd = 0; vd < vdim; vd++)
4261 {
4262 const int vdof = dof*vdim_factor + vd*dof_stride;
4263 const int vtdof = tdof*vdim_factor + vd*tdof_stride;
4264
4265 if (R_ && !H1var) { (*R_)->Add(vtdof, vdof, 1.0); }
4266 if (dof_tdof) { (*dof_tdof)[vdof] = vtdof; }
4267 }
4268 ++tdof;
4269 }
4270 }
4271
4272 // send identity rows
4273 MFEM_VERIFY(!send_msg.empty(), "");
4274 NeighborRowMessage::IsendAll(send_msg.back(), MyComm);
4275#ifdef MFEM_PMATRIX_STATS
4276 n_msgs_sent += send_msg.back().size();
4277#endif
4278
4279 if (R_ && !H1var) { (*R_)->Finalize(); }
4280
4281 // *** STEP 4: main loop ***
4282
4283 // a single instance (recv_msg) is reused for all incoming messages
4284 NeighborRowMessage recv_msg;
4285 recv_msg.SetNCMesh(pncmesh);
4286 recv_msg.SetSpace(this);
4287 recv_msg.SetFEC(fec);
4288
4289 int num_finalized = num_true_dofs;
4290 PMatrixRow buffer;
4291 buffer.elems.reserve(1024);
4292
4293 // The lowest order may be finalized by receiving messages, but the
4294 // intermediate orders not owned may have ghost DOFs which may be dependencies
4295 // for other DOFs. Thus we must allow finalizing intermediate DOFs, when they
4296 // are ghosts.
4297 Array<bool> intermediate(ndofs);
4298 intermediate = false;
4299
4300 MarkIntermediateEntityDofs(1, intermediate);
4301 MarkIntermediateEntityDofs(2, intermediate);
4302
4303 while (num_finalized < ndofs)
4304 {
4305 // prepare a new round of send buffers
4306 MFEM_VERIFY(!send_msg.empty(), "");
4307 if (send_msg.back().size())
4308 {
4309 send_msg.emplace_back();
4310 }
4311
4312 // check for incoming messages, receive PMatrixRows
4313 int rank, size;
4314 while (NeighborRowMessage::IProbe(rank, size, MyComm))
4315 {
4316 // Note that Recv calls Decode(rank), setting rows in recv_msg.
4317 recv_msg.Recv(rank, size, MyComm);
4318
4319#ifdef MFEM_PMATRIX_STATS
4320 n_msgs_recv++;
4321 n_rows_recv += recv_msg.GetRows().size();
4322#endif
4323
4324 for (const auto &ri : recv_msg.GetRows())
4325 {
4326 const int dof = PackDof(ri.entity, ri.index, ri.edof, ri.var);
4327 pmatrix[dof] = ri.row;
4328
4329 if (dof < ndofs && !finalized[dof]) { ++num_finalized; }
4330 finalized[dof] = true;
4331
4332 if (ri.group >= 0 && dof_group[dof] != ri.group)
4333 {
4334 // the sender didn't see the complete group, forward the message
4335 MFEM_VERIFY(!send_msg.empty(), "");
4336 ForwardRow(ri.row, dof, ri.group, dof_group[dof], send_msg.back());
4337 }
4338 }
4339 }
4340
4341 // finalize all rows that can currently be finalized
4342 bool done = false;
4343 while (!done)
4344 {
4345 done = true;
4346 for (int dof = 0; dof < ndofs; dof++)
4347 {
4348 const bool owned = (dof_owner[dof] == 0);
4349 if (!finalized[dof]
4350 && (owned || intermediate[dof])
4351 && DofFinalizable(dof, finalized, deps))
4352 {
4353 const int* dep_col = deps.GetRowColumns(dof);
4354 const real_t* dep_coef = deps.GetRowEntries(dof);
4355
4356 // form linear combination of rows
4357 buffer.elems.clear();
4358 for (int j = 0; j < deps.RowSize(dof); j++)
4359 {
4360 buffer.AddRow(pmatrix[dep_col[j]], dep_coef[j]);
4361 }
4362 buffer.Collapse();
4363 pmatrix[dof] = buffer;
4364
4365 finalized[dof] = true;
4366 ++num_finalized;
4367 done = false;
4368
4369 // send row to neighbors who need it
4370 const bool shared = (dof_group[dof] != 0);
4371 if (shared)
4372 {
4373 MFEM_VERIFY(!send_msg.empty(), "");
4374 ScheduleSendRow(pmatrix[dof], dof, dof_group[dof],
4375 send_msg.back());
4376 }
4377 }
4378 }
4379 }
4380
4381#ifdef MFEM_DEBUG_PMATRIX
4382 static int dump = 0;
4383 if (dump < 10)
4384 {
4385 char fname[100];
4386 snprintf(fname, 100, "dofs%02d.txt", MyRank);
4387 std::ofstream f(fname);
4388 DebugDumpDOFs(f, deps, dof_group, dof_owner, finalized);
4389 dump++;
4390 }
4391#endif
4392
4393 // send current batch of messages
4394 MFEM_VERIFY(!send_msg.empty(), "");
4395 NeighborRowMessage::IsendAll(send_msg.back(), MyComm);
4396#ifdef MFEM_PMATRIX_STATS
4397 n_msgs_sent += send_msg.back().size();
4398#endif
4399 }
4400
4401 if (H1var)
4402 {
4403 const int allnedofs = nedofs;
4404 // TODO: isn't this necessary even in the serial FiniteElementSpace?
4405 // See FiniteElementSpace::BuildConformingInterpolation()
4406 SetVarOrderLocalDofs();
4407
4408 const int ldof_stride = bynodes ? ndofs : 1;
4409
4410 {
4411 // recalculate global offsets
4412 HYPRE_BigInt loc_sizes[1] = { ndofs*vdim };
4413 Array<HYPRE_BigInt>* offsets[1] = { &dof_offs };
4414 pmesh->GenerateOffsets(1, loc_sizes, offsets);
4415 }
4416
4417 // Extract only the rows of pmatrix corresponding to local DOFs, as given
4418 // by all2local.
4419 std::vector<PMatrixRow> pmatrix_new(ndofs);
4420 {
4421 int dofnew = -1;
4422 bool validMap = true;
4423 for (int i=0; i<all2local.Size(); ++i)
4424 {
4425 if (all2local[i] >= 0)
4426 {
4427 if (all2local[i] - dofnew != 1)
4428 {
4429 validMap = false;
4430 }
4431
4432 dofnew = all2local[i];
4433
4434 pmatrix_new[all2local[i]] = pmatrix[i];
4435 }
4436 }
4437 MFEM_VERIFY(validMap && dofnew == ndofs - 1, "");
4438 }
4439
4440 if (P_)
4441 {
4442 *P_ = MakeVDimHypreMatrix(pmatrix_new, ndofs, num_true_dofs,
4443 dof_offs, tdof_offs);
4444 }
4445
4446 // Note that tdof2ldof is set only for edges and faces containing interior
4447 // true DOFs.
4448 MFEM_VERIFY(R_ && nedofs == lnedofs, "");
4449
4450 *R_ = new SparseMatrix(num_true_dofs*vdim, ndofs*vdim);
4451
4452 // Set vertex rows
4453 // For vertices, the T-dofs are always contained in the local L-dofs.
4454 for (int dof = 0; dof < nvdofs; dof++)
4455 {
4456 for (int vd = 0; vd < vdim; vd++)
4457 {
4458 const int valldof = dof*vdim_factor + vd*dof_stride;
4459 const int vdof = dof*vdim_factor + vd*ldof_stride;
4460 const int vtdof = (*dof_tdof)[valldof];
4461 if (vtdof >= 0) { (*R_)->Add(vtdof, vdof, 1.0); }
4462 }
4463 }
4464
4465 // Set edge and face rows
4466 nedofs = allnedofs;
4467 SetTDOF2LDOFinfo(num_true_dofs, vdim_factor, dof_stride, allnedofs);
4468
4469 Array<HYPRE_BigInt> all_dof_offs(NRanks);
4470 MPI_Allgather(&dof_offs[0], 1, HYPRE_MPI_BIG_INT, all_dof_offs.GetData(),
4471 1, HYPRE_MPI_BIG_INT, MyComm);
4472
4473 SetRestrictionMatrixEdgesFaces(vdim_factor, ldof_stride, tdof_stride,
4474 *dof_tdof, all_dof_offs);
4475 nedofs = lnedofs;
4476
4477 // Set element rows
4478 // For element interiors, all DOFs are T-dofs and local L-dofs.
4479
4480 const int nalldofs = dof_tdof->Size() / vdim;
4481 MFEM_VERIFY(nalldofs * vdim == dof_tdof->Size(), "");
4482 for (int edof=0; edof<nbdofs; ++edof)
4483 {
4484 const int dof = ndofs - nbdofs + edof;
4485 const int alldof = nalldofs - nbdofs + edof;
4486 for (int vd = 0; vd < vdim; vd++)
4487 {
4488 const int valldof = alldof*vdim_factor + vd*dof_stride;
4489 const int vdof = dof*vdim_factor + vd*ldof_stride;
4490 const int vtdof = (*dof_tdof)[valldof];
4491 (*R_)->Add(vtdof, vdof, 1.0);
4492 }
4493 }
4494
4495 // Verify that all rows of R are set
4496 for (int tdof=0; tdof<num_true_dofs; ++tdof)
4497 {
4498 if ((*R_)->RowSize(tdof) == 0)
4499 {
4500 MFEM_ABORT("Empty row of R");
4501 }
4502 }
4503
4504 (*R_)->Finalize();
4505
4506 // Update dof_tdof
4507 Array<int> dof_tdof_new(ndofs * vdim);
4508 for (int i=0; i<all2local.Size(); ++i)
4509 {
4510 if (all2local[i] >= 0)
4511 {
4512 for (int vd = 0; vd < vdim; vd++)
4513 {
4514 const int vdof = i*vdim_factor + vd*dof_stride;
4515 const int ldof = all2local[i]*vdim_factor + vd*ldof_stride;
4516 dof_tdof_new[ldof] = (*dof_tdof)[vdof];
4517 }
4518 }
4519 }
4520
4521 Swap(dof_tdof_new, *dof_tdof);
4522
4523 // Save variant 0 order from ghost edges and faces, before destroying
4524 // var_edge_orders and var_face_orders.
4525
4526 MFEM_VERIFY(var_edge_dofs.Size() - 1 == pncmesh->GetNEdges() +
4527 pncmesh->GetNGhostEdges(), "");
4528 MFEM_VERIFY(var_face_dofs.Size() == -1 ||
4529 var_face_dofs.Size() - 1 == pncmesh->GetNFaces() + pncmesh->GetNGhostFaces(),
4530 "");
4531
4532 ghost_edge_orders.SetSize(pncmesh->GetNGhostEdges());
4533 ghost_face_orders.SetSize(pncmesh->GetNGhostFaces());
4534
4535 for (int i=0; i<pncmesh->GetNGhostEdges(); ++i)
4536 {
4537 ghost_edge_orders[i] = GetEdgeOrder(pncmesh->GetNEdges() + i);
4538 }
4539
4540 if (pmesh->Dimension() > 2)
4541 {
4542 for (int i=0; i<pncmesh->GetNGhostFaces(); ++i)
4543 {
4544 ghost_face_orders[i] = GetFaceOrder(pncmesh->GetNFaces() + i);
4545 }
4546 }
4547
4548 // Update var_edge_dofs and var_face_dofs
4549 var_edge_dofs.Swap(loc_var_edge_dofs);
4550 loc_var_edge_dofs.Clear();
4551
4552 var_face_dofs.Swap(loc_var_face_dofs);
4553 loc_var_face_dofs.Clear();
4554
4555 Swap(var_edge_orders, loc_var_edge_orders);
4556 Swap(var_face_orders, loc_var_face_orders);
4557
4558 loc_var_edge_orders.SetSize(0);
4559 loc_var_face_orders.SetSize(0);
4560 }
4561 else if (P_)
4562 {
4563 *P_ = MakeVDimHypreMatrix(pmatrix, ndofs, num_true_dofs,
4564 dof_offs, tdof_offs);
4565 }
4566
4567 // clean up possible remaining messages in the queue to avoid receiving
4568 // them erroneously in the next run
4569 int rank, size;
4570 while (NeighborRowMessage::IProbe(rank, size, MyComm))
4571 {
4572 recv_msg.RecvDrop(rank, size, MyComm);
4573 }
4574
4575 // make sure we can discard all send buffers
4576 for (auto &msg : send_msg)
4577 {
4579 }
4580
4581#ifdef MFEM_PMATRIX_STATS
4582 int n_rounds = send_msg.size();
4583 int glob_rounds, glob_msgs_sent, glob_msgs_recv;
4584 int glob_rows_sent, glob_rows_recv, glob_rows_fwd;
4585
4586 MPI_Reduce(&n_rounds, &glob_rounds, 1, MPI_INT, MPI_SUM, 0, MyComm);
4587 MPI_Reduce(&n_msgs_sent, &glob_msgs_sent, 1, MPI_INT, MPI_SUM, 0, MyComm);
4588 MPI_Reduce(&n_msgs_recv, &glob_msgs_recv, 1, MPI_INT, MPI_SUM, 0, MyComm);
4589 MPI_Reduce(&n_rows_sent, &glob_rows_sent, 1, MPI_INT, MPI_SUM, 0, MyComm);
4590 MPI_Reduce(&n_rows_recv, &glob_rows_recv, 1, MPI_INT, MPI_SUM, 0, MyComm);
4591 MPI_Reduce(&n_rows_fwd, &glob_rows_fwd, 1, MPI_INT, MPI_SUM, 0, MyComm);
4592
4593 if (MyRank == 0)
4594 {
4595 mfem::out << "P matrix stats (avg per rank): "
4596 << real_t(glob_rounds)/NRanks << " rounds, "
4597 << real_t(glob_msgs_sent)/NRanks << " msgs sent, "
4598 << real_t(glob_msgs_recv)/NRanks << " msgs recv, "
4599 << real_t(glob_rows_sent)/NRanks << " rows sent, "
4600 << real_t(glob_rows_recv)/NRanks << " rows recv, "
4601 << real_t(glob_rows_fwd)/NRanks << " rows forwarded."
4602 << std::endl;
4603 }
4604#endif
4605
4606 return num_true_dofs*vdim;
4607}
4608
4609HypreParMatrix* ParFiniteElementSpace
4610::MakeVDimHypreMatrix(const std::vector<PMatrixRow> &rows,
4611 int local_rows, int local_cols,
4612 Array<HYPRE_BigInt> &row_starts,
4613 Array<HYPRE_BigInt> &col_starts) const
4614{
4615 bool assumed = HYPRE_AssumedPartitionCheck();
4616 bool bynodes = (ordering == Ordering::byNODES);
4617
4618 HYPRE_BigInt first_col = col_starts[assumed ? 0 : MyRank];
4619 HYPRE_BigInt next_col = col_starts[assumed ? 1 : MyRank+1];
4620
4621 // count nonzeros in diagonal/off-diagonal parts
4622 HYPRE_Int nnz_diag = 0, nnz_offd = 0;
4623 std::map<HYPRE_BigInt, int> col_map;
4624 for (int i = 0; i < local_rows; i++)
4625 {
4626 for (unsigned j = 0; j < rows[i].elems.size(); j++)
4627 {
4628 const PMatrixElement &elem = rows[i].elems[j];
4629 HYPRE_BigInt col = elem.column;
4630 if (col >= first_col && col < next_col)
4631 {
4632 nnz_diag += vdim;
4633 }
4634 else
4635 {
4636 nnz_offd += vdim;
4637 for (int vd = 0; vd < vdim; vd++)
4638 {
4639 col_map[col] = -1;
4640 col += elem.stride;
4641 }
4642 }
4643 }
4644 }
4645
4646 // create offd column mapping
4647 HYPRE_BigInt *cmap = Memory<HYPRE_BigInt>(static_cast<int>(col_map.size()));
4648 int offd_col = 0;
4649 for (auto it = col_map.begin(); it != col_map.end(); ++it)
4650 {
4651 cmap[offd_col] = it->first;
4652 it->second = offd_col++;
4653 }
4654
4655 HYPRE_Int *I_diag = Memory<HYPRE_Int>(vdim*local_rows + 1);
4656 HYPRE_Int *I_offd = Memory<HYPRE_Int>(vdim*local_rows + 1);
4657
4658 HYPRE_Int *J_diag = Memory<HYPRE_Int>(nnz_diag);
4659 HYPRE_Int *J_offd = Memory<HYPRE_Int>(nnz_offd);
4660
4661 real_t *A_diag = Memory<real_t>(nnz_diag);
4662 real_t *A_offd = Memory<real_t>(nnz_offd);
4663
4664 int vdim1 = bynodes ? vdim : 1;
4665 int vdim2 = bynodes ? 1 : vdim;
4666 int vdim_offset = bynodes ? local_cols : 1;
4667
4668 // copy the diag/offd elements
4669 nnz_diag = nnz_offd = 0;
4670 int vrow = 0;
4671 for (int vd1 = 0; vd1 < vdim1; vd1++)
4672 {
4673 for (int i = 0; i < local_rows; i++)
4674 {
4675 for (int vd2 = 0; vd2 < vdim2; vd2++)
4676 {
4677 I_diag[vrow] = nnz_diag;
4678 I_offd[vrow++] = nnz_offd;
4679
4680 int vd = bynodes ? vd1 : vd2;
4681 for (unsigned j = 0; j < rows[i].elems.size(); j++)
4682 {
4683 const PMatrixElement &elem = rows[i].elems[j];
4684 if (elem.column >= first_col && elem.column < next_col)
4685 {
4686 J_diag[nnz_diag] = elem.column + vd*vdim_offset - first_col;
4687 A_diag[nnz_diag++] = elem.value;
4688 }
4689 else
4690 {
4691 J_offd[nnz_offd] = col_map[elem.column + vd*elem.stride];
4692 A_offd[nnz_offd++] = elem.value;
4693 }
4694 }
4695 }
4696 }
4697 }
4698 MFEM_ASSERT(vrow == vdim*local_rows, "");
4699 I_diag[vrow] = nnz_diag;
4700 I_offd[vrow] = nnz_offd;
4701
4702 return new HypreParMatrix(MyComm,
4703 row_starts.Last(), col_starts.Last(),
4704 row_starts.GetData(), col_starts.GetData(),
4705 I_diag, J_diag, A_diag,
4706 I_offd, J_offd, A_offd,
4707 static_cast<HYPRE_Int>(col_map.size()), cmap);
4708}
4709
4710template <typename int_type>
4711static int_type* make_i_array(int nrows)
4712{
4713 int_type *I = Memory<int_type>(nrows+1);
4714 for (int i = 0; i <= nrows; i++) { I[i] = -1; }
4715 return I;
4716}
4717
4718template <typename int_type>
4719static int_type* make_j_array(int_type* I, int nrows)
4720{
4721 int nnz = 0;
4722 for (int i = 0; i < nrows; i++)
4723 {
4724 if (I[i] >= 0) { nnz++; }
4725 }
4726 int_type *J = Memory<int_type>(nnz);
4727
4728 I[nrows] = -1;
4729 for (int i = 0, k = 0; i <= nrows; i++)
4730 {
4731 int_type col = I[i];
4732 I[i] = k;
4733 if (col >= 0) { J[k++] = col; }
4734 }
4735 return J;
4736}
4737
4738HypreParMatrix*
4739ParFiniteElementSpace::RebalanceMatrix(int old_ndofs,
4740 const Table* old_elem_dof,
4741 const Table* old_elem_fos)
4742{
4743 MFEM_VERIFY(Nonconforming(), "Only supported for nonconforming meshes.");
4744 MFEM_VERIFY(old_dof_offsets.Size(), "ParFiniteElementSpace::Update needs to "
4745 "be called before ParFiniteElementSpace::RebalanceMatrix");
4746
4747 HYPRE_BigInt old_offset = HYPRE_AssumedPartitionCheck()
4748 ? old_dof_offsets[0] : old_dof_offsets[MyRank];
4749
4750 // send old DOFs of elements we used to own
4751 ParNCMesh* old_pncmesh = pmesh->pncmesh;
4752 old_pncmesh->SendRebalanceDofs(old_ndofs, *old_elem_dof, old_offset, this);
4753
4754 Array<int> dofs;
4755 int vsize = GetVSize();
4756
4757 const Array<int> &old_index = old_pncmesh->GetRebalanceOldIndex();
4758 MFEM_VERIFY(old_index.Size() == pmesh->GetNE(),
4759 "Mesh::Rebalance was not called before "
4760 "ParFiniteElementSpace::RebalanceMatrix");
4761
4762 // prepare the local (diagonal) part of the matrix
4763 HYPRE_Int* i_diag = make_i_array<HYPRE_Int>(vsize);
4764 for (int i = 0; i < pmesh->GetNE(); i++)
4765 {
4766 if (old_index[i] >= 0) // we had this element before
4767 {
4768 const int* old_dofs = old_elem_dof->GetRow(old_index[i]);
4769 GetElementDofs(i, dofs);
4770
4771 for (int vd = 0; vd < vdim; vd++)
4772 {
4773 for (int j = 0; j < dofs.Size(); j++)
4774 {
4775 const int row = UnsignIndex(DofToVDof(dofs[j], vd));
4776 const int col = UnsignIndex(DofToVDof(old_dofs[j], vd,
4777 old_ndofs));
4778 i_diag[row] = col;
4779 }
4780 }
4781 }
4782 }
4783 HYPRE_Int* j_diag = make_j_array(i_diag, vsize);
4784
4785 // receive old DOFs for elements we obtained from others in Rebalance
4786 Array<int> new_elements;
4787 Array<long> old_remote_dofs;
4788 old_pncmesh->RecvRebalanceDofs(new_elements, old_remote_dofs);
4789
4790 // create the off-diagonal part of the matrix
4791 HYPRE_BigInt* i_offd = make_i_array<HYPRE_BigInt>(vsize);
4792 for (int i = 0, pos = 0; i < new_elements.Size(); i++)
4793 {
4794 GetElementDofs(new_elements[i], dofs);
4795 const long* old_dofs = &old_remote_dofs[pos];
4796 pos += dofs.Size() * vdim;
4797
4798 for (int vd = 0; vd < vdim; vd++)
4799 {
4800 for (int j = 0; j < dofs.Size(); j++)
4801 {
4802 const int row = UnsignIndex(DofToVDof(dofs[j], vd));
4803 if (i_diag[row] == i_diag[row+1]) // diag row empty?
4804 {
4805 i_offd[row] = old_dofs[j + vd * dofs.Size()];
4806 }
4807 }
4808 }
4809 }
4810 HYPRE_BigInt* j_offd = make_j_array(i_offd, vsize);
4811
4812#ifndef HYPRE_MIXEDINT
4813 HYPRE_Int *i_offd_hi = i_offd;
4814#else
4815 // Copy of i_offd array as array of HYPRE_Int
4816 HYPRE_Int *i_offd_hi = Memory<HYPRE_Int>(vsize + 1);
4817 std::copy(i_offd, i_offd + vsize + 1, i_offd_hi);
4818 Memory<HYPRE_BigInt>(i_offd, vsize + 1, true).Delete();
4819#endif
4820
4821 // create the offd column map
4822 int offd_cols = i_offd_hi[vsize];
4823 Array<Pair<HYPRE_BigInt, int> > cmap_offd(offd_cols);
4824 for (int i = 0; i < offd_cols; i++)
4825 {
4826 cmap_offd[i].one = j_offd[i];
4827 cmap_offd[i].two = i;
4828 }
4829
4830#ifndef HYPRE_MIXEDINT
4831 HYPRE_Int *j_offd_hi = j_offd;
4832#else
4833 HYPRE_Int *j_offd_hi = Memory<HYPRE_Int>(offd_cols);
4834 Memory<HYPRE_BigInt>(j_offd, offd_cols, true).Delete();
4835#endif
4836
4837 SortPairs<HYPRE_BigInt, int>(cmap_offd, offd_cols);
4838
4839 HYPRE_BigInt* cmap = Memory<HYPRE_BigInt>(offd_cols);
4840 for (int i = 0; i < offd_cols; i++)
4841 {
4842 cmap[i] = cmap_offd[i].one;
4843 j_offd_hi[cmap_offd[i].two] = i;
4844 }
4845
4846 HypreParMatrix *M;
4847 M = new HypreParMatrix(MyComm, MyRank, NRanks, dof_offsets, old_dof_offsets,
4848 i_diag, j_diag, i_offd_hi, j_offd_hi, cmap, offd_cols);
4849 return M;
4850}
4851
4852HypreParMatrix*
4853ParFiniteElementSpace::ParallelDerefinementMatrix(int old_ndofs,
4854 const Table* old_elem_dof,
4855 const Table *old_elem_fos)
4856{
4857 int nrk = HYPRE_AssumedPartitionCheck() ? 2 : NRanks;
4858
4859 MFEM_VERIFY(Nonconforming(), "Not implemented for conforming meshes.");
4860 MFEM_VERIFY(old_dof_offsets[nrk], "Missing previous (finer) space.");
4861
4862#if 0 // check no longer seems to work with NC tet refinement
4863 MFEM_VERIFY(dof_offsets[nrk] <= old_dof_offsets[nrk],
4864 "Previous space is not finer.");
4865#endif
4866
4867 // Note to the reader: please make sure you first read
4868 // FiniteElementSpace::RefinementMatrix, then
4869 // FiniteElementSpace::DerefinementMatrix, and only then this function.
4870 // You have been warned! :-)
4871
4872 Mesh::GeometryList elem_geoms(*mesh);
4873
4874 Array<int> dofs, old_dofs, old_vdofs;
4875 Vector row;
4876
4877 ParNCMesh* old_pncmesh = pmesh->pncmesh;
4878
4879 int ldof[Geometry::NumGeom];
4880 for (int i = 0; i < Geometry::NumGeom; i++)
4881 {
4882 ldof[i] = 0;
4883 }
4884 for (int i = 0; i < elem_geoms.Size(); i++)
4885 {
4886 Geometry::Type geom = elem_geoms[i];
4887 ldof[geom] = fec->FiniteElementForGeometry(geom)->GetDof();
4888 }
4889
4890 const CoarseFineTransformations &dtrans =
4891 old_pncmesh->GetDerefinementTransforms();
4892 const Array<int> &old_ranks = old_pncmesh->GetDerefineOldRanks();
4893
4894 // key: other rank
4895 // value: send or receive buffer
4896 std::map<int, std::vector<HYPRE_BigInt>> to_send;
4897 std::map<int, std::vector<HYPRE_BigInt>> to_recv;
4898 // key: index into dtrans.embeddings
4899 // value: [start, stop]
4900 std::unordered_map<int, std::array<size_t, 2>> recv_messages;
4901
4902 HYPRE_BigInt old_offset = HYPRE_AssumedPartitionCheck()
4903 ? old_dof_offsets[0] : old_dof_offsets[MyRank];
4904
4905 // communicate DOFs for derefinements that straddle processor boundaries,
4906 // note that this is infrequent due to the way elements are ordered
4907 for (int k = 0; k < dtrans.embeddings.Size(); k++)
4908 {
4909 const Embedding &emb = dtrans.embeddings[k];
4910
4911 const int fine_rank = old_ranks[k];
4912 const int coarse_rank = (emb.parent < 0) ? FlipIndexSign(emb.parent)
4913 : old_pncmesh->ElementRank(emb.parent);
4914
4915 if (coarse_rank != MyRank && fine_rank == MyRank)
4916 {
4917 old_elem_dof->GetRow(k, dofs);
4918 DofsToVDofs(dofs, old_ndofs);
4919
4920 std::vector<HYPRE_BigInt>& send_buf = to_send[coarse_rank];
4921 auto pos = send_buf.size();
4922 send_buf.resize(pos + dofs.Size());
4923 for (int i = 0; i < dofs.Size(); i++)
4924 {
4925 send_buf[pos + i] = old_offset + dofs[i];
4926 }
4927 }
4928 else if (coarse_rank == MyRank && fine_rank != MyRank)
4929 {
4930 MFEM_ASSERT(emb.parent >= 0, "");
4931 Geometry::Type geom = mesh->GetElementBaseGeometry(emb.parent);
4932
4933 std::vector<HYPRE_BigInt>& recv_buf = to_recv[fine_rank];
4934 auto& msg = recv_messages[k];
4935 msg[0] = recv_buf.size();
4936 recv_buf.resize(recv_buf.size() + ldof[geom] * vdim);
4937 msg[1] = recv_buf.size();
4938 }
4939 }
4940
4941 // assume embedding orders are consistent (i.e. what we expect to receive
4942 // first from a given rank is sent first, etc.)
4943 std::vector<MPI_Request> requests;
4944 requests.reserve(to_send.size() + to_recv.size());
4945 // enqueue recvs
4946 for (auto &v : to_recv)
4947 {
4948 requests.emplace_back();
4949 MPI_Irecv(v.second.data(), v.second.size(), HYPRE_MPI_BIG_INT, v.first,
4951 &requests.back());
4952 }
4953 // enqueue sends
4954 for (auto &v : to_send)
4955 {
4956 requests.emplace_back();
4957 MPI_Isend(v.second.data(), v.second.size(), HYPRE_MPI_BIG_INT, v.first,
4959 &requests.back());
4960 }
4961
4962 DenseTensor localR[Geometry::NumGeom];
4963 for (int i = 0; i < elem_geoms.Size(); i++)
4964 {
4965 GetLocalDerefinementMatrices(elem_geoms[i], localR[elem_geoms[i]]);
4966 }
4967
4968 // create the diagonal part of the derefinement matrix
4969 SparseMatrix *diag = new SparseMatrix(ndofs*vdim, old_ndofs*vdim);
4970
4971 Array<char> mark(diag->Height());
4972 mark = 0;
4973
4975
4976 for (int k = 0; k < dtrans.embeddings.Size(); k++)
4977 {
4978 const Embedding &emb = dtrans.embeddings[k];
4979 if (emb.parent < 0) { continue; }
4980
4981 int coarse_rank = old_pncmesh->ElementRank(emb.parent);
4982 int fine_rank = old_ranks[k];
4983
4984 if (coarse_rank == MyRank && fine_rank == MyRank)
4985 {
4986 Geometry::Type geom = mesh->GetElementBaseGeometry(emb.parent);
4987 DenseMatrix &lR = localR[geom](emb.matrix);
4988
4989 elem_dof->GetRow(emb.parent, dofs);
4990 old_elem_dof->GetRow(k, old_dofs);
4991
4992 for (int vd = 0; vd < vdim; vd++)
4993 {
4994 old_dofs.Copy(old_vdofs);
4995 DofsToVDofs(vd, old_vdofs, old_ndofs);
4996
4997 for (int i = 0; i < lR.Height(); i++)
4998 {
4999 if (!std::isfinite(lR(i, 0))) { continue; }
5000
5001 const int r = DofToVDof(dofs[i], vd);
5002 const int m = UnsignIndex(r);
5003
5004 if (is_dg || !mark[m])
5005 {
5006 lR.GetRow(i, row);
5007 diag->SetRow(r, old_vdofs, row);
5008 mark[m] = 1;
5009 }
5010 }
5011 }
5012 }
5013 }
5014 diag->Finalize();
5015
5016 // wait for all sends/receives to complete
5017 MPI_Waitall(requests.size(), requests.data(), MPI_STATUSES_IGNORE);
5018
5019 // create the off-diagonal part of the derefinement matrix
5020 SparseMatrix *offd = new SparseMatrix(ndofs*vdim, 1);
5021
5022 std::map<HYPRE_BigInt, int> col_map;
5023 for (int k = 0; k < dtrans.embeddings.Size(); k++)
5024 {
5025 const Embedding &emb = dtrans.embeddings[k];
5026 if (emb.parent < 0) { continue; }
5027
5028 int coarse_rank = old_pncmesh->ElementRank(emb.parent);
5029 int fine_rank = old_ranks[k];
5030
5031 if (coarse_rank == MyRank && fine_rank != MyRank)
5032 {
5033 Geometry::Type geom = mesh->GetElementBaseGeometry(emb.parent);
5034 DenseMatrix &lR = localR[geom](emb.matrix);
5035
5036 elem_dof->GetRow(emb.parent, dofs);
5037
5038 auto& odofs = to_recv.at(fine_rank);
5039 auto &msg = recv_messages[k];
5040 MFEM_ASSERT(msg[1] > msg[0], "");
5041
5042 for (int vd = 0; vd < vdim; vd++)
5043 {
5044 MFEM_ASSERT(ldof[geom], "");
5045 HYPRE_BigInt *remote_dofs = odofs.data() + msg[0] + vd * ldof[geom];
5046
5047 for (int i = 0; i < lR.Height(); i++)
5048 {
5049 if (!std::isfinite(lR(i, 0))) { continue; }
5050
5051 const int m = UnsignIndex(DofToVDof(dofs[i], vd));
5052
5053 if (is_dg || !mark[m])
5054 {
5055 lR.GetRow(i, row);
5056 MFEM_ASSERT(ldof[geom] == row.Size(), "");
5057 for (int j = 0; j < ldof[geom]; j++)
5058 {
5059 if (row[j] == 0.0) { continue; } // NOTE: lR thresholded
5060 int &lcol = col_map[remote_dofs[j]];
5061 if (!lcol) { lcol = static_cast<int>(col_map.size()); }
5062 offd->_Set_(m, lcol-1, row[j]);
5063 }
5064 mark[m] = 1;
5065 }
5066 }
5067 }
5068 }
5069 }
5070
5071 offd->Finalize(0);
5072 offd->SetWidth(static_cast<int>(col_map.size()));
5073
5074 // create offd column mapping for use by hypre
5075 HYPRE_BigInt *cmap = Memory<HYPRE_BigInt>(offd->Width());
5076 for (auto it = col_map.begin(); it != col_map.end(); ++it)
5077 {
5078 cmap[it->second-1] = it->first;
5079 }
5080
5081 // reorder offd columns so that 'cmap' is monotonic
5082 // NOTE: this is easier and probably faster (offd is small) than making
5083 // sure cmap is determined and sorted before the offd matrix is created
5084 {
5085 int width = offd->Width();
5086 Array<Pair<HYPRE_BigInt, int> > reorder(width);
5087 for (int i = 0; i < width; i++)
5088 {
5089 reorder[i].one = cmap[i];
5090 reorder[i].two = i;
5091 }
5092 reorder.Sort();
5093
5094 Array<int> reindex(width);
5095 for (int i = 0; i < width; i++)
5096 {
5097 reindex[reorder[i].two] = i;
5098 cmap[i] = reorder[i].one;
5099 }
5100
5101 int *J = offd->GetJ();
5102 for (int i = 0; i < offd->NumNonZeroElems(); i++)
5103 {
5104 J[i] = reindex[J[i]];
5105 }
5106 offd->SortColumnIndices();
5107 }
5108
5109 HypreParMatrix* new_R;
5110 new_R = new HypreParMatrix(MyComm, dof_offsets[nrk], old_dof_offsets[nrk],
5111 dof_offsets, old_dof_offsets, diag, offd, cmap,
5112 true);
5113
5114 new_R->SetOwnerFlags(new_R->OwnsDiag(), new_R->OwnsOffd(), 1);
5115
5116 return new_R;
5117}
5118
5119void ParFiniteElementSpace::Destroy()
5120{
5121 ldof_group.DeleteAll();
5122 ldof_ltdof.DeleteAll();
5123 dof_offsets.DeleteAll();
5124 tdof_offsets.DeleteAll();
5125 tdof_nb_offsets.DeleteAll();
5126 // preserve old_dof_offsets
5127 ldof_sign.DeleteAll();
5128
5129 delete P; P = NULL;
5130 delete Pconf; Pconf = NULL;
5131 delete Rconf; Rconf = NULL;
5132 delete R; R = NULL;
5133
5134 delete gcomm; gcomm = NULL;
5135
5136 num_face_nbr_dofs = -1;
5141}
5142
5143void ParFiniteElementSpace::CopyProlongationAndRestriction(
5144 const FiniteElementSpace &fes, const Array<int> *perm)
5145{
5146 const ParFiniteElementSpace *pfes
5147 = dynamic_cast<const ParFiniteElementSpace*>(&fes);
5148 MFEM_VERIFY(pfes != NULL, "");
5149 MFEM_VERIFY(P == NULL, "");
5150 MFEM_VERIFY(R == NULL, "");
5151
5152 // Ensure R and P matrices are built
5153 pfes->Dof_TrueDof_Matrix();
5154
5155 SparseMatrix *perm_mat = NULL, *perm_mat_tr = NULL;
5156 if (perm)
5157 {
5158 // Note: although n and fes.GetVSize() are typically equal, in
5159 // variable-order spaces they may differ, since nonconforming edges/faces
5160 // my have fictitious DOFs.
5161 int n = perm->Size();
5162 perm_mat = new SparseMatrix(n, fes.GetVSize());
5163 for (int i=0; i<n; ++i)
5164 {
5165 real_t s;
5166 int j = DecodeDof((*perm)[i], s);
5167 perm_mat->Set(i, j, s);
5168 }
5169 perm_mat->Finalize();
5170 perm_mat_tr = Transpose(*perm_mat);
5171 }
5172
5173 if (pfes->P != NULL)
5174 {
5175 if (perm) { P = pfes->P->LeftDiagMult(*perm_mat); }
5176 else { P = new HypreParMatrix(*pfes->P); }
5177 nonconf_P = true;
5178 }
5179 else if (perm != NULL)
5180 {
5181 HYPRE_BigInt glob_nrows = GlobalVSize();
5182 HYPRE_BigInt glob_ncols = GlobalTrueVSize();
5183 HYPRE_BigInt *col_starts = GetTrueDofOffsets();
5184 HYPRE_BigInt *row_starts = GetDofOffsets();
5185 P = new HypreParMatrix(MyComm, glob_nrows, glob_ncols, row_starts,
5186 col_starts, perm_mat);
5187 nonconf_P = true;
5188 }
5189 if (pfes->R != NULL)
5190 {
5191 if (perm) { R = Mult(*pfes->R, *perm_mat_tr); }
5192 else { R = new SparseMatrix(*pfes->R); }
5193 }
5194 else if (perm != NULL)
5195 {
5196 R = perm_mat_tr;
5197 perm_mat_tr = NULL;
5198 }
5199
5200 delete perm_mat;
5201 delete perm_mat_tr;
5202}
5203
5205 const FiniteElementSpace &coarse_fes, OperatorHandle &T) const
5206{
5209 GetTransferOperator(coarse_fes, Tgf);
5210 Dof_TrueDof_Matrix(); // Make sure R is built - we need R in all cases.
5211 if (T.Type() == Operator::Hypre_ParCSR)
5212 {
5213 const ParFiniteElementSpace *c_pfes =
5214 dynamic_cast<const ParFiniteElementSpace *>(&coarse_fes);
5215 MFEM_ASSERT(c_pfes != NULL, "coarse_fes must be a parallel space");
5216 SparseMatrix *RA = mfem::Mult(*R, *Tgf.As<SparseMatrix>());
5217 Tgf.Clear();
5218 T.Reset(c_pfes->Dof_TrueDof_Matrix()->
5219 LeftDiagMult(*RA, GetTrueDofOffsets()));
5220 delete RA;
5221 }
5222 else
5223 {
5224 T.Reset(new TripleProductOperator(R, Tgf.Ptr(),
5225 coarse_fes.GetProlongationMatrix(),
5226 false, Tgf.OwnsOperator(), false));
5227 Tgf.SetOperatorOwner(false);
5228 }
5229}
5230
5231void ParFiniteElementSpace::Update(bool want_transform)
5232{
5233 lastUpdatePRef = false;
5234
5235 {
5236 int int_orders_changed = (int) orders_changed;
5237 MPI_Allreduce(MPI_IN_PLACE, &int_orders_changed, 1, MPI_INT,
5238 MPI_MAX, MyComm);
5239 orders_changed = (bool) int_orders_changed;
5240
5241 int var = (elem_order.Size() > 0);
5242 MPI_Allreduce(MPI_IN_PLACE, &var, 1, MPI_INT, MPI_MAX, MyComm);
5243 variableOrder = (bool) var;
5244 }
5245
5246 if (variableOrder && elem_order.Size() == 0)
5247 {
5249 elem_order = fec->GetOrder();
5250 }
5251
5253 {
5254 return; // no need to update, no-op
5255 }
5256 if (want_transform && mesh->GetSequence() != mesh_sequence + 1 &&
5258 {
5259 MFEM_ABORT("Error in update sequence. Space needs to be updated after "
5260 "each mesh modification.");
5261 }
5262
5263 if (NURBSext)
5264 {
5265 UpdateNURBS();
5266 return;
5267 }
5268
5269 Table* old_elem_dof = NULL;
5270 Table* old_elem_fos = NULL;
5271 int old_ndofs = 0;
5272
5273 // save old DOF table
5274 if (want_transform)
5275 {
5276 old_elem_dof = elem_dof;
5277 old_elem_fos = elem_fos;
5278 elem_dof = NULL;
5279 elem_fos = NULL;
5280 old_ndofs = ndofs;
5281 Swap(dof_offsets, old_dof_offsets);
5282 }
5283
5284 Destroy(); // Does not clear elem_order
5285 FiniteElementSpace::Destroy(); // calls Th.Clear()
5286
5287 // In the variable-order case, we call CommunicateGhostOrder whether h-
5288 // or p-refinement is done.
5289 if (variableOrder) { CommunicateGhostOrder(); }
5290
5292 Construct();
5293
5295
5296 if (want_transform)
5297 {
5298 // calculate appropriate GridFunction transformation
5299 switch (mesh->GetLastOperation())
5300 {
5301 case Mesh::REFINE:
5302 {
5304 {
5305 Th.Reset(new RefinementOperator(this, old_elem_dof,
5306 old_elem_fos, old_ndofs));
5307 // The RefinementOperator takes ownership of 'old_elem_dofs', so
5308 // we no longer own it:
5309 old_elem_dof = NULL;
5310 old_elem_fos = NULL;
5311 }
5312 else
5313 {
5314 // calculate fully assembled matrix
5315 Th.Reset(RefinementMatrix(old_ndofs, old_elem_dof, old_elem_fos));
5316 }
5317 break;
5318 }
5319
5320 case Mesh::DEREFINE:
5321 {
5322#if 0
5323 Th.Reset(ParallelDerefinementMatrix(old_ndofs, old_elem_dof,
5324 old_elem_fos));
5325#else
5326 Th.Reset(new ParDerefineMatrixOp(*this, old_ndofs, old_elem_dof,
5327 old_elem_fos));
5328#endif
5329 if (Nonconforming())
5330 {
5331 Th.SetOperatorOwner(false);
5332 Th.Reset(new TripleProductOperator(P, R, Th.Ptr(),
5333 false, false, true));
5334 }
5335 break;
5336 }
5337
5338 case Mesh::REBALANCE:
5339 {
5340 Th.Reset(RebalanceMatrix(old_ndofs, old_elem_dof, old_elem_fos));
5341 break;
5342 }
5343
5344 default:
5345 break;
5346 }
5347
5348 delete old_elem_dof;
5349 delete old_elem_fos;
5350 }
5351}
5352
5354 bool want_transfer)
5355{
5356 MFEM_VERIFY(PRefinementSupported(),
5357 "p-refinement is not supported in this space");
5358
5359 if (want_transfer)
5360 {
5361 pfes_prev.reset(new ParFiniteElementSpace(pmesh, fec, vdim, ordering));
5362 for (int i = 0; i<pmesh->GetNE(); i++)
5363 {
5364 pfes_prev->SetElementOrder(i, GetElementOrder(i));
5365 }
5366 pfes_prev->Update(false);
5367 }
5368
5369 for (auto ref : refs)
5370 {
5371 SetElementOrder(ref.index, GetElementOrder(ref.index) + ref.delta);
5372 }
5373
5374 Update(false);
5375
5376 if (want_transfer)
5377 {
5378 PTh.reset(new PRefinementTransferOperator(*pfes_prev, *this));
5379 }
5380
5381 lastUpdatePRef = true;
5382}
5383
5384void ParFiniteElementSpace::UpdateMeshPointer(Mesh *new_mesh)
5385{
5386 ParMesh *new_pmesh = dynamic_cast<ParMesh*>(new_mesh);
5387 MFEM_VERIFY(new_pmesh != NULL,
5388 "ParFiniteElementSpace::UpdateMeshPointer(...) must be a ParMesh");
5389 mesh = new_mesh;
5390 pmesh = new_pmesh;
5391}
5392
5394{
5395 if (IsVariableOrder())
5396 {
5397 int order = elem_order.Size() > 0 ? elem_order.Max() : fec->GetOrder();
5398 MPI_Allreduce(MPI_IN_PLACE, &order, 1, MPI_INT, MPI_MAX, MyComm);
5399 return order;
5400 }
5401 else
5402 {
5403 return fec->GetOrder();
5404 }
5405}
5406
5407// This function is an extension of FiniteElementSpace::CalcEdgeFaceVarOrders in
5408// the parallel case, to use ghost_orders, which contains ghost element indices
5409// and their orders. The order on each ghost element is applied to the element's
5410// edges and faces, in @a edge_orders and @a face_orders.
5412 Array<VarOrderBits> &edge_orders,
5413 Array<VarOrderBits> &face_orders) const
5414{
5415 edge_orders.SetSize(pncmesh->GetNEdges() + pncmesh->GetNGhostEdges());
5416 face_orders.SetSize(pncmesh->GetNFaces() + pncmesh->GetNGhostFaces());
5417
5418 edge_orders = 0;
5419 face_orders = 0;
5420
5421 const int npref = ghost_orders.Size();
5422 for (int i=0; i<npref; ++i)
5423 {
5424 const int elem = ghost_orders[i].element; // Index in NCMesh::elements
5425 const int order = ghost_orders[i].order;
5426 const VarOrderBits mask = (VarOrderBits(1) << order);
5427
5428 Array<int> edges;
5429 pncmesh->FindEdgesOfGhostElement(elem, edges);
5430
5431 for (auto edge : edges) { edge_orders[edge] |= mask; }
5432
5433 if (mesh->Dimension() > 2)
5434 {
5435 Array<int> faces;
5436 pncmesh->FindFacesOfGhostElement(elem, faces);
5437
5438 for (auto face : faces) { face_orders[face] |= mask; }
5439 }
5440 }
5441}
5442
5444 const Array<VarOrderBits> &face_orders,
5445 Array<VarOrderBits> &edge_orders) const
5446{
5447 // Apply the lowest order (first variant) on each ghost face to its edges
5448 for (int i=0; i<pncmesh->GetNGhostFaces(); ++i)
5449 {
5450 const int face = pncmesh->GetNFaces() + i;
5451 VarOrderBits orders = face_orders[face];
5452
5453 if (orders == 0) { continue; }
5454
5455 // Find the lowest order and use that.
5456 int orderV0 = -1;
5457 for (int order = 0; orders != 0; order++, orders >>= 1)
5458 {
5459 if (orders & 1)
5460 {
5461 orderV0 = order;
5462 break;
5463 }
5464 }
5465
5466 MFEM_VERIFY(orderV0 > 0, "");
5467
5468 const VarOrderBits mask = (VarOrderBits(1) << orderV0);
5469
5470 Array<int> edges;
5471 pncmesh->FindEdgesOfGhostFace(face, edges);
5472
5473 for (auto edge : edges)
5474 {
5475 edge_orders[edge] |= mask;
5476 }
5477 }
5478}
5479
5481 int lsize, const GroupCommunicator &gc_, bool local_)
5482 : gc(gc_), local(local_)
5483{
5484 const Table &group_ldof = gc.GroupLDofTable();
5485
5486 int n_external = 0;
5487 for (int g=1; g<group_ldof.Size(); ++g)
5488 {
5489 if (!gc.GetGroupTopology().IAmMaster(g))
5490 {
5491 n_external += group_ldof.RowSize(g);
5492 }
5493 }
5494 int tsize = lsize - n_external;
5495
5496 height = lsize;
5497 width = tsize;
5498
5499 external_ldofs.Reserve(n_external);
5500 for (int gr = 1; gr < group_ldof.Size(); gr++)
5501 {
5502 if (!gc.GetGroupTopology().IAmMaster(gr))
5503 {
5504 external_ldofs.Append(group_ldof.GetRow(gr), group_ldof.RowSize(gr));
5505 }
5506 }
5508}
5509
5515
5517 const ParFiniteElementSpace &pfes, bool local_)
5518 : Operator(pfes.GetVSize(), pfes.GetTrueVSize()),
5519 external_ldofs(),
5520 gc(pfes.GroupComm()),
5521 local(local_)
5522{
5523 MFEM_VERIFY(pfes.Conforming(), "");
5524 const Table &group_ldof = gc.GroupLDofTable();
5526 for (int gr = 1; gr < group_ldof.Size(); gr++)
5527 {
5528 if (!gc.GetGroupTopology().IAmMaster(gr))
5529 {
5530 external_ldofs.Append(group_ldof.GetRow(gr), group_ldof.RowSize(gr));
5531 }
5532 }
5534 MFEM_ASSERT(external_ldofs.Size() == Height()-Width(), "");
5535#ifdef MFEM_DEBUG
5536 for (int j = 1; j < external_ldofs.Size(); j++)
5537 {
5538 // Check for repeated ldofs.
5539 MFEM_VERIFY(external_ldofs[j-1] < external_ldofs[j], "");
5540 }
5541 int j = 0;
5542 for (int i = 0; i < external_ldofs.Size(); i++)
5543 {
5544 const int end = external_ldofs[i];
5545 for ( ; j < end; j++)
5546 {
5547 MFEM_VERIFY(j-i == pfes.GetLocalTDofNumber(j), "");
5548 }
5549 j = end+1;
5550 }
5551 for ( ; j < Height(); j++)
5552 {
5553 MFEM_VERIFY(j-external_ldofs.Size() == pfes.GetLocalTDofNumber(j), "");
5554 }
5555 // gc.PrintInfo();
5556 // pfes.Dof_TrueDof_Matrix()->PrintCommPkg();
5557#endif
5558}
5559
5561{
5562 MFEM_ASSERT(x.Size() == Width(), "");
5563 MFEM_ASSERT(y.Size() == Height(), "");
5564
5565 const real_t *xdata = x.HostRead();
5566 real_t *ydata = y.HostWrite();
5567 const int m = external_ldofs.Size();
5568
5569 const int in_layout = 2; // 2 - input is ltdofs array
5570 if (local)
5571 {
5573 }
5574 else
5575 {
5576 gc.BcastBegin(const_cast<real_t*>(xdata), in_layout);
5577 }
5578
5579 int j = 0;
5580 for (int i = 0; i < m; i++)
5581 {
5582 const int end = external_ldofs[i];
5583 if (end > j) { std::copy(xdata+j-i, xdata+end-i, ydata+j); }
5584 j = end+1;
5585 }
5586 if (Width() > (j-m)) { std::copy(xdata+j-m, xdata+Width(), ydata+j); }
5587
5588 const int out_layout = 0; // 0 - output is ldofs array
5589 if (!local)
5590 {
5591 gc.BcastEnd(ydata, out_layout);
5592 }
5593}
5594
5596 const Vector &x, Vector &y) const
5597{
5598 MFEM_ASSERT(x.Size() == Height(), "");
5599 MFEM_ASSERT(y.Size() == Width(), "");
5600
5601 const real_t *xdata = x.HostRead();
5602 real_t *ydata = y.HostWrite();
5603 const int m = external_ldofs.Size();
5604
5605 if (!local)
5606 {
5607 gc.ReduceBegin(xdata);
5608 }
5609
5610 int j = 0;
5611 for (int i = 0; i < m; i++)
5612 {
5613 const int end = external_ldofs[i];
5614 if (end > j) { std::copy(xdata+j, xdata+end, ydata+j-i); }
5615 j = end+1;
5616 }
5617 if (Height() > j) { std::copy(xdata+j, xdata+Height(), ydata+j-m); }
5618
5619 const int out_layout = 2; // 2 - output is an array on all ltdofs
5620 if (!local)
5621 {
5622 gc.ReduceEnd<real_t>(ydata, out_layout, GroupCommunicator::Sum);
5623 }
5624}
5625
5630
5635
5637 const ParFiniteElementSpace &pfes, bool local_)
5638 : DeviceConformingProlongationOperator(pfes.GetVSize(),
5639 pfes.GroupComm(),
5640 local_)
5641{
5642 MFEM_ASSERT(pfes.Conforming(), "internal error");
5643}
5644
5646 Vector &y) const
5647{
5648 if (!local)
5649 {
5651 }
5652 else
5653 {
5655 *y.GetArrayView());
5656 }
5657}
5658
5660 Vector &y) const
5661{
5662 if (!local)
5663 {
5665 *y.GetArrayView());
5666 }
5667 else
5668 {
5670 }
5671}
5672
5673} // namespace mfem
5674
5675#endif
T Max() const
Find the maximal element in the array, using the comparison operator < for class T.
Definition array.cpp:69
const T * HostRead() const
Shortcut for mfem::Read(a.GetMemory(), a.Size(), false).
Definition array.hpp:414
void Sort()
Sorts the array in ascending order. This requires operator< to be defined for T.
Definition array.hpp:341
void Reserve(int capacity)
Ensures that the allocated size is at least the given size.
Definition array.hpp:210
void SetSize(int nsize)
Change the logical size of the array, keep existing entries.
Definition array.hpp:869
void LoseData()
NULL-ifies the data.
Definition array.hpp:186
int Size() const
Return the logical size of the array.
Definition array.hpp:192
void MakeRef(T *data_, int size_, bool own_data=false)
Make this Array a reference to a pointer.
Definition array.hpp:1082
void DeleteAll()
Delete the whole array.
Definition array.hpp:1062
int Append(const T &el)
Append element 'el' to array, resize if necessary.
Definition array.hpp:941
void Swap(Array &other)
Swap the contents of the Array with other.
Definition array.hpp:814
T * HostWrite()
Shortcut for mfem::Write(a.GetMemory(), a.Size(), false).
Definition array.hpp:422
Operator that extracts face degrees of freedom for H1, ND, or RT FiniteElementSpaces.
Auxiliary class used by ParFiniteElementSpace.
Definition pfespace.hpp:616
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:619
real_t * GetData() const
Returns the matrix data array. Warning: this method casts away constness.
Definition densemat.hpp:135
Auxiliary device class used by ParFiniteElementSpace.
Definition pfespace.hpp:645
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 ...
DeviceConformingProlongationOperator(int lsize, const GroupCommunicator &gc_, bool local_=false)
void RestrictTranspose(const Array< T > &x_tdof, Array< T > &x_ldof) const
Transpose of Restrict(): copy the true-dof data x_tdof into the owned local dofs of x_ldof and set th...
void Restrict(const Array< T > &x_ldof, Array< T > &x_tdof) const
Kernel: copy owned ldofs from x_ldof to ltdofs in x_tdof, i.e. x_tdof[i] = x_ldof[ltdof_ldof[i]].
void ProlongateTranspose(const Array< T > &x_ldof, Array< T > &x_tdof, Op op=Op::Sum) const
Transpose of Prolongate(): reduce the local-dof data x_ldof into the true-dof data x_tdof,...
void Prolongate(const Array< T > &x_tdof, Array< T > &x_ldof) const
Prolongate the true-dof data x_tdof to the local-dof data x_ldof.
static bool Allows(unsigned long b_mask)
Return true if any of the backends in the backend mask, b_mask, are allowed.
Definition device.hpp:271
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:55
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].
const int * GetDofOrdering(Geometry::Type geom, int p, int ori) const
Variable order version of DofOrderForOrientation().
Definition fe_coll.hpp:238
int GetOrder() const
Return the order (polynomial degree) of the FE collection, corresponding to the order/degree returned...
Definition fe_coll.hpp:248
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
int GetNumDof(Geometry::Type geom, int p) const
Variable order version of DofForGeometry().
Definition fe_coll.hpp:226
const FiniteElement * GetFE(Geometry::Type geom, int p) const
Variable order version of FiniteElementForGeometry().
Definition fe_coll.hpp:203
@ DISCONTINUOUS
Field is discontinuous across element interfaces.
Definition fe_coll.hpp:48
@ TANGENTIAL
Tangential components of vector field.
Definition fe_coll.hpp:46
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:563
GridFunction interpolation operator applicable after mesh refinement.
Definition fespace.hpp:492
Class FiniteElementSpace - responsible for providing FEM view of the mesh, mainly managing the set of...
Definition fespace.hpp:210
friend void Mesh::Swap(Mesh &, bool)
DofTransformation DoFTrans
Definition fespace.hpp:295
bool IsVariableOrder() const
Returns true if the space contains elements of varying polynomial orders.
Definition fespace.hpp:673
Array< StatelessDofTransformation * > DoFTransArray
Definition fespace.hpp:294
void DofsToVDofs(Array< int > &dofs, int ndofs=-1) const
Compute the full set of vdofs corresponding to each entry in dofs.
Definition fespace.cpp:232
void GetEdgeInteriorDofs(int i, Array< int > &dofs) const
Returns the indices of the degrees of freedom for the interior of the specified edge.
Definition fespace.cpp:3841
bool IsVariableOrderH1() const
Returns true if the space is H1 and has variable-order elements.
Definition fespace.hpp:393
Array< int > face_min_nghb_order
Definition fespace.hpp:268
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:3538
std::shared_ptr< PRefinementTransferOperator > PTh
Definition fespace.hpp:316
NURBSExtension * NURBSext
Definition fespace.hpp:286
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:3650
virtual void GetExteriorVDofs(Array< int > &exterior_vdofs, int component=-1) const
Mark degrees of freedom associated with exterior faces of the mesh. For spaces with 'vdim' > 1,...
Definition fespace.cpp:683
int GetEdgeOrder(int edge, int variant=0) const
Definition fespace.cpp:3379
int GetFaceOrder(int face, int variant=0) const
Returns the polynomial degree of the i'th face finite element.
Definition fespace.cpp:3395
int GetNumBorderDofs(Geometry::Type geom, int order) const
Definition fespace.cpp:1050
static int MinOrder(VarOrderBits bits)
Return the minimum order (least significant bit set) in the bit mask.
Definition fespace.cpp:3019
int GetNDofs() const
Returns number of degrees of freedom. This is the number of Local Degrees of Freedom.
Definition fespace.hpp:821
bool orders_changed
True if at least one element order changed (variable-order space only).
Definition fespace.hpp:344
friend class PRefinementTransferOperator
Definition fespace.hpp:212
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:4080
virtual const Operator * GetProlongationMatrix() const
Definition fespace.hpp:691
int GetEntityDofs(int entity, int index, Array< int > &dofs, Geometry::Type master_geom=Geometry::INVALID, int variant=0) const
Helper to get vertex, edge or face DOFs (entity=0,1,2 resp.).
Definition fespace.cpp:1059
Array< char > var_edge_orders
Definition fespace.hpp:263
std::unique_ptr< Operator > R_transpose
Operator computing the action of the transpose of the restriction.
Definition fespace.hpp:307
void UpdateElementOrders()
Resize the elem_order array on mesh change.
Definition fespace.cpp:4156
const FiniteElementCollection * fec
Associated FE collection (not owned).
Definition fespace.hpp:222
int VDofToDof(int vdof) const
Compute the inverse of the Dof to VDof mapping for a single index vdof.
Definition fespace.hpp:1131
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:299
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:3860
Array< char > elem_order
Definition fespace.hpp:239
int FirstFaceDof(int face, int variant=0) const
Definition fespace.hpp:446
void GetLocalDerefinementMatrices(Geometry::Type geom, DenseTensor &localR) const
Definition fespace.cpp:2383
int GetNE() const
Returns number of elements in the mesh.
Definition fespace.hpp:867
int vdim
Vector dimension (number of unknowns per degree of freedom).
Definition fespace.hpp:225
Table var_face_dofs
NOTE: also used for spaces with mixed faces.
Definition fespace.hpp:248
bool lastUpdatePRef
Flag to indicate whether the last update was for p-refinement.
Definition fespace.hpp:319
std::unordered_map< key_face, std::unique_ptr< FaceRestriction >, TupleHasher > L2F
Definition fespace.hpp:326
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:3738
void GetEssentialBdrEdgesFaces(const Array< int > &bdr_attr_is_ess, std::set< int > &edges, std::set< int > &faces) const
Definition fespace.cpp:4482
OperatorHandle Th
Transformation to apply to GridFunctions after space Update().
Definition fespace.hpp:314
SparseMatrix * RefinementMatrix(int old_ndofs, const Table *old_elem_dof, const Table *old_elem_fos)
Definition fespace.cpp:1811
std::uint64_t VarOrderBits
Bit-mask representing a set of orders needed by an edge/face.
Definition fespace.hpp:258
int ndofs
Number of degrees of freedom. Number of unknowns is ndofs * vdim.
Definition fespace.hpp:233
void GetBoundaryLoopEdgeDofs(const Array< int > &boundary_element_indices, Array< int > &boundary_edge_dofs, Array< int > *dof_edges=nullptr, Array< int > *dof_boundary_elements=nullptr) const
Extract the edge degrees of freedom of a boundary "loop".
Definition fespace.cpp:4530
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:756
int GetElementOrder(int i) const
Returns the order of the i'th finite element.
Definition fespace.cpp:195
Mesh * mesh
The mesh that FE space lives on (not owned).
Definition fespace.hpp:219
Ordering::Type ordering
Definition fespace.hpp:230
void SetElementOrder(int i, int p)
Sets the order of the i'th finite element.
Definition fespace.cpp:170
const FiniteElementCollection * FEColl() const
Definition fespace.hpp:854
int GetNVariants(int entity, int index) const
Return number of possible DOF variants for edge/face (var. order spaces).
Definition fespace.cpp:3416
int GetVSize() const
Return the number of vector dofs, i.e. GetNDofs() x GetVDim().
Definition fespace.hpp:824
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:3643
bool IsDGSpace() const
Return whether or not the space is discontinuous (L2)
Definition fespace.hpp:1587
Array< int > edge_min_nghb_order
Minimum order among neighboring elements.
Definition fespace.hpp:268
static int DecodeDof(int dof)
Helper to return the DOF associated with a sign encoded DOF.
Definition fespace.hpp:1153
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:550
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:268
int FindEdgeDof(int edge, int ndof) const
Definition fespace.hpp:439
void BuildElementToDofTable() const
Definition fespace.cpp:356
Abstract class for all finite elements.
Definition fe_base.hpp:294
int GetDof() const
Returns the number of degrees of freedom in the finite element.
Definition fe_base.hpp:410
static const int NumGeom
Definition geom.hpp:46
static const int Dimension[NumGeom]
Definition geom.hpp:51
static bool IsTensorProduct(Type geom)
Definition geom.hpp:112
Communicator performing operations within groups defined by a GroupTopology with arbitrary-size data ...
Table & GroupLDofTable()
Fill-in the returned Table reference to initialize the GroupCommunicator then call Finalize().
void ReduceEnd(T *ldata, int layout, void(*Op)(OpData< T >)) const
Finalize reduction operation started with the host version of ReduceBegin().
const GroupTopology & GetGroupTopology() const
Get a const reference to the associated GroupTopology object.
void Reduce(T *ldata, void(*Op)(OpData< T >)) const
Reduce within each group where the master is the root, host version.
void BcastEnd(T *ldata, int layout) const
Finalize a broadcast started with the host version of BcastBegin().
void ReduceBegin(const T *ldata) const
Begin reduction operation within each group where the master is the root, host version.
void Create(const Array< int > &ldof_group)
Initialize the communicator from a local-dof to group map. Finalize() is called internally.
static void Sum(OpData< T >)
Reduce operation Sum, instantiated for int, double and float.
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 BcastBegin(T *ldata, int layout) const
Begin a broadcast within each group where the master is the root, host version.
void Finalize()
Allocate internal buffers after the GroupLDofTable is defined.
const DeviceGroupCommunicator & GetDeviceComm() const
Return the device communicator, 'device_gc', constructing it if it was not already constructed.
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'.
Wrapper for hypre's ParCSR matrix class.
Definition hypre.hpp:419
void GetDiag(Vector &diag) const
Get the local diagonal of the matrix.
Definition hypre.cpp:1610
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:2052
Identity Operator I: x -> x.
Definition operator.hpp:878
Operator that extracts face degrees of freedom for L2 interface spaces.
Mesh data type.
Definition mesh.hpp:67
Operation GetLastOperation() const
Return type of last modification of the mesh.
Definition mesh.hpp:2553
int GetNEdges() const
Return the number of edges.
Definition mesh.hpp:1396
void GetBdrElementFace(int i, int *f, int *o) const
Definition mesh.cpp:8369
NURBSExtension * NURBSext
Optional NURBS mesh extension.
Definition mesh.hpp:317
void GetFaceInfos(int Face, int *Inf1, int *Inf2) const
Definition mesh.cpp:1638
int GetNumFaces() const
Return the number of faces (3D), edges (2D) or vertices (1D).
Definition mesh.cpp:7302
Geometry::Type GetFaceGeometry(int i) const
Return the Geometry::Type associated with face i.
Definition mesh.cpp:1651
Geometry::Type GetBdrElementGeometry(int i) const
Definition mesh.hpp:1560
@ REBALANCE
Definition mesh.hpp:304
bool Nonconforming() const
Definition mesh.hpp:2539
int GetNFaces() const
Return the number of faces in a 3D mesh.
Definition mesh.hpp:1399
long GetSequence() const
Definition mesh.hpp:2559
int GetNE() const
Returns number of elements.
Definition mesh.hpp:1390
int Dimension() const
Dimension of the reference space used within the elements.
Definition mesh.hpp:1314
void GetFaceElements(int Face, int *Elem1, int *Elem2) const
Return the indices of the elements sharing face Face.
Definition mesh.cpp:1632
void GetFaceVertices(int i, Array< int > &vert) const
Returns the indices of the vertices of face i.
Definition mesh.hpp:1640
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:1348
Geometry::Type GetTypicalFaceGeometry() const
If the local mesh is not empty, return GetFaceGeometry(0); otherwise return a typical face geometry p...
Definition mesh.cpp:1671
Geometry::Type GetElementBaseGeometry(int i) const
Definition mesh.hpp:1569
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:5639
const NCList & GetNCList(int entity)
Return vertex/edge/face list (entity = 0/1/2, respectively).
Definition ncmesh.hpp:391
int GetEdgeNCOrientation(const MeshId &edge_id) const
Definition ncmesh.cpp:5658
int GetFaceVerticesEdges(const MeshId &face_id, int vert_index[4], int edge_index[4], int edge_orientation[4]) const
Definition ncmesh.cpp:5670
Geometry::Type GetFaceGeometry(int index) const
Return face geometry type. index is the Mesh face number.
Definition ncmesh.hpp:506
int GetNVertices() const
Return the number of vertices in the NCMesh.
Definition ncmesh.hpp:219
int MyRank
used in parallel, or when loading a parallel file in serial
Definition ncmesh.hpp:589
int GetNFaces() const
Return the number of (2D) faces in the NCMesh.
Definition ncmesh.hpp:223
int GetNEdges() const
Return the number of edges in the NCMesh.
Definition ncmesh.hpp:221
static const DenseMatrix & GetFaceTransform(int ori)
Definition doftrans.hpp:324
NURBSExtension generally contains multiple NURBSPatch objects spanning an entire Mesh....
Definition nurbs.hpp:575
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:27
int width
Dimension of the input / number of columns in the matrix.
Definition operator.hpp:30
int Height() const
Get the height (size of output) of the Operator. Synonym with NumRows().
Definition operator.hpp:68
int height
Dimension of the output / number of rows in the matrix.
Definition operator.hpp:29
int Width() const
Get the width (size of input) of the Operator. Synonym with NumCols().
Definition operator.hpp:74
@ ANY_TYPE
ID for the base class Operator, i.e. any type.
Definition operator.hpp:320
@ MFEM_SPARSEMAT
ID for class SparseMatrix.
Definition operator.hpp:321
@ Hypre_ParCSR
ID for class HypreParMatrix.
Definition operator.hpp:322
int NumRows() const
Get the number of rows (size of output) of the Operator. Synonym with Height().
Definition operator.hpp:71
Abstract parallel finite element space.
Definition pfespace.hpp:31
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 GetBoundaryLoopEdgeDofs(const Array< int > &boundary_element_indices, Array< int > &ess_tdof_list, Array< int > &boundary_edge_dofs_out, Array< int > *ldof_marker=nullptr, Array< int > *dof_edges=nullptr, Array< int > *dof_boundary_elements=nullptr, Array< int > *ess_edge_list=nullptr)
Extract the edge degrees of freedom of a boundary "loop" on a parallel mesh (see the serial FiniteEle...
void GetSharedTriangleDofs(int group, int fi, Array< int > &dofs) const
Definition pfespace.cpp:739
void GetSharedEdgeDofs(int group, int ei, Array< int > &dofs) const
Definition pfespace.cpp:715
int GetMaxElementOrder() const override
Returns the maximum polynomial order over all elements globally.
const FaceRestriction * GetFaceRestriction(ElementDofOrdering f_ordering, FaceType type, L2FaceValues mul=L2FaceValues::DoubleValued) const override
Definition pfespace.cpp:670
HYPRE_BigInt * GetTrueDofOffsets() const
Definition pfespace.hpp:358
void GetEssentialTrueDofs(const Array< int > &bdr_attr_is_ess, Array< int > &ess_tdof_list, int component=-1) const override
void GetExteriorTrueDofs(Array< int > &ext_tdof_list, int component=-1) const override
void GetExteriorVDofs(Array< int > &ext_dofs, int component=-1) const override
Determine the external degrees of freedom.
HYPRE_BigInt GlobalVSize() const
Definition pfespace.hpp:359
const Operator * GetRestrictionOperator() const override
int GetLocalTDofNumber(int ldof) const
friend struct ParDerefineMatrixOp
Definition pfespace.hpp:32
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.
void ApplyGhostElementOrdersToEdgesAndFaces(Array< VarOrderBits > &edge_orders, Array< VarOrderBits > &face_orders) const override
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:34
void DivideByGroupSize(real_t *vec)
Scale a vector of true dofs.
HYPRE_BigInt GlobalTrueVSize() const
Definition pfespace.hpp:361
int GetTrueVSize() const override
Return the number of local vector true dofs.
Definition pfespace.hpp:365
const FiniteElement * GetFaceNbrFE(int i, int ndofs=0) const
void GhostFaceOrderToEdges(const Array< VarOrderBits > &face_orders, Array< VarOrderBits > &edge_orders) const override
HYPRE_BigInt GetMyDofOffset() const
HYPRE_BigInt * GetDofOffsets() const
Definition pfespace.hpp:357
Array< HYPRE_BigInt > face_nbr_glob_dof_map
Definition pfespace.hpp:278
void GetEssentialTrueDofsVar(const Array< int > &bdr_attr_is_ess, const Array< int > &ess_dofs, Array< int > &true_ess_dofs, int component) const
void GetFaceNbrFaceVDofs(int i, Array< int > &vdofs) const
const Operator * GetProlongationMatrix() const override
bool HaveDofSigns() const
Return true if the parallel FE space has DOFs with signs opposite of the DOFs in the respective seria...
Definition pfespace.hpp:345
GroupCommunicator * ScalarGroupComm()
Return a new GroupCommunicator on scalar dofs, i.e. for VDim = 1.
bool OrderPropagation(const std::set< int > &edges, const std::set< int > &faces, Array< VarOrderBits > &edge_orders, Array< VarOrderBits > &face_orders) const override
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:403
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:647
void ApplyDofSigns(real_t *h_data) const
Apply the DOF signs to the given host data h_data which must be of size GetVSize() if HaveDofSigns() ...
Definition pfespace.cpp:575
HYPRE_BigInt GetMyTDofOffset() const
void GetSharedQuadrilateralDofs(int group, int fi, Array< int > &dofs) const
Definition pfespace.cpp:764
void GetElementDofs(int i, Array< int > &dofs, DofTransformation &doftrans) const override
The same as GetElementDofs(), but with a user-provided DofTransformation object.
Definition pfespace.cpp:593
void PRefineAndUpdate(const Array< pRefinement > &refs, bool want_transfer=true) override
const SparseMatrix * GetRestrictionMatrix() const override
Get the R matrix which restricts a local dof vector to true dof vector.
Definition pfespace.hpp:517
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...
int TrueVSize() const
Obsolete, kept for backward compatibility.
Definition pfespace.hpp:577
void MarkIntermediateEntityDofs(int entity, Array< bool > &intermediate) const
const FiniteElement * GetFaceNbrFaceFE(int i) const
const FiniteElement * GetFE(int i) const override
Definition pfespace.cpp:663
void GetBdrElementDofs(int i, Array< int > &dofs, DofTransformation &doftrans) const override
The same as GetBdrElementDofs(), but with a user-provided DofTransformation object.
Definition pfespace.cpp:620
Operator that extracts Face degrees of freedom in parallel.
Class for parallel meshes.
Definition pmesh.hpp:35
int GroupNQuadrilaterals(int group) const
Definition pmesh.hpp:478
Table send_face_nbr_elements
Definition pmesh.hpp:467
MPI_Comm GetComm() const
Definition pmesh.hpp:403
int GetMyRank() const
Definition pmesh.hpp:405
void ExchangeFaceNbrData(Table *gr_sface, int *s2l_face)
Definition pmesh.cpp:2188
int GetNRanks() const
Definition pmesh.hpp:404
int GroupVertex(int group, int i) const
Accessors for entities within a shared group structure.
Definition pmesh.hpp:491
void GetFaceNbrElementFaces(int i, Array< int > &faces, Array< int > &orientation) const
Definition pmesh.cpp:2843
void GetGlobalFaceIndices(Array< HYPRE_BigInt > &gi) const
AMR meshes are not supported.
Definition pmesh.cpp:6906
void GetGlobalEdgeIndices(Array< HYPRE_BigInt > &gi) const
AMR meshes are not supported.
Definition pmesh.cpp:6883
Array< Element * > face_nbr_elements
Definition pmesh.hpp:464
GroupTopology gtopo
Definition pmesh.hpp:457
int GroupNTriangles(int group) const
Definition pmesh.hpp:477
int GroupNEdges(int group) const
Definition pmesh.hpp:476
void GenerateOffsets(int N, HYPRE_BigInt loc_sizes[], Array< HYPRE_BigInt > *offsets[]) const
Definition pmesh.cpp:1953
Array< int > face_nbr_elements_offset
Definition pmesh.hpp:462
int GetNFaceNeighbors() const
Definition pmesh.hpp:578
void GroupQuadrilateral(int group, int i, int &face, int &o) const
Definition pmesh.cpp:1645
int GetNGroups() const
Definition pmesh.hpp:472
ParNCMesh * pncmesh
Definition pmesh.hpp:470
int GetFaceNbrRank(int fn) const
Definition pmesh.cpp:2825
void GroupTriangle(int group, int i, int &face, int &o) const
Definition pmesh.cpp:1634
void GroupEdge(int group, int i, int &edge, int &o) const
Definition pmesh.cpp:1626
int GroupNVertices(int group) const
Definition pmesh.hpp:475
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:2869
int GetFaceOrientation(int index) const
Return (shared) face orientation relative to its owner element.
Definition pncmesh.hpp:149
GroupId GetEntityGroupId(int entity, int index)
Definition pncmesh.hpp:172
int GetNGhostEdges() const
Definition pncmesh.hpp:123
void DecodeGroups(std::istream &is, Array< GroupId > &ids)
Definition pncmesh.cpp:3403
const CommGroup & GetGroup(GroupId id) const
Return a list of ranks contained in the group of the given ID.
Definition pncmesh.hpp:184
void FindEdgesOfGhostElement(int elem, Array< int > &edges)
Definition pncmesh.cpp:319
void FindFacesOfGhostElement(int elem, Array< int > &faces)
Definition pncmesh.cpp:345
int GetNGhostFaces() const
Definition pncmesh.hpp:124
void EncodeGroups(std::ostream &os, const Array< GroupId > &ids)
Definition pncmesh.cpp:3361
void FindEdgesOfGhostFace(int face, Array< int > &edges)
Definition pncmesh.cpp:298
void AdjustMeshIds(Array< MeshId > ids[], int rank)
Definition pncmesh.cpp:3123
std::vector< int > CommGroup
Definition pncmesh.hpp:155
void DecodeMeshIds(std::istream &is, Array< MeshId > ids[])
Definition pncmesh.cpp:3304
void EncodeMeshIds(std::ostream &os, Array< MeshId > ids[])
Definition pncmesh.cpp:3261
int GetMyRank() const
Return the MPI rank for this process.
Definition pncmesh.hpp:221
int GetNElements() const
Definition pncmesh.hpp:120
int GetNGhostVertices() const
Definition pncmesh.hpp:122
bool GroupContains(GroupId id, int rank) const
Return true if group 'id' contains the given rank.
Definition pncmesh.cpp:528
void CommunicateGhostData(const Array< VarOrderElemInfo > &sendData, Array< VarOrderElemInfo > &recvData)
Definition pncmesh.cpp:3716
Parallel version of NURBSExtension.
Definition nurbs.hpp:1148
GroupTopology gtopo
Definition nurbs.hpp:1167
Array< int > ldof_group
Definition nurbs.hpp:1169
Data type sparse matrix.
Definition sparsemat.hpp:51
void BooleanMult(const Array< int > &x, Array< int > &y) const
y = A * x, treating all entries as booleans (zero=false, nonzero=true).
Table stores the connectivity of elements of TYPE I to elements of TYPE II. For example,...
Definition table.hpp:43
int * GetJ()
Definition table.hpp:128
void AddConnections(int r, const int *c, int nc)
Definition table.cpp:152
int RowSize(int i) const
Definition table.hpp:122
void ShiftUpI()
Definition table.cpp:163
void Clear()
Definition table.cpp:420
void GetRow(int i, Array< int > &row) const
Return row i in array row (the Table must be finalized)
Definition table.cpp:233
void AddConnection(int r, int c)
Definition table.hpp:89
void MakeI(int nrows)
Definition table.cpp:130
int Size() const
Returns the number of TYPE I elements.
Definition table.hpp:103
int Size_of_connections() const
Returns the number of connections in the table.
Definition table.hpp:110
void AddColumnsInRow(int r, int ncol)
Definition table.hpp:87
void MakeJ()
Definition table.cpp:140
int * GetI()
Definition table.hpp:127
void AddAColumnInRow(int r)
Definition table.hpp:86
void SetDims(int rows, int nnz)
Set the rows and the number of all connections for the table.
Definition table.cpp:188
The transpose of a given operator. Switches the roles of the methods Mult() and MultTranspose().
Definition operator.hpp:922
General triple product operator x -> A*B*C*x, with ownership of the factors.
Vector data type.
Definition vector.hpp:82
virtual const real_t * HostRead() const
Shortcut for mfem::Read(vec.GetMemory(), vec.Size(), false).
Definition vector.hpp:524
MemoryView< Array< real_t > > GetArrayView()
Create a mutable (non-const) view of the Vector as Array<real_t> that can be used to pass Vector obje...
Definition vector.hpp:565
void SetSubVector(const Array< int > &dofs, const real_t value)
Set the entries listed in dofs to the given value.
Definition vector.cpp:702
int Size() const
Returns the size of the vector.
Definition vector.hpp:234
virtual real_t * HostWrite()
Shortcut for mfem::Write(vec.GetMemory(), vec.Size(), false).
Definition vector.hpp:532
const int * ess_tdof_list
int dim
Definition ex24.cpp:53
HYPRE_Int HYPRE_BigInt
int index(int i, int j, int nx, int ny)
Definition life.cpp:236
mfem::real_t real_t
void write(std::ostream &os, T value)
Write 'value' to stream.
Definition binaryio.hpp:37
T read(std::istream &is)
Read a value from the stream and return it.
Definition binaryio.hpp:44
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:505
OutStream out(std::cout)
Global stream used by the library for standard output. Initially it uses the same std::streambuf as s...
Definition globals.hpp:66
void Transpose(const Table &A, Table &At, int ncols_A_)
Transpose a Table.
Definition table.cpp:443
MFEM_HOST_DEVICE int FlipIndexSign(int i)
Signed indices i -> -1 - i are used as a convention to encode orientation.
Definition globals.hpp:117
MFEM_HOST_DEVICE int UnsignIndex(int i)
Definition globals.hpp:118
void Swap(T &a, T &b)
Swap objects of type T. The operation is performed using the most specialized swap function from the ...
Definition array.hpp:767
BiLinear2DFiniteElement QuadrilateralFE
float real_t
Definition config.hpp:46
double bisect(ElementTransformation &Tr, Coefficient *LvlSet)
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:49
std::function< real_t(const Vector &)> f(real_t mass_coeff)
Definition lor_mms.hpp:30
MFEM_EXPORT Linear2DFiniteElement TriangleFE
Definition fe.cpp:32
@ DEREFINEMENT_MATRIX_CONSTRUCTION_DATA
FaceType
Definition mesh.hpp:49
real_t p(const Vector &x, real_t t)
@ DEVICE_MASK
Biwise-OR of all device backends.
Definition device.hpp:104
MeshIdAndType GetMeshIdAndType(int index) const
Return a mesh id and type for a given nc index.
Definition ncmesh.cpp:3990
static void IsendAll(MapT &rank_msg, MPI_Comm comm)
static bool IProbe(int &rank, int &size, MPI_Comm comm)