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