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