5537 lines
164 KiB
C++
5537 lines
164 KiB
C++
// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
|
|
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
|
|
// LICENSE and NOTICE for details. LLNL-CODE-806117.
|
|
//
|
|
// This file is part of the MFEM library. For more information and source code
|
|
// availability visit https://mfem.org.
|
|
//
|
|
// MFEM is free software; you can redistribute it and/or modify it under the
|
|
// terms of the BSD-3 license. We welcome feedback and contributions, see file
|
|
// CONTRIBUTING.md for details.
|
|
|
|
#include "../config/config.hpp"
|
|
|
|
#ifdef MFEM_USE_MPI
|
|
|
|
#include "pfespace.hpp"
|
|
#include "prestriction.hpp"
|
|
#include "transfer.hpp"
|
|
|
|
#include "../general/forall.hpp"
|
|
#include "../general/sort_pairs.hpp"
|
|
#include "../mesh/mesh_headers.hpp"
|
|
#include "../general/binaryio.hpp"
|
|
|
|
#include "pderefmat_op.hpp"
|
|
|
|
#include <limits>
|
|
#include <list>
|
|
|
|
namespace mfem
|
|
{
|
|
ParFiniteElementSpace::ParFiniteElementSpace(
|
|
const ParFiniteElementSpace &orig, ParMesh *pmesh,
|
|
const FiniteElementCollection *fec)
|
|
: FiniteElementSpace(orig, pmesh, fec)
|
|
{
|
|
ParInit(pmesh ? pmesh : orig.pmesh);
|
|
}
|
|
|
|
ParFiniteElementSpace::ParFiniteElementSpace(
|
|
const FiniteElementSpace &orig, ParMesh &pmesh,
|
|
const FiniteElementCollection *fec)
|
|
: FiniteElementSpace(orig, &pmesh, fec)
|
|
{
|
|
ParInit(&pmesh);
|
|
}
|
|
|
|
ParFiniteElementSpace::ParFiniteElementSpace(
|
|
ParMesh *pm, const FiniteElementSpace *global_fes, const int *partitioning,
|
|
const FiniteElementCollection *f)
|
|
: FiniteElementSpace(pm, MakeLocalNURBSext(global_fes->GetNURBSext(),
|
|
pm->NURBSext),
|
|
f ? f : global_fes->FEColl(),
|
|
global_fes->GetVDim(), global_fes->GetOrdering())
|
|
{
|
|
ParInit(pm);
|
|
// For NURBS spaces, the variable-order data is contained in the
|
|
// NURBSExtension of 'global_fes' and inside the ParNURBSExtension of 'pm'.
|
|
|
|
// TODO: when general variable-order support is added, copy the local portion
|
|
// of the variable-order data from 'global_fes' to 'this'.
|
|
}
|
|
|
|
ParFiniteElementSpace::ParFiniteElementSpace(
|
|
ParMesh *pm, const FiniteElementCollection *f, int dim, int ordering)
|
|
: FiniteElementSpace(pm, f, dim, ordering)
|
|
{
|
|
ParInit(pm);
|
|
}
|
|
|
|
ParFiniteElementSpace::ParFiniteElementSpace(
|
|
ParMesh *pm, NURBSExtension *ext, const FiniteElementCollection *f,
|
|
int dim, int ordering)
|
|
: FiniteElementSpace(pm, ext, f, dim, ordering)
|
|
{
|
|
ParInit(pm);
|
|
}
|
|
|
|
// static method
|
|
ParNURBSExtension *ParFiniteElementSpace::MakeLocalNURBSext(
|
|
const NURBSExtension *globNURBSext, const NURBSExtension *parNURBSext)
|
|
{
|
|
if (globNURBSext == NULL) { return NULL; }
|
|
const ParNURBSExtension *pNURBSext =
|
|
dynamic_cast<const ParNURBSExtension*>(parNURBSext);
|
|
MFEM_ASSERT(pNURBSext, "need a ParNURBSExtension");
|
|
// make a copy of globNURBSext:
|
|
NURBSExtension *tmp_globNURBSext = new NURBSExtension(*globNURBSext);
|
|
// tmp_globNURBSext will be deleted by the following ParNURBSExtension ctor:
|
|
return new ParNURBSExtension(tmp_globNURBSext, pNURBSext);
|
|
}
|
|
|
|
void ParFiniteElementSpace::ParInit(ParMesh *pm)
|
|
{
|
|
pmesh = pm;
|
|
pncmesh = nullptr;
|
|
|
|
MyComm = pmesh->GetComm();
|
|
NRanks = pmesh->GetNRanks();
|
|
MyRank = pmesh->GetMyRank();
|
|
|
|
gcomm = nullptr;
|
|
|
|
P = nullptr;
|
|
Pconf = nullptr;
|
|
nonconf_P = false;
|
|
Rconf = nullptr;
|
|
R = nullptr;
|
|
num_face_nbr_dofs = -1;
|
|
|
|
if (NURBSext && !pNURBSext())
|
|
{
|
|
// This is necessary in some cases: e.g. when the FiniteElementSpace
|
|
// constructor creates a serial NURBSExtension of higher order than the
|
|
// mesh NURBSExtension.
|
|
MFEM_ASSERT(own_ext, "internal error");
|
|
|
|
ParNURBSExtension *pNe = new ParNURBSExtension(
|
|
NURBSext, dynamic_cast<ParNURBSExtension *>(pmesh->NURBSext));
|
|
// serial NURBSext is destroyed by the above constructor
|
|
NURBSext = pNe;
|
|
UpdateNURBS();
|
|
}
|
|
|
|
Construct(); // parallel version of Construct().
|
|
|
|
// Apply the ldof_signs to the elem_dof Table
|
|
if (Conforming() && !NURBSext)
|
|
{
|
|
ApplyLDofSigns(*elem_dof);
|
|
}
|
|
|
|
// Check for shared triangular faces with interior Nedelec DoFs
|
|
CheckNDSTriaDofs();
|
|
}
|
|
|
|
void ParFiniteElementSpace::CommunicateGhostOrder()
|
|
{
|
|
// Variable-order space needs a nontrivial P matrix + also ghost elements
|
|
// in parallel, we thus require the mesh to be NC.
|
|
MFEM_VERIFY(variableOrder && Nonconforming(),
|
|
"Variable-order space requires a nonconforming mesh.");
|
|
|
|
// Check whether h-refinement was done.
|
|
const bool href = mesh->GetLastOperation() == Mesh::REFINE &&
|
|
mesh->GetSequence() != mesh_sequence;
|
|
if (href && mesh->GetSequence() != mesh_sequence + 1)
|
|
{
|
|
MFEM_ABORT("Error in update sequence. Space needs to be updated after "
|
|
"each mesh modification.");
|
|
}
|
|
|
|
if (href)
|
|
{
|
|
// Update elems_pref and elem_orders
|
|
FiniteElementSpace::UpdateElementOrders();
|
|
}
|
|
|
|
int local_orders_changed = orders_changed;
|
|
int global_orders_changed = 0;
|
|
|
|
MPI_Allreduce(&local_orders_changed, &global_orders_changed, 1, MPI_INT,
|
|
MPI_MAX, MyComm);
|
|
|
|
if ((global_orders_changed == 0 && !href) || NRanks == 1)
|
|
{
|
|
return;
|
|
}
|
|
|
|
MFEM_ASSERT(mesh->GetNE() == pncmesh->GetNElements(), "");
|
|
|
|
Array<ParNCMesh::VarOrderElemInfo> localOrders(mesh->GetNE());
|
|
for (int i=0; i<mesh->GetNE(); ++i)
|
|
{
|
|
ParNCMesh::VarOrderElemInfo order_i{(unsigned int) i, elem_order[i]};
|
|
localOrders[i] = order_i;
|
|
}
|
|
|
|
pncmesh->CommunicateGhostData(localOrders, ghost_orders);
|
|
}
|
|
|
|
void ParFiniteElementSpace::Construct()
|
|
{
|
|
if (NURBSext)
|
|
{
|
|
ConstructTrueNURBSDofs();
|
|
GenerateGlobalOffsets();
|
|
}
|
|
else if (Conforming())
|
|
{
|
|
ConstructTrueDofs();
|
|
GenerateGlobalOffsets();
|
|
}
|
|
else // Nonconforming()
|
|
{
|
|
pncmesh = pmesh->pncmesh;
|
|
|
|
// Initialize 'gcomm' for the cut (aka "partially conforming") space.
|
|
// In the process, the array 'ldof_ltdof' is also initialized (for the cut
|
|
// space) and used; however, it will be overwritten below with the real
|
|
// true dofs. Also, 'ldof_sign' and 'ldof_group' are constructed for the
|
|
// cut space.
|
|
ConstructTrueDofs();
|
|
|
|
ngedofs = ngfdofs = 0;
|
|
|
|
// calculate number of ghost DOFs
|
|
ngvdofs = pncmesh->GetNGhostVertices()
|
|
* fec->DofForGeometry(Geometry::Type::POINT);
|
|
|
|
if (pmesh->Dimension() > 1)
|
|
{
|
|
if (IsVariableOrder())
|
|
{
|
|
// Note that this requires fespace to have edge order and DOF info
|
|
// for ghost edges, so var_edge_dofs must include ghost edges.
|
|
// These are set by ApplyGhostElementOrdersToEdgesAndFaces, which is
|
|
// called by CalcEdgeFaceVarOrders.
|
|
for (int i = 0; i < pncmesh->GetNGhostEdges(); ++i)
|
|
{
|
|
const int ghostEdge = pncmesh->GetNEdges() + i;
|
|
const int nvar = GetNVariants(1, ghostEdge);
|
|
for (int var=0; var<nvar; ++var)
|
|
{
|
|
const int eo = GetEdgeOrder(ghostEdge, var);
|
|
const int dofs = fec->GetNumDof(Geometry::SEGMENT, eo);
|
|
ngedofs += dofs;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ngedofs = pncmesh->GetNGhostEdges()
|
|
* fec->DofForGeometry(Geometry::Type::SEGMENT);
|
|
}
|
|
}
|
|
|
|
if (pmesh->Dimension() > 2)
|
|
{
|
|
if (IsVariableOrder())
|
|
{
|
|
// Note that this requires fespace to have face order and DOF info
|
|
// for ghost faces, so var_face_dofs must include ghost faces.
|
|
// These are set by ApplyGhostElementOrdersToEdgesAndFaces, which is
|
|
// called by CalcEdgeFaceVarOrders.
|
|
for (int i = 0; i < pncmesh->GetNGhostFaces(); ++i)
|
|
{
|
|
const int ghostFace = pncmesh->GetNFaces() + i;
|
|
const int nvar = GetNVariants(2, ghostFace);
|
|
for (int var=0; var<nvar; ++var)
|
|
{
|
|
const int fo = GetFaceOrder(ghostFace, var);
|
|
const int dofs = fec->GetNumDof(Geometry::SQUARE, fo);
|
|
ngfdofs += dofs;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ngfdofs = pncmesh->GetNGhostFaces()
|
|
* fec->DofForGeometry(Geometry::Type::SQUARE);
|
|
}
|
|
}
|
|
|
|
// Total number of ghost DOFs. Ghost DOFs start at index 'ndofs', i.e.,
|
|
// after all regular DOFs. Ghost element internal ("bubble") DOFs are not
|
|
// included.
|
|
ngdofs = ngvdofs + ngedofs + ngfdofs;
|
|
|
|
if (IsVariableOrder())
|
|
{
|
|
SetVarDofMap(var_edge_dofs, var_edge_dofmap);
|
|
SetVarDofMap(var_face_dofs, var_face_dofmap);
|
|
}
|
|
|
|
// get P and R matrices, initialize DOF offsets, etc. NOTE: in the NC
|
|
// case this needs to be done here to get the number of true DOFs
|
|
ltdof_size = BuildParallelConformingInterpolation(
|
|
&P, &R, dof_offsets, tdof_offsets, &ldof_ltdof, false);
|
|
|
|
// TODO future: split BuildParallelConformingInterpolation into two parts
|
|
// to overlap its communication with processing between this constructor
|
|
// and the point where the P matrix is actually needed.
|
|
}
|
|
}
|
|
|
|
void ParFiniteElementSpace::PrintPartitionStats()
|
|
{
|
|
long long ltdofs = ltdof_size;
|
|
long long min_ltdofs, max_ltdofs, sum_ltdofs;
|
|
|
|
MPI_Reduce(<dofs, &min_ltdofs, 1, MPI_LONG_LONG, MPI_MIN, 0, MyComm);
|
|
MPI_Reduce(<dofs, &max_ltdofs, 1, MPI_LONG_LONG, MPI_MAX, 0, MyComm);
|
|
MPI_Reduce(<dofs, &sum_ltdofs, 1, MPI_LONG_LONG, MPI_SUM, 0, MyComm);
|
|
|
|
if (MyRank == 0)
|
|
{
|
|
real_t avg = real_t(sum_ltdofs) / NRanks;
|
|
mfem::out << "True DOF partitioning: min " << min_ltdofs
|
|
<< ", avg " << std::fixed << std::setprecision(1) << avg
|
|
<< ", max " << max_ltdofs
|
|
<< ", (max-avg)/avg " << 100.0*(max_ltdofs - avg)/avg
|
|
<< "%" << std::endl;
|
|
}
|
|
|
|
if (NRanks <= 32)
|
|
{
|
|
if (MyRank == 0)
|
|
{
|
|
mfem::out << "True DOFs by rank: " << ltdofs;
|
|
for (int i = 1; i < NRanks; i++)
|
|
{
|
|
MPI_Status status;
|
|
MPI_Recv(<dofs, 1, MPI_LONG_LONG, i, 123, MyComm, &status);
|
|
mfem::out << " " << ltdofs;
|
|
}
|
|
mfem::out << "\n";
|
|
}
|
|
else
|
|
{
|
|
MPI_Send(<dofs, 1, MPI_LONG_LONG, 0, 123, MyComm);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ParFiniteElementSpace::GetGroupComm(
|
|
GroupCommunicator &gc, int ldof_type, Array<int> *g_ldof_sign)
|
|
{
|
|
int gr;
|
|
int ng = pmesh->GetNGroups();
|
|
int nvd, ned, ntd = 0, nqd = 0;
|
|
Array<int> dofs;
|
|
|
|
int group_ldof_counter;
|
|
Table &group_ldof = gc.GroupLDofTable();
|
|
|
|
nvd = fec->DofForGeometry(Geometry::POINT);
|
|
ned = fec->DofForGeometry(Geometry::SEGMENT);
|
|
|
|
if (mesh->Dimension() >= 3)
|
|
{
|
|
if (mesh->HasGeometry(Geometry::TRIANGLE))
|
|
{
|
|
ntd = fec->DofForGeometry(Geometry::TRIANGLE);
|
|
}
|
|
if (mesh->HasGeometry(Geometry::SQUARE))
|
|
{
|
|
nqd = fec->DofForGeometry(Geometry::SQUARE);
|
|
}
|
|
}
|
|
|
|
if (g_ldof_sign)
|
|
{
|
|
g_ldof_sign->SetSize(GetNDofs());
|
|
*g_ldof_sign = 1;
|
|
}
|
|
|
|
// count the number of ldofs in all groups (excluding the local group 0)
|
|
group_ldof_counter = 0;
|
|
for (gr = 1; gr < ng; gr++)
|
|
{
|
|
group_ldof_counter += nvd * pmesh->GroupNVertices(gr);
|
|
group_ldof_counter += ned * pmesh->GroupNEdges(gr);
|
|
group_ldof_counter += ntd * pmesh->GroupNTriangles(gr);
|
|
group_ldof_counter += nqd * pmesh->GroupNQuadrilaterals(gr);
|
|
}
|
|
if (ldof_type)
|
|
{
|
|
group_ldof_counter *= vdim;
|
|
}
|
|
// allocate the I and J arrays in group_ldof
|
|
group_ldof.SetDims(ng, group_ldof_counter);
|
|
|
|
// build the full group_ldof table
|
|
group_ldof_counter = 0;
|
|
group_ldof.GetI()[0] = group_ldof.GetI()[1] = 0;
|
|
for (gr = 1; gr < ng; gr++)
|
|
{
|
|
int j, k, l, m, o, nv, ne, nt, nq;
|
|
const int *ind;
|
|
|
|
nv = pmesh->GroupNVertices(gr);
|
|
ne = pmesh->GroupNEdges(gr);
|
|
nt = pmesh->GroupNTriangles(gr);
|
|
nq = pmesh->GroupNQuadrilaterals(gr);
|
|
|
|
// vertices
|
|
if (nvd > 0)
|
|
{
|
|
for (j = 0; j < nv; j++)
|
|
{
|
|
k = pmesh->GroupVertex(gr, j);
|
|
|
|
dofs.SetSize(nvd);
|
|
m = nvd * k;
|
|
for (l = 0; l < nvd; l++, m++)
|
|
{
|
|
dofs[l] = m;
|
|
}
|
|
|
|
if (ldof_type)
|
|
{
|
|
DofsToVDofs(dofs);
|
|
}
|
|
|
|
for (l = 0; l < dofs.Size(); l++)
|
|
{
|
|
group_ldof.GetJ()[group_ldof_counter++] = dofs[l];
|
|
}
|
|
}
|
|
}
|
|
|
|
// edges
|
|
if (ned > 0)
|
|
{
|
|
for (j = 0; j < ne; j++)
|
|
{
|
|
pmesh->GroupEdge(gr, j, k, o);
|
|
|
|
dofs.SetSize(ned);
|
|
m = nvdofs+k*ned;
|
|
ind = fec->DofOrderForOrientation(Geometry::SEGMENT, o);
|
|
for (l = 0; l < ned; l++)
|
|
{
|
|
if (ind[l] < 0)
|
|
{
|
|
dofs[l] = m + FlipIndexSign(ind[l]);
|
|
if (g_ldof_sign)
|
|
{
|
|
(*g_ldof_sign)[dofs[l]] = -1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
dofs[l] = m + ind[l];
|
|
}
|
|
}
|
|
|
|
if (ldof_type)
|
|
{
|
|
DofsToVDofs(dofs);
|
|
}
|
|
|
|
for (l = 0; l < dofs.Size(); l++)
|
|
{
|
|
group_ldof.GetJ()[group_ldof_counter++] = dofs[l];
|
|
}
|
|
}
|
|
}
|
|
|
|
// triangles
|
|
if (ntd > 0)
|
|
{
|
|
for (j = 0; j < nt; j++)
|
|
{
|
|
pmesh->GroupTriangle(gr, j, k, o);
|
|
|
|
dofs.SetSize(ntd);
|
|
m = nvdofs + nedofs + FirstFaceDof(k);
|
|
ind = fec->DofOrderForOrientation(Geometry::TRIANGLE, o);
|
|
for (l = 0; l < ntd; l++)
|
|
{
|
|
if (ind[l] < 0)
|
|
{
|
|
dofs[l] = m + FlipIndexSign(ind[l]);
|
|
if (g_ldof_sign)
|
|
{
|
|
(*g_ldof_sign)[dofs[l]] = -1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
dofs[l] = m + ind[l];
|
|
}
|
|
}
|
|
|
|
if (ldof_type)
|
|
{
|
|
DofsToVDofs(dofs);
|
|
}
|
|
|
|
for (l = 0; l < dofs.Size(); l++)
|
|
{
|
|
group_ldof.GetJ()[group_ldof_counter++] = dofs[l];
|
|
}
|
|
}
|
|
}
|
|
|
|
// quadrilaterals
|
|
if (nqd > 0)
|
|
{
|
|
for (j = 0; j < nq; j++)
|
|
{
|
|
pmesh->GroupQuadrilateral(gr, j, k, o);
|
|
|
|
dofs.SetSize(nqd);
|
|
m = nvdofs + nedofs + FirstFaceDof(k);
|
|
ind = fec->DofOrderForOrientation(Geometry::SQUARE, o);
|
|
for (l = 0; l < nqd; l++)
|
|
{
|
|
if (ind[l] < 0)
|
|
{
|
|
dofs[l] = m + FlipIndexSign(ind[l]);
|
|
if (g_ldof_sign)
|
|
{
|
|
(*g_ldof_sign)[dofs[l]] = -1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
dofs[l] = m + ind[l];
|
|
}
|
|
}
|
|
|
|
if (ldof_type)
|
|
{
|
|
DofsToVDofs(dofs);
|
|
}
|
|
|
|
for (l = 0; l < dofs.Size(); l++)
|
|
{
|
|
group_ldof.GetJ()[group_ldof_counter++] = dofs[l];
|
|
}
|
|
}
|
|
}
|
|
|
|
group_ldof.GetI()[gr+1] = group_ldof_counter;
|
|
}
|
|
|
|
gc.Finalize();
|
|
}
|
|
|
|
void ParFiniteElementSpace::ApplyLDofSigns(Array<int> &dofs) const
|
|
{
|
|
MFEM_ASSERT(Conforming(), "wrong code path");
|
|
|
|
for (int i = 0; i < dofs.Size(); i++)
|
|
{
|
|
if (dofs[i] < 0)
|
|
{
|
|
if (ldof_sign[FlipIndexSign(dofs[i])] < 0)
|
|
{
|
|
dofs[i] = FlipIndexSign(dofs[i]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (ldof_sign[dofs[i]] < 0)
|
|
{
|
|
dofs[i] = FlipIndexSign(dofs[i]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ParFiniteElementSpace::ApplyLDofSigns(Table &el_dof) const
|
|
{
|
|
Array<int> all_dofs(el_dof.GetJ(), el_dof.Size_of_connections());
|
|
ApplyLDofSigns(all_dofs);
|
|
}
|
|
|
|
void ParFiniteElementSpace::GetElementDofs(int i, Array<int> &dofs,
|
|
DofTransformation &doftrans) const
|
|
{
|
|
doftrans.SetDofTransformation(nullptr);
|
|
|
|
if (elem_dof)
|
|
{
|
|
elem_dof->GetRow(i, dofs);
|
|
|
|
if (DoFTransArray[mesh->GetElementBaseGeometry(i)])
|
|
{
|
|
Array<int> Fo;
|
|
elem_fos->GetRow(i, Fo);
|
|
doftrans.SetDofTransformation(
|
|
*DoFTransArray[mesh->GetElementBaseGeometry(i)]);
|
|
doftrans.SetFaceOrientations(Fo);
|
|
doftrans.SetVDim();
|
|
}
|
|
return;
|
|
}
|
|
FiniteElementSpace::GetElementDofs(i, dofs, doftrans);
|
|
if (Conforming())
|
|
{
|
|
ApplyLDofSigns(dofs);
|
|
}
|
|
}
|
|
|
|
void ParFiniteElementSpace::GetBdrElementDofs(int i, Array<int> &dofs,
|
|
DofTransformation &doftrans) const
|
|
{
|
|
doftrans.SetDofTransformation(nullptr);
|
|
|
|
if (bdr_elem_dof)
|
|
{
|
|
bdr_elem_dof->GetRow(i, dofs);
|
|
|
|
if (DoFTransArray[mesh->GetBdrElementGeometry(i)])
|
|
{
|
|
Array<int> Fo;
|
|
bdr_elem_fos->GetRow(i, Fo);
|
|
doftrans.SetDofTransformation(
|
|
*DoFTransArray[mesh->GetBdrElementGeometry(i)]);
|
|
doftrans.SetFaceOrientations(Fo);
|
|
doftrans.SetVDim();
|
|
}
|
|
return;
|
|
}
|
|
FiniteElementSpace::GetBdrElementDofs(i, dofs, doftrans);
|
|
if (Conforming())
|
|
{
|
|
ApplyLDofSigns(dofs);
|
|
}
|
|
}
|
|
|
|
int ParFiniteElementSpace::GetFaceDofs(int i, Array<int> &dofs,
|
|
int variant) const
|
|
{
|
|
if (face_dof != nullptr && variant == 0)
|
|
{
|
|
face_dof->GetRow(i, dofs);
|
|
return fec->GetOrder();
|
|
}
|
|
int p = FiniteElementSpace::GetFaceDofs(i, dofs, variant);
|
|
if (Conforming())
|
|
{
|
|
ApplyLDofSigns(dofs);
|
|
}
|
|
return p;
|
|
}
|
|
|
|
const FiniteElement *ParFiniteElementSpace::GetFE(int i) const
|
|
{
|
|
int ne = mesh->GetNE();
|
|
if (i >= ne) { return GetFaceNbrFE(i - ne); }
|
|
else { return FiniteElementSpace::GetFE(i); }
|
|
}
|
|
|
|
const FaceRestriction *ParFiniteElementSpace::GetFaceRestriction(
|
|
ElementDofOrdering f_ordering, FaceType type, L2FaceValues mul) const
|
|
{
|
|
const bool is_dg_space = IsDGSpace();
|
|
const L2FaceValues m = (is_dg_space && mul==L2FaceValues::DoubleValued) ?
|
|
L2FaceValues::DoubleValued : L2FaceValues::SingleValued;
|
|
auto key = std::make_tuple(is_dg_space, f_ordering, type, m);
|
|
auto itr = L2F.find(key);
|
|
if (itr != L2F.end())
|
|
{
|
|
return itr->second.get();
|
|
}
|
|
else
|
|
{
|
|
std::unique_ptr<FaceRestriction> res;
|
|
if (is_dg_space)
|
|
{
|
|
if (Conforming())
|
|
{
|
|
res.reset(new ParL2FaceRestriction(*this, f_ordering, type, m));
|
|
}
|
|
else
|
|
{
|
|
res.reset(new ParNCL2FaceRestriction(*this, f_ordering, type, m));
|
|
}
|
|
}
|
|
else if (dynamic_cast<const DG_Interface_FECollection*>(fec))
|
|
{
|
|
res.reset(new L2InterfaceFaceRestriction(*this, f_ordering, type));
|
|
}
|
|
else
|
|
{
|
|
if (Conforming())
|
|
{
|
|
res.reset(new ConformingFaceRestriction(*this, f_ordering, type));
|
|
}
|
|
else
|
|
{
|
|
res.reset(new ParNCH1FaceRestriction(*this, f_ordering, type));
|
|
}
|
|
}
|
|
return L2F.emplace(key, std::move(res)).first->second.get();
|
|
}
|
|
}
|
|
|
|
void ParFiniteElementSpace::GetSharedEdgeDofs(
|
|
int group, int ei, Array<int> &dofs) const
|
|
{
|
|
int l_edge, ori;
|
|
MFEM_ASSERT(0 <= ei && ei < pmesh->GroupNEdges(group), "invalid edge index");
|
|
pmesh->GroupEdge(group, ei, l_edge, ori);
|
|
if (ori > 0) // ori = +1 or -1
|
|
{
|
|
GetEdgeDofs(l_edge, dofs);
|
|
}
|
|
else
|
|
{
|
|
Array<int> rdofs;
|
|
fec->SubDofOrder(Geometry::SEGMENT, 1, 1, dofs);
|
|
GetEdgeDofs(l_edge, rdofs);
|
|
for (int i = 0; i < dofs.Size(); i++)
|
|
{
|
|
const int di = dofs[i];
|
|
dofs[i] = di >= 0 ? rdofs[di] :
|
|
FlipIndexSign(rdofs[FlipIndexSign(di)]);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ParFiniteElementSpace::GetSharedTriangleDofs(
|
|
int group, int fi, Array<int> &dofs) const
|
|
{
|
|
int l_face, ori;
|
|
MFEM_ASSERT(0 <= fi && fi < pmesh->GroupNTriangles(group),
|
|
"invalid triangular face index");
|
|
pmesh->GroupTriangle(group, fi, l_face, ori);
|
|
if (ori == 0)
|
|
{
|
|
GetFaceDofs(l_face, dofs);
|
|
}
|
|
else
|
|
{
|
|
Array<int> rdofs;
|
|
fec->SubDofOrder(Geometry::TRIANGLE, 2, ori, dofs);
|
|
GetFaceDofs(l_face, rdofs);
|
|
for (int i = 0; i < dofs.Size(); i++)
|
|
{
|
|
const int di = dofs[i];
|
|
dofs[i] = di >= 0 ? rdofs[di] :
|
|
FlipIndexSign(rdofs[FlipIndexSign(di)]);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ParFiniteElementSpace::GetSharedQuadrilateralDofs(
|
|
int group, int fi, Array<int> &dofs) const
|
|
{
|
|
int l_face, ori;
|
|
MFEM_ASSERT(0 <= fi && fi < pmesh->GroupNQuadrilaterals(group),
|
|
"invalid quadrilateral face index");
|
|
pmesh->GroupQuadrilateral(group, fi, l_face, ori);
|
|
if (ori == 0)
|
|
{
|
|
GetFaceDofs(l_face, dofs);
|
|
}
|
|
else
|
|
{
|
|
Array<int> rdofs;
|
|
fec->SubDofOrder(Geometry::SQUARE, 2, ori, dofs);
|
|
GetFaceDofs(l_face, rdofs);
|
|
for (int i = 0; i < dofs.Size(); i++)
|
|
{
|
|
const int di = dofs[i];
|
|
dofs[i] = (di >= 0) ? rdofs[di] :
|
|
FlipIndexSign(rdofs[FlipIndexSign(di)]);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ParFiniteElementSpace::GenerateGlobalOffsets() const
|
|
{
|
|
MFEM_ASSERT(Conforming(), "wrong code path");
|
|
|
|
HYPRE_BigInt ldof[2];
|
|
Array<HYPRE_BigInt> *offsets[2] = { &dof_offsets, &tdof_offsets };
|
|
|
|
ldof[0] = GetVSize();
|
|
ldof[1] = TrueVSize();
|
|
|
|
pmesh->GenerateOffsets(2, ldof, offsets);
|
|
|
|
if (HYPRE_AssumedPartitionCheck())
|
|
{
|
|
// communicate the neighbor offsets in tdof_nb_offsets
|
|
GroupTopology > = GetGroupTopo();
|
|
int nsize = gt.GetNumNeighbors()-1;
|
|
MPI_Request *requests = new MPI_Request[2*nsize];
|
|
MPI_Status *statuses = new MPI_Status[2*nsize];
|
|
tdof_nb_offsets.SetSize(nsize+1);
|
|
tdof_nb_offsets[0] = tdof_offsets[0];
|
|
|
|
// send and receive neighbors' local tdof offsets
|
|
int request_counter = 0;
|
|
for (int i = 1; i <= nsize; i++)
|
|
{
|
|
MPI_Irecv(&tdof_nb_offsets[i], 1, HYPRE_MPI_BIG_INT,
|
|
gt.GetNeighborRank(i), 5365, MyComm,
|
|
&requests[request_counter++]);
|
|
}
|
|
for (int i = 1; i <= nsize; i++)
|
|
{
|
|
MPI_Isend(&tdof_nb_offsets[0], 1, HYPRE_MPI_BIG_INT,
|
|
gt.GetNeighborRank(i), 5365, MyComm,
|
|
&requests[request_counter++]);
|
|
}
|
|
MPI_Waitall(request_counter, requests, statuses);
|
|
|
|
delete [] statuses;
|
|
delete [] requests;
|
|
}
|
|
}
|
|
|
|
void ParFiniteElementSpace::CheckNDSTriaDofs()
|
|
{
|
|
// Check for Nedelec basis
|
|
bool nd_basis = dynamic_cast<const ND_FECollection*>(fec);
|
|
if (!nd_basis)
|
|
{
|
|
nd_strias = false;
|
|
return;
|
|
}
|
|
|
|
// Check for interior face dofs on triangles (the use of TETRAHEDRON
|
|
// is not an error)
|
|
bool nd_fdof = fec->HasFaceDofs(Geometry::TETRAHEDRON,
|
|
GetMaxElementOrder());
|
|
if (!nd_fdof)
|
|
{
|
|
nd_strias = false;
|
|
return;
|
|
}
|
|
|
|
// Check for shared triangle faces
|
|
bool strias = false;
|
|
{
|
|
int ngrps = pmesh->GetNGroups();
|
|
for (int g = 1; g < ngrps; g++)
|
|
{
|
|
strias |= pmesh->GroupNTriangles(g);
|
|
}
|
|
}
|
|
|
|
// Combine results
|
|
int loc_nd_strias = strias ? 1 : 0;
|
|
int glb_nd_strias = 0;
|
|
MPI_Allreduce(&loc_nd_strias, &glb_nd_strias, 1, MPI_INT, MPI_SUM, MyComm);
|
|
nd_strias = glb_nd_strias > 0;
|
|
}
|
|
|
|
void ParFiniteElementSpace::Build_Dof_TrueDof_Matrix() const // matrix P
|
|
{
|
|
MFEM_ASSERT(Conforming(), "wrong code path");
|
|
|
|
if (P) { return; }
|
|
|
|
if (!nd_strias)
|
|
{
|
|
// Safe to assume 1-1 correspondence between shared dofs
|
|
int ldof = GetVSize();
|
|
int ltdof = TrueVSize();
|
|
|
|
HYPRE_Int *i_diag = Memory<HYPRE_Int>(ldof+1);
|
|
HYPRE_Int *j_diag = Memory<HYPRE_Int>(ltdof);
|
|
int diag_counter;
|
|
|
|
HYPRE_Int *i_offd = Memory<HYPRE_Int>(ldof+1);
|
|
HYPRE_Int *j_offd = Memory<HYPRE_Int>(ldof-ltdof);
|
|
int offd_counter;
|
|
|
|
HYPRE_BigInt *cmap = Memory<HYPRE_BigInt>(ldof-ltdof);
|
|
|
|
HYPRE_BigInt *col_starts = GetTrueDofOffsets();
|
|
HYPRE_BigInt *row_starts = GetDofOffsets();
|
|
|
|
Array<Pair<HYPRE_BigInt, int> > cmap_j_offd(ldof-ltdof);
|
|
|
|
i_diag[0] = i_offd[0] = 0;
|
|
diag_counter = offd_counter = 0;
|
|
for (int i = 0; i < ldof; i++)
|
|
{
|
|
int ltdof_i = GetLocalTDofNumber(i);
|
|
if (ltdof_i >= 0)
|
|
{
|
|
j_diag[diag_counter++] = ltdof_i;
|
|
}
|
|
else
|
|
{
|
|
cmap_j_offd[offd_counter].one = GetGlobalTDofNumber(i);
|
|
cmap_j_offd[offd_counter].two = offd_counter;
|
|
offd_counter++;
|
|
}
|
|
i_diag[i+1] = diag_counter;
|
|
i_offd[i+1] = offd_counter;
|
|
}
|
|
|
|
SortPairs<HYPRE_BigInt, int>(cmap_j_offd, offd_counter);
|
|
|
|
for (int i = 0; i < offd_counter; i++)
|
|
{
|
|
cmap[i] = cmap_j_offd[i].one;
|
|
j_offd[cmap_j_offd[i].two] = i;
|
|
}
|
|
|
|
P = new HypreParMatrix(MyComm, MyRank, NRanks, row_starts, col_starts,
|
|
i_diag, j_diag, i_offd, j_offd,
|
|
cmap, offd_counter);
|
|
}
|
|
else
|
|
{
|
|
// Some shared dofs will be linear combinations of others
|
|
HYPRE_BigInt ldof = GetVSize();
|
|
HYPRE_BigInt ltdof = TrueVSize();
|
|
|
|
HYPRE_BigInt gdof = -1;
|
|
HYPRE_BigInt gtdof = -1;
|
|
|
|
MPI_Allreduce(&ldof, &gdof, 1, HYPRE_MPI_BIG_INT, MPI_SUM, MyComm);
|
|
MPI_Allreduce(<dof, >dof, 1, HYPRE_MPI_BIG_INT, MPI_SUM, MyComm);
|
|
|
|
// Ensure face orientations have been communicated
|
|
pmesh->ExchangeFaceNbrData();
|
|
|
|
// Locate and count non-zeros in off-diagonal portion of P
|
|
int nnz_offd = 0;
|
|
Array<int> ldsize(ldof); ldsize = 0;
|
|
Array<int> ltori(ldof); ltori = 0; // Local triangle orientations
|
|
{
|
|
int ngrps = pmesh->GetNGroups();
|
|
int nedofs = fec->DofForGeometry(Geometry::SEGMENT);
|
|
Array<int> sdofs;
|
|
for (int g = 1; g < ngrps; g++)
|
|
{
|
|
if (pmesh->gtopo.IAmMaster(g))
|
|
{
|
|
continue;
|
|
}
|
|
for (int ei=0; ei<pmesh->GroupNEdges(g); ei++)
|
|
{
|
|
this->GetSharedEdgeDofs(g, ei, sdofs);
|
|
for (int i=0; i<sdofs.Size(); i++)
|
|
{
|
|
int ind = (sdofs[i]>=0) ? sdofs[i] : (-sdofs[i]-1);
|
|
if (ldsize[ind] == 0) { nnz_offd++; }
|
|
ldsize[ind] = 1;
|
|
}
|
|
}
|
|
for (int fi=0; fi<pmesh->GroupNTriangles(g); fi++)
|
|
{
|
|
int face, ori, info1, info2;
|
|
pmesh->GroupTriangle(g, fi, face, ori);
|
|
pmesh->GetFaceInfos(face, &info1, &info2);
|
|
this->GetSharedTriangleDofs(g, fi, sdofs);
|
|
for (int i=0; i<3*nedofs; i++)
|
|
{
|
|
int ind = (sdofs[i]>=0) ? sdofs[i] : (-sdofs[i]-1);
|
|
if (ldsize[ind] == 0) { nnz_offd++; }
|
|
ldsize[ind] = 1;
|
|
}
|
|
for (int i=3*nedofs; i<sdofs.Size(); i++)
|
|
{
|
|
if (ldsize[sdofs[i]] == 0) { nnz_offd += 2; }
|
|
ldsize[sdofs[i]] = 2;
|
|
ltori[sdofs[i]] = info2 % 64;
|
|
}
|
|
}
|
|
for (int fi=0; fi<pmesh->GroupNQuadrilaterals(g); fi++)
|
|
{
|
|
this->GetSharedQuadrilateralDofs(g, fi, sdofs);
|
|
for (int i=0; i<sdofs.Size(); i++)
|
|
{
|
|
int ind = (sdofs[i]>=0) ? sdofs[i] : (-sdofs[i]-1);
|
|
if (ldsize[ind] == 0) { nnz_offd++; }
|
|
ldsize[ind] = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
HYPRE_Int *i_diag = Memory<HYPRE_Int>(ldof+1);
|
|
HYPRE_Int *j_diag = Memory<HYPRE_Int>(ltdof);
|
|
real_t *d_diag = Memory<real_t>(ltdof);
|
|
int diag_counter;
|
|
|
|
HYPRE_Int *i_offd = Memory<HYPRE_Int>(ldof+1);
|
|
HYPRE_Int *j_offd = Memory<HYPRE_Int>(nnz_offd);
|
|
real_t *d_offd = Memory<real_t>(nnz_offd);
|
|
int offd_counter;
|
|
|
|
HYPRE_BigInt *cmap = Memory<HYPRE_BigInt>(ldof-ltdof);
|
|
|
|
HYPRE_BigInt *col_starts = GetTrueDofOffsets();
|
|
HYPRE_BigInt *row_starts = GetDofOffsets();
|
|
|
|
Array<Pair<HYPRE_BigInt, int> > cmap_j_offd(ldof-ltdof);
|
|
|
|
i_diag[0] = i_offd[0] = 0;
|
|
diag_counter = offd_counter = 0;
|
|
int offd_col_counter = 0;
|
|
for (int i = 0; i < ldof; i++)
|
|
{
|
|
int ltdofi = GetLocalTDofNumber(i);
|
|
if (ltdofi >= 0)
|
|
{
|
|
j_diag[diag_counter] = ltdofi;
|
|
d_diag[diag_counter++] = 1.0;
|
|
}
|
|
else
|
|
{
|
|
if (ldsize[i] == 1)
|
|
{
|
|
cmap_j_offd[offd_col_counter].one = GetGlobalTDofNumber(i);
|
|
cmap_j_offd[offd_col_counter].two = offd_counter;
|
|
offd_counter++;
|
|
offd_col_counter++;
|
|
}
|
|
else
|
|
{
|
|
cmap_j_offd[offd_col_counter].one = GetGlobalTDofNumber(i);
|
|
cmap_j_offd[offd_col_counter].two = offd_counter;
|
|
offd_counter += 2;
|
|
offd_col_counter++;
|
|
i_diag[i+1] = diag_counter;
|
|
i_offd[i+1] = offd_counter;
|
|
i++;
|
|
cmap_j_offd[offd_col_counter].one = GetGlobalTDofNumber(i);
|
|
cmap_j_offd[offd_col_counter].two = offd_counter;
|
|
offd_counter += 2;
|
|
offd_col_counter++;
|
|
}
|
|
}
|
|
i_diag[i+1] = diag_counter;
|
|
i_offd[i+1] = offd_counter;
|
|
}
|
|
|
|
SortPairs<HYPRE_BigInt, int>(cmap_j_offd, offd_col_counter);
|
|
|
|
for (int i = 0; i < nnz_offd; i++)
|
|
{
|
|
j_offd[i] = -1;
|
|
d_offd[i] = 0.0;
|
|
}
|
|
|
|
for (int i = 0; i < offd_col_counter; i++)
|
|
{
|
|
cmap[i] = cmap_j_offd[i].one;
|
|
j_offd[cmap_j_offd[i].two] = i;
|
|
}
|
|
|
|
for (int i = 0; i < ldof; i++)
|
|
{
|
|
if (i_offd[i+1] == i_offd[i] + 1)
|
|
{
|
|
d_offd[i_offd[i]] = 1.0;
|
|
}
|
|
else if (i_offd[i+1] == i_offd[i] + 2)
|
|
{
|
|
const real_t *T =
|
|
ND_DofTransformation::GetFaceTransform(ltori[i]).GetData();
|
|
j_offd[i_offd[i] + 1] = j_offd[i_offd[i]] + 1;
|
|
d_offd[i_offd[i]] = T[0]; d_offd[i_offd[i] + 1] = T[2];
|
|
i++;
|
|
j_offd[i_offd[i] + 1] = j_offd[i_offd[i]];
|
|
j_offd[i_offd[i]] = j_offd[i_offd[i] + 1] - 1;
|
|
d_offd[i_offd[i]] = T[1]; d_offd[i_offd[i] + 1] = T[3];
|
|
}
|
|
}
|
|
|
|
P = new HypreParMatrix(MyComm, gdof, gtdof, row_starts, col_starts,
|
|
i_diag, j_diag, d_diag, i_offd, j_offd, d_offd,
|
|
offd_col_counter, cmap);
|
|
}
|
|
|
|
SparseMatrix Pdiag;
|
|
P->GetDiag(Pdiag);
|
|
R = Transpose(Pdiag);
|
|
}
|
|
|
|
HypreParMatrix *ParFiniteElementSpace::GetPartialConformingInterpolation()
|
|
{
|
|
HypreParMatrix *P_pc;
|
|
Array<HYPRE_BigInt> P_pc_row_starts, P_pc_col_starts;
|
|
BuildParallelConformingInterpolation(&P_pc, NULL, P_pc_row_starts,
|
|
P_pc_col_starts, NULL, true);
|
|
P_pc->CopyRowStarts();
|
|
P_pc->CopyColStarts();
|
|
return P_pc;
|
|
}
|
|
|
|
void ParFiniteElementSpace::DivideByGroupSize(real_t *vec)
|
|
{
|
|
GroupTopology > = GetGroupTopo();
|
|
for (int i = 0; i < ldof_group.Size(); i++)
|
|
{
|
|
if (gt.IAmMaster(ldof_group[i])) // we are the master
|
|
{
|
|
if (ldof_ltdof[i] >= 0) // see note below
|
|
{
|
|
vec[ldof_ltdof[i]] /= gt.GetGroupSize(ldof_group[i]);
|
|
}
|
|
// NOTE: in NC meshes, ldof_ltdof generated for the gtopo
|
|
// groups by ConstructTrueDofs gets overwritten by
|
|
// BuildParallelConformingInterpolation. Some DOFs that are
|
|
// seen as true by the conforming code are actually slaves and
|
|
// end up with a -1 in ldof_ltdof.
|
|
}
|
|
}
|
|
}
|
|
|
|
GroupCommunicator *ParFiniteElementSpace::ScalarGroupComm()
|
|
{
|
|
GroupCommunicator *gc = new GroupCommunicator(GetGroupTopo());
|
|
if (NURBSext)
|
|
{
|
|
gc->Create(pNURBSext()->ldof_group);
|
|
}
|
|
else
|
|
{
|
|
GetGroupComm(*gc, 0);
|
|
}
|
|
return gc;
|
|
}
|
|
|
|
void ParFiniteElementSpace::Synchronize(Array<int> &ldof_marker) const
|
|
{
|
|
// For non-conforming mesh, synchronization is performed on the cut (aka
|
|
// "partially conforming") space.
|
|
|
|
MFEM_VERIFY(ldof_marker.Size() == GetVSize(), "invalid in/out array");
|
|
|
|
// implement allreduce(|) as reduce(|) + broadcast
|
|
gcomm->Reduce<int>(ldof_marker, GroupCommunicator::BitOR);
|
|
gcomm->Bcast(ldof_marker);
|
|
}
|
|
|
|
void ParFiniteElementSpace::GetEssentialVDofs(const Array<int> &bdr_attr_is_ess,
|
|
Array<int> &ess_dofs,
|
|
int component) const
|
|
{
|
|
FiniteElementSpace::GetEssentialVDofs(bdr_attr_is_ess, ess_dofs, component);
|
|
|
|
// Make sure that processors without boundary elements mark
|
|
// their boundary dofs (if they have any).
|
|
Synchronize(ess_dofs);
|
|
}
|
|
|
|
void ParFiniteElementSpace::GetEssentialTrueDofs(const Array<int>
|
|
&bdr_attr_is_ess,
|
|
Array<int> &ess_tdof_list,
|
|
int component) const
|
|
{
|
|
Array<int> ess_dofs, true_ess_dofs;
|
|
|
|
GetEssentialVDofs(bdr_attr_is_ess, ess_dofs, component);
|
|
|
|
if (IsVariableOrderH1())
|
|
{
|
|
GetEssentialTrueDofsVar(bdr_attr_is_ess, ess_dofs, true_ess_dofs,
|
|
component);
|
|
}
|
|
else
|
|
{
|
|
GetRestrictionMatrix()->BooleanMult(ess_dofs, true_ess_dofs);
|
|
}
|
|
|
|
#ifdef MFEM_DEBUG
|
|
// Verify that in boolean arithmetic: P^T ess_dofs = R ess_dofs.
|
|
Array<int> true_ess_dofs2(true_ess_dofs.Size());
|
|
auto Pt = std::unique_ptr<HypreParMatrix>(Dof_TrueDof_Matrix()->Transpose());
|
|
|
|
const int *ess_dofs_data = ess_dofs.HostRead();
|
|
Pt->BooleanMult(1, ess_dofs_data, 0, true_ess_dofs2);
|
|
int counter = 0;
|
|
const int *ted = true_ess_dofs.HostRead();
|
|
std::string error_msg = "failed dof: ";
|
|
for (int i = 0; i < true_ess_dofs.Size(); i++)
|
|
{
|
|
if (bool(ted[i]) != bool(true_ess_dofs2[i]))
|
|
{
|
|
error_msg += std::to_string(i) += "(R ";
|
|
error_msg += std::to_string(bool(ted[i])) += " P^T ";
|
|
error_msg += std::to_string(bool(true_ess_dofs2[i])) += ") ";
|
|
++counter;
|
|
}
|
|
}
|
|
MFEM_ASSERT(R->Height() == P->Width(), "!");
|
|
|
|
if (!IsVariableOrder())
|
|
{
|
|
MFEM_ASSERT(R->Width() == P->Height(), "!");
|
|
MFEM_ASSERT(R->Width() == ess_dofs.Size(), "!");
|
|
MFEM_VERIFY(counter == 0, "internal MFEM error: counter = " << counter
|
|
<< ", rank = " << MyRank << ", " << error_msg);
|
|
}
|
|
#endif
|
|
|
|
MarkerToList(true_ess_dofs, ess_tdof_list);
|
|
}
|
|
|
|
void ParFiniteElementSpace::GetEssentialTrueDofsVar(const Array<int>
|
|
&bdr_attr_is_ess,
|
|
const Array<int> &ess_dofs,
|
|
Array<int> &true_ess_dofs,
|
|
int component) const
|
|
{
|
|
MFEM_VERIFY(IsVariableOrder() && R,
|
|
"GetEssentialTrueDofsVar is only for variable-order spaces");
|
|
|
|
true_ess_dofs.SetSize(R->Height(), Device::GetDeviceMemoryType());
|
|
|
|
const int ntdofs = tdof2ldof.Size();
|
|
MFEM_VERIFY(vdim * ntdofs == R->NumRows() &&
|
|
vdim * ntdofs == true_ess_dofs.Size(), "");
|
|
MFEM_VERIFY(ldof_ltdof.Size() == ndofs && ess_dofs.Size() == vdim * ndofs, "");
|
|
|
|
true_ess_dofs = 0;
|
|
|
|
const bool bynodes = (ordering == Ordering::byNODES);
|
|
const int vdim_factor = bynodes ? 1 : vdim;
|
|
const int num_true_dofs = R->NumRows() / vdim;
|
|
const int tdof_stride = bynodes ? num_true_dofs : 1;
|
|
|
|
// Use ldof_ltdof for vertex and element T-dofs
|
|
for (int l=0; l<ndofs; ++l)
|
|
{
|
|
const int tdof = ldof_ltdof[l];
|
|
if (tdof >= 0 && ess_dofs[l])
|
|
{
|
|
for (int vd = 0; vd < vdim; vd++)
|
|
{
|
|
if (component >= 0 && vd != component) { continue; }
|
|
const int vtdof = tdof*vdim_factor + vd*tdof_stride;
|
|
true_ess_dofs[vtdof] = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Find all essential boundary edges and faces.
|
|
std::set<int> edges, faces;
|
|
GetEssentialBdrEdgesFaces(bdr_attr_is_ess, edges, faces);
|
|
|
|
// Use tdof2ldof for edge and face T-dofs
|
|
for (int tdof=0; tdof<ntdofs; ++tdof)
|
|
{
|
|
// Not set for vertex and element T-dofs
|
|
if (!tdof2ldof[tdof].set) { continue; }
|
|
|
|
const bool edge = tdof2ldof[tdof].isEdge;
|
|
const int index = tdof2ldof[tdof].idx;
|
|
|
|
const bool bdry = edge ? edges.count(index) > 0 : faces.count(index) > 0;
|
|
if (!bdry) { continue; }
|
|
|
|
for (int vd = 0; vd < vdim; vd++)
|
|
{
|
|
if (component >= 0 && vd != component) { continue; }
|
|
const int vtdof = tdof*vdim_factor + vd*tdof_stride;
|
|
true_ess_dofs[vtdof] = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ParFiniteElementSpace::GetExteriorVDofs(Array<int> &ext_dofs,
|
|
int component) const
|
|
{
|
|
FiniteElementSpace::GetExteriorVDofs(ext_dofs, component);
|
|
|
|
// Make sure that processors without boundary elements mark
|
|
// their boundary dofs (if they have any).
|
|
Synchronize(ext_dofs);
|
|
}
|
|
|
|
void ParFiniteElementSpace::GetExteriorTrueDofs(Array<int> &ext_tdof_list,
|
|
int component) const
|
|
{
|
|
Array<int> ext_dofs, true_ext_dofs;
|
|
|
|
GetExteriorVDofs(ext_dofs, component);
|
|
GetRestrictionMatrix()->BooleanMult(ext_dofs, true_ext_dofs);
|
|
|
|
#ifdef MFEM_DEBUG
|
|
// Verify that in boolean arithmetic: P^T ext_dofs = R ext_dofs.
|
|
Array<int> true_ext_dofs2(true_ext_dofs.Size());
|
|
auto Pt = std::unique_ptr<HypreParMatrix>(Dof_TrueDof_Matrix()->Transpose());
|
|
|
|
const int *ext_dofs_data = ext_dofs.HostRead();
|
|
Pt->BooleanMult(1, ext_dofs_data, 0, true_ext_dofs2);
|
|
int counter = 0;
|
|
const int *ted = true_ext_dofs.HostRead();
|
|
std::string error_msg = "failed dof: ";
|
|
for (int i = 0; i < true_ext_dofs.Size(); i++)
|
|
{
|
|
if (bool(ted[i]) != bool(true_ext_dofs2[i]))
|
|
{
|
|
error_msg += std::to_string(i) += "(R ";
|
|
error_msg += std::to_string(bool(ted[i])) += " P^T ";
|
|
error_msg += std::to_string(bool(true_ext_dofs2[i])) += ") ";
|
|
++counter;
|
|
}
|
|
}
|
|
MFEM_ASSERT(R->Height() == P->Width(), "!");
|
|
MFEM_ASSERT(R->Width() == P->Height(), "!");
|
|
MFEM_ASSERT(R->Width() == ext_dofs.Size(), "!");
|
|
MFEM_VERIFY(counter == 0, "internal MFEM error: counter = " << counter
|
|
<< ", rank = " << MyRank << ", " << error_msg);
|
|
#endif
|
|
|
|
MarkerToList(true_ext_dofs, ext_tdof_list);
|
|
}
|
|
|
|
int ParFiniteElementSpace::GetLocalTDofNumber(int ldof) const
|
|
{
|
|
if (Nonconforming())
|
|
{
|
|
Dof_TrueDof_Matrix(); // make sure P has been built
|
|
|
|
return ldof_ltdof[ldof]; // NOTE: contains -1 for slaves/DOFs we don't own
|
|
}
|
|
else
|
|
{
|
|
if (GetGroupTopo().IAmMaster(ldof_group[ldof]))
|
|
{
|
|
return ldof_ltdof[ldof];
|
|
}
|
|
else
|
|
{
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
HYPRE_BigInt ParFiniteElementSpace::GetGlobalTDofNumber(int ldof) const
|
|
{
|
|
if (Nonconforming())
|
|
{
|
|
MFEM_VERIFY(ldof_ltdof[ldof] >= 0, "ldof " << ldof << " not a true DOF.");
|
|
|
|
return GetMyTDofOffset() + ldof_ltdof[ldof];
|
|
}
|
|
else
|
|
{
|
|
if (HYPRE_AssumedPartitionCheck())
|
|
{
|
|
return ldof_ltdof[ldof] +
|
|
tdof_nb_offsets[GetGroupTopo().GetGroupMaster(ldof_group[ldof])];
|
|
}
|
|
else
|
|
{
|
|
return ldof_ltdof[ldof] +
|
|
tdof_offsets[GetGroupTopo().GetGroupMasterRank(ldof_group[ldof])];
|
|
}
|
|
}
|
|
}
|
|
|
|
HYPRE_BigInt ParFiniteElementSpace::GetGlobalScalarTDofNumber(int sldof)
|
|
{
|
|
if (Nonconforming())
|
|
{
|
|
MFEM_ABORT("Not implemented for NC mesh.");
|
|
}
|
|
|
|
if (HYPRE_AssumedPartitionCheck())
|
|
{
|
|
if (ordering == Ordering::byNODES)
|
|
{
|
|
return ldof_ltdof[sldof] +
|
|
tdof_nb_offsets[GetGroupTopo().GetGroupMaster(
|
|
ldof_group[sldof])] / vdim;
|
|
}
|
|
else
|
|
{
|
|
return (ldof_ltdof[sldof*vdim] +
|
|
tdof_nb_offsets[GetGroupTopo().GetGroupMaster(
|
|
ldof_group[sldof*vdim])]) / vdim;
|
|
}
|
|
}
|
|
|
|
if (ordering == Ordering::byNODES)
|
|
{
|
|
return ldof_ltdof[sldof] +
|
|
tdof_offsets[GetGroupTopo().GetGroupMasterRank(
|
|
ldof_group[sldof])] / vdim;
|
|
}
|
|
else
|
|
{
|
|
return (ldof_ltdof[sldof*vdim] +
|
|
tdof_offsets[GetGroupTopo().GetGroupMasterRank(
|
|
ldof_group[sldof*vdim])]) / vdim;
|
|
}
|
|
}
|
|
|
|
HYPRE_BigInt ParFiniteElementSpace::GetMyDofOffset() const
|
|
{
|
|
return HYPRE_AssumedPartitionCheck() ? dof_offsets[0] : dof_offsets[MyRank];
|
|
}
|
|
|
|
HYPRE_BigInt ParFiniteElementSpace::GetMyTDofOffset() const
|
|
{
|
|
return HYPRE_AssumedPartitionCheck()? tdof_offsets[0] : tdof_offsets[MyRank];
|
|
}
|
|
|
|
const Operator *ParFiniteElementSpace::GetProlongationMatrix() const
|
|
{
|
|
if (Conforming())
|
|
{
|
|
if (Pconf) { return Pconf; }
|
|
|
|
if (nd_strias) { return Dof_TrueDof_Matrix(); }
|
|
|
|
if (NRanks == 1)
|
|
{
|
|
Pconf = new IdentityOperator(GetTrueVSize());
|
|
}
|
|
else
|
|
{
|
|
if (!Device::Allows(Backend::DEVICE_MASK))
|
|
{
|
|
Pconf = new ConformingProlongationOperator(*this);
|
|
}
|
|
else
|
|
{
|
|
Pconf = new DeviceConformingProlongationOperator(*this);
|
|
}
|
|
}
|
|
return Pconf;
|
|
}
|
|
else
|
|
{
|
|
return Dof_TrueDof_Matrix();
|
|
}
|
|
}
|
|
|
|
const Operator *ParFiniteElementSpace::GetRestrictionOperator() const
|
|
{
|
|
if (Conforming())
|
|
{
|
|
if (Rconf) { return Rconf; }
|
|
|
|
if (NRanks == 1)
|
|
{
|
|
R_transpose.reset(new IdentityOperator(GetTrueVSize()));
|
|
}
|
|
else
|
|
{
|
|
if (!Device::Allows(Backend::DEVICE_MASK))
|
|
{
|
|
R_transpose.reset(new ConformingProlongationOperator(*this, true));
|
|
}
|
|
else
|
|
{
|
|
R_transpose.reset(
|
|
new DeviceConformingProlongationOperator(*this, true));
|
|
}
|
|
}
|
|
Rconf = new TransposeOperator(*R_transpose);
|
|
return Rconf;
|
|
}
|
|
else
|
|
{
|
|
Dof_TrueDof_Matrix();
|
|
if (!R_transpose) { R_transpose.reset(new TransposeOperator(R)); }
|
|
return R;
|
|
}
|
|
}
|
|
|
|
void ParFiniteElementSpace::ExchangeFaceNbrData()
|
|
{
|
|
if (num_face_nbr_dofs >= 0) { return; }
|
|
|
|
pmesh->ExchangeFaceNbrData();
|
|
|
|
int num_face_nbrs = pmesh->GetNFaceNeighbors();
|
|
|
|
if (num_face_nbrs == 0)
|
|
{
|
|
num_face_nbr_dofs = 0;
|
|
return;
|
|
}
|
|
|
|
MPI_Request *requests = new MPI_Request[2*num_face_nbrs];
|
|
MPI_Request *send_requests = requests;
|
|
MPI_Request *recv_requests = requests + num_face_nbrs;
|
|
MPI_Status *statuses = new MPI_Status[num_face_nbrs];
|
|
|
|
Array<int> ldofs;
|
|
Array<int> ldof_marker(GetVSize());
|
|
ldof_marker = -1;
|
|
|
|
Table send_nbr_elem_dof;
|
|
|
|
send_nbr_elem_dof.MakeI(pmesh->send_face_nbr_elements.Size_of_connections());
|
|
send_face_nbr_ldof.MakeI(num_face_nbrs);
|
|
face_nbr_ldof.MakeI(num_face_nbrs);
|
|
int *send_el_off = pmesh->send_face_nbr_elements.GetI();
|
|
int *recv_el_off = pmesh->face_nbr_elements_offset;
|
|
for (int fn = 0; fn < num_face_nbrs; fn++)
|
|
{
|
|
int *my_elems = pmesh->send_face_nbr_elements.GetRow(fn);
|
|
int num_my_elems = pmesh->send_face_nbr_elements.RowSize(fn);
|
|
|
|
for (int i = 0; i < num_my_elems; i++)
|
|
{
|
|
GetElementVDofs(my_elems[i], ldofs);
|
|
for (int j = 0; j < ldofs.Size(); j++)
|
|
{
|
|
int ldof = UnsignIndex(ldofs[j]);
|
|
|
|
if (ldof_marker[ldof] != fn)
|
|
{
|
|
ldof_marker[ldof] = fn;
|
|
send_face_nbr_ldof.AddAColumnInRow(fn);
|
|
}
|
|
}
|
|
send_nbr_elem_dof.AddColumnsInRow(send_el_off[fn] + i, ldofs.Size());
|
|
}
|
|
|
|
int nbr_rank = pmesh->GetFaceNbrRank(fn);
|
|
int tag = 0;
|
|
MPI_Isend(&send_face_nbr_ldof.GetI()[fn], 1, MPI_INT, nbr_rank, tag,
|
|
MyComm, &send_requests[fn]);
|
|
|
|
MPI_Irecv(&face_nbr_ldof.GetI()[fn], 1, MPI_INT, nbr_rank, tag,
|
|
MyComm, &recv_requests[fn]);
|
|
}
|
|
|
|
MPI_Waitall(num_face_nbrs, recv_requests, statuses);
|
|
face_nbr_ldof.MakeJ();
|
|
|
|
num_face_nbr_dofs = face_nbr_ldof.Size_of_connections();
|
|
|
|
MPI_Waitall(num_face_nbrs, send_requests, statuses);
|
|
send_face_nbr_ldof.MakeJ();
|
|
|
|
// send/receive the I arrays of send_nbr_elem_dof/face_nbr_element_dof,
|
|
// respectively (they contain the number of dofs for each face-neighbor
|
|
// element)
|
|
face_nbr_element_dof.MakeI(recv_el_off[num_face_nbrs]);
|
|
|
|
int *send_I = send_nbr_elem_dof.GetI();
|
|
int *recv_I = face_nbr_element_dof.GetI();
|
|
for (int fn = 0; fn < num_face_nbrs; fn++)
|
|
{
|
|
int nbr_rank = pmesh->GetFaceNbrRank(fn);
|
|
int tag = 0;
|
|
MPI_Isend(send_I + send_el_off[fn], send_el_off[fn+1] - send_el_off[fn],
|
|
MPI_INT, nbr_rank, tag, MyComm, &send_requests[fn]);
|
|
|
|
MPI_Irecv(recv_I + recv_el_off[fn], recv_el_off[fn+1] - recv_el_off[fn],
|
|
MPI_INT, nbr_rank, tag, MyComm, &recv_requests[fn]);
|
|
}
|
|
|
|
MPI_Waitall(num_face_nbrs, send_requests, statuses);
|
|
send_nbr_elem_dof.MakeJ();
|
|
|
|
ldof_marker = -1;
|
|
|
|
for (int fn = 0; fn < num_face_nbrs; fn++)
|
|
{
|
|
int *my_elems = pmesh->send_face_nbr_elements.GetRow(fn);
|
|
int num_my_elems = pmesh->send_face_nbr_elements.RowSize(fn);
|
|
|
|
for (int i = 0; i < num_my_elems; i++)
|
|
{
|
|
GetElementVDofs(my_elems[i], ldofs);
|
|
for (int j = 0; j < ldofs.Size(); j++)
|
|
{
|
|
int ldof = UnsignIndex(ldofs[j]);
|
|
|
|
if (ldof_marker[ldof] != fn)
|
|
{
|
|
ldof_marker[ldof] = fn;
|
|
send_face_nbr_ldof.AddConnection(fn, ldofs[j]);
|
|
}
|
|
}
|
|
send_nbr_elem_dof.AddConnections(
|
|
send_el_off[fn] + i, ldofs, ldofs.Size());
|
|
}
|
|
}
|
|
send_face_nbr_ldof.ShiftUpI();
|
|
send_nbr_elem_dof.ShiftUpI();
|
|
|
|
// convert the ldof indices in send_nbr_elem_dof
|
|
int *send_J = send_nbr_elem_dof.GetJ();
|
|
for (int fn = 0, j = 0; fn < num_face_nbrs; fn++)
|
|
{
|
|
int num_ldofs = send_face_nbr_ldof.RowSize(fn);
|
|
int *ldofs_fn = send_face_nbr_ldof.GetRow(fn);
|
|
int j_end = send_I[send_el_off[fn+1]];
|
|
|
|
for (int i = 0; i < num_ldofs; i++)
|
|
{
|
|
int ldof = UnsignIndex(ldofs_fn[i]);
|
|
ldof_marker[ldof] = i;
|
|
}
|
|
|
|
for ( ; j < j_end; j++)
|
|
{
|
|
const int ldof = UnsignIndex(send_J[j]);
|
|
send_J[j] = (send_J[j] >= 0 ? ldof_marker[ldof] :
|
|
FlipIndexSign(ldof_marker[ldof]));
|
|
}
|
|
}
|
|
|
|
MPI_Waitall(num_face_nbrs, recv_requests, statuses);
|
|
face_nbr_element_dof.MakeJ();
|
|
|
|
// send/receive the J arrays of send_nbr_elem_dof/face_nbr_element_dof,
|
|
// respectively (they contain the element dofs in enumeration local for
|
|
// the face-neighbor pair)
|
|
int *recv_J = face_nbr_element_dof.GetJ();
|
|
for (int fn = 0; fn < num_face_nbrs; fn++)
|
|
{
|
|
int nbr_rank = pmesh->GetFaceNbrRank(fn);
|
|
int tag = 0;
|
|
|
|
MPI_Isend(send_J + send_I[send_el_off[fn]],
|
|
send_I[send_el_off[fn+1]] - send_I[send_el_off[fn]],
|
|
MPI_INT, nbr_rank, tag, MyComm, &send_requests[fn]);
|
|
|
|
MPI_Irecv(recv_J + recv_I[recv_el_off[fn]],
|
|
recv_I[recv_el_off[fn+1]] - recv_I[recv_el_off[fn]],
|
|
MPI_INT, nbr_rank, tag, MyComm, &recv_requests[fn]);
|
|
}
|
|
|
|
MPI_Waitall(num_face_nbrs, recv_requests, statuses);
|
|
|
|
// shift the J array of face_nbr_element_dof
|
|
for (int fn = 0, j = 0; fn < num_face_nbrs; fn++)
|
|
{
|
|
int shift = face_nbr_ldof.GetI()[fn];
|
|
int j_end = recv_I[recv_el_off[fn+1]];
|
|
|
|
for ( ; j < j_end; j++)
|
|
{
|
|
if (recv_J[j] >= 0)
|
|
{
|
|
recv_J[j] += shift;
|
|
}
|
|
else
|
|
{
|
|
recv_J[j] -= shift;
|
|
}
|
|
}
|
|
}
|
|
|
|
MPI_Waitall(num_face_nbrs, send_requests, statuses);
|
|
|
|
// send/receive the J arrays of send_face_nbr_ldof/face_nbr_ldof,
|
|
// respectively
|
|
for (int fn = 0; fn < num_face_nbrs; fn++)
|
|
{
|
|
int nbr_rank = pmesh->GetFaceNbrRank(fn);
|
|
int tag = 0;
|
|
|
|
MPI_Isend(send_face_nbr_ldof.GetRow(fn),
|
|
send_face_nbr_ldof.RowSize(fn),
|
|
MPI_INT, nbr_rank, tag, MyComm, &send_requests[fn]);
|
|
|
|
MPI_Irecv(face_nbr_ldof.GetRow(fn),
|
|
face_nbr_ldof.RowSize(fn),
|
|
MPI_INT, nbr_rank, tag, MyComm, &recv_requests[fn]);
|
|
}
|
|
|
|
MPI_Waitall(num_face_nbrs, recv_requests, statuses);
|
|
MPI_Waitall(num_face_nbrs, send_requests, statuses);
|
|
|
|
// send my_dof_offset (i.e. my_ldof_offset) to face neighbors and receive
|
|
// their offset in dof_face_nbr_offsets, used to define face_nbr_glob_dof_map
|
|
face_nbr_glob_dof_map.SetSize(num_face_nbr_dofs);
|
|
Array<HYPRE_BigInt> dof_face_nbr_offsets(num_face_nbrs);
|
|
HYPRE_BigInt my_dof_offset = GetMyDofOffset();
|
|
for (int fn = 0; fn < num_face_nbrs; fn++)
|
|
{
|
|
int nbr_rank = pmesh->GetFaceNbrRank(fn);
|
|
int tag = 0;
|
|
|
|
MPI_Isend(&my_dof_offset, 1, HYPRE_MPI_BIG_INT, nbr_rank, tag,
|
|
MyComm, &send_requests[fn]);
|
|
|
|
MPI_Irecv(&dof_face_nbr_offsets[fn], 1, HYPRE_MPI_BIG_INT, nbr_rank, tag,
|
|
MyComm, &recv_requests[fn]);
|
|
}
|
|
|
|
MPI_Waitall(num_face_nbrs, recv_requests, statuses);
|
|
|
|
// set the array face_nbr_glob_dof_map which holds the global ldof indices of
|
|
// the face-neighbor dofs
|
|
for (int fn = 0, j = 0; fn < num_face_nbrs; fn++)
|
|
{
|
|
for (int j_end = face_nbr_ldof.GetI()[fn+1]; j < j_end; j++)
|
|
{
|
|
const int ldof = UnsignIndex(face_nbr_ldof.GetJ()[j]);
|
|
face_nbr_glob_dof_map[j] = dof_face_nbr_offsets[fn] + ldof;
|
|
}
|
|
}
|
|
|
|
MPI_Waitall(num_face_nbrs, send_requests, statuses);
|
|
|
|
delete [] statuses;
|
|
delete [] requests;
|
|
}
|
|
|
|
void ParFiniteElementSpace::GetFaceNbrElementVDofs(
|
|
int i, Array<int> &vdofs, DofTransformation &doftrans) const
|
|
{
|
|
doftrans.SetDofTransformation(nullptr);
|
|
|
|
face_nbr_element_dof.GetRow(i, vdofs);
|
|
|
|
if (DoFTransArray[GetFaceNbrFE(i)->GetGeomType()])
|
|
{
|
|
Array<int> F, Fo;
|
|
pmesh->GetFaceNbrElementFaces(pmesh->GetNE() + i, F, Fo);
|
|
doftrans.SetDofTransformation(
|
|
*DoFTransArray[GetFaceNbrFE(i)->GetGeomType()]);
|
|
doftrans.SetFaceOrientations(Fo);
|
|
doftrans.SetVDim(vdim, ordering);
|
|
}
|
|
}
|
|
|
|
DofTransformation *ParFiniteElementSpace::GetFaceNbrElementVDofs(
|
|
int i, Array<int> &vdofs) const
|
|
{
|
|
GetFaceNbrElementVDofs(i, vdofs, DoFTrans);
|
|
return DoFTrans.GetDofTransformation() ? &DoFTrans : NULL;
|
|
}
|
|
|
|
void ParFiniteElementSpace::GetFaceNbrFaceVDofs(int i, Array<int> &vdofs) const
|
|
{
|
|
// Works for NC mesh where 'i' is an index returned by
|
|
// ParMesh::GetSharedFace() such that i >= Mesh::GetNumFaces(), i.e. 'i' is
|
|
// the index of a ghost face.
|
|
MFEM_ASSERT(Nonconforming() && i >= pmesh->GetNumFaces(), "");
|
|
int el1, el2, inf1, inf2;
|
|
pmesh->GetFaceElements(i, &el1, &el2);
|
|
el2 = FlipIndexSign(el2);
|
|
pmesh->GetFaceInfos(i, &inf1, &inf2);
|
|
MFEM_ASSERT(0 <= el2 && el2 < face_nbr_element_dof.Size(), "");
|
|
const int nd = face_nbr_element_dof.RowSize(el2);
|
|
const int *vol_vdofs = face_nbr_element_dof.GetRow(el2);
|
|
const Element *face_nbr_el = pmesh->face_nbr_elements[el2];
|
|
Geometry::Type geom = face_nbr_el->GetGeometryType();
|
|
const int face_dim = Geometry::Dimension[geom]-1;
|
|
|
|
fec->SubDofOrder(geom, face_dim, inf2, vdofs);
|
|
// Convert local dofs to local vdofs.
|
|
Ordering::DofsToVDofs<Ordering::byNODES>(nd/vdim, vdim, vdofs);
|
|
// Convert local vdofs to global vdofs.
|
|
for (int j = 0; j < vdofs.Size(); j++)
|
|
{
|
|
const int ldof = vdofs[j];
|
|
vdofs[j] = (ldof >= 0) ? vol_vdofs[ldof] :
|
|
FlipIndexSign(vol_vdofs[FlipIndexSign(ldof)]);
|
|
}
|
|
}
|
|
|
|
const FiniteElement *ParFiniteElementSpace::GetFaceNbrFE(int i, int ndofs) const
|
|
{
|
|
if (NURBSext)
|
|
{
|
|
mfem_error("ParFiniteElementSpace::GetFaceNbrFE"
|
|
" does not support NURBS!");
|
|
}
|
|
|
|
if (ndofs > 0)
|
|
{
|
|
for (int order = fec->GetOrder(); ; ++order)
|
|
{
|
|
const FiniteElement *FE =
|
|
fec->GetFE(pmesh->face_nbr_elements[i]->GetGeometryType(), order);
|
|
const int ndofs_order = FE->GetDof();
|
|
if (ndofs_order == ndofs)
|
|
{
|
|
return FE;
|
|
}
|
|
else if (ndofs_order > ndofs)
|
|
{
|
|
MFEM_ABORT("Finite element order not found in GetFaceNbrFE");
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return fec->FiniteElementForGeometry(
|
|
pmesh->face_nbr_elements[i]->GetGeometryType());
|
|
}
|
|
}
|
|
|
|
const FiniteElement *ParFiniteElementSpace::GetFaceNbrFaceFE(int i) const
|
|
{
|
|
// Works for NC mesh where 'i' is an index returned by
|
|
// ParMesh::GetSharedFace() such that i >= Mesh::GetNumFaces(), i.e. 'i' is
|
|
// the index of a ghost face.
|
|
// Works in tandem with GetFaceNbrFaceVDofs() defined above.
|
|
|
|
MFEM_ASSERT(Nonconforming() && !NURBSext, "");
|
|
Geometry::Type face_geom = pmesh->GetFaceGeometry(i);
|
|
return fec->FiniteElementForGeometry(face_geom);
|
|
}
|
|
|
|
void ParFiniteElementSpace::Lose_Dof_TrueDof_Matrix()
|
|
{
|
|
P -> StealData();
|
|
#if MFEM_HYPRE_VERSION <= 22200
|
|
hypre_ParCSRMatrix *csrP = (hypre_ParCSRMatrix*)(*P);
|
|
hypre_ParCSRMatrixOwnsRowStarts(csrP) = 1;
|
|
hypre_ParCSRMatrixOwnsColStarts(csrP) = 1;
|
|
dof_offsets.LoseData();
|
|
tdof_offsets.LoseData();
|
|
#else
|
|
dof_offsets.DeleteAll();
|
|
tdof_offsets.DeleteAll();
|
|
#endif
|
|
}
|
|
|
|
void ParFiniteElementSpace::ConstructTrueDofs()
|
|
{
|
|
int i, gr, n = GetVSize();
|
|
GroupTopology > = pmesh->gtopo;
|
|
gcomm = new GroupCommunicator(gt);
|
|
Table &group_ldof = gcomm->GroupLDofTable();
|
|
|
|
GetGroupComm(*gcomm, 1, &ldof_sign);
|
|
|
|
// Define ldof_group and mark ldof_ltdof with
|
|
// -1 for ldof that is ours
|
|
// -2 for ldof that is in a group with another master
|
|
ldof_group.SetSize(n);
|
|
ldof_ltdof.SetSize(n);
|
|
ldof_group = 0;
|
|
ldof_ltdof = -1;
|
|
|
|
for (gr = 1; gr < group_ldof.Size(); gr++)
|
|
{
|
|
const int *ldofs = group_ldof.GetRow(gr);
|
|
const int nldofs = group_ldof.RowSize(gr);
|
|
for (i = 0; i < nldofs; i++)
|
|
{
|
|
ldof_group[ldofs[i]] = gr;
|
|
}
|
|
|
|
if (!gt.IAmMaster(gr)) // we are not the master
|
|
{
|
|
for (i = 0; i < nldofs; i++)
|
|
{
|
|
ldof_ltdof[ldofs[i]] = -2;
|
|
}
|
|
}
|
|
}
|
|
|
|
// count ltdof_size
|
|
ltdof_size = 0;
|
|
for (i = 0; i < n; i++)
|
|
{
|
|
if (ldof_ltdof[i] == -1)
|
|
{
|
|
ldof_ltdof[i] = ltdof_size++;
|
|
}
|
|
}
|
|
gcomm->SetLTDofTable(ldof_ltdof);
|
|
|
|
// have the group masters broadcast their ltdofs to the rest of the group
|
|
gcomm->Bcast(ldof_ltdof);
|
|
}
|
|
|
|
void ParFiniteElementSpace::ConstructTrueNURBSDofs()
|
|
{
|
|
int n = GetVSize();
|
|
GroupTopology > = pNURBSext()->gtopo;
|
|
gcomm = new GroupCommunicator(gt);
|
|
|
|
// pNURBSext()->ldof_group is for scalar space!
|
|
if (vdim == 1)
|
|
{
|
|
ldof_group.MakeRef(pNURBSext()->ldof_group);
|
|
}
|
|
else
|
|
{
|
|
const int *scalar_ldof_group = pNURBSext()->ldof_group;
|
|
ldof_group.SetSize(n);
|
|
for (int i = 0; i < n; i++)
|
|
{
|
|
ldof_group[i] = scalar_ldof_group[VDofToDof(i)];
|
|
}
|
|
}
|
|
|
|
gcomm->Create(ldof_group);
|
|
|
|
// ldof_sign.SetSize(n);
|
|
// ldof_sign = 1;
|
|
ldof_sign.DeleteAll();
|
|
|
|
ltdof_size = 0;
|
|
ldof_ltdof.SetSize(n);
|
|
for (int i = 0; i < n; i++)
|
|
{
|
|
if (gt.IAmMaster(ldof_group[i]))
|
|
{
|
|
ldof_ltdof[i] = ltdof_size;
|
|
ltdof_size++;
|
|
}
|
|
else
|
|
{
|
|
ldof_ltdof[i] = -2;
|
|
}
|
|
}
|
|
gcomm->SetLTDofTable(ldof_ltdof);
|
|
|
|
// have the group masters broadcast their ltdofs to the rest of the group
|
|
gcomm->Bcast(ldof_ltdof);
|
|
}
|
|
|
|
void ParFiniteElementSpace::GetGhostVertexDofs(const MeshId &id,
|
|
Array<int> &dofs) const
|
|
{
|
|
int nv = fec->DofForGeometry(Geometry::POINT);
|
|
dofs.SetSize(nv);
|
|
for (int j = 0; j < nv; j++)
|
|
{
|
|
dofs[j] = ndofs + nv*id.index + j;
|
|
}
|
|
}
|
|
|
|
static const char* msg_orders_changed =
|
|
"Element orders changed, you need to Update() the space first.";
|
|
|
|
void ParFiniteElementSpace::GetGhostEdgeDofs(const MeshId &edge_id,
|
|
Array<int> &dofs, int variant) const
|
|
{
|
|
MFEM_VERIFY(!orders_changed, msg_orders_changed);
|
|
|
|
int order, ne, base;
|
|
if (IsVariableOrder())
|
|
{
|
|
const int edge = edge_id.index;
|
|
const int* beg = var_edge_dofs.GetRow(edge);
|
|
|
|
base = beg[variant];
|
|
ne = beg[variant+1] - base;
|
|
|
|
base -= nedofs;
|
|
|
|
order = var_edge_orders[var_edge_dofs.GetI()[edge] + variant];
|
|
MFEM_ASSERT(fec->GetNumDof(Geometry::SEGMENT, order) == ne, "");
|
|
}
|
|
else
|
|
{
|
|
order = fec->GetOrder();
|
|
ne = fec->GetNumDof(Geometry::SEGMENT, order);
|
|
base = (edge_id.index - pncmesh->GetNEdges())*ne;
|
|
}
|
|
|
|
int nv = fec->GetNumDof(Geometry::POINT, order);
|
|
|
|
dofs.SetSize(2*nv + ne);
|
|
|
|
int V[2], ghost = pncmesh->GetNVertices();
|
|
pmesh->pncmesh->GetEdgeVertices(edge_id, V);
|
|
|
|
for (int i = 0; i < 2; i++)
|
|
{
|
|
int k = (V[i] < ghost) ? V[i]*nv : (ndofs + (V[i] - ghost)*nv);
|
|
for (int j = 0; j < nv; j++)
|
|
{
|
|
dofs[i*nv + j] = k++;
|
|
}
|
|
}
|
|
|
|
int k = ndofs + ngvdofs + base;
|
|
for (int j = 0; j < ne; j++)
|
|
{
|
|
dofs[2*nv + j] = k++;
|
|
}
|
|
}
|
|
|
|
void ParFiniteElementSpace::GetGhostFaceDofs(const MeshId &face_id,
|
|
Array<int> &dofs) const
|
|
{
|
|
MFEM_VERIFY(!orders_changed, msg_orders_changed);
|
|
|
|
int nfv, V[4], E[4], Eo[4];
|
|
nfv = pmesh->pncmesh->GetFaceVerticesEdges(face_id, V, E, Eo);
|
|
|
|
int nv = fec->DofForGeometry(Geometry::POINT);
|
|
int ne = fec->DofForGeometry(Geometry::SEGMENT);
|
|
int nf_tri = fec->DofForGeometry(Geometry::TRIANGLE);
|
|
int nf_quad = fec->DofForGeometry(Geometry::SQUARE);
|
|
int nf = (nfv == 3) ? nf_tri : nf_quad;
|
|
|
|
const int ghost_face_index = face_id.index - pncmesh->GetNFaces();
|
|
|
|
Array<int> evar(nfv);
|
|
|
|
int base;
|
|
if (IsVariableOrder())
|
|
{
|
|
const int face = face_id.index;
|
|
const int* beg = var_face_dofs.GetRow(face);
|
|
constexpr int variant = 0; // Face variant
|
|
|
|
base = beg[variant];
|
|
nf = beg[variant+1] - base;
|
|
|
|
base -= nfdofs;
|
|
|
|
int allne = 0;
|
|
|
|
const int fo = GetFaceOrder(face, variant);
|
|
for (int i = 0; i < nfv; i++)
|
|
{
|
|
// Find the edge variant matching the face order
|
|
evar[i] = 0;
|
|
int eo = 0;
|
|
while (eo != -1)
|
|
{
|
|
eo = GetEdgeOrder(E[i], evar[i]);
|
|
if (eo == fo)
|
|
{
|
|
break;
|
|
}
|
|
|
|
evar[i]++;
|
|
}
|
|
|
|
MFEM_VERIFY(eo == fo, "Edge must have same order as face");
|
|
|
|
const int* ebeg = var_edge_dofs.GetRow(E[i]);
|
|
const int ne_i = ebeg[evar[i] + 1] - ebeg[evar[i]];
|
|
allne += ne_i;
|
|
}
|
|
|
|
dofs.SetSize((nfv * nv) + allne + nf);
|
|
}
|
|
else
|
|
{
|
|
base = nf_quad * ghost_face_index;
|
|
// TODO: why nf_quad and never nf_tri? Is it because only quad faces are
|
|
// supported for NCMesh? If so, why even have nf_tri?
|
|
|
|
dofs.SetSize(nfv*(nv + ne) + nf);
|
|
}
|
|
|
|
int offset = 0;
|
|
for (int i = 0; i < nfv; i++)
|
|
{
|
|
const int ghost = pncmesh->GetNVertices();
|
|
const int first = (V[i] < ghost) ? V[i]*nv : (ndofs + (V[i] - ghost)*nv);
|
|
for (int j = 0; j < nv; j++)
|
|
{
|
|
dofs[offset++] = first + j;
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < nfv; i++)
|
|
{
|
|
const int ghost = pncmesh->GetNEdges();
|
|
if (IsVariableOrder())
|
|
{
|
|
const int variant = evar[i]; // Edge variant
|
|
|
|
const int* beg = var_edge_dofs.GetRow(E[i]);
|
|
int ebase = beg[variant];
|
|
ne = beg[variant+1] - ebase;
|
|
|
|
MFEM_ASSERT(ebase == FindEdgeDof(E[i], ne), "sanity check?");
|
|
|
|
const int first = (E[i] < ghost) ? nvdofs + ebase
|
|
/* */ : ndofs + ngvdofs + ebase - nedofs;
|
|
|
|
const int edge_order = var_edge_orders[var_edge_dofs.GetI()[E[i]] + variant];
|
|
const int *ind = fec->GetDofOrdering(Geometry::SEGMENT, edge_order, Eo[i]);
|
|
|
|
MFEM_ASSERT(fec->GetNumDof(Geometry::SEGMENT, edge_order) == ne, "");
|
|
|
|
for (int j = 0; j < ne; j++)
|
|
{
|
|
dofs[offset++] = (ind[j] >= 0) ? (first + ind[j]) :
|
|
FlipIndexSign(first + FlipIndexSign(ind[j]));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const int first = (E[i] < ghost) ? nvdofs + E[i]*ne
|
|
/* */ : ndofs + ngvdofs + (E[i] - ghost)*ne;
|
|
const int *ind = fec->DofOrderForOrientation(Geometry::SEGMENT, Eo[i]);
|
|
for (int j = 0; j < ne; j++)
|
|
{
|
|
dofs[offset++] = (ind[j] >= 0) ? (first + ind[j]) :
|
|
FlipIndexSign(first + FlipIndexSign(ind[j]));
|
|
}
|
|
}
|
|
}
|
|
|
|
const int first = ndofs + ngvdofs + ngedofs + base;
|
|
for (int j = 0; j < nf; j++)
|
|
{
|
|
dofs[offset++] = first + j;
|
|
}
|
|
}
|
|
|
|
void ParFiniteElementSpace::GetGhostDofs(int entity, const MeshId &id,
|
|
Array<int> &dofs, int var) const
|
|
{
|
|
// helper to get ghost vertex, ghost edge or ghost face DOFs
|
|
switch (entity)
|
|
{
|
|
case 0: GetGhostVertexDofs(id, dofs); break;
|
|
case 1: GetGhostEdgeDofs(id, dofs, var); break;
|
|
case 2: GetGhostFaceDofs(id, dofs); break;
|
|
}
|
|
}
|
|
|
|
void ParFiniteElementSpace::GetBareDofsVar(int entity, int index,
|
|
Array<int> &dofs) const
|
|
{
|
|
int ned, ghost, first;
|
|
switch (entity)
|
|
{
|
|
case 0:
|
|
ned = fec->DofForGeometry(Geometry::POINT);
|
|
ghost = pncmesh->GetNVertices();
|
|
first = (index < ghost)
|
|
? index*ned // regular vertex
|
|
: ndofs + (index - ghost)*ned; // ghost vertex
|
|
break;
|
|
case 1:
|
|
ghost = pncmesh->GetNEdges();
|
|
{
|
|
const int* row = var_edge_dofs.GetRow(index);
|
|
const int* rowNext = var_edge_dofs.GetRow(index + 1);
|
|
ned = rowNext[0] - row[0];
|
|
first = (index < ghost)
|
|
? nvdofs + row[0] // regular edge
|
|
: ndofs + ngvdofs + row[0] - nedofs; // ghost edge
|
|
}
|
|
break;
|
|
default:
|
|
ghost = pncmesh->GetNFaces();
|
|
{
|
|
const int row0 = FirstFaceDof(index);
|
|
ned = FirstFaceDof(index + 1) - row0;
|
|
if (index < ghost) // regular face
|
|
{
|
|
first = nvdofs + nedofs + row0;
|
|
}
|
|
else // ghost face
|
|
{
|
|
first = ndofs + ngvdofs + ngedofs + row0 - nfdofs;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
dofs.SetSize(ned);
|
|
for (int i = 0; i < ned; i++)
|
|
{
|
|
dofs[i] = first + i;
|
|
}
|
|
}
|
|
|
|
void ParFiniteElementSpace::GetBareDofs(int entity, int index,
|
|
Array<int> &dofs) const
|
|
{
|
|
if (IsVariableOrder())
|
|
{
|
|
GetBareDofsVar(entity, index, dofs);
|
|
return;
|
|
}
|
|
|
|
int ned, ghost, first;
|
|
switch (entity)
|
|
{
|
|
case 0:
|
|
ned = fec->DofForGeometry(Geometry::POINT);
|
|
ghost = pncmesh->GetNVertices();
|
|
first = (index < ghost)
|
|
? index*ned // regular vertex
|
|
: ndofs + (index - ghost)*ned; // ghost vertex
|
|
break;
|
|
|
|
case 1:
|
|
ned = fec->DofForGeometry(Geometry::SEGMENT);
|
|
ghost = pncmesh->GetNEdges();
|
|
first = (index < ghost)
|
|
? nvdofs + index*ned // regular edge
|
|
: ndofs + ngvdofs + (index - ghost)*ned; // ghost edge
|
|
break;
|
|
|
|
default:
|
|
Geometry::Type geom = pncmesh->GetFaceGeometry(index);
|
|
MFEM_ASSERT(geom == Geometry::SQUARE ||
|
|
geom == Geometry::TRIANGLE, "");
|
|
|
|
ned = fec->DofForGeometry(geom);
|
|
ghost = pncmesh->GetNFaces();
|
|
|
|
if (index < ghost) // regular face
|
|
{
|
|
first = nvdofs + nedofs + FirstFaceDof(index);
|
|
}
|
|
else // ghost face
|
|
{
|
|
index -= ghost;
|
|
int stride = fec->DofForGeometry(Geometry::SQUARE);
|
|
first = ndofs + ngvdofs + ngedofs + index*stride;
|
|
}
|
|
break;
|
|
}
|
|
|
|
dofs.SetSize(ned);
|
|
for (int i = 0; i < ned; i++)
|
|
{
|
|
dofs[i] = first + i;
|
|
}
|
|
}
|
|
|
|
int ParFiniteElementSpace::PackDofVar(int entity, int index, int edof,
|
|
int var) const
|
|
{
|
|
int ghost, ned;
|
|
switch (entity)
|
|
{
|
|
case 0:
|
|
// Vertices have 0 or 1 DOFs, regardless of order.
|
|
ghost = pncmesh->GetNVertices();
|
|
ned = fec->DofForGeometry(Geometry::POINT);
|
|
|
|
return (index < ghost)
|
|
? index*ned + edof // regular vertex
|
|
: ndofs + (index - ghost)*ned + edof; // ghost vertex
|
|
|
|
case 1:
|
|
ghost = pncmesh->GetNEdges();
|
|
{
|
|
const int* row = var_edge_dofs.GetRow(index);
|
|
MFEM_ASSERT(0 <= var && var < var_edge_dofs.RowSize(index), "");
|
|
const int d = row[var] + edof;
|
|
if (index < ghost) // regular edge
|
|
{
|
|
return nvdofs + d;
|
|
}
|
|
else // ghost edge
|
|
{
|
|
return ndofs + ngvdofs + d - nedofs;
|
|
}
|
|
}
|
|
default:
|
|
ghost = pncmesh->GetNFaces();
|
|
if (index < ghost) // regular face
|
|
{
|
|
MFEM_ASSERT(0 <= var && var < var_face_dofs.RowSize(index), "");
|
|
return nvdofs + nedofs + FirstFaceDof(index, var) + edof;
|
|
}
|
|
else // ghost face
|
|
{
|
|
return ndofs + ngvdofs + ngedofs + FirstFaceDof(index, var) - nfdofs + edof;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int bisect(const int* array, int size, int value)
|
|
{
|
|
const int* end = array + size;
|
|
const int* pos = std::upper_bound(array, end, value);
|
|
MFEM_VERIFY(pos != array, "value not found");
|
|
if (pos == end)
|
|
{
|
|
MFEM_VERIFY(*(array+size - 1) == value, "Last entry must be exact")
|
|
}
|
|
return pos - array - 1;
|
|
}
|
|
|
|
void ParFiniteElementSpace::UnpackDofVar(int dof, int &entity, int &index,
|
|
int &edof, int &order) const
|
|
{
|
|
order = -1;
|
|
MFEM_ASSERT(dof >= 0, "");
|
|
if (dof < ndofs)
|
|
{
|
|
if (dof < nvdofs) // regular vertex
|
|
{
|
|
int nv = fec->DofForGeometry(Geometry::POINT);
|
|
entity = 0, index = dof / nv, edof = dof % nv;
|
|
return;
|
|
}
|
|
dof -= nvdofs;
|
|
if (dof < nedofs) // regular edge
|
|
{
|
|
entity = 1;
|
|
index = var_edge_dofmap[dof].index;
|
|
edof = var_edge_dofmap[dof].edof;
|
|
|
|
// Convert from local to global offset.
|
|
int os = 0;
|
|
order = -1;
|
|
const int edge = index;
|
|
const int nvar = this->GetNVariants(1, edge);
|
|
for (int v=0; v<nvar; ++v)
|
|
{
|
|
const int eo = this->GetEdgeOrder(edge, v);
|
|
const int dofs = fec->GetNumDof(Geometry::SEGMENT, eo);
|
|
if (edof < os + dofs)
|
|
{
|
|
order = eo;
|
|
break;
|
|
}
|
|
|
|
os += dofs;
|
|
}
|
|
|
|
MFEM_ASSERT(order >= 0, "");
|
|
|
|
edof -= os; // Local offset
|
|
return;
|
|
}
|
|
dof -= nedofs;
|
|
if (dof < nfdofs) // regular face
|
|
{
|
|
entity = 2;
|
|
index = var_face_dofmap[dof].index;
|
|
edof = var_face_dofmap[dof].edof;
|
|
|
|
// Convert from local to global offset.
|
|
int os = 0;
|
|
order = -1;
|
|
const int face = index;
|
|
const Geometry::Type geom = pncmesh->GetFaceGeometry(face);
|
|
const int nvar = this->GetNVariants(2, face);
|
|
for (int v=0; v<nvar; ++v)
|
|
{
|
|
const int fo = this->GetFaceOrder(face, v);
|
|
const int dofs = fec->GetNumDof(geom, fo);
|
|
if (edof < os + dofs)
|
|
{
|
|
order = fo;
|
|
break;
|
|
}
|
|
|
|
os += dofs;
|
|
}
|
|
|
|
MFEM_ASSERT(order >= 0, "");
|
|
|
|
edof -= os; // Local offset
|
|
return;
|
|
}
|
|
MFEM_ABORT("Cannot unpack internal DOF");
|
|
}
|
|
else
|
|
{
|
|
dof -= ndofs;
|
|
if (dof < ngvdofs) // ghost vertex
|
|
{
|
|
int nv = fec->DofForGeometry(Geometry::POINT);
|
|
entity = 0, index = pncmesh->GetNVertices() + dof / nv, edof = dof % nv;
|
|
return;
|
|
}
|
|
|
|
dof -= ngvdofs;
|
|
if (dof < ngedofs) // ghost edge
|
|
{
|
|
entity = 1;
|
|
index = var_edge_dofmap[dof + nedofs].index;
|
|
edof = var_edge_dofmap[dof + nedofs].edof;
|
|
return;
|
|
}
|
|
|
|
dof -= ngedofs;
|
|
if (dof < ngfdofs) // ghost face
|
|
{
|
|
entity = 2;
|
|
index = var_face_dofmap[dof + nfdofs].index;
|
|
edof = var_face_dofmap[dof + nfdofs].edof;
|
|
return;
|
|
}
|
|
MFEM_ABORT("Out of range DOF.");
|
|
}
|
|
}
|
|
|
|
int ParFiniteElementSpace::PackDof(int entity, int index, int edof,
|
|
int var) const
|
|
{
|
|
if (IsVariableOrder())
|
|
{
|
|
return PackDofVar(entity, index, edof, var);
|
|
}
|
|
|
|
// DOFs are ordered as follows:
|
|
// vertices | edges | faces | internal | ghost vert. | g. edges | g. faces
|
|
|
|
int ghost, ned;
|
|
switch (entity)
|
|
{
|
|
case 0:
|
|
ghost = pncmesh->GetNVertices();
|
|
ned = fec->DofForGeometry(Geometry::POINT);
|
|
|
|
return (index < ghost)
|
|
? index*ned + edof // regular vertex
|
|
: ndofs + (index - ghost)*ned + edof; // ghost vertex
|
|
|
|
case 1:
|
|
ghost = pncmesh->GetNEdges();
|
|
ned = fec->DofForGeometry(Geometry::SEGMENT);
|
|
|
|
return (index < ghost)
|
|
? nvdofs + index*ned + edof // regular edge
|
|
: ndofs + ngvdofs + (index - ghost)*ned + edof; // ghost edge
|
|
|
|
default:
|
|
ghost = pncmesh->GetNFaces();
|
|
ned = fec->DofForGeometry(pncmesh->GetFaceGeometry(index));
|
|
|
|
if (index < ghost) // regular face
|
|
{
|
|
return nvdofs + nedofs + FirstFaceDof(index) + edof;
|
|
}
|
|
else // ghost face
|
|
{
|
|
index -= ghost;
|
|
int stride = fec->DofForGeometry(Geometry::SQUARE);
|
|
return ndofs + ngvdofs + ngedofs + index*stride + edof;
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Dissect a DOF number to obtain the entity type (0=vertex, 1=edge, 2=face),
|
|
* entity index and the DOF number within the entity.
|
|
*/
|
|
void ParFiniteElementSpace::UnpackDof(int dof,
|
|
int &entity, int &index,
|
|
int &edof, int &order) const
|
|
{
|
|
order = -1;
|
|
|
|
if (IsVariableOrder())
|
|
{
|
|
UnpackDofVar(dof, entity, index, edof, order);
|
|
return;
|
|
}
|
|
|
|
MFEM_ASSERT(dof >= 0, "");
|
|
if (dof < ndofs)
|
|
{
|
|
if (dof < nvdofs) // regular vertex
|
|
{
|
|
int nv = fec->DofForGeometry(Geometry::POINT);
|
|
entity = 0, index = dof / nv, edof = dof % nv;
|
|
return;
|
|
}
|
|
dof -= nvdofs;
|
|
if (dof < nedofs) // regular edge
|
|
{
|
|
int ne = fec->DofForGeometry(Geometry::SEGMENT);
|
|
entity = 1, index = dof / ne, edof = dof % ne;
|
|
return;
|
|
}
|
|
dof -= nedofs;
|
|
if (dof < nfdofs) // regular face
|
|
{
|
|
if (uni_fdof >= 0) // uniform faces
|
|
{
|
|
int nf = fec->DofForGeometry(pmesh->GetTypicalFaceGeometry());
|
|
index = dof / nf, edof = dof % nf;
|
|
}
|
|
else // mixed faces or var-order space
|
|
{
|
|
const Table &table = var_face_dofs;
|
|
|
|
MFEM_ASSERT(table.Size() > 0, "");
|
|
int jpos = bisect(table.GetJ(), table.Size_of_connections(), dof);
|
|
index = bisect(table.GetI(), table.Size(), jpos);
|
|
edof = dof - table.GetRow(index)[0];
|
|
}
|
|
entity = 2;
|
|
return;
|
|
}
|
|
MFEM_ABORT("Cannot unpack internal DOF");
|
|
}
|
|
else
|
|
{
|
|
dof -= ndofs;
|
|
if (dof < ngvdofs) // ghost vertex
|
|
{
|
|
int nv = fec->DofForGeometry(Geometry::POINT);
|
|
entity = 0, index = pncmesh->GetNVertices() + dof / nv, edof = dof % nv;
|
|
return;
|
|
}
|
|
dof -= ngvdofs;
|
|
if (dof < ngedofs) // ghost edge
|
|
{
|
|
int ne = fec->DofForGeometry(Geometry::SEGMENT);
|
|
entity = 1, index = pncmesh->GetNEdges() + dof / ne, edof = dof % ne;
|
|
return;
|
|
}
|
|
dof -= ngedofs;
|
|
if (dof < ngfdofs) // ghost face
|
|
{
|
|
int stride = fec->DofForGeometry(Geometry::SQUARE);
|
|
index = pncmesh->GetNFaces() + dof / stride, edof = dof % stride;
|
|
entity = 2;
|
|
return;
|
|
}
|
|
MFEM_ABORT("Out of range DOF.");
|
|
}
|
|
}
|
|
|
|
/** Represents an element of the P matrix. The column number is global and
|
|
* corresponds to vector dimension 0. The other dimension columns are offset
|
|
* by 'stride'.
|
|
*/
|
|
struct PMatrixElement
|
|
{
|
|
HYPRE_BigInt column;
|
|
int stride;
|
|
double value;
|
|
|
|
PMatrixElement(HYPRE_BigInt col = 0, int str = 0, double val = 0)
|
|
: column(col), stride(str), value(val) {}
|
|
|
|
bool operator<(const PMatrixElement &other) const
|
|
{ return column < other.column; }
|
|
|
|
typedef std::vector<PMatrixElement> List;
|
|
};
|
|
|
|
/** Represents one row of the P matrix, for the construction code below. The row
|
|
* is complete: diagonal and off-diagonal elements are not distinguished.
|
|
*/
|
|
struct PMatrixRow
|
|
{
|
|
PMatrixElement::List elems;
|
|
|
|
/// Add other row, times 'coef'.
|
|
void AddRow(const PMatrixRow &other, real_t coef)
|
|
{
|
|
elems.reserve(elems.size() + other.elems.size());
|
|
for (const PMatrixElement &oei : other.elems)
|
|
{
|
|
elems.emplace_back(oei.column, oei.stride, coef * oei.value);
|
|
}
|
|
}
|
|
|
|
/// Remove duplicate columns and sum their values.
|
|
void Collapse()
|
|
{
|
|
if (!elems.size()) { return; }
|
|
std::sort(elems.begin(), elems.end());
|
|
|
|
int j = 0;
|
|
for (unsigned i = 1; i < elems.size(); i++)
|
|
{
|
|
if (elems[j].column == elems[i].column)
|
|
{
|
|
elems[j].value += elems[i].value;
|
|
}
|
|
else
|
|
{
|
|
elems[++j] = elems[i];
|
|
}
|
|
}
|
|
elems.resize(j+1);
|
|
}
|
|
|
|
void write(std::ostream &os, real_t sign) const
|
|
{
|
|
bin_io::write<int>(os, static_cast<int>(elems.size()));
|
|
for (unsigned i = 0; i < elems.size(); i++)
|
|
{
|
|
const PMatrixElement &e = elems[i];
|
|
bin_io::write<HYPRE_BigInt>(os, e.column);
|
|
bin_io::write<int>(os, e.stride);
|
|
bin_io::write<real_t>(os, e.value * sign);
|
|
}
|
|
}
|
|
|
|
void read(std::istream &is, real_t sign)
|
|
{
|
|
elems.resize(bin_io::read<int>(is));
|
|
for (unsigned i = 0; i < elems.size(); i++)
|
|
{
|
|
PMatrixElement &e = elems[i];
|
|
e.column = bin_io::read<HYPRE_BigInt>(is);
|
|
e.stride = bin_io::read<int>(is);
|
|
e.value = bin_io::read<real_t>(is) * sign;
|
|
}
|
|
}
|
|
};
|
|
|
|
class NeighborOrderMessage : public VarMessage<VarMessageTag::NEIGHBOR_ORDER_VM>
|
|
{
|
|
public:
|
|
typedef NCMesh::MeshId MeshId;
|
|
typedef ParNCMesh::GroupId GroupId;
|
|
|
|
struct OrderInfo
|
|
{
|
|
int entity, index, order;
|
|
GroupId group;
|
|
|
|
OrderInfo(int ent, int idx, int p, GroupId grp)
|
|
: entity(ent), index(idx), order(p), group(grp) {}
|
|
};
|
|
|
|
NeighborOrderMessage() : pncmesh(NULL) {}
|
|
|
|
void AddOrder(int ent, int idx, int p, GroupId grp)
|
|
{
|
|
msgs.emplace_back(ent, idx, p, grp);
|
|
}
|
|
|
|
void SetNCMesh(ParNCMesh* pnc) { pncmesh = pnc; }
|
|
|
|
const std::vector<OrderInfo>& GetMsgs() const { return msgs; }
|
|
|
|
typedef std::map<int, NeighborOrderMessage> Map;
|
|
|
|
protected:
|
|
std::vector<OrderInfo> msgs;
|
|
|
|
ParNCMesh *pncmesh;
|
|
|
|
/// Encode a NeighborOrderMessage for sending via MPI.
|
|
void Encode(int rank) override;
|
|
/// Decode a NeighborOrderMessage received via MPI.
|
|
void Decode(int rank) override;
|
|
};
|
|
|
|
void NeighborOrderMessage::Encode(int rank)
|
|
{
|
|
std::ostringstream stream;
|
|
|
|
Array<MeshId> ent_ids[3];
|
|
Array<GroupId> group_ids[3];
|
|
Array<int> row_idx[3];
|
|
|
|
// Encode MeshIds and groups
|
|
for (unsigned i = 0; i < msgs.size(); i++)
|
|
{
|
|
const OrderInfo &ri = msgs[i];
|
|
const MeshId &id = *pncmesh->GetNCList(ri.entity).GetMeshIdAndType(ri.index).id;
|
|
ent_ids[ri.entity].Append(id);
|
|
row_idx[ri.entity].Append(i);
|
|
group_ids[ri.entity].Append(ri.group);
|
|
}
|
|
|
|
Array<GroupId> all_group_ids;
|
|
all_group_ids.Reserve(msgs.size());
|
|
for (int i = 0; i < 3; i++)
|
|
{
|
|
all_group_ids.Append(group_ids[i]);
|
|
}
|
|
|
|
pncmesh->AdjustMeshIds(ent_ids, rank);
|
|
pncmesh->EncodeMeshIds(stream, ent_ids);
|
|
pncmesh->EncodeGroups(stream, all_group_ids);
|
|
|
|
// Write all rows to the stream
|
|
for (int ent = 0; ent < 3; ent++)
|
|
{
|
|
for (int i = 0; i < ent_ids[ent].Size(); i++)
|
|
{
|
|
const OrderInfo &ri = msgs[row_idx[ent][i]];
|
|
MFEM_ASSERT(ent == ri.entity, "");
|
|
|
|
bin_io::write<int>(stream, ri.order);
|
|
}
|
|
}
|
|
|
|
msgs.clear();
|
|
stream.str().swap(data);
|
|
}
|
|
|
|
void NeighborOrderMessage::Decode(int rank)
|
|
{
|
|
std::istringstream stream(data);
|
|
|
|
Array<MeshId> ent_ids[3];
|
|
Array<GroupId> group_ids;
|
|
|
|
// decode vertex/edge/face IDs and groups
|
|
pncmesh->DecodeMeshIds(stream, ent_ids);
|
|
pncmesh->DecodeGroups(stream, group_ids);
|
|
|
|
int nrows = ent_ids[0].Size() + ent_ids[1].Size() + ent_ids[2].Size();
|
|
MFEM_ASSERT(nrows == group_ids.Size(), "");
|
|
|
|
msgs.clear();
|
|
msgs.reserve(nrows);
|
|
|
|
// Read messages. ent = {0,1,2} means vertex, edge and face entity
|
|
for (int ent = 1, gi = 0; ent < 3; ent++)
|
|
{
|
|
// extract the vertex list, edge list or face list.
|
|
const Array<MeshId> &ids = ent_ids[ent];
|
|
for (int i = 0; i < ids.Size(); i++)
|
|
{
|
|
const MeshId &id = ids[i];
|
|
// read the particular value off the stream.
|
|
int order_i = bin_io::read<int>(stream);
|
|
|
|
// Create an entry for this entity, recording the index of the mesh
|
|
// element
|
|
msgs.emplace_back(ent, id.index, order_i, group_ids[gi++]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Represents a message to another processor containing P matrix rows.
|
|
* Used by ParFiniteElementSpace::BuildParallelConformingInterpolation.
|
|
*/
|
|
class NeighborRowMessage : public VarMessage<VarMessageTag::NEIGHBOR_ROW_VM>
|
|
{
|
|
public:
|
|
typedef NCMesh::MeshId MeshId;
|
|
typedef ParNCMesh::GroupId GroupId;
|
|
struct RowInfo
|
|
{
|
|
int entity, index, edof, var;
|
|
GroupId group;
|
|
PMatrixRow row;
|
|
|
|
RowInfo(int ent, int idx, int edof, GroupId grp, const PMatrixRow &row,
|
|
int v = 0)
|
|
: entity(ent), index(idx), edof(edof), var(v), group(grp), row(row) {}
|
|
|
|
RowInfo(int ent, int idx, int edof, GroupId grp, int v = 0)
|
|
: entity(ent), index(idx), edof(edof), var(v), group(grp) {}
|
|
};
|
|
|
|
NeighborRowMessage() : pncmesh(NULL) {}
|
|
|
|
void AddRow(int entity, int index, int edof, GroupId group,
|
|
const PMatrixRow &row, int order)
|
|
{
|
|
int var = 0;
|
|
if (varOrder && entity == 1)
|
|
{
|
|
bool found = false;
|
|
while (!found)
|
|
{
|
|
const int order_v = fes->GetEdgeOrder(index, var);
|
|
MFEM_ASSERT(order_v >= 0, "");
|
|
if (order == order_v)
|
|
{
|
|
found = true;
|
|
}
|
|
else
|
|
{
|
|
var++;
|
|
}
|
|
}
|
|
if (!found)
|
|
{
|
|
var = -1;
|
|
}
|
|
}
|
|
else if (varOrder && entity == 2)
|
|
{
|
|
bool found = false;
|
|
while (!found)
|
|
{
|
|
const int order_v = fes->GetFaceOrder(index, var);
|
|
MFEM_ASSERT(order_v >= 0, "");
|
|
if (order == order_v)
|
|
{
|
|
found = true;
|
|
}
|
|
else
|
|
{
|
|
var++;
|
|
}
|
|
}
|
|
|
|
if (!found)
|
|
{
|
|
var = -1;
|
|
}
|
|
}
|
|
|
|
rows.emplace_back(entity, index, edof, group, row, var);
|
|
}
|
|
|
|
const std::vector<RowInfo>& GetRows() const { return rows; }
|
|
|
|
void SetNCMesh(ParNCMesh* pnc) { pncmesh = pnc; }
|
|
void SetFEC(const FiniteElementCollection* fec_) { this->fec = fec_; }
|
|
void SetSpace(const ParFiniteElementSpace* fes_)
|
|
{
|
|
this->fes = fes_;
|
|
varOrder = fes->IsVariableOrder();
|
|
}
|
|
|
|
typedef std::map<int, NeighborRowMessage> Map;
|
|
|
|
protected:
|
|
std::vector<RowInfo> rows;
|
|
|
|
ParNCMesh *pncmesh;
|
|
const FiniteElementCollection* fec;
|
|
const ParFiniteElementSpace* fes;
|
|
|
|
bool varOrder = false;
|
|
|
|
int GetEdgeVarOffset(int edge, int var);
|
|
int GetFaceVarOffset(int face, int var);
|
|
|
|
/// Encode a NeighborRowMessage for sending via MPI.
|
|
void Encode(int rank) override;
|
|
/// Decode a NeighborRowMessage received via MPI.
|
|
void Decode(int rank) override;
|
|
};
|
|
|
|
void NeighborRowMessage::Encode(int rank)
|
|
{
|
|
std::ostringstream stream;
|
|
|
|
Array<MeshId> ent_ids[3];
|
|
Array<GroupId> group_ids[3];
|
|
Array<int> row_idx[3];
|
|
|
|
// Encode MeshIds and groups
|
|
for (unsigned i = 0; i < rows.size(); i++)
|
|
{
|
|
const RowInfo &ri = rows[i];
|
|
const MeshId &id = *pncmesh->GetNCList(ri.entity).GetMeshIdAndType(ri.index).id;
|
|
ent_ids[ri.entity].Append(id);
|
|
row_idx[ri.entity].Append(i);
|
|
group_ids[ri.entity].Append(ri.group);
|
|
}
|
|
|
|
Array<GroupId> all_group_ids;
|
|
all_group_ids.Reserve(static_cast<int>(rows.size()));
|
|
for (int i = 0; i < 3; i++)
|
|
{
|
|
all_group_ids.Append(group_ids[i]);
|
|
}
|
|
|
|
pncmesh->AdjustMeshIds(ent_ids, rank);
|
|
pncmesh->EncodeMeshIds(stream, ent_ids);
|
|
pncmesh->EncodeGroups(stream, all_group_ids);
|
|
|
|
// Write all rows to the stream
|
|
for (int ent = 0; ent < 3; ent++)
|
|
{
|
|
const Array<MeshId> &ids = ent_ids[ent];
|
|
for (int i = 0; i < ids.Size(); i++)
|
|
{
|
|
const MeshId &id = ids[i];
|
|
const RowInfo &ri = rows[row_idx[ent][i]];
|
|
MFEM_ASSERT(ent == ri.entity, "");
|
|
|
|
#ifdef MFEM_DEBUG_PMATRIX
|
|
mfem::out << "Rank " << pncmesh->MyRank << " sending to " << rank
|
|
<< ": ent " << ri.entity << ", index " << ri.index
|
|
<< ", edof " << ri.edof << " (id " << id.element << "/"
|
|
<< int(id.local) << ")" << std::endl;
|
|
#endif
|
|
|
|
// Handle orientation and sign change
|
|
int edof = ri.edof;
|
|
int order_i = fec->GetOrder();
|
|
real_t s = 1.0;
|
|
if (ent == 1)
|
|
{
|
|
const int eo = pncmesh->GetEdgeNCOrientation(id);
|
|
|
|
const int *ind = nullptr;
|
|
int osvar = 0; // Offset for DOFs in the variable-order case
|
|
if (varOrder)
|
|
{
|
|
order_i = fes->GetEdgeOrder(ri.index, ri.var);
|
|
ind = fec->GetDofOrdering(Geometry::SEGMENT, order_i, eo);
|
|
}
|
|
else
|
|
{
|
|
ind = fec->DofOrderForOrientation(Geometry::SEGMENT, eo);
|
|
}
|
|
|
|
if (ind && (edof = ind[edof]) < 0)
|
|
{
|
|
edof = FlipIndexSign(edof);
|
|
s = -1;
|
|
}
|
|
|
|
edof += osvar;
|
|
}
|
|
|
|
if (ent == 2 && varOrder)
|
|
{
|
|
int var = ri.var;
|
|
order_i = fes->GetFaceOrder(ri.index, var);
|
|
}
|
|
|
|
bin_io::write<int>(stream, edof);
|
|
bin_io::write<int>(stream, order_i);
|
|
ri.row.write(stream, s);
|
|
}
|
|
}
|
|
|
|
rows.clear();
|
|
stream.str().swap(data);
|
|
}
|
|
|
|
int NeighborRowMessage::GetEdgeVarOffset(int edge, int var)
|
|
{
|
|
int os = 0;
|
|
for (int v=0; v<var; ++v)
|
|
{
|
|
const int eo = fes->GetEdgeOrder(edge, v);
|
|
const int dofs = fec->GetNumDof(Geometry::SEGMENT, eo);
|
|
os += dofs;
|
|
}
|
|
|
|
return os;
|
|
}
|
|
|
|
int NeighborRowMessage::GetFaceVarOffset(int face, int var)
|
|
{
|
|
Geometry::Type geom = pncmesh->GetFaceGeometry(face);
|
|
int os = 0;
|
|
for (int v=0; v<var; ++v)
|
|
{
|
|
const int fo = fes->GetFaceOrder(face, v);
|
|
const int dofs = fec->GetNumDof(geom, fo);
|
|
os += dofs;
|
|
}
|
|
|
|
return os;
|
|
}
|
|
|
|
void NeighborRowMessage::Decode(int rank)
|
|
{
|
|
std::istringstream stream(data);
|
|
|
|
Array<MeshId> ent_ids[3];
|
|
Array<GroupId> group_ids;
|
|
|
|
// decode vertex/edge/face IDs and groups
|
|
pncmesh->DecodeMeshIds(stream, ent_ids);
|
|
pncmesh->DecodeGroups(stream, group_ids);
|
|
|
|
int nrows = ent_ids[0].Size() + ent_ids[1].Size() + ent_ids[2].Size();
|
|
MFEM_ASSERT(nrows == group_ids.Size(), "");
|
|
|
|
rows.clear();
|
|
rows.reserve(nrows);
|
|
|
|
// read rows ent = {0,1,2} means vertex, edge and face entity
|
|
for (int ent = 0, gi = 0; ent < 3; ent++)
|
|
{
|
|
// extract the vertex list, edge list or face list.
|
|
const Array<MeshId> &ids = ent_ids[ent];
|
|
for (int i = 0; i < ids.Size(); i++)
|
|
{
|
|
const MeshId &id = ids[i];
|
|
// read the particular element dof value off the stream.
|
|
int edof = bin_io::read<int>(stream);
|
|
int order_i = bin_io::read<int>(stream);
|
|
MFEM_ASSERT(order_i >= 0, "");
|
|
|
|
// Handle orientation and sign change. This flips the sign on dofs
|
|
// where necessary, and for edges and faces also reorders if flipped,
|
|
// i.e. an edge with 1 -> 2 -> 3 -> 4 might become -4 -> -3 -> -2 -> -1
|
|
// This cannot treat all face dofs, as they can have rotations and
|
|
// reflections.
|
|
const int *ind = nullptr;
|
|
Geometry::Type geom = Geometry::Type::INVALID;
|
|
int osvar = 0;
|
|
int var = 0;
|
|
if (ent == 1)
|
|
{
|
|
// edge NC orientation is element defined.
|
|
int eo = pncmesh->GetEdgeNCOrientation(id);
|
|
|
|
if (varOrder)
|
|
{
|
|
int order = -1;
|
|
bool found = false;
|
|
while (!found)
|
|
{
|
|
order = fes->GetEdgeOrder(id.index, var);
|
|
if (order == -1)
|
|
{
|
|
// Not found
|
|
var = -1;
|
|
break;
|
|
}
|
|
if (order == order_i)
|
|
{
|
|
found = true;
|
|
}
|
|
else
|
|
{
|
|
var++;
|
|
}
|
|
}
|
|
|
|
if (order < 0)
|
|
{
|
|
// Read the stream for this row and ignore it. This is an
|
|
// invalid row for an intermediate order or ghost edge not
|
|
// used on this rank.
|
|
RowInfo tmprow(1, 0, 0, 0); // Fake, unused row, just to read stream.
|
|
tmprow.row.read(stream, 1.0);
|
|
gi++;
|
|
continue;
|
|
}
|
|
ind = fec->GetDofOrdering(Geometry::SEGMENT, order, eo);
|
|
}
|
|
else
|
|
{
|
|
ind = fec->DofOrderForOrientation(Geometry::SEGMENT, eo);
|
|
}
|
|
}
|
|
else if (ent == 2)
|
|
{
|
|
geom = pncmesh->GetFaceGeometry(id.index);
|
|
const int fo = pncmesh->GetFaceOrientation(id.index);
|
|
if (varOrder)
|
|
{
|
|
MFEM_ASSERT(geom == Geometry::SQUARE,
|
|
"Only quadrilateral faces are supported in "
|
|
"variable-order spaces");
|
|
|
|
int order = -1;
|
|
bool found = false;
|
|
while (!found)
|
|
{
|
|
order = fes->GetFaceOrder(id.index, var);
|
|
if (order == -1)
|
|
{
|
|
// Not found
|
|
var = -1;
|
|
break;
|
|
}
|
|
if (order == order_i)
|
|
{
|
|
found = true;
|
|
}
|
|
else
|
|
{
|
|
var++;
|
|
}
|
|
}
|
|
|
|
if (order < 0)
|
|
{
|
|
// Read the stream for this row and ignore it. This is an
|
|
// invalid row for an intermediate order or ghost face not
|
|
// used on this rank.
|
|
RowInfo tmprow(1, 0, 0, 0); // Fake, unused row, just to read stream.
|
|
tmprow.row.read(stream, 1.0);
|
|
gi++;
|
|
continue;
|
|
}
|
|
|
|
if (order >= 0)
|
|
{
|
|
ind = fec->GetDofOrdering(geom, order, fo);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ind = fec->DofOrderForOrientation(geom, fo);
|
|
}
|
|
}
|
|
// Tri faces with second order basis have dofs that must be processed
|
|
// in pairs, as the doftransformation is not diagonal.
|
|
const bool process_dof_pairs = (ent == 2 &&
|
|
fec->GetContType() == FiniteElementCollection::TANGENTIAL
|
|
&& !Geometry::IsTensorProduct(geom));
|
|
|
|
#ifdef MFEM_DEBUG_PMATRIX
|
|
mfem::out << "Rank " << pncmesh->MyRank << " receiving from " << rank
|
|
<< ": ent " << ent << ", index " << id.index
|
|
<< ", edof " << edof << " (id " << id.element << "/"
|
|
<< int(id.local) << ")" << std::endl;
|
|
#endif
|
|
|
|
// If edof arrived with a negative index, flip it, and the scaling.
|
|
real_t s = (edof < 0) ? -1.0 : 1.0;
|
|
edof = UnsignIndex(edof);
|
|
if (ind && (edof = ind[edof]) < 0)
|
|
{
|
|
edof = FlipIndexSign(edof);
|
|
s *= -1.0;
|
|
}
|
|
|
|
edof += osvar;
|
|
|
|
// Create a row for this entity, recording the index of the mesh
|
|
// element
|
|
rows.emplace_back(ent, id.index, edof, group_ids[gi++], var);
|
|
rows.back().row.read(stream, s);
|
|
|
|
#ifdef MFEM_DEBUG_PMATRIX
|
|
mfem::out << "Rank " << pncmesh->MyRank << " receiving from " << rank
|
|
<< ": ent " << rows.back().entity << ", index "
|
|
<< rows.back().index << ", edof " << rows.back().edof
|
|
<< std::endl;
|
|
#endif
|
|
|
|
if (process_dof_pairs)
|
|
{
|
|
// ND face dofs need to be processed together, as the transformation
|
|
// is given by a 2x2 matrix, so we manually apply an extra increment
|
|
// to the loop counter and add in a new row. Once these rows are
|
|
// placed, they represent the Identity transformation. To map across
|
|
// the processor boundary, we also need to apply a Primal
|
|
// Transformation (see doftrans.hpp) to a notional "global dof"
|
|
// orientation. For simplicity we perform the action of these 2x2
|
|
// matrices manually using the AddRow capability, followed by a
|
|
// Collapse.
|
|
|
|
// To perform the operations, we add and subtract initial versions
|
|
// of the rows, that represent [1 0; 0 1] in row major notation. The
|
|
// first row represents the 1 at (0,0) in [1 0; 0 1] The second row
|
|
// represents the 1 at (1,1) in [1 0; 0 1]
|
|
|
|
// We can safely bind this reference as rows was reserved above so
|
|
// there is no hidden copying that could result in a dangling
|
|
// reference.
|
|
auto &first_row = rows.back().row;
|
|
// This is the first "fundamental unit" used in the transformation.
|
|
const auto initial_first_row = first_row;
|
|
// Extract the next dof too, and apply any dof order transformation
|
|
// expected.
|
|
const MeshId &next_id = ids[++i];
|
|
const int fo = pncmesh->GetFaceOrientation(next_id.index);
|
|
ind = fec->DofOrderForOrientation(geom, fo);
|
|
edof = bin_io::read<int>(stream);
|
|
order_i = bin_io::read<int>(stream);
|
|
|
|
// If edof arrived with a negative index, flip it, and the scaling.
|
|
s = (edof < 0) ? -1.0 : 1.0;
|
|
edof = UnsignIndex(edof);
|
|
if (ind && (edof = ind[edof]) < 0)
|
|
{
|
|
edof = FlipIndexSign(edof);
|
|
s *= -1.0;
|
|
}
|
|
|
|
rows.emplace_back(ent, next_id.index, edof, group_ids[gi++]);
|
|
rows.back().row.read(stream, s);
|
|
auto &second_row = rows.back().row;
|
|
|
|
// This is the second "fundamental unit" used in the transformation.
|
|
const auto initial_second_row = second_row;
|
|
|
|
// Transform the received dofs by the primal transform. This is
|
|
// because within mfem as a face is visited its orientation is
|
|
// assigned to match the element that visited it first. Thus on
|
|
// processor boundaries, the transform will always be identity going
|
|
// into the element. However, the sending processor also thought the
|
|
// face orientation was zero, so it has sent the information in a
|
|
// different orientation. To map onto the local orientation
|
|
// definition, extract the orientation of the sending rank (the
|
|
// lower rank face defines the orientation fo), then apply the
|
|
// transform to the dependencies. The action of this transform on
|
|
// the dependencies is performed by adding scaled versions of the
|
|
// original two rows (which by the mfem assumption of face
|
|
// orientation, represent the identity transform).
|
|
const real_t *T =
|
|
ND_DofTransformation::GetFaceTransform(fo).GetData();
|
|
|
|
MFEM_ASSERT(fo != 2 &&
|
|
fo != 4, "This code branch is ambiguous for face orientations 2 and 4."
|
|
" Please report this mesh for further testing.\n");
|
|
|
|
first_row.AddRow(initial_first_row, T[0] - 1.0); // (0,0)
|
|
first_row.AddRow(initial_second_row, T[2]); // (0,1)
|
|
second_row.AddRow(initial_first_row, T[1]); // (1,0)
|
|
second_row.AddRow(initial_second_row, T[3] - 1.0); // (1,1)
|
|
|
|
first_row.Collapse();
|
|
second_row.Collapse();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
ParFiniteElementSpace::ScheduleSendRow(const PMatrixRow &row, int dof,
|
|
GroupId group_id,
|
|
NeighborRowMessage::Map &send_msg) const
|
|
{
|
|
int ent, idx, edof, order;
|
|
UnpackDof(dof, ent, idx, edof, order);
|
|
|
|
for (const auto &rank : pncmesh->GetGroup(group_id))
|
|
{
|
|
if (rank != MyRank)
|
|
{
|
|
NeighborRowMessage &msg = send_msg[rank];
|
|
msg.SetSpace(this);
|
|
msg.AddRow(ent, idx, edof, group_id, row, order);
|
|
msg.SetNCMesh(pncmesh);
|
|
msg.SetFEC(fec);
|
|
#ifdef MFEM_PMATRIX_STATS
|
|
n_rows_sent++;
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
void ParFiniteElementSpace::ForwardRow(const PMatrixRow &row, int dof,
|
|
GroupId group_sent_id, GroupId group_id,
|
|
NeighborRowMessage::Map &send_msg) const
|
|
{
|
|
int ent, idx, edof, order;
|
|
UnpackDof(dof, ent, idx, edof, order);
|
|
|
|
const ParNCMesh::CommGroup &group = pncmesh->GetGroup(group_id);
|
|
for (unsigned i = 0; i < group.size(); i++)
|
|
{
|
|
int rank = group[i];
|
|
if (rank != MyRank && !pncmesh->GroupContains(group_sent_id, rank))
|
|
{
|
|
NeighborRowMessage &msg = send_msg[rank];
|
|
GroupId invalid = -1; // to prevent forwarding again
|
|
msg.SetSpace(this);
|
|
msg.AddRow(ent, idx, edof, invalid, row, order);
|
|
msg.SetNCMesh(pncmesh);
|
|
msg.SetFEC(fec);
|
|
#ifdef MFEM_PMATRIX_STATS
|
|
n_rows_fwd++;
|
|
#endif
|
|
#ifdef MFEM_DEBUG_PMATRIX
|
|
mfem::out << "Rank " << pncmesh->GetMyRank() << " forwarding to "
|
|
<< rank << ": ent " << ent << ", index" << idx
|
|
<< ", edof " << edof << std::endl;
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef MFEM_DEBUG_PMATRIX
|
|
void ParFiniteElementSpace
|
|
::DebugDumpDOFs(std::ostream &os,
|
|
const SparseMatrix &deps,
|
|
const Array<GroupId> &dof_group,
|
|
const Array<GroupId> &dof_owner,
|
|
const Array<bool> &finalized) const
|
|
{
|
|
for (int i = 0; i < dof_group.Size(); i++)
|
|
{
|
|
os << i << ": ";
|
|
if (i < (nvdofs + nedofs + nfdofs) || i >= ndofs)
|
|
{
|
|
int ent, idx, edof;
|
|
UnpackDof(i, ent, idx, edof);
|
|
|
|
os << edof << " @ ";
|
|
if (i > ndofs) { os << "ghost "; }
|
|
switch (ent)
|
|
{
|
|
case 0: os << "vertex "; break;
|
|
case 1: os << "edge "; break;
|
|
default: os << "face "; break;
|
|
}
|
|
os << idx << "; ";
|
|
|
|
if (i < deps.Height() && deps.RowSize(i))
|
|
{
|
|
os << "depends on ";
|
|
for (int j = 0; j < deps.RowSize(i); j++)
|
|
{
|
|
os << deps.GetRowColumns(i)[j] << " ("
|
|
<< deps.GetRowEntries(i)[j] << ")";
|
|
if (j < deps.RowSize(i)-1) { os << ", "; }
|
|
}
|
|
os << "; ";
|
|
}
|
|
else
|
|
{
|
|
os << "no deps; ";
|
|
}
|
|
|
|
os << "group " << dof_group[i] << " (";
|
|
const ParNCMesh::CommGroup &g = pncmesh->GetGroup(dof_group[i]);
|
|
for (unsigned j = 0; j < g.size(); j++)
|
|
{
|
|
if (j) { os << ", "; }
|
|
os << g[j];
|
|
}
|
|
|
|
os << "), owner " << dof_owner[i] << " (rank "
|
|
<< pncmesh->GetGroup(dof_owner[i])[0] << "); "
|
|
<< (finalized[i] ? "finalized" : "NOT finalized");
|
|
}
|
|
else
|
|
{
|
|
os << "internal";
|
|
}
|
|
os << "\n";
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void ParFiniteElementSpace::ScheduleSendOrder(
|
|
int ent, int idx, int order, GroupId group_id,
|
|
NeighborOrderMessage::Map &send_msg) const
|
|
{
|
|
for (const auto &rank : pncmesh->GetGroup(group_id))
|
|
{
|
|
if (rank != MyRank)
|
|
{
|
|
NeighborOrderMessage &msg = send_msg[rank];
|
|
msg.AddOrder(ent, idx, order, group_id);
|
|
msg.SetNCMesh(pncmesh);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool ParFiniteElementSpace::OrderPropagation(
|
|
const std::set<int> &edges, const std::set<int> &faces,
|
|
Array<VarOrderBits> &edge_orders, Array<VarOrderBits> &face_orders) const
|
|
{
|
|
// Initialize `changed` flag, based on serial changes to edges and faces.
|
|
bool changed = edges.size() > 0 || faces.size() > 0;
|
|
|
|
// If no rank has changes, exit.
|
|
int orders_changed = (int) changed;
|
|
MPI_Allreduce(MPI_IN_PLACE, &orders_changed, 1, MPI_INT, MPI_MAX, MyComm);
|
|
if (orders_changed == 0)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
NeighborOrderMessage::Map send_msg;
|
|
|
|
// Schedule messages
|
|
for (int entity = 1; entity <= 2; ++entity)
|
|
{
|
|
const std::set<int> &indices = entity == 1 ? edges : faces;
|
|
const Array<VarOrderBits> &orders = entity == 1 ? edge_orders : face_orders;
|
|
for (auto idx : indices)
|
|
{
|
|
GroupId group = pncmesh->GetEntityGroupId(entity, idx);
|
|
|
|
if (group != 0)
|
|
{
|
|
ScheduleSendOrder(entity, idx, MinOrder(orders[idx]),
|
|
group, send_msg);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Send messages
|
|
NeighborOrderMessage::IsendAll(send_msg, MyComm);
|
|
|
|
MPI_Barrier(MyComm); // This barrier is necessary for hp-refinement
|
|
|
|
NeighborOrderMessage recv_msg;
|
|
recv_msg.SetNCMesh(pncmesh);
|
|
|
|
// Check for and receive incoming messages
|
|
int rank, size;
|
|
while (NeighborOrderMessage::IProbe(rank, size, MyComm))
|
|
{
|
|
// Note that Recv calls Decode(rank), setting msgs in recv_msg.
|
|
recv_msg.Recv(rank, size, MyComm);
|
|
|
|
for (const auto &ri : recv_msg.GetMsgs())
|
|
{
|
|
const VarOrderBits mask = (VarOrderBits(1) << ri.order);
|
|
if (ri.entity == 1)
|
|
{
|
|
const VarOrderBits initOrders = edge_orders[ri.index];
|
|
edge_orders[ri.index] |= mask;
|
|
if (edge_orders[ri.index] != initOrders)
|
|
{
|
|
changed = true;
|
|
}
|
|
}
|
|
else if (ri.entity == 2)
|
|
{
|
|
const VarOrderBits initOrders = face_orders[ri.index];
|
|
face_orders[ri.index] |= mask;
|
|
if (face_orders[ri.index] != initOrders)
|
|
{
|
|
changed = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
MFEM_ABORT("Invalid entity type");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Clean up possible remaining messages in the queue to avoid receiving them
|
|
// erroneously in the next run
|
|
while (NeighborOrderMessage::IProbe(rank, size, MyComm))
|
|
{
|
|
recv_msg.RecvDrop(rank, size, MyComm);
|
|
}
|
|
|
|
// Make sure we can discard all send buffers
|
|
NeighborOrderMessage::WaitAllSent(send_msg);
|
|
|
|
orders_changed = (int) changed;
|
|
MPI_Allreduce(MPI_IN_PLACE, &orders_changed, 1, MPI_INT, MPI_MAX, MyComm);
|
|
return (orders_changed == 0);
|
|
}
|
|
|
|
void ParFiniteElementSpace::MarkIntermediateEntityDofs(
|
|
int entity, Array<bool> & intermediate) const
|
|
{
|
|
if (!IsVariableOrder()) { return; }
|
|
|
|
MFEM_VERIFY(intermediate.Size() == ndofs, "");
|
|
|
|
const int os = entity == 1 ? nvdofs : nvdofs + nedofs;
|
|
|
|
const int n = entity == 1 ? pmesh->GetNEdges() : pmesh->GetNFaces();
|
|
for (int e=0; e<n; ++e)
|
|
{
|
|
const int nvar = GetNVariants(entity, e);
|
|
for (int var = 1; var < nvar - 1; ++var) // Intermediate variants
|
|
{
|
|
Array<int> dofs;
|
|
GetEntityDofs(entity, e, dofs, Geometry::INVALID, // dummy geom
|
|
var);
|
|
for (auto dof : dofs)
|
|
{
|
|
if (dof >= os) // Skip dofs for vertices (and edges in face case)
|
|
{
|
|
intermediate[dof] = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ParFiniteElementSpace::SetVarDofMap(const Table & dofs,
|
|
Array<VarOrderDofInfo> & dmap)
|
|
{
|
|
if (dofs.Size() < 1)
|
|
{
|
|
dmap.SetSize(0);
|
|
return;
|
|
}
|
|
|
|
MFEM_ASSERT(dofs.RowSize(dofs.Size() - 1) == 1, "");
|
|
const int* rowLast = dofs.GetRow(dofs.Size() - 1);
|
|
const int ndofs = rowLast[0];
|
|
|
|
dmap.SetSize(ndofs);
|
|
|
|
for (int r = 0; r < dofs.Size() - 1; ++r)
|
|
{
|
|
const int* row = dofs.GetRow(r);
|
|
const int* row1 = dofs.GetRow(r+1);
|
|
|
|
for (int d=row[0]; d<row1[0]; ++d) // d = dof
|
|
{
|
|
dmap[d].index = r; // row index
|
|
dmap[d].edof = d - row[0]; // entity index
|
|
}
|
|
}
|
|
}
|
|
|
|
void ParFiniteElementSpace::SetTDOF2LDOFinfo(int ntdofs, int vdim_factor,
|
|
int dof_stride, int allnedofs)
|
|
{
|
|
if (!IsVariableOrder()) { return; }
|
|
|
|
tdof2ldof.SetSize(ntdofs);
|
|
for (int i=0; i<ntdofs; ++i)
|
|
{
|
|
tdof2ldof[i].set = false;
|
|
}
|
|
|
|
// All T-dofs are on conforming and master edges and faces, and we only need
|
|
// data for such entities shared with other MPI ranks.
|
|
|
|
for (int entity = 1; entity < pmesh->Dimension(); entity++)
|
|
{
|
|
const Table &ent_dofs = (entity == 1) ? var_edge_dofs : var_face_dofs;
|
|
const int num_ent = (entity == 1) ? pmesh->GetNEdges() :
|
|
pmesh->GetNFaces();
|
|
MFEM_ASSERT(ent_dofs.Size() >= num_ent+1, "");
|
|
|
|
for (int idx = 0; idx < num_ent; idx++)
|
|
{
|
|
if (ent_dofs.RowSize(idx) == 0) { continue; }
|
|
|
|
Geometry::Type geom =
|
|
(entity == 1) ? Geometry::SEGMENT : pmesh->GetFaceGeometry(idx);
|
|
|
|
// Loop over all DOFs to find T-dofs, since some T-dofs may not be
|
|
// contained in the L-dofs.
|
|
|
|
// Get the lowest order variant DOFs and FE
|
|
Array<int> dofs;
|
|
const int order0 = GetEntityDofs(entity, idx, dofs, geom, 0);
|
|
|
|
int numVert = 2; // Edge case
|
|
if (entity == 2) // Face case
|
|
{
|
|
Array<int> verts;
|
|
pmesh->GetFaceVertices(idx, verts);
|
|
numVert = verts.Size();
|
|
MFEM_VERIFY(numVert == 4, "Only quadrilateral faces are supported");
|
|
}
|
|
|
|
// Interior DOFs start at index idof0
|
|
const int idof0 = GetNumBorderDofs(geom, order0);
|
|
const int minOrder = entity == 1 ? edge_min_nghb_order[idx] :
|
|
face_min_nghb_order[idx];
|
|
|
|
constexpr int vd = 0; // First vector dimension only
|
|
for (int i=idof0; i<dofs.Size(); ++i)
|
|
{
|
|
const int dof_i = dofs[i];
|
|
const int vdof_i = dof_i*vdim_factor + vd*dof_stride;
|
|
const int tdof = ldof_ltdof[vdof_i];
|
|
if (tdof < 0) { continue; }
|
|
|
|
MFEM_ASSERT(!tdof2ldof[tdof].set, "");
|
|
|
|
tdof2ldof[tdof].set = true;
|
|
tdof2ldof[tdof].minOrder = minOrder;
|
|
tdof2ldof[tdof].isEdge = (entity == 1);
|
|
tdof2ldof[tdof].idx = idx;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ParFiniteElementSpace
|
|
::SetRestrictionMatrixEdgesFaces(int vdim_factor, int dof_stride,
|
|
int tdof_stride, const Array<int> &dof_tdof,
|
|
const Array<HYPRE_BigInt> &dof_offs)
|
|
{
|
|
MFEM_VERIFY(IsVariableOrder(), "");
|
|
|
|
const int ntdofs = tdof2ldof.Size();
|
|
MFEM_VERIFY(vdim * ntdofs == R->NumRows(), "");
|
|
|
|
int prevEntity = -1;
|
|
int prevIndex = -1;
|
|
int tdi = -1;
|
|
int idof0 = -1;
|
|
Array<int> ldofs, tdofs;
|
|
DenseMatrix I;
|
|
|
|
for (int tdof=0; tdof<ntdofs; ++tdof)
|
|
{
|
|
if (!tdof2ldof[tdof].set) { continue; } // Skip vertex and element T-dofs
|
|
|
|
const int minOrder = tdof2ldof[tdof].minOrder;
|
|
const bool edge = tdof2ldof[tdof].isEdge;
|
|
const int index = tdof2ldof[tdof].idx;
|
|
const int entity = edge ? 1 : 2;
|
|
MFEM_ASSERT(!pncmesh->IsGhost(entity, index),
|
|
"True DOFs are not defined on ghost entities");
|
|
|
|
if (entity != prevEntity || index != prevIndex)
|
|
{
|
|
tdi = 0;
|
|
}
|
|
else
|
|
{
|
|
tdi++;
|
|
}
|
|
|
|
prevEntity = entity;
|
|
prevIndex = index;
|
|
|
|
if (tdi == 0) // Update I for a new entity
|
|
{
|
|
// Only square faces are supported currently
|
|
const Geometry::Type geom = edge ? Geometry::SEGMENT : Geometry::SQUARE;
|
|
|
|
const FiniteElement *feT = fec->FiniteElementForGeometry(geom);
|
|
const FiniteElement *feL = fec->FiniteElementForGeometry(geom);
|
|
|
|
int tdofOrder = -1;
|
|
if (entity == 1)
|
|
{
|
|
tdofOrder = GetEdgeOrder(index, 0);
|
|
GetEdgeDofs(index, tdofs, 0);
|
|
for (int var=0; ; ++var)
|
|
{
|
|
const int order_var = GetEdgeOrder(index, var);
|
|
if (order_var == minOrder)
|
|
{
|
|
GetEdgeDofs(index, ldofs, var);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else // entity == 2
|
|
{
|
|
tdofOrder = GetFaceOrder(index, 0);
|
|
GetFaceDofs(index, tdofs, 0);
|
|
for (int var=0; ; ++var)
|
|
{
|
|
const int order_var = GetFaceOrder(index, var);
|
|
if (order_var == minOrder)
|
|
{
|
|
GetFaceDofs(index, ldofs, var);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
MFEM_VERIFY(tdofs.Size() > 0 && ldofs.Size() > 0, "");
|
|
|
|
// Interior DOFs start at index idof0
|
|
idof0 = GetNumBorderDofs(geom, tdofOrder);
|
|
|
|
feT = fec->GetFE(geom, tdofOrder);
|
|
feL = fec->GetFE(geom, minOrder);
|
|
|
|
MFEM_VERIFY(feT && feL, "");
|
|
|
|
IsoparametricTransformation T;
|
|
|
|
switch (geom)
|
|
{
|
|
case Geometry::SQUARE: T.SetFE(&QuadrilateralFE); break;
|
|
case Geometry::SEGMENT: T.SetFE(&SegmentFE); break;
|
|
default: MFEM_ABORT("unsupported geometry");
|
|
}
|
|
|
|
// Interpolate T-dofs of order tdofOrder from L-dofs of order minOrder
|
|
T.SetIdentityTransformation(geom);
|
|
feT->GetTransferMatrix(*feL, T, I);
|
|
}
|
|
|
|
for (int ldi=0; ldi<ldofs.Size(); ++ldi)
|
|
{
|
|
const real_t value = I(tdi + idof0, ldi);
|
|
if (std::abs(value) > 1e-12)
|
|
{
|
|
const int ldof = all2local[ldofs[ldi]];
|
|
for (int vd = 0; vd < vdim; vd++)
|
|
{
|
|
const int vdof = ldof*vdim_factor + vd*dof_stride;
|
|
const int vtdof = tdof*vdim_factor + vd*tdof_stride;
|
|
R->Add(vtdof, vdof, value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int ParFiniteElementSpace
|
|
::BuildParallelConformingInterpolation(HypreParMatrix **P_, SparseMatrix **R_,
|
|
Array<HYPRE_BigInt> &dof_offs,
|
|
Array<HYPRE_BigInt> &tdof_offs,
|
|
Array<int> *dof_tdof,
|
|
bool partial)
|
|
{
|
|
const bool dg = (nvdofs == 0 && nedofs == 0 && nfdofs == 0);
|
|
const bool H1var = IsVariableOrderH1();
|
|
|
|
#ifdef MFEM_PMATRIX_STATS
|
|
n_msgs_sent = n_msgs_recv = 0;
|
|
n_rows_sent = n_rows_recv = n_rows_fwd = 0;
|
|
#endif
|
|
|
|
// *** STEP 1: build master-slave dependency lists ***
|
|
|
|
const int total_dofs = ndofs + ngdofs;
|
|
SparseMatrix deps(ndofs, total_dofs);
|
|
|
|
if (!dg && !partial)
|
|
{
|
|
VariableOrderMinimumRule(deps);
|
|
|
|
Array<int> master_dofs, slave_dofs;
|
|
|
|
// loop through *all* master edges/faces, constrain their slaves
|
|
for (int entity = 0; entity <= 2; entity++)
|
|
{
|
|
const NCMesh::NCList &list = pncmesh->GetNCList(entity);
|
|
if (list.masters.Size() == 0) { continue; }
|
|
|
|
IsoparametricTransformation T;
|
|
DenseMatrix I;
|
|
|
|
// process masters that we own or that affect our edges/faces
|
|
for (const auto &mf : list.masters)
|
|
{
|
|
// get master DOFs
|
|
if (entity == 1 && skip_edge.Size() > 0)
|
|
{
|
|
if (skip_edge[mf.index])
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
else if (entity == 2 && skip_face.Size() > 0)
|
|
{
|
|
if (skip_face[mf.index])
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (pncmesh->IsGhost(entity, mf.index))
|
|
{
|
|
GetGhostDofs(entity, mf, master_dofs, 0);
|
|
}
|
|
else
|
|
{
|
|
GetEntityDofs(entity, mf.index, master_dofs, mf.Geom(), 0);
|
|
}
|
|
|
|
if (master_dofs.Size() == 0) { continue; }
|
|
|
|
const FiniteElement *fe = fec->FiniteElementForGeometry(mf.Geom());
|
|
|
|
if (IsVariableOrder())
|
|
{
|
|
int mfOrder = -1;
|
|
if (entity == 1) { mfOrder = GetEdgeOrder(mf.index, 0); }
|
|
else if (entity == 2) { mfOrder = GetFaceOrder(mf.index, 0); }
|
|
|
|
if (entity != 0) { fe = fec->GetFE(mf.Geom(), mfOrder); }
|
|
}
|
|
|
|
if (fe == nullptr) { continue; }
|
|
|
|
switch (mf.Geom())
|
|
{
|
|
case Geometry::SQUARE: T.SetFE(&QuadrilateralFE); break;
|
|
case Geometry::TRIANGLE: T.SetFE(&TriangleFE); break;
|
|
case Geometry::SEGMENT: T.SetFE(&SegmentFE); break;
|
|
default: MFEM_ABORT("unsupported geometry");
|
|
}
|
|
|
|
// constrain slaves that exist in our mesh
|
|
for (int si = mf.slaves_begin; si < mf.slaves_end; si++)
|
|
{
|
|
const NCMesh::Slave &sf = list.slaves[si];
|
|
if (pncmesh->IsGhost(entity, sf.index)) { continue; }
|
|
|
|
constexpr int variant = 0;
|
|
const int q = GetEntityDofs(entity, sf.index, slave_dofs, mf.Geom(), variant);
|
|
if (q < 0) { break; }
|
|
|
|
list.OrientedPointMatrix(sf, T.GetPointMat());
|
|
|
|
const auto *slave_fe = fec->GetFE(mf.Geom(), q);
|
|
slave_fe->GetTransferMatrix(*fe, T, I);
|
|
|
|
// make each slave DOF dependent on all master DOFs
|
|
AddDependencies(deps, master_dofs, slave_dofs, I);
|
|
}
|
|
}
|
|
}
|
|
|
|
deps.Finalize();
|
|
}
|
|
|
|
// *** STEP 2: initialize group and owner ID for each DOF ***
|
|
|
|
Array<GroupId> dof_group(total_dofs);
|
|
Array<GroupId> dof_owner(total_dofs);
|
|
dof_group = 0;
|
|
dof_owner = 0;
|
|
|
|
if (!dg)
|
|
{
|
|
Array<int> dofs;
|
|
|
|
auto initialize_group_and_owner = [&dof_group, &dof_owner, &dofs,
|
|
this](int entity, const MeshId &id)
|
|
{
|
|
if (id.index < 0) { return; }
|
|
|
|
GroupId owner = pncmesh->GetEntityOwnerId(entity, id.index);
|
|
GroupId group = pncmesh->GetEntityGroupId(entity, id.index);
|
|
|
|
GetBareDofs(entity, id.index, dofs);
|
|
|
|
for (auto dof : dofs)
|
|
{
|
|
dof_owner[dof] = owner;
|
|
dof_group[dof] = group;
|
|
}
|
|
};
|
|
|
|
// initialize dof_group[], dof_owner[] in sequence
|
|
for (int entity : {0,1,2})
|
|
{
|
|
for (const auto &id : pncmesh->GetNCList(entity).conforming)
|
|
{
|
|
initialize_group_and_owner(entity, id);
|
|
}
|
|
for (const auto &id : pncmesh->GetNCList(entity).masters)
|
|
{
|
|
initialize_group_and_owner(entity, id);
|
|
}
|
|
for (const auto &id : pncmesh->GetNCList(entity).slaves)
|
|
{
|
|
initialize_group_and_owner(entity, id);
|
|
}
|
|
}
|
|
}
|
|
|
|
// *** STEP 3: count true DOFs and calculate P row/column partitions ***
|
|
|
|
Array<bool> finalized(total_dofs);
|
|
finalized = false;
|
|
|
|
// DOFs that stayed independent and are ours are true DOFs
|
|
int num_true_dofs = 0;
|
|
for (int i = 0; i < ndofs; ++i)
|
|
{
|
|
if (dof_owner[i] == 0 && deps.RowSize(i) == 0)
|
|
{
|
|
++num_true_dofs;
|
|
finalized[i] = true;
|
|
}
|
|
}
|
|
|
|
#ifdef MFEM_DEBUG_PMATRIX
|
|
// Helper for dumping diagnostics on one dof
|
|
auto dof_diagnostics = [&](int dof, bool print_diagnostic)
|
|
{
|
|
const auto &comm_group = pncmesh->GetGroup(dof_group[dof]);
|
|
std::stringstream msg;
|
|
msg << std::boolalpha;
|
|
msg << "R" << Mpi::WorldRank() << " dof " << dof
|
|
<< " owner_rank " << pncmesh->GetGroup(dof_owner[dof])[0] << " CommGroup {";
|
|
for (const auto &x : comm_group)
|
|
{
|
|
msg << x << ' ';
|
|
}
|
|
msg << "} finalized " << finalized[dof];
|
|
|
|
Array<int> cols;
|
|
if (dof < ndofs)
|
|
{
|
|
Vector row;
|
|
deps.GetRow(dof, cols, row);
|
|
msg << " deps cols {";
|
|
for (const auto &x : cols)
|
|
{
|
|
msg << x << ' ';
|
|
}
|
|
msg << '}';
|
|
}
|
|
|
|
int entity, index, edof;
|
|
UnpackDof(dof, entity, index, edof);
|
|
msg << " entity " << entity << " index " << index << " edof " << edof;
|
|
return msg.str();
|
|
};
|
|
#endif
|
|
|
|
// calculate global offsets
|
|
{
|
|
HYPRE_BigInt loc_sizes[2] = { ndofs*vdim, num_true_dofs*vdim };
|
|
Array<HYPRE_BigInt>* offsets[2] = { &dof_offs, &tdof_offs };
|
|
pmesh->GenerateOffsets(2, loc_sizes, offsets); // calls MPI_Scan, MPI_Bcast
|
|
}
|
|
|
|
HYPRE_BigInt my_tdof_offset =
|
|
tdof_offs[HYPRE_AssumedPartitionCheck() ? 0 : MyRank];
|
|
|
|
if (R_ && !H1var)
|
|
{
|
|
// initialize the restriction matrix (also parallel but block-diagonal)
|
|
*R_ = new SparseMatrix(num_true_dofs*vdim, ndofs*vdim);
|
|
}
|
|
if (dof_tdof)
|
|
{
|
|
dof_tdof->SetSize(ndofs*vdim);
|
|
*dof_tdof = -1;
|
|
}
|
|
|
|
std::vector<PMatrixRow> pmatrix(total_dofs);
|
|
|
|
const bool bynodes = (ordering == Ordering::byNODES);
|
|
const int vdim_factor = bynodes ? 1 : vdim;
|
|
const int dof_stride = bynodes ? ndofs : 1;
|
|
const int tdof_stride = bynodes ? num_true_dofs : 1;
|
|
|
|
// big container for all messages we send (the list is for iterations)
|
|
std::list<NeighborRowMessage::Map> send_msg;
|
|
send_msg.emplace_back();
|
|
|
|
// put identity in P and R for true DOFs, set ldof_ltdof (dof_tdof)
|
|
for (int dof = 0, tdof = 0; dof < ndofs; dof++)
|
|
{
|
|
if (finalized[dof])
|
|
{
|
|
pmatrix[dof].elems.emplace_back(
|
|
my_tdof_offset + vdim_factor*tdof, tdof_stride, 1.);
|
|
|
|
// prepare messages to neighbors with identity rows
|
|
if (dof_group[dof] != 0)
|
|
{
|
|
MFEM_VERIFY(!send_msg.empty(), "");
|
|
ScheduleSendRow(pmatrix[dof], dof, dof_group[dof], send_msg.back());
|
|
}
|
|
|
|
for (int vd = 0; vd < vdim; vd++)
|
|
{
|
|
const int vdof = dof*vdim_factor + vd*dof_stride;
|
|
const int vtdof = tdof*vdim_factor + vd*tdof_stride;
|
|
|
|
if (R_ && !H1var) { (*R_)->Add(vtdof, vdof, 1.0); }
|
|
if (dof_tdof) { (*dof_tdof)[vdof] = vtdof; }
|
|
}
|
|
++tdof;
|
|
}
|
|
}
|
|
|
|
// send identity rows
|
|
MFEM_VERIFY(!send_msg.empty(), "");
|
|
NeighborRowMessage::IsendAll(send_msg.back(), MyComm);
|
|
#ifdef MFEM_PMATRIX_STATS
|
|
n_msgs_sent += send_msg.back().size();
|
|
#endif
|
|
|
|
if (R_ && !H1var) { (*R_)->Finalize(); }
|
|
|
|
// *** STEP 4: main loop ***
|
|
|
|
// a single instance (recv_msg) is reused for all incoming messages
|
|
NeighborRowMessage recv_msg;
|
|
recv_msg.SetNCMesh(pncmesh);
|
|
recv_msg.SetSpace(this);
|
|
recv_msg.SetFEC(fec);
|
|
|
|
int num_finalized = num_true_dofs;
|
|
PMatrixRow buffer;
|
|
buffer.elems.reserve(1024);
|
|
|
|
// The lowest order may be finalized by receiving messages, but the
|
|
// intermediate orders not owned may have ghost DOFs which may be dependencies
|
|
// for other DOFs. Thus we must allow finalizing intermediate DOFs, when they
|
|
// are ghosts.
|
|
Array<bool> intermediate(ndofs);
|
|
intermediate = false;
|
|
|
|
MarkIntermediateEntityDofs(1, intermediate);
|
|
MarkIntermediateEntityDofs(2, intermediate);
|
|
|
|
while (num_finalized < ndofs)
|
|
{
|
|
// prepare a new round of send buffers
|
|
MFEM_VERIFY(!send_msg.empty(), "");
|
|
if (send_msg.back().size())
|
|
{
|
|
send_msg.emplace_back();
|
|
}
|
|
|
|
// check for incoming messages, receive PMatrixRows
|
|
int rank, size;
|
|
while (NeighborRowMessage::IProbe(rank, size, MyComm))
|
|
{
|
|
// Note that Recv calls Decode(rank), setting rows in recv_msg.
|
|
recv_msg.Recv(rank, size, MyComm);
|
|
|
|
#ifdef MFEM_PMATRIX_STATS
|
|
n_msgs_recv++;
|
|
n_rows_recv += recv_msg.GetRows().size();
|
|
#endif
|
|
|
|
for (const auto &ri : recv_msg.GetRows())
|
|
{
|
|
const int dof = PackDof(ri.entity, ri.index, ri.edof, ri.var);
|
|
pmatrix[dof] = ri.row;
|
|
|
|
if (dof < ndofs && !finalized[dof]) { ++num_finalized; }
|
|
finalized[dof] = true;
|
|
|
|
if (ri.group >= 0 && dof_group[dof] != ri.group)
|
|
{
|
|
// the sender didn't see the complete group, forward the message
|
|
MFEM_VERIFY(!send_msg.empty(), "");
|
|
ForwardRow(ri.row, dof, ri.group, dof_group[dof], send_msg.back());
|
|
}
|
|
}
|
|
}
|
|
|
|
// finalize all rows that can currently be finalized
|
|
bool done = false;
|
|
while (!done)
|
|
{
|
|
done = true;
|
|
for (int dof = 0; dof < ndofs; dof++)
|
|
{
|
|
const bool owned = (dof_owner[dof] == 0);
|
|
if (!finalized[dof]
|
|
&& (owned || intermediate[dof])
|
|
&& DofFinalizable(dof, finalized, deps))
|
|
{
|
|
const int* dep_col = deps.GetRowColumns(dof);
|
|
const real_t* dep_coef = deps.GetRowEntries(dof);
|
|
|
|
// form linear combination of rows
|
|
buffer.elems.clear();
|
|
for (int j = 0; j < deps.RowSize(dof); j++)
|
|
{
|
|
buffer.AddRow(pmatrix[dep_col[j]], dep_coef[j]);
|
|
}
|
|
buffer.Collapse();
|
|
pmatrix[dof] = buffer;
|
|
|
|
finalized[dof] = true;
|
|
++num_finalized;
|
|
done = false;
|
|
|
|
// send row to neighbors who need it
|
|
const bool shared = (dof_group[dof] != 0);
|
|
if (shared)
|
|
{
|
|
MFEM_VERIFY(!send_msg.empty(), "");
|
|
ScheduleSendRow(pmatrix[dof], dof, dof_group[dof],
|
|
send_msg.back());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef MFEM_DEBUG_PMATRIX
|
|
static int dump = 0;
|
|
if (dump < 10)
|
|
{
|
|
char fname[100];
|
|
snprintf(fname, 100, "dofs%02d.txt", MyRank);
|
|
std::ofstream f(fname);
|
|
DebugDumpDOFs(f, deps, dof_group, dof_owner, finalized);
|
|
dump++;
|
|
}
|
|
#endif
|
|
|
|
// send current batch of messages
|
|
MFEM_VERIFY(!send_msg.empty(), "");
|
|
NeighborRowMessage::IsendAll(send_msg.back(), MyComm);
|
|
#ifdef MFEM_PMATRIX_STATS
|
|
n_msgs_sent += send_msg.back().size();
|
|
#endif
|
|
}
|
|
|
|
if (H1var)
|
|
{
|
|
const int allnedofs = nedofs;
|
|
// TODO: isn't this necessary even in the serial FiniteElementSpace?
|
|
// See FiniteElementSpace::BuildConformingInterpolation()
|
|
SetVarOrderLocalDofs();
|
|
|
|
const int ldof_stride = bynodes ? ndofs : 1;
|
|
|
|
{
|
|
// recalculate global offsets
|
|
HYPRE_BigInt loc_sizes[1] = { ndofs*vdim };
|
|
Array<HYPRE_BigInt>* offsets[1] = { &dof_offs };
|
|
pmesh->GenerateOffsets(1, loc_sizes, offsets);
|
|
}
|
|
|
|
// Extract only the rows of pmatrix corresponding to local DOFs, as given
|
|
// by all2local.
|
|
std::vector<PMatrixRow> pmatrix_new(ndofs);
|
|
{
|
|
int dofnew = -1;
|
|
bool validMap = true;
|
|
for (int i=0; i<all2local.Size(); ++i)
|
|
{
|
|
if (all2local[i] >= 0)
|
|
{
|
|
if (all2local[i] - dofnew != 1)
|
|
{
|
|
validMap = false;
|
|
}
|
|
|
|
dofnew = all2local[i];
|
|
|
|
pmatrix_new[all2local[i]] = pmatrix[i];
|
|
}
|
|
}
|
|
MFEM_VERIFY(validMap && dofnew == ndofs - 1, "");
|
|
}
|
|
|
|
if (P_)
|
|
{
|
|
*P_ = MakeVDimHypreMatrix(pmatrix_new, ndofs, num_true_dofs,
|
|
dof_offs, tdof_offs);
|
|
}
|
|
|
|
// Note that tdof2ldof is set only for edges and faces containing interior
|
|
// true DOFs.
|
|
MFEM_VERIFY(R_ && nedofs == lnedofs, "");
|
|
|
|
*R_ = new SparseMatrix(num_true_dofs*vdim, ndofs*vdim);
|
|
|
|
// Set vertex rows
|
|
// For vertices, the T-dofs are always contained in the local L-dofs.
|
|
for (int dof = 0; dof < nvdofs; dof++)
|
|
{
|
|
for (int vd = 0; vd < vdim; vd++)
|
|
{
|
|
const int valldof = dof*vdim_factor + vd*dof_stride;
|
|
const int vdof = dof*vdim_factor + vd*ldof_stride;
|
|
const int vtdof = (*dof_tdof)[valldof];
|
|
if (vtdof >= 0) { (*R_)->Add(vtdof, vdof, 1.0); }
|
|
}
|
|
}
|
|
|
|
// Set edge and face rows
|
|
nedofs = allnedofs;
|
|
SetTDOF2LDOFinfo(num_true_dofs, vdim_factor, dof_stride, allnedofs);
|
|
|
|
Array<HYPRE_BigInt> all_dof_offs(NRanks);
|
|
MPI_Allgather(&dof_offs[0], 1, HYPRE_MPI_BIG_INT, all_dof_offs.GetData(),
|
|
1, HYPRE_MPI_BIG_INT, MyComm);
|
|
|
|
SetRestrictionMatrixEdgesFaces(vdim_factor, ldof_stride, tdof_stride,
|
|
*dof_tdof, all_dof_offs);
|
|
nedofs = lnedofs;
|
|
|
|
// Set element rows
|
|
// For element interiors, all DOFs are T-dofs and local L-dofs.
|
|
|
|
const int nalldofs = dof_tdof->Size() / vdim;
|
|
MFEM_VERIFY(nalldofs * vdim == dof_tdof->Size(), "");
|
|
for (int edof=0; edof<nbdofs; ++edof)
|
|
{
|
|
const int dof = ndofs - nbdofs + edof;
|
|
const int alldof = nalldofs - nbdofs + edof;
|
|
for (int vd = 0; vd < vdim; vd++)
|
|
{
|
|
const int valldof = alldof*vdim_factor + vd*dof_stride;
|
|
const int vdof = dof*vdim_factor + vd*ldof_stride;
|
|
const int vtdof = (*dof_tdof)[valldof];
|
|
(*R_)->Add(vtdof, vdof, 1.0);
|
|
}
|
|
}
|
|
|
|
// Verify that all rows of R are set
|
|
for (int tdof=0; tdof<num_true_dofs; ++tdof)
|
|
{
|
|
if ((*R_)->RowSize(tdof) == 0)
|
|
{
|
|
MFEM_ABORT("Empty row of R");
|
|
}
|
|
}
|
|
|
|
(*R_)->Finalize();
|
|
|
|
// Update dof_tdof
|
|
Array<int> dof_tdof_new(ndofs * vdim);
|
|
for (int i=0; i<all2local.Size(); ++i)
|
|
{
|
|
if (all2local[i] >= 0)
|
|
{
|
|
for (int vd = 0; vd < vdim; vd++)
|
|
{
|
|
const int vdof = i*vdim_factor + vd*dof_stride;
|
|
const int ldof = all2local[i]*vdim_factor + vd*ldof_stride;
|
|
dof_tdof_new[ldof] = (*dof_tdof)[vdof];
|
|
}
|
|
}
|
|
}
|
|
|
|
Swap(dof_tdof_new, *dof_tdof);
|
|
|
|
// Save variant 0 order from ghost edges and faces, before destroying
|
|
// var_edge_orders and var_face_orders.
|
|
|
|
MFEM_VERIFY(var_edge_dofs.Size() - 1 == pncmesh->GetNEdges() +
|
|
pncmesh->GetNGhostEdges(), "");
|
|
MFEM_VERIFY(var_face_dofs.Size() == -1 ||
|
|
var_face_dofs.Size() - 1 == pncmesh->GetNFaces() + pncmesh->GetNGhostFaces(),
|
|
"");
|
|
|
|
ghost_edge_orders.SetSize(pncmesh->GetNGhostEdges());
|
|
ghost_face_orders.SetSize(pncmesh->GetNGhostFaces());
|
|
|
|
for (int i=0; i<pncmesh->GetNGhostEdges(); ++i)
|
|
{
|
|
ghost_edge_orders[i] = GetEdgeOrder(pncmesh->GetNEdges() + i);
|
|
}
|
|
|
|
if (pmesh->Dimension() > 2)
|
|
{
|
|
for (int i=0; i<pncmesh->GetNGhostFaces(); ++i)
|
|
{
|
|
ghost_face_orders[i] = GetFaceOrder(pncmesh->GetNFaces() + i);
|
|
}
|
|
}
|
|
|
|
// Update var_edge_dofs and var_face_dofs
|
|
var_edge_dofs.Swap(loc_var_edge_dofs);
|
|
loc_var_edge_dofs.Clear();
|
|
|
|
var_face_dofs.Swap(loc_var_face_dofs);
|
|
loc_var_face_dofs.Clear();
|
|
|
|
Swap(var_edge_orders, loc_var_edge_orders);
|
|
Swap(var_face_orders, loc_var_face_orders);
|
|
|
|
loc_var_edge_orders.SetSize(0);
|
|
loc_var_face_orders.SetSize(0);
|
|
}
|
|
else if (P_)
|
|
{
|
|
*P_ = MakeVDimHypreMatrix(pmatrix, ndofs, num_true_dofs,
|
|
dof_offs, tdof_offs);
|
|
}
|
|
|
|
// clean up possible remaining messages in the queue to avoid receiving
|
|
// them erroneously in the next run
|
|
int rank, size;
|
|
while (NeighborRowMessage::IProbe(rank, size, MyComm))
|
|
{
|
|
recv_msg.RecvDrop(rank, size, MyComm);
|
|
}
|
|
|
|
// make sure we can discard all send buffers
|
|
for (auto &msg : send_msg)
|
|
{
|
|
NeighborRowMessage::WaitAllSent(msg);
|
|
}
|
|
|
|
#ifdef MFEM_PMATRIX_STATS
|
|
int n_rounds = send_msg.size();
|
|
int glob_rounds, glob_msgs_sent, glob_msgs_recv;
|
|
int glob_rows_sent, glob_rows_recv, glob_rows_fwd;
|
|
|
|
MPI_Reduce(&n_rounds, &glob_rounds, 1, MPI_INT, MPI_SUM, 0, MyComm);
|
|
MPI_Reduce(&n_msgs_sent, &glob_msgs_sent, 1, MPI_INT, MPI_SUM, 0, MyComm);
|
|
MPI_Reduce(&n_msgs_recv, &glob_msgs_recv, 1, MPI_INT, MPI_SUM, 0, MyComm);
|
|
MPI_Reduce(&n_rows_sent, &glob_rows_sent, 1, MPI_INT, MPI_SUM, 0, MyComm);
|
|
MPI_Reduce(&n_rows_recv, &glob_rows_recv, 1, MPI_INT, MPI_SUM, 0, MyComm);
|
|
MPI_Reduce(&n_rows_fwd, &glob_rows_fwd, 1, MPI_INT, MPI_SUM, 0, MyComm);
|
|
|
|
if (MyRank == 0)
|
|
{
|
|
mfem::out << "P matrix stats (avg per rank): "
|
|
<< real_t(glob_rounds)/NRanks << " rounds, "
|
|
<< real_t(glob_msgs_sent)/NRanks << " msgs sent, "
|
|
<< real_t(glob_msgs_recv)/NRanks << " msgs recv, "
|
|
<< real_t(glob_rows_sent)/NRanks << " rows sent, "
|
|
<< real_t(glob_rows_recv)/NRanks << " rows recv, "
|
|
<< real_t(glob_rows_fwd)/NRanks << " rows forwarded."
|
|
<< std::endl;
|
|
}
|
|
#endif
|
|
|
|
return num_true_dofs*vdim;
|
|
}
|
|
|
|
HypreParMatrix* ParFiniteElementSpace
|
|
::MakeVDimHypreMatrix(const std::vector<PMatrixRow> &rows,
|
|
int local_rows, int local_cols,
|
|
Array<HYPRE_BigInt> &row_starts,
|
|
Array<HYPRE_BigInt> &col_starts) const
|
|
{
|
|
bool assumed = HYPRE_AssumedPartitionCheck();
|
|
bool bynodes = (ordering == Ordering::byNODES);
|
|
|
|
HYPRE_BigInt first_col = col_starts[assumed ? 0 : MyRank];
|
|
HYPRE_BigInt next_col = col_starts[assumed ? 1 : MyRank+1];
|
|
|
|
// count nonzeros in diagonal/off-diagonal parts
|
|
HYPRE_Int nnz_diag = 0, nnz_offd = 0;
|
|
std::map<HYPRE_BigInt, int> col_map;
|
|
for (int i = 0; i < local_rows; i++)
|
|
{
|
|
for (unsigned j = 0; j < rows[i].elems.size(); j++)
|
|
{
|
|
const PMatrixElement &elem = rows[i].elems[j];
|
|
HYPRE_BigInt col = elem.column;
|
|
if (col >= first_col && col < next_col)
|
|
{
|
|
nnz_diag += vdim;
|
|
}
|
|
else
|
|
{
|
|
nnz_offd += vdim;
|
|
for (int vd = 0; vd < vdim; vd++)
|
|
{
|
|
col_map[col] = -1;
|
|
col += elem.stride;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// create offd column mapping
|
|
HYPRE_BigInt *cmap = Memory<HYPRE_BigInt>(static_cast<int>(col_map.size()));
|
|
int offd_col = 0;
|
|
for (auto it = col_map.begin(); it != col_map.end(); ++it)
|
|
{
|
|
cmap[offd_col] = it->first;
|
|
it->second = offd_col++;
|
|
}
|
|
|
|
HYPRE_Int *I_diag = Memory<HYPRE_Int>(vdim*local_rows + 1);
|
|
HYPRE_Int *I_offd = Memory<HYPRE_Int>(vdim*local_rows + 1);
|
|
|
|
HYPRE_Int *J_diag = Memory<HYPRE_Int>(nnz_diag);
|
|
HYPRE_Int *J_offd = Memory<HYPRE_Int>(nnz_offd);
|
|
|
|
real_t *A_diag = Memory<real_t>(nnz_diag);
|
|
real_t *A_offd = Memory<real_t>(nnz_offd);
|
|
|
|
int vdim1 = bynodes ? vdim : 1;
|
|
int vdim2 = bynodes ? 1 : vdim;
|
|
int vdim_offset = bynodes ? local_cols : 1;
|
|
|
|
// copy the diag/offd elements
|
|
nnz_diag = nnz_offd = 0;
|
|
int vrow = 0;
|
|
for (int vd1 = 0; vd1 < vdim1; vd1++)
|
|
{
|
|
for (int i = 0; i < local_rows; i++)
|
|
{
|
|
for (int vd2 = 0; vd2 < vdim2; vd2++)
|
|
{
|
|
I_diag[vrow] = nnz_diag;
|
|
I_offd[vrow++] = nnz_offd;
|
|
|
|
int vd = bynodes ? vd1 : vd2;
|
|
for (unsigned j = 0; j < rows[i].elems.size(); j++)
|
|
{
|
|
const PMatrixElement &elem = rows[i].elems[j];
|
|
if (elem.column >= first_col && elem.column < next_col)
|
|
{
|
|
J_diag[nnz_diag] = elem.column + vd*vdim_offset - first_col;
|
|
A_diag[nnz_diag++] = elem.value;
|
|
}
|
|
else
|
|
{
|
|
J_offd[nnz_offd] = col_map[elem.column + vd*elem.stride];
|
|
A_offd[nnz_offd++] = elem.value;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
MFEM_ASSERT(vrow == vdim*local_rows, "");
|
|
I_diag[vrow] = nnz_diag;
|
|
I_offd[vrow] = nnz_offd;
|
|
|
|
return new HypreParMatrix(MyComm,
|
|
row_starts.Last(), col_starts.Last(),
|
|
row_starts.GetData(), col_starts.GetData(),
|
|
I_diag, J_diag, A_diag,
|
|
I_offd, J_offd, A_offd,
|
|
static_cast<HYPRE_Int>(col_map.size()), cmap);
|
|
}
|
|
|
|
template <typename int_type>
|
|
static int_type* make_i_array(int nrows)
|
|
{
|
|
int_type *I = Memory<int_type>(nrows+1);
|
|
for (int i = 0; i <= nrows; i++) { I[i] = -1; }
|
|
return I;
|
|
}
|
|
|
|
template <typename int_type>
|
|
static int_type* make_j_array(int_type* I, int nrows)
|
|
{
|
|
int nnz = 0;
|
|
for (int i = 0; i < nrows; i++)
|
|
{
|
|
if (I[i] >= 0) { nnz++; }
|
|
}
|
|
int_type *J = Memory<int_type>(nnz);
|
|
|
|
I[nrows] = -1;
|
|
for (int i = 0, k = 0; i <= nrows; i++)
|
|
{
|
|
int_type col = I[i];
|
|
I[i] = k;
|
|
if (col >= 0) { J[k++] = col; }
|
|
}
|
|
return J;
|
|
}
|
|
|
|
HypreParMatrix*
|
|
ParFiniteElementSpace::RebalanceMatrix(int old_ndofs,
|
|
const Table* old_elem_dof,
|
|
const Table* old_elem_fos)
|
|
{
|
|
MFEM_VERIFY(Nonconforming(), "Only supported for nonconforming meshes.");
|
|
MFEM_VERIFY(old_dof_offsets.Size(), "ParFiniteElementSpace::Update needs to "
|
|
"be called before ParFiniteElementSpace::RebalanceMatrix");
|
|
|
|
HYPRE_BigInt old_offset = HYPRE_AssumedPartitionCheck()
|
|
? old_dof_offsets[0] : old_dof_offsets[MyRank];
|
|
|
|
// send old DOFs of elements we used to own
|
|
ParNCMesh* old_pncmesh = pmesh->pncmesh;
|
|
old_pncmesh->SendRebalanceDofs(old_ndofs, *old_elem_dof, old_offset, this);
|
|
|
|
Array<int> dofs;
|
|
int vsize = GetVSize();
|
|
|
|
const Array<int> &old_index = old_pncmesh->GetRebalanceOldIndex();
|
|
MFEM_VERIFY(old_index.Size() == pmesh->GetNE(),
|
|
"Mesh::Rebalance was not called before "
|
|
"ParFiniteElementSpace::RebalanceMatrix");
|
|
|
|
// prepare the local (diagonal) part of the matrix
|
|
HYPRE_Int* i_diag = make_i_array<HYPRE_Int>(vsize);
|
|
for (int i = 0; i < pmesh->GetNE(); i++)
|
|
{
|
|
if (old_index[i] >= 0) // we had this element before
|
|
{
|
|
const int* old_dofs = old_elem_dof->GetRow(old_index[i]);
|
|
GetElementDofs(i, dofs);
|
|
|
|
for (int vd = 0; vd < vdim; vd++)
|
|
{
|
|
for (int j = 0; j < dofs.Size(); j++)
|
|
{
|
|
const int row = UnsignIndex(DofToVDof(dofs[j], vd));
|
|
const int col = UnsignIndex(DofToVDof(old_dofs[j], vd,
|
|
old_ndofs));
|
|
i_diag[row] = col;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
HYPRE_Int* j_diag = make_j_array(i_diag, vsize);
|
|
|
|
// receive old DOFs for elements we obtained from others in Rebalance
|
|
Array<int> new_elements;
|
|
Array<long> old_remote_dofs;
|
|
old_pncmesh->RecvRebalanceDofs(new_elements, old_remote_dofs);
|
|
|
|
// create the off-diagonal part of the matrix
|
|
HYPRE_BigInt* i_offd = make_i_array<HYPRE_BigInt>(vsize);
|
|
for (int i = 0, pos = 0; i < new_elements.Size(); i++)
|
|
{
|
|
GetElementDofs(new_elements[i], dofs);
|
|
const long* old_dofs = &old_remote_dofs[pos];
|
|
pos += dofs.Size() * vdim;
|
|
|
|
for (int vd = 0; vd < vdim; vd++)
|
|
{
|
|
for (int j = 0; j < dofs.Size(); j++)
|
|
{
|
|
const int row = UnsignIndex(DofToVDof(dofs[j], vd));
|
|
if (i_diag[row] == i_diag[row+1]) // diag row empty?
|
|
{
|
|
i_offd[row] = old_dofs[j + vd * dofs.Size()];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
HYPRE_BigInt* j_offd = make_j_array(i_offd, vsize);
|
|
|
|
#ifndef HYPRE_MIXEDINT
|
|
HYPRE_Int *i_offd_hi = i_offd;
|
|
#else
|
|
// Copy of i_offd array as array of HYPRE_Int
|
|
HYPRE_Int *i_offd_hi = Memory<HYPRE_Int>(vsize + 1);
|
|
std::copy(i_offd, i_offd + vsize + 1, i_offd_hi);
|
|
Memory<HYPRE_BigInt>(i_offd, vsize + 1, true).Delete();
|
|
#endif
|
|
|
|
// create the offd column map
|
|
int offd_cols = i_offd_hi[vsize];
|
|
Array<Pair<HYPRE_BigInt, int> > cmap_offd(offd_cols);
|
|
for (int i = 0; i < offd_cols; i++)
|
|
{
|
|
cmap_offd[i].one = j_offd[i];
|
|
cmap_offd[i].two = i;
|
|
}
|
|
|
|
#ifndef HYPRE_MIXEDINT
|
|
HYPRE_Int *j_offd_hi = j_offd;
|
|
#else
|
|
HYPRE_Int *j_offd_hi = Memory<HYPRE_Int>(offd_cols);
|
|
Memory<HYPRE_BigInt>(j_offd, offd_cols, true).Delete();
|
|
#endif
|
|
|
|
SortPairs<HYPRE_BigInt, int>(cmap_offd, offd_cols);
|
|
|
|
HYPRE_BigInt* cmap = Memory<HYPRE_BigInt>(offd_cols);
|
|
for (int i = 0; i < offd_cols; i++)
|
|
{
|
|
cmap[i] = cmap_offd[i].one;
|
|
j_offd_hi[cmap_offd[i].two] = i;
|
|
}
|
|
|
|
HypreParMatrix *M;
|
|
M = new HypreParMatrix(MyComm, MyRank, NRanks, dof_offsets, old_dof_offsets,
|
|
i_diag, j_diag, i_offd_hi, j_offd_hi, cmap, offd_cols);
|
|
return M;
|
|
}
|
|
|
|
HypreParMatrix*
|
|
ParFiniteElementSpace::ParallelDerefinementMatrix(int old_ndofs,
|
|
const Table* old_elem_dof,
|
|
const Table *old_elem_fos)
|
|
{
|
|
int nrk = HYPRE_AssumedPartitionCheck() ? 2 : NRanks;
|
|
|
|
MFEM_VERIFY(Nonconforming(), "Not implemented for conforming meshes.");
|
|
MFEM_VERIFY(old_dof_offsets[nrk], "Missing previous (finer) space.");
|
|
|
|
#if 0 // check no longer seems to work with NC tet refinement
|
|
MFEM_VERIFY(dof_offsets[nrk] <= old_dof_offsets[nrk],
|
|
"Previous space is not finer.");
|
|
#endif
|
|
|
|
// Note to the reader: please make sure you first read
|
|
// FiniteElementSpace::RefinementMatrix, then
|
|
// FiniteElementSpace::DerefinementMatrix, and only then this function.
|
|
// You have been warned! :-)
|
|
|
|
Mesh::GeometryList elem_geoms(*mesh);
|
|
|
|
Array<int> dofs, old_dofs, old_vdofs;
|
|
Vector row;
|
|
|
|
ParNCMesh* old_pncmesh = pmesh->pncmesh;
|
|
|
|
int ldof[Geometry::NumGeom];
|
|
for (int i = 0; i < Geometry::NumGeom; i++)
|
|
{
|
|
ldof[i] = 0;
|
|
}
|
|
for (int i = 0; i < elem_geoms.Size(); i++)
|
|
{
|
|
Geometry::Type geom = elem_geoms[i];
|
|
ldof[geom] = fec->FiniteElementForGeometry(geom)->GetDof();
|
|
}
|
|
|
|
const CoarseFineTransformations &dtrans =
|
|
old_pncmesh->GetDerefinementTransforms();
|
|
const Array<int> &old_ranks = old_pncmesh->GetDerefineOldRanks();
|
|
|
|
// key: other rank
|
|
// value: send or recieve buffer
|
|
std::map<int, std::vector<HYPRE_BigInt>> to_send;
|
|
std::map<int, std::vector<HYPRE_BigInt>> to_recv;
|
|
// key: index into dtrans.embeddings
|
|
// value: [start, stop]
|
|
std::unordered_map<int, std::array<size_t, 2>> recv_messages;
|
|
|
|
HYPRE_BigInt old_offset = HYPRE_AssumedPartitionCheck()
|
|
? old_dof_offsets[0] : old_dof_offsets[MyRank];
|
|
|
|
// communicate DOFs for derefinements that straddle processor boundaries,
|
|
// note that this is infrequent due to the way elements are ordered
|
|
for (int k = 0; k < dtrans.embeddings.Size(); k++)
|
|
{
|
|
const Embedding &emb = dtrans.embeddings[k];
|
|
|
|
const int fine_rank = old_ranks[k];
|
|
const int coarse_rank = (emb.parent < 0) ? FlipIndexSign(emb.parent)
|
|
: old_pncmesh->ElementRank(emb.parent);
|
|
|
|
if (coarse_rank != MyRank && fine_rank == MyRank)
|
|
{
|
|
old_elem_dof->GetRow(k, dofs);
|
|
DofsToVDofs(dofs, old_ndofs);
|
|
|
|
std::vector<HYPRE_BigInt>& send_buf = to_send[coarse_rank];
|
|
auto pos = send_buf.size();
|
|
send_buf.resize(pos + dofs.Size());
|
|
for (int i = 0; i < dofs.Size(); i++)
|
|
{
|
|
send_buf[pos + i] = old_offset + dofs[i];
|
|
}
|
|
}
|
|
else if (coarse_rank == MyRank && fine_rank != MyRank)
|
|
{
|
|
MFEM_ASSERT(emb.parent >= 0, "");
|
|
Geometry::Type geom = mesh->GetElementBaseGeometry(emb.parent);
|
|
|
|
std::vector<HYPRE_BigInt>& recv_buf = to_recv[fine_rank];
|
|
auto& msg = recv_messages[k];
|
|
msg[0] = recv_buf.size();
|
|
recv_buf.resize(recv_buf.size() + ldof[geom] * vdim);
|
|
msg[1] = recv_buf.size();
|
|
}
|
|
}
|
|
|
|
// assume embedding orders are consistent (i.e. what we expect to receive
|
|
// first from a given rank is sent first, etc.)
|
|
std::vector<MPI_Request> requests;
|
|
requests.reserve(to_send.size() + to_recv.size());
|
|
// enqueue recvs
|
|
for (auto &v : to_recv)
|
|
{
|
|
requests.emplace_back();
|
|
MPI_Irecv(v.second.data(), v.second.size(), HYPRE_MPI_BIG_INT, v.first,
|
|
MessageTag::DEREFINEMENT_MATRIX_CONSTRUCTION_DATA, MyComm,
|
|
&requests.back());
|
|
}
|
|
// enqueue sends
|
|
for (auto &v : to_send)
|
|
{
|
|
requests.emplace_back();
|
|
MPI_Isend(v.second.data(), v.second.size(), HYPRE_MPI_BIG_INT, v.first,
|
|
MessageTag::DEREFINEMENT_MATRIX_CONSTRUCTION_DATA, MyComm,
|
|
&requests.back());
|
|
}
|
|
|
|
DenseTensor localR[Geometry::NumGeom];
|
|
for (int i = 0; i < elem_geoms.Size(); i++)
|
|
{
|
|
GetLocalDerefinementMatrices(elem_geoms[i], localR[elem_geoms[i]]);
|
|
}
|
|
|
|
// create the diagonal part of the derefinement matrix
|
|
SparseMatrix *diag = new SparseMatrix(ndofs*vdim, old_ndofs*vdim);
|
|
|
|
Array<char> mark(diag->Height());
|
|
mark = 0;
|
|
|
|
bool is_dg = FEColl()->GetContType() == FiniteElementCollection::DISCONTINUOUS;
|
|
|
|
for (int k = 0; k < dtrans.embeddings.Size(); k++)
|
|
{
|
|
const Embedding &emb = dtrans.embeddings[k];
|
|
if (emb.parent < 0) { continue; }
|
|
|
|
int coarse_rank = old_pncmesh->ElementRank(emb.parent);
|
|
int fine_rank = old_ranks[k];
|
|
|
|
if (coarse_rank == MyRank && fine_rank == MyRank)
|
|
{
|
|
Geometry::Type geom = mesh->GetElementBaseGeometry(emb.parent);
|
|
DenseMatrix &lR = localR[geom](emb.matrix);
|
|
|
|
elem_dof->GetRow(emb.parent, dofs);
|
|
old_elem_dof->GetRow(k, old_dofs);
|
|
|
|
for (int vd = 0; vd < vdim; vd++)
|
|
{
|
|
old_dofs.Copy(old_vdofs);
|
|
DofsToVDofs(vd, old_vdofs, old_ndofs);
|
|
|
|
for (int i = 0; i < lR.Height(); i++)
|
|
{
|
|
if (!std::isfinite(lR(i, 0))) { continue; }
|
|
|
|
const int r = DofToVDof(dofs[i], vd);
|
|
const int m = UnsignIndex(r);
|
|
|
|
if (is_dg || !mark[m])
|
|
{
|
|
lR.GetRow(i, row);
|
|
diag->SetRow(r, old_vdofs, row);
|
|
mark[m] = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
diag->Finalize();
|
|
|
|
// wait for all sends/receives to complete
|
|
MPI_Waitall(requests.size(), requests.data(), MPI_STATUSES_IGNORE);
|
|
|
|
// create the off-diagonal part of the derefinement matrix
|
|
SparseMatrix *offd = new SparseMatrix(ndofs*vdim, 1);
|
|
|
|
std::map<HYPRE_BigInt, int> col_map;
|
|
for (int k = 0; k < dtrans.embeddings.Size(); k++)
|
|
{
|
|
const Embedding &emb = dtrans.embeddings[k];
|
|
if (emb.parent < 0) { continue; }
|
|
|
|
int coarse_rank = old_pncmesh->ElementRank(emb.parent);
|
|
int fine_rank = old_ranks[k];
|
|
|
|
if (coarse_rank == MyRank && fine_rank != MyRank)
|
|
{
|
|
Geometry::Type geom = mesh->GetElementBaseGeometry(emb.parent);
|
|
DenseMatrix &lR = localR[geom](emb.matrix);
|
|
|
|
elem_dof->GetRow(emb.parent, dofs);
|
|
|
|
auto& odofs = to_recv.at(fine_rank);
|
|
auto &msg = recv_messages[k];
|
|
MFEM_ASSERT(msg[1] > msg[0], "");
|
|
|
|
for (int vd = 0; vd < vdim; vd++)
|
|
{
|
|
MFEM_ASSERT(ldof[geom], "");
|
|
HYPRE_BigInt *remote_dofs = odofs.data() + msg[0] + vd * ldof[geom];
|
|
|
|
for (int i = 0; i < lR.Height(); i++)
|
|
{
|
|
if (!std::isfinite(lR(i, 0))) { continue; }
|
|
|
|
const int m = UnsignIndex(DofToVDof(dofs[i], vd));
|
|
|
|
if (is_dg || !mark[m])
|
|
{
|
|
lR.GetRow(i, row);
|
|
MFEM_ASSERT(ldof[geom] == row.Size(), "");
|
|
for (int j = 0; j < ldof[geom]; j++)
|
|
{
|
|
if (row[j] == 0.0) { continue; } // NOTE: lR thresholded
|
|
int &lcol = col_map[remote_dofs[j]];
|
|
if (!lcol) { lcol = static_cast<int>(col_map.size()); }
|
|
offd->_Set_(m, lcol-1, row[j]);
|
|
}
|
|
mark[m] = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
offd->Finalize(0);
|
|
offd->SetWidth(static_cast<int>(col_map.size()));
|
|
|
|
// create offd column mapping for use by hypre
|
|
HYPRE_BigInt *cmap = Memory<HYPRE_BigInt>(offd->Width());
|
|
for (auto it = col_map.begin(); it != col_map.end(); ++it)
|
|
{
|
|
cmap[it->second-1] = it->first;
|
|
}
|
|
|
|
// reorder offd columns so that 'cmap' is monotonic
|
|
// NOTE: this is easier and probably faster (offd is small) than making
|
|
// sure cmap is determined and sorted before the offd matrix is created
|
|
{
|
|
int width = offd->Width();
|
|
Array<Pair<HYPRE_BigInt, int> > reorder(width);
|
|
for (int i = 0; i < width; i++)
|
|
{
|
|
reorder[i].one = cmap[i];
|
|
reorder[i].two = i;
|
|
}
|
|
reorder.Sort();
|
|
|
|
Array<int> reindex(width);
|
|
for (int i = 0; i < width; i++)
|
|
{
|
|
reindex[reorder[i].two] = i;
|
|
cmap[i] = reorder[i].one;
|
|
}
|
|
|
|
int *J = offd->GetJ();
|
|
for (int i = 0; i < offd->NumNonZeroElems(); i++)
|
|
{
|
|
J[i] = reindex[J[i]];
|
|
}
|
|
offd->SortColumnIndices();
|
|
}
|
|
|
|
HypreParMatrix* new_R;
|
|
new_R = new HypreParMatrix(MyComm, dof_offsets[nrk], old_dof_offsets[nrk],
|
|
dof_offsets, old_dof_offsets, diag, offd, cmap,
|
|
true);
|
|
|
|
new_R->SetOwnerFlags(new_R->OwnsDiag(), new_R->OwnsOffd(), 1);
|
|
|
|
return new_R;
|
|
}
|
|
|
|
void ParFiniteElementSpace::Destroy()
|
|
{
|
|
ldof_group.DeleteAll();
|
|
ldof_ltdof.DeleteAll();
|
|
dof_offsets.DeleteAll();
|
|
tdof_offsets.DeleteAll();
|
|
tdof_nb_offsets.DeleteAll();
|
|
// preserve old_dof_offsets
|
|
ldof_sign.DeleteAll();
|
|
|
|
delete P; P = NULL;
|
|
delete Pconf; Pconf = NULL;
|
|
delete Rconf; Rconf = NULL;
|
|
delete R; R = NULL;
|
|
|
|
delete gcomm; gcomm = NULL;
|
|
|
|
num_face_nbr_dofs = -1;
|
|
face_nbr_element_dof.Clear();
|
|
face_nbr_ldof.Clear();
|
|
face_nbr_glob_dof_map.DeleteAll();
|
|
send_face_nbr_ldof.Clear();
|
|
}
|
|
|
|
void ParFiniteElementSpace::CopyProlongationAndRestriction(
|
|
const FiniteElementSpace &fes, const Array<int> *perm)
|
|
{
|
|
const ParFiniteElementSpace *pfes
|
|
= dynamic_cast<const ParFiniteElementSpace*>(&fes);
|
|
MFEM_VERIFY(pfes != NULL, "");
|
|
MFEM_VERIFY(P == NULL, "");
|
|
MFEM_VERIFY(R == NULL, "");
|
|
|
|
// Ensure R and P matrices are built
|
|
pfes->Dof_TrueDof_Matrix();
|
|
|
|
SparseMatrix *perm_mat = NULL, *perm_mat_tr = NULL;
|
|
if (perm)
|
|
{
|
|
// Note: although n and fes.GetVSize() are typically equal, in
|
|
// variable-order spaces they may differ, since nonconforming edges/faces
|
|
// my have fictitious DOFs.
|
|
int n = perm->Size();
|
|
perm_mat = new SparseMatrix(n, fes.GetVSize());
|
|
for (int i=0; i<n; ++i)
|
|
{
|
|
real_t s;
|
|
int j = DecodeDof((*perm)[i], s);
|
|
perm_mat->Set(i, j, s);
|
|
}
|
|
perm_mat->Finalize();
|
|
perm_mat_tr = Transpose(*perm_mat);
|
|
}
|
|
|
|
if (pfes->P != NULL)
|
|
{
|
|
if (perm) { P = pfes->P->LeftDiagMult(*perm_mat); }
|
|
else { P = new HypreParMatrix(*pfes->P); }
|
|
nonconf_P = true;
|
|
}
|
|
else if (perm != NULL)
|
|
{
|
|
HYPRE_BigInt glob_nrows = GlobalVSize();
|
|
HYPRE_BigInt glob_ncols = GlobalTrueVSize();
|
|
HYPRE_BigInt *col_starts = GetTrueDofOffsets();
|
|
HYPRE_BigInt *row_starts = GetDofOffsets();
|
|
P = new HypreParMatrix(MyComm, glob_nrows, glob_ncols, row_starts,
|
|
col_starts, perm_mat);
|
|
nonconf_P = true;
|
|
}
|
|
if (pfes->R != NULL)
|
|
{
|
|
if (perm) { R = Mult(*pfes->R, *perm_mat_tr); }
|
|
else { R = new SparseMatrix(*pfes->R); }
|
|
}
|
|
else if (perm != NULL)
|
|
{
|
|
R = perm_mat_tr;
|
|
perm_mat_tr = NULL;
|
|
}
|
|
|
|
delete perm_mat;
|
|
delete perm_mat_tr;
|
|
}
|
|
|
|
void ParFiniteElementSpace::GetTrueTransferOperator(
|
|
const FiniteElementSpace &coarse_fes, OperatorHandle &T) const
|
|
{
|
|
OperatorHandle Tgf(T.Type() == Operator::Hypre_ParCSR ?
|
|
Operator::MFEM_SPARSEMAT : Operator::ANY_TYPE);
|
|
GetTransferOperator(coarse_fes, Tgf);
|
|
Dof_TrueDof_Matrix(); // Make sure R is built - we need R in all cases.
|
|
if (T.Type() == Operator::Hypre_ParCSR)
|
|
{
|
|
const ParFiniteElementSpace *c_pfes =
|
|
dynamic_cast<const ParFiniteElementSpace *>(&coarse_fes);
|
|
MFEM_ASSERT(c_pfes != NULL, "coarse_fes must be a parallel space");
|
|
SparseMatrix *RA = mfem::Mult(*R, *Tgf.As<SparseMatrix>());
|
|
Tgf.Clear();
|
|
T.Reset(c_pfes->Dof_TrueDof_Matrix()->
|
|
LeftDiagMult(*RA, GetTrueDofOffsets()));
|
|
delete RA;
|
|
}
|
|
else
|
|
{
|
|
T.Reset(new TripleProductOperator(R, Tgf.Ptr(),
|
|
coarse_fes.GetProlongationMatrix(),
|
|
false, Tgf.OwnsOperator(), false));
|
|
Tgf.SetOperatorOwner(false);
|
|
}
|
|
}
|
|
|
|
void ParFiniteElementSpace::Update(bool want_transform)
|
|
{
|
|
lastUpdatePRef = false;
|
|
|
|
{
|
|
int int_orders_changed = (int) orders_changed;
|
|
MPI_Allreduce(MPI_IN_PLACE, &int_orders_changed, 1, MPI_INT,
|
|
MPI_MAX, MyComm);
|
|
orders_changed = (bool) int_orders_changed;
|
|
|
|
int var = (elem_order.Size() > 0);
|
|
MPI_Allreduce(MPI_IN_PLACE, &var, 1, MPI_INT, MPI_MAX, MyComm);
|
|
variableOrder = (bool) var;
|
|
}
|
|
|
|
if (variableOrder && elem_order.Size() == 0)
|
|
{
|
|
elem_order.SetSize(GetNE());
|
|
elem_order = fec->GetOrder();
|
|
}
|
|
|
|
if (mesh->GetSequence() == mesh_sequence && !orders_changed)
|
|
{
|
|
return; // no need to update, no-op
|
|
}
|
|
if (want_transform && mesh->GetSequence() != mesh_sequence + 1 &&
|
|
!variableOrder)
|
|
{
|
|
MFEM_ABORT("Error in update sequence. Space needs to be updated after "
|
|
"each mesh modification.");
|
|
}
|
|
|
|
if (NURBSext)
|
|
{
|
|
UpdateNURBS();
|
|
return;
|
|
}
|
|
|
|
Table* old_elem_dof = NULL;
|
|
Table* old_elem_fos = NULL;
|
|
int old_ndofs = 0;
|
|
|
|
// save old DOF table
|
|
if (want_transform)
|
|
{
|
|
old_elem_dof = elem_dof;
|
|
old_elem_fos = elem_fos;
|
|
elem_dof = NULL;
|
|
elem_fos = NULL;
|
|
old_ndofs = ndofs;
|
|
Swap(dof_offsets, old_dof_offsets);
|
|
}
|
|
|
|
Destroy(); // Does not clear elem_order
|
|
FiniteElementSpace::Destroy(); // calls Th.Clear()
|
|
|
|
// In the variable-order case, we call CommunicateGhostOrder whether h-
|
|
// or p-refinement is done.
|
|
if (variableOrder) { CommunicateGhostOrder(); }
|
|
|
|
FiniteElementSpace::Construct();
|
|
Construct();
|
|
|
|
BuildElementToDofTable();
|
|
|
|
if (want_transform)
|
|
{
|
|
// calculate appropriate GridFunction transformation
|
|
switch (mesh->GetLastOperation())
|
|
{
|
|
case Mesh::REFINE:
|
|
{
|
|
if (Th.Type() != Operator::MFEM_SPARSEMAT)
|
|
{
|
|
Th.Reset(new RefinementOperator(this, old_elem_dof,
|
|
old_elem_fos, old_ndofs));
|
|
// The RefinementOperator takes ownership of 'old_elem_dofs', so
|
|
// we no longer own it:
|
|
old_elem_dof = NULL;
|
|
old_elem_fos = NULL;
|
|
}
|
|
else
|
|
{
|
|
// calculate fully assembled matrix
|
|
Th.Reset(RefinementMatrix(old_ndofs, old_elem_dof, old_elem_fos));
|
|
}
|
|
break;
|
|
}
|
|
|
|
case Mesh::DEREFINE:
|
|
{
|
|
#if 0
|
|
Th.Reset(ParallelDerefinementMatrix(old_ndofs, old_elem_dof,
|
|
old_elem_fos));
|
|
#else
|
|
Th.Reset(new ParDerefineMatrixOp(*this, old_ndofs, old_elem_dof,
|
|
old_elem_fos));
|
|
#endif
|
|
if (Nonconforming())
|
|
{
|
|
Th.SetOperatorOwner(false);
|
|
Th.Reset(new TripleProductOperator(P, R, Th.Ptr(),
|
|
false, false, true));
|
|
}
|
|
break;
|
|
}
|
|
|
|
case Mesh::REBALANCE:
|
|
{
|
|
Th.Reset(RebalanceMatrix(old_ndofs, old_elem_dof, old_elem_fos));
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
delete old_elem_dof;
|
|
delete old_elem_fos;
|
|
}
|
|
}
|
|
|
|
void ParFiniteElementSpace::PRefineAndUpdate(const Array<pRefinement> & refs,
|
|
bool want_transfer)
|
|
{
|
|
MFEM_VERIFY(PRefinementSupported(),
|
|
"p-refinement is not supported in this space");
|
|
|
|
if (want_transfer)
|
|
{
|
|
pfes_prev.reset(new ParFiniteElementSpace(pmesh, fec, vdim, ordering));
|
|
for (int i = 0; i<pmesh->GetNE(); i++)
|
|
{
|
|
pfes_prev->SetElementOrder(i, GetElementOrder(i));
|
|
}
|
|
pfes_prev->Update(false);
|
|
}
|
|
|
|
for (auto ref : refs)
|
|
{
|
|
SetElementOrder(ref.index, GetElementOrder(ref.index) + ref.delta);
|
|
}
|
|
|
|
Update(false);
|
|
|
|
if (want_transfer)
|
|
{
|
|
PTh.reset(new PRefinementTransferOperator(*pfes_prev, *this));
|
|
}
|
|
|
|
lastUpdatePRef = true;
|
|
}
|
|
|
|
void ParFiniteElementSpace::UpdateMeshPointer(Mesh *new_mesh)
|
|
{
|
|
ParMesh *new_pmesh = dynamic_cast<ParMesh*>(new_mesh);
|
|
MFEM_VERIFY(new_pmesh != NULL,
|
|
"ParFiniteElementSpace::UpdateMeshPointer(...) must be a ParMesh");
|
|
mesh = new_mesh;
|
|
pmesh = new_pmesh;
|
|
}
|
|
|
|
int ParFiniteElementSpace::GetMaxElementOrder() const
|
|
{
|
|
if (IsVariableOrder())
|
|
{
|
|
int order = elem_order.Size() > 0 ? elem_order.Max() : fec->GetOrder();
|
|
MPI_Allreduce(MPI_IN_PLACE, &order, 1, MPI_INT, MPI_MAX, MyComm);
|
|
return order;
|
|
}
|
|
else
|
|
{
|
|
return fec->GetOrder();
|
|
}
|
|
}
|
|
|
|
// This function is an extension of FiniteElementSpace::CalcEdgeFaceVarOrders in
|
|
// the parallel case, to use ghost_orders, which contains ghost element indices
|
|
// and their orders. The order on each ghost element is applied to the element's
|
|
// edges and faces, in @a edge_orders and @a face_orders.
|
|
void ParFiniteElementSpace::ApplyGhostElementOrdersToEdgesAndFaces(
|
|
Array<VarOrderBits> &edge_orders,
|
|
Array<VarOrderBits> &face_orders) const
|
|
{
|
|
edge_orders.SetSize(pncmesh->GetNEdges() + pncmesh->GetNGhostEdges());
|
|
face_orders.SetSize(pncmesh->GetNFaces() + pncmesh->GetNGhostFaces());
|
|
|
|
edge_orders = 0;
|
|
face_orders = 0;
|
|
|
|
const int npref = ghost_orders.Size();
|
|
for (int i=0; i<npref; ++i)
|
|
{
|
|
const int elem = ghost_orders[i].element; // Index in NCMesh::elements
|
|
const int order = ghost_orders[i].order;
|
|
const VarOrderBits mask = (VarOrderBits(1) << order);
|
|
|
|
Array<int> edges;
|
|
pncmesh->FindEdgesOfGhostElement(elem, edges);
|
|
|
|
for (auto edge : edges) { edge_orders[edge] |= mask; }
|
|
|
|
if (mesh->Dimension() > 2)
|
|
{
|
|
Array<int> faces;
|
|
pncmesh->FindFacesOfGhostElement(elem, faces);
|
|
|
|
for (auto face : faces) { face_orders[face] |= mask; }
|
|
}
|
|
}
|
|
}
|
|
|
|
void ParFiniteElementSpace::GhostFaceOrderToEdges(
|
|
const Array<VarOrderBits> &face_orders,
|
|
Array<VarOrderBits> &edge_orders) const
|
|
{
|
|
// Apply the lowest order (first variant) on each ghost face to its edges
|
|
for (int i=0; i<pncmesh->GetNGhostFaces(); ++i)
|
|
{
|
|
const int face = pncmesh->GetNFaces() + i;
|
|
VarOrderBits orders = face_orders[face];
|
|
|
|
if (orders == 0) { continue; }
|
|
|
|
// Find the lowest order and use that.
|
|
int orderV0 = -1;
|
|
for (int order = 0; orders != 0; order++, orders >>= 1)
|
|
{
|
|
if (orders & 1)
|
|
{
|
|
orderV0 = order;
|
|
break;
|
|
}
|
|
}
|
|
|
|
MFEM_VERIFY(orderV0 > 0, "");
|
|
|
|
const VarOrderBits mask = (VarOrderBits(1) << orderV0);
|
|
|
|
Array<int> edges;
|
|
pncmesh->FindEdgesOfGhostFace(face, edges);
|
|
|
|
for (auto edge : edges)
|
|
{
|
|
edge_orders[edge] |= mask;
|
|
}
|
|
}
|
|
}
|
|
|
|
ConformingProlongationOperator::ConformingProlongationOperator(
|
|
int lsize, const GroupCommunicator &gc_, bool local_)
|
|
: gc(gc_), local(local_)
|
|
{
|
|
const Table &group_ldof = gc.GroupLDofTable();
|
|
|
|
int n_external = 0;
|
|
for (int g=1; g<group_ldof.Size(); ++g)
|
|
{
|
|
if (!gc.GetGroupTopology().IAmMaster(g))
|
|
{
|
|
n_external += group_ldof.RowSize(g);
|
|
}
|
|
}
|
|
int tsize = lsize - n_external;
|
|
|
|
height = lsize;
|
|
width = tsize;
|
|
|
|
external_ldofs.Reserve(n_external);
|
|
for (int gr = 1; gr < group_ldof.Size(); gr++)
|
|
{
|
|
if (!gc.GetGroupTopology().IAmMaster(gr))
|
|
{
|
|
external_ldofs.Append(group_ldof.GetRow(gr), group_ldof.RowSize(gr));
|
|
}
|
|
}
|
|
external_ldofs.Sort();
|
|
}
|
|
|
|
const GroupCommunicator &ConformingProlongationOperator::GetGroupCommunicator()
|
|
const
|
|
{
|
|
return gc;
|
|
}
|
|
|
|
ConformingProlongationOperator::ConformingProlongationOperator(
|
|
const ParFiniteElementSpace &pfes, bool local_)
|
|
: Operator(pfes.GetVSize(), pfes.GetTrueVSize()),
|
|
external_ldofs(),
|
|
gc(pfes.GroupComm()),
|
|
local(local_)
|
|
{
|
|
MFEM_VERIFY(pfes.Conforming(), "");
|
|
const Table &group_ldof = gc.GroupLDofTable();
|
|
external_ldofs.Reserve(Height()-Width());
|
|
for (int gr = 1; gr < group_ldof.Size(); gr++)
|
|
{
|
|
if (!gc.GetGroupTopology().IAmMaster(gr))
|
|
{
|
|
external_ldofs.Append(group_ldof.GetRow(gr), group_ldof.RowSize(gr));
|
|
}
|
|
}
|
|
external_ldofs.Sort();
|
|
MFEM_ASSERT(external_ldofs.Size() == Height()-Width(), "");
|
|
#ifdef MFEM_DEBUG
|
|
for (int j = 1; j < external_ldofs.Size(); j++)
|
|
{
|
|
// Check for repeated ldofs.
|
|
MFEM_VERIFY(external_ldofs[j-1] < external_ldofs[j], "");
|
|
}
|
|
int j = 0;
|
|
for (int i = 0; i < external_ldofs.Size(); i++)
|
|
{
|
|
const int end = external_ldofs[i];
|
|
for ( ; j < end; j++)
|
|
{
|
|
MFEM_VERIFY(j-i == pfes.GetLocalTDofNumber(j), "");
|
|
}
|
|
j = end+1;
|
|
}
|
|
for ( ; j < Height(); j++)
|
|
{
|
|
MFEM_VERIFY(j-external_ldofs.Size() == pfes.GetLocalTDofNumber(j), "");
|
|
}
|
|
// gc.PrintInfo();
|
|
// pfes.Dof_TrueDof_Matrix()->PrintCommPkg();
|
|
#endif
|
|
}
|
|
|
|
void ConformingProlongationOperator::Mult(const Vector &x, Vector &y) const
|
|
{
|
|
MFEM_ASSERT(x.Size() == Width(), "");
|
|
MFEM_ASSERT(y.Size() == Height(), "");
|
|
|
|
const real_t *xdata = x.HostRead();
|
|
real_t *ydata = y.HostWrite();
|
|
const int m = external_ldofs.Size();
|
|
|
|
const int in_layout = 2; // 2 - input is ltdofs array
|
|
if (local)
|
|
{
|
|
y = 0.0;
|
|
}
|
|
else
|
|
{
|
|
gc.BcastBegin(const_cast<real_t*>(xdata), in_layout);
|
|
}
|
|
|
|
int j = 0;
|
|
for (int i = 0; i < m; i++)
|
|
{
|
|
const int end = external_ldofs[i];
|
|
if (end > j) { std::copy(xdata+j-i, xdata+end-i, ydata+j); }
|
|
j = end+1;
|
|
}
|
|
if (Width() > (j-m)) { std::copy(xdata+j-m, xdata+Width(), ydata+j); }
|
|
|
|
const int out_layout = 0; // 0 - output is ldofs array
|
|
if (!local)
|
|
{
|
|
gc.BcastEnd(ydata, out_layout);
|
|
}
|
|
}
|
|
|
|
void ConformingProlongationOperator::MultTranspose(
|
|
const Vector &x, Vector &y) const
|
|
{
|
|
MFEM_ASSERT(x.Size() == Height(), "");
|
|
MFEM_ASSERT(y.Size() == Width(), "");
|
|
|
|
const real_t *xdata = x.HostRead();
|
|
real_t *ydata = y.HostWrite();
|
|
const int m = external_ldofs.Size();
|
|
|
|
if (!local)
|
|
{
|
|
gc.ReduceBegin(xdata);
|
|
}
|
|
|
|
int j = 0;
|
|
for (int i = 0; i < m; i++)
|
|
{
|
|
const int end = external_ldofs[i];
|
|
if (end > j) { std::copy(xdata+j, xdata+end, ydata+j-i); }
|
|
j = end+1;
|
|
}
|
|
if (Height() > j) { std::copy(xdata+j, xdata+Height(), ydata+j-m); }
|
|
|
|
const int out_layout = 2; // 2 - output is an array on all ltdofs
|
|
if (!local)
|
|
{
|
|
gc.ReduceEnd<real_t>(ydata, out_layout, GroupCommunicator::Sum);
|
|
}
|
|
}
|
|
|
|
DeviceConformingProlongationOperator::DeviceConformingProlongationOperator(
|
|
const GroupCommunicator &gc_, const SparseMatrix *R, bool local_)
|
|
: ConformingProlongationOperator(R->Width(), gc_, local_),
|
|
mpi_gpu_aware(Device::GetGPUAwareMPI())
|
|
{
|
|
MFEM_ASSERT(R->Finalized(), "");
|
|
const int tdofs = R->Height();
|
|
MFEM_ASSERT(tdofs == R->HostReadI()[tdofs], "");
|
|
ltdof_ldof.SetSize(tdofs);
|
|
ltdof_ldof.CopyFrom(R->HostReadJ());
|
|
{
|
|
Table nbr_ltdof;
|
|
gc.GetNeighborLTDofTable(nbr_ltdof);
|
|
const int nb_connections = nbr_ltdof.Size_of_connections();
|
|
shr_ltdof.SetSize(nb_connections);
|
|
if (nb_connections > 0) { shr_ltdof.CopyFrom(nbr_ltdof.GetJ()); }
|
|
shr_buf.SetSize(nb_connections);
|
|
shr_buf.UseDevice(true);
|
|
shr_buf_offsets = nbr_ltdof.GetIMemory();
|
|
{
|
|
Array<int> shared_ltdof(nbr_ltdof.GetJ(), nb_connections);
|
|
Array<int> unique_ltdof(shared_ltdof);
|
|
unique_ltdof.Sort();
|
|
unique_ltdof.Unique();
|
|
// Note: the next loop modifies the J array of nbr_ltdof
|
|
for (int i = 0; i < shared_ltdof.Size(); i++)
|
|
{
|
|
shared_ltdof[i] = unique_ltdof.FindSorted(shared_ltdof[i]);
|
|
MFEM_ASSERT(shared_ltdof[i] != -1, "internal error");
|
|
}
|
|
Table unique_shr;
|
|
Transpose(shared_ltdof, unique_shr, unique_ltdof.Size());
|
|
unq_ltdof = unique_ltdof;
|
|
// Steal I and J arrays from the unique_shr table.
|
|
unq_shr_i.GetMemory() = unique_shr.GetIMemory();
|
|
unq_shr_i.SetSize(unique_shr.Size()+1);
|
|
unq_shr_j.GetMemory() = unique_shr.GetJMemory();
|
|
unq_shr_j.SetSize(unique_shr.Size_of_connections());
|
|
unique_shr.LoseData();
|
|
}
|
|
nbr_ltdof.GetJMemory().Delete();
|
|
nbr_ltdof.LoseData();
|
|
}
|
|
{
|
|
Table nbr_ldof;
|
|
gc.GetNeighborLDofTable(nbr_ldof);
|
|
const int nb_connections = nbr_ldof.Size_of_connections();
|
|
ext_ldof.SetSize(nb_connections);
|
|
if (nb_connections > 0) { ext_ldof.CopyFrom(nbr_ldof.GetJ()); }
|
|
ext_ldof.GetMemory().UseDevice(true);
|
|
ext_buf.SetSize(nb_connections);
|
|
ext_buf.UseDevice(true);
|
|
ext_buf_offsets = nbr_ldof.GetIMemory();
|
|
nbr_ldof.GetJMemory().Delete();
|
|
nbr_ldof.LoseData();
|
|
}
|
|
const GroupTopology >opo = gc.GetGroupTopology();
|
|
int req_counter = 0;
|
|
for (int nbr = 1; nbr < gtopo.GetNumNeighbors(); nbr++)
|
|
{
|
|
const int send_offset = shr_buf_offsets[nbr];
|
|
const int send_size = shr_buf_offsets[nbr+1] - send_offset;
|
|
if (send_size > 0) { req_counter++; }
|
|
|
|
const int recv_offset = ext_buf_offsets[nbr];
|
|
const int recv_size = ext_buf_offsets[nbr+1] - recv_offset;
|
|
if (recv_size > 0) { req_counter++; }
|
|
}
|
|
requests = new MPI_Request[req_counter];
|
|
}
|
|
|
|
DeviceConformingProlongationOperator::DeviceConformingProlongationOperator(
|
|
const ParFiniteElementSpace &pfes, bool local_)
|
|
: DeviceConformingProlongationOperator(pfes.GroupComm(),
|
|
pfes.GetRestrictionMatrix(),
|
|
local_)
|
|
{
|
|
MFEM_ASSERT(pfes.Conforming(), "internal error");
|
|
MFEM_ASSERT(pfes.GetRestrictionMatrix()->Height() == pfes.GetTrueVSize(), "");
|
|
}
|
|
|
|
static void ExtractSubVector(const Array<int> &indices,
|
|
const Vector &vin, Vector &vout)
|
|
{
|
|
MFEM_ASSERT(indices.Size() == vout.Size(), "incompatible sizes!");
|
|
auto y = vout.Write();
|
|
const auto x = vin.Read();
|
|
const auto I = indices.Read();
|
|
mfem::forall(indices.Size(), [=] MFEM_HOST_DEVICE (int i)
|
|
{
|
|
y[i] = x[I[i]];
|
|
}); // indices can be repeated
|
|
}
|
|
|
|
void DeviceConformingProlongationOperator::BcastBeginCopy(
|
|
const Vector &x) const
|
|
{
|
|
// shr_buf[i] = src[shr_ltdof[i]]
|
|
if (shr_ltdof.Size() == 0) { return; }
|
|
ExtractSubVector(shr_ltdof, x, shr_buf);
|
|
// If the above kernel is executed asynchronously, we should wait for it to
|
|
// complete
|
|
if (mpi_gpu_aware) { MFEM_STREAM_SYNC; }
|
|
}
|
|
|
|
static void SetSubVector(const Array<int> &indices,
|
|
const Vector &vin, Vector &vout)
|
|
{
|
|
MFEM_ASSERT(indices.Size() == vin.Size(), "incompatible sizes!");
|
|
// Use ReadWrite() since we modify only a subset of the indices:
|
|
auto y = vout.ReadWrite();
|
|
const auto x = vin.Read();
|
|
const auto I = indices.Read();
|
|
mfem::forall(indices.Size(), [=] MFEM_HOST_DEVICE (int i)
|
|
{
|
|
y[I[i]] = x[i];
|
|
});
|
|
}
|
|
|
|
void DeviceConformingProlongationOperator::BcastLocalCopy(
|
|
const Vector &x, Vector &y) const
|
|
{
|
|
// dst[ltdof_ldof[i]] = src[i]
|
|
if (ltdof_ldof.Size() == 0) { return; }
|
|
SetSubVector(ltdof_ldof, x, y);
|
|
}
|
|
|
|
void DeviceConformingProlongationOperator::BcastEndCopy(
|
|
Vector &y) const
|
|
{
|
|
// dst[ext_ldof[i]] = ext_buf[i]
|
|
if (ext_ldof.Size() == 0) { return; }
|
|
SetSubVector(ext_ldof, ext_buf, y);
|
|
}
|
|
|
|
void DeviceConformingProlongationOperator::Mult(const Vector &x,
|
|
Vector &y) const
|
|
{
|
|
const GroupTopology >opo = gc.GetGroupTopology();
|
|
int req_counter = 0;
|
|
// Make sure 'y' is marked as valid on device and for use on device.
|
|
// This ensures that there is no unnecessary host to device copy when the
|
|
// input 'y' is valid on host (in 'y.SetSubVector(ext_ldof, 0.0)' when local
|
|
// is true) or BcastLocalCopy (when local is false).
|
|
y.Write();
|
|
if (local)
|
|
{
|
|
// done on device since we've marked ext_ldof for use on device:
|
|
y.SetSubVector(ext_ldof, 0.0);
|
|
}
|
|
else
|
|
{
|
|
BcastBeginCopy(x); // copy to 'shr_buf'
|
|
for (int nbr = 1; nbr < gtopo.GetNumNeighbors(); nbr++)
|
|
{
|
|
const int send_offset = shr_buf_offsets[nbr];
|
|
const int send_size = shr_buf_offsets[nbr+1] - send_offset;
|
|
if (send_size > 0)
|
|
{
|
|
auto send_buf = mpi_gpu_aware ? shr_buf.Read() : shr_buf.HostRead();
|
|
MPI_Isend(send_buf + send_offset, send_size, MPITypeMap<real_t>::mpi_type,
|
|
gtopo.GetNeighborRank(nbr), 41822,
|
|
gtopo.GetComm(), &requests[req_counter++]);
|
|
}
|
|
const int recv_offset = ext_buf_offsets[nbr];
|
|
const int recv_size = ext_buf_offsets[nbr+1] - recv_offset;
|
|
if (recv_size > 0)
|
|
{
|
|
auto recv_buf = mpi_gpu_aware ? ext_buf.Write() : ext_buf.HostWrite();
|
|
MPI_Irecv(recv_buf + recv_offset, recv_size, MPITypeMap<real_t>::mpi_type,
|
|
gtopo.GetNeighborRank(nbr), 41822,
|
|
gtopo.GetComm(), &requests[req_counter++]);
|
|
}
|
|
}
|
|
}
|
|
BcastLocalCopy(x, y);
|
|
if (!local)
|
|
{
|
|
MPI_Waitall(req_counter, requests, MPI_STATUSES_IGNORE);
|
|
BcastEndCopy(y); // copy from 'ext_buf'
|
|
}
|
|
}
|
|
|
|
DeviceConformingProlongationOperator::~DeviceConformingProlongationOperator()
|
|
{
|
|
delete [] requests;
|
|
ext_buf_offsets.Delete();
|
|
shr_buf_offsets.Delete();
|
|
}
|
|
|
|
void DeviceConformingProlongationOperator::ReduceBeginCopy(
|
|
const Vector &x) const
|
|
{
|
|
// ext_buf[i] = src[ext_ldof[i]]
|
|
if (ext_ldof.Size() == 0) { return; }
|
|
ExtractSubVector(ext_ldof, x, ext_buf);
|
|
// If the above kernel is executed asynchronously, we should wait for it to
|
|
// complete
|
|
if (mpi_gpu_aware) { MFEM_STREAM_SYNC; }
|
|
}
|
|
|
|
void DeviceConformingProlongationOperator::ReduceLocalCopy(
|
|
const Vector &x, Vector &y) const
|
|
{
|
|
// dst[i] = src[ltdof_ldof[i]]
|
|
if (ltdof_ldof.Size() == 0) { return; }
|
|
ExtractSubVector(ltdof_ldof, x, y);
|
|
}
|
|
|
|
static void AddSubVector(const Array<int> &unique_dst_indices,
|
|
const Array<int> &unique_to_src_offsets,
|
|
const Array<int> &unique_to_src_indices,
|
|
const Vector &src,
|
|
Vector &dst)
|
|
{
|
|
auto y = dst.ReadWrite();
|
|
const auto x = src.Read();
|
|
const auto DST_I = unique_dst_indices.Read();
|
|
const auto SRC_O = unique_to_src_offsets.Read();
|
|
const auto SRC_I = unique_to_src_indices.Read();
|
|
mfem::forall(unique_dst_indices.Size(), [=] MFEM_HOST_DEVICE (int i)
|
|
{
|
|
const int dst_idx = DST_I[i];
|
|
real_t sum = y[dst_idx];
|
|
const int end = SRC_O[i+1];
|
|
for (int j = SRC_O[i]; j != end; ++j) { sum += x[SRC_I[j]]; }
|
|
y[dst_idx] = sum;
|
|
});
|
|
}
|
|
|
|
void DeviceConformingProlongationOperator::ReduceEndAssemble(Vector &y) const
|
|
{
|
|
// dst[shr_ltdof[i]] += shr_buf[i]
|
|
if (unq_ltdof.Size() == 0) { return; }
|
|
AddSubVector(unq_ltdof, unq_shr_i, unq_shr_j, shr_buf, y);
|
|
}
|
|
|
|
void DeviceConformingProlongationOperator::MultTranspose(const Vector &x,
|
|
Vector &y) const
|
|
{
|
|
const GroupTopology >opo = gc.GetGroupTopology();
|
|
int req_counter = 0;
|
|
if (!local)
|
|
{
|
|
ReduceBeginCopy(x); // copy to 'ext_buf'
|
|
for (int nbr = 1; nbr < gtopo.GetNumNeighbors(); nbr++)
|
|
{
|
|
const int send_offset = ext_buf_offsets[nbr];
|
|
const int send_size = ext_buf_offsets[nbr+1] - send_offset;
|
|
if (send_size > 0)
|
|
{
|
|
auto send_buf = mpi_gpu_aware ? ext_buf.Read() : ext_buf.HostRead();
|
|
MPI_Isend(send_buf + send_offset, send_size, MPITypeMap<real_t>::mpi_type,
|
|
gtopo.GetNeighborRank(nbr), 41823,
|
|
gtopo.GetComm(), &requests[req_counter++]);
|
|
}
|
|
const int recv_offset = shr_buf_offsets[nbr];
|
|
const int recv_size = shr_buf_offsets[nbr+1] - recv_offset;
|
|
if (recv_size > 0)
|
|
{
|
|
auto recv_buf = mpi_gpu_aware ? shr_buf.Write() : shr_buf.HostWrite();
|
|
MPI_Irecv(recv_buf + recv_offset, recv_size, MPITypeMap<real_t>::mpi_type,
|
|
gtopo.GetNeighborRank(nbr), 41823,
|
|
gtopo.GetComm(), &requests[req_counter++]);
|
|
}
|
|
}
|
|
}
|
|
ReduceLocalCopy(x, y);
|
|
if (!local)
|
|
{
|
|
MPI_Waitall(req_counter, requests, MPI_STATUSES_IGNORE);
|
|
ReduceEndAssemble(y); // assemble from 'shr_buf'
|
|
}
|
|
}
|
|
|
|
} // namespace mfem
|
|
|
|
#endif
|