Files
mfem/fem/restriction.cpp
T
2026-02-24 11:17:54 -08:00

2468 lines
80 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 "restriction.hpp"
#include "normal_deriv_restriction.hpp"
#include "gridfunc.hpp"
#include "fespace.hpp"
#include "pgridfunc.hpp"
#include "qspace.hpp"
#include "fe/face_map_utils.hpp"
#include "../general/forall.hpp"
#include <climits>
namespace mfem
{
ElementRestriction::ElementRestriction(const FiniteElementSpace &f,
ElementDofOrdering e_ordering)
: fes(f),
ne(fes.GetNE()),
vdim(fes.GetVDim()),
byvdim(fes.GetOrdering() == Ordering::byVDIM),
ndofs(fes.GetNDofs()),
dof(fes.GetTypicalFE()->GetDof()),
nedofs(ne*dof),
offsets(ndofs+1),
indices(ne*dof),
gather_map(ne*dof)
{
// Assuming all finite elements are the same.
MFEM_VERIFY(!f.IsVariableOrder(), "Variable-order spaces are not supported");
height = vdim*ne*dof;
width = fes.GetVSize();
const bool dof_reorder = (e_ordering == ElementDofOrdering::LEXICOGRAPHIC);
const int *dof_map = NULL;
if (dof_reorder && ne > 0)
{
for (int e = 0; e < ne; ++e)
{
const FiniteElement *fe = fes.GetFE(e);
auto el_t = dynamic_cast<const TensorBasisElement*>(fe);
auto el_n = dynamic_cast<const NodalFiniteElement*>(fe);
if (el_t || el_n) { continue; }
MFEM_ABORT("Finite element not suitable for lexicographic ordering");
}
const FiniteElement *fe = fes.GetTypicalFE();
auto el_t = dynamic_cast<const TensorBasisElement*>(fe);
auto el_n = dynamic_cast<const NodalFiniteElement*>(fe);
const Array<int> &fe_dof_map =
(el_t) ? el_t->GetDofMap() : el_n->GetLexicographicOrdering();
MFEM_VERIFY(fe_dof_map.Size() > 0, "invalid dof map");
dof_map = fe_dof_map.HostRead();
}
const Table& e2dTable = fes.GetElementToDofTable();
const int* element_map = e2dTable.GetJ();
// We will be keeping a count of how many local nodes point to its global dof
for (int i = 0; i <= ndofs; ++i)
{
offsets[i] = 0;
}
for (int e = 0; e < ne; ++e)
{
for (int d = 0; d < dof; ++d)
{
const int sgid = element_map[dof*e + d]; // signed
const int gid = (sgid >= 0) ? sgid : -1 - sgid;
++offsets[gid + 1];
}
}
// Aggregate to find offsets for each global dof
for (int i = 1; i <= ndofs; ++i)
{
offsets[i] += offsets[i - 1];
}
// For each global dof, fill in all local nodes that point to it
for (int e = 0; e < ne; ++e)
{
for (int d = 0; d < dof; ++d)
{
const int sdid = dof_reorder ? dof_map[d] : 0; // signed
const int did = (!dof_reorder)?d:(sdid >= 0 ? sdid : -1-sdid);
const int sgid = element_map[dof*e + did]; // signed
const int gid = (sgid >= 0) ? sgid : -1-sgid;
const int lid = dof*e + d;
const bool plus = (sgid >= 0 && sdid >= 0) || (sgid < 0 && sdid < 0);
gather_map[lid] = plus ? gid : -1-gid;
indices[offsets[gid]++] = plus ? lid : -1-lid;
}
}
// We shifted the offsets vector by 1 by using it as a counter.
// Now we shift it back.
for (int i = ndofs; i > 0; --i)
{
offsets[i] = offsets[i - 1];
}
offsets[0] = 0;
}
void ElementRestriction::Mult(const Vector& x, Vector& y) const
{
// Assumes all elements have the same number of dofs
const int nd = dof;
const int vd = vdim;
const bool t = byvdim;
auto d_x = Reshape(x.Read(), t?vd:ndofs, t?ndofs:vd);
auto d_y = Reshape(y.Write(), nd, vd, ne);
auto d_gather_map = gather_map.Read();
mfem::forall(dof*ne, [=] MFEM_HOST_DEVICE (int i)
{
const int gid = d_gather_map[i];
const bool plus = gid >= 0;
const int j = plus ? gid : -1-gid;
for (int c = 0; c < vd; ++c)
{
const real_t dof_value = d_x(t?c:j, t?j:c);
d_y(i % nd, c, i / nd) = plus ? dof_value : -dof_value;
}
});
}
void ElementRestriction::AbsMult(const Vector& x, Vector& y) const
{
// Assumes all elements have the same number of dofs
const int nd = dof;
const int vd = vdim;
const bool t = byvdim;
auto d_x = Reshape(x.Read(), t?vd:ndofs, t?ndofs:vd);
auto d_y = Reshape(y.Write(), nd, vd, ne);
auto d_gather_map = gather_map.Read();
mfem::forall(dof*ne, [=] MFEM_HOST_DEVICE (int i)
{
const int gid = d_gather_map[i];
const int j = gid >= 0 ? gid : -1-gid;
for (int c = 0; c < vd; ++c)
{
d_y(i % nd, c, i / nd) = d_x(t?c:j, t?j:c);
}
});
}
template <bool ADD>
void ElementRestriction::TAddMultTranspose(const Vector& x, Vector& y) const
{
// Assumes all elements have the same number of dofs
const int nd = dof;
const int vd = vdim;
const bool t = byvdim;
auto d_offsets = offsets.Read();
auto d_indices = indices.Read();
auto d_x = Reshape(x.Read(), nd, vd, ne);
auto d_y = Reshape(ADD ? y.ReadWrite() : y.Write(), t?vd:ndofs, t?ndofs:vd);
mfem::forall(ndofs, [=] MFEM_HOST_DEVICE (int i)
{
const int offset = d_offsets[i];
const int next_offset = d_offsets[i + 1];
for (int c = 0; c < vd; ++c)
{
real_t dof_value = 0;
for (int j = offset; j < next_offset; ++j)
{
const int idx_j = (d_indices[j] >= 0) ? d_indices[j] : -1 - d_indices[j];
dof_value += ((d_indices[j] >= 0) ? d_x(idx_j % nd, c, idx_j / nd) :
-d_x(idx_j % nd, c, idx_j / nd));
}
if (ADD) { d_y(t?c:i,t?i:c) += dof_value; }
else { d_y(t?c:i,t?i:c) = dof_value; }
}
});
}
void ElementRestriction::MultTranspose(const Vector& x, Vector& y) const
{
constexpr bool ADD = false;
TAddMultTranspose<ADD>(x, y);
}
void ElementRestriction::AddMultTranspose(const Vector& x, Vector& y,
const real_t a) const
{
MFEM_VERIFY(a == 1.0, "General coefficient case is not yet supported!");
constexpr bool ADD = true;
TAddMultTranspose<ADD>(x, y);
}
void ElementRestriction::AbsMultTranspose(const Vector& x, Vector& y) const
{
// Assumes all elements have the same number of dofs
const int nd = dof;
const int vd = vdim;
const bool t = byvdim;
auto d_offsets = offsets.Read();
auto d_indices = indices.Read();
auto d_x = Reshape(x.Read(), nd, vd, ne);
auto d_y = Reshape(y.Write(), t?vd:ndofs, t?ndofs:vd);
mfem::forall(ndofs, [=] MFEM_HOST_DEVICE (int i)
{
const int offset = d_offsets[i];
const int next_offset = d_offsets[i + 1];
for (int c = 0; c < vd; ++c)
{
real_t dof_value = 0;
for (int j = offset; j < next_offset; ++j)
{
const int idx_j = (d_indices[j] >= 0) ? d_indices[j] : -1 - d_indices[j];
dof_value += d_x(idx_j % nd, c, idx_j / nd);
}
d_y(t?c:i,t?i:c) = dof_value;
}
});
}
void ElementRestriction::MultLeftInverse(const Vector& x, Vector& y) const
{
// Assumes all elements have the same number of dofs
const int nd = dof;
const int vd = vdim;
const bool t = byvdim;
auto d_offsets = offsets.Read();
auto d_indices = indices.Read();
auto d_x = Reshape(x.Read(), nd, vd, ne);
auto d_y = Reshape(y.Write(), t?vd:ndofs, t?ndofs:vd);
mfem::forall(ndofs, [=] MFEM_HOST_DEVICE (int i)
{
const int next_offset = d_offsets[i + 1];
for (int c = 0; c < vd; ++c)
{
real_t dof_value = 0;
const int j = next_offset - 1;
const int idx_j = (d_indices[j] >= 0) ? d_indices[j] : -1 - d_indices[j];
dof_value = (d_indices[j] >= 0) ? d_x(idx_j % nd, c, idx_j / nd) :
-d_x(idx_j % nd, c, idx_j / nd);
d_y(t?c:i,t?i:c) = dof_value;
}
});
}
void ElementRestriction::BooleanMask(Vector& y) const
{
// Assumes all elements have the same number of dofs
const int nd = dof;
const int vd = vdim;
const bool t = byvdim;
Array<char> processed(vd * ndofs);
processed = 0;
auto d_offsets = offsets.HostRead();
auto d_indices = indices.HostRead();
auto d_x = Reshape(processed.HostReadWrite(), t?vd:ndofs, t?ndofs:vd);
auto d_y = Reshape(y.HostWrite(), nd, vd, ne);
for (int i = 0; i < ndofs; ++i)
{
const int offset = d_offsets[i];
const int next_offset = d_offsets[i+1];
for (int c = 0; c < vd; ++c)
{
for (int j = offset; j < next_offset; ++j)
{
const int idx_j = d_indices[j];
if (d_x(t?c:i,t?i:c))
{
d_y(idx_j % nd, c, idx_j / nd) = 0.0;
}
else
{
d_y(idx_j % nd, c, idx_j / nd) = 1.0;
d_x(t?c:i,t?i:c) = 1;
}
}
}
}
}
void ElementRestriction::FillSparseMatrix(const Vector &mat_ea,
SparseMatrix &mat) const
{
mat.GetMemoryI().New(mat.Height()+1, mat.GetMemoryI().GetMemoryType());
const int nnz = FillI(mat);
mat.GetMemoryJ().New(nnz, mat.GetMemoryJ().GetMemoryType());
mat.GetMemoryData().New(nnz, mat.GetMemoryData().GetMemoryType());
FillJAndData(mat_ea, mat);
}
static MFEM_HOST_DEVICE int GetMinElt(const int *my_elts, const int nbElts,
const int *nbr_elts, const int nbrNbElts)
{
// Find the minimal element index found in both my_elts[] and nbr_elts[]
int min_el = INT_MAX;
for (int i = 0; i < nbElts; i++)
{
const int e_i = my_elts[i];
if (e_i >= min_el) { continue; }
for (int j = 0; j < nbrNbElts; j++)
{
if (e_i==nbr_elts[j])
{
min_el = e_i; // we already know e_i < min_el
break;
}
}
}
return min_el;
}
/** Returns the index where a non-zero entry should be added and increment the
number of non-zeros for the row i_L. */
static MFEM_HOST_DEVICE int GetAndIncrementNnzIndex(const int i_L, int* I)
{
int ind = AtomicAdd(I[i_L],1);
return ind;
}
int ElementRestriction::FillI(SparseMatrix &mat) const
{
const int all_dofs = ndofs;
const int vd = vdim;
const int elt_dofs = dof;
auto I = mat.ReadWriteI();
auto d_offsets = offsets.Read();
auto d_indices = indices.Read();
auto d_gather_map = gather_map.Read();
Array<int> ij_elts(indices.Size() * 2);
auto d_ij_elts = Reshape(ij_elts.Write(), indices.Size(), 2);
mfem::forall(vd*all_dofs+1, [=] MFEM_HOST_DEVICE (int i_L)
{
I[i_L] = 0;
});
mfem::forall(ne*elt_dofs, [=] MFEM_HOST_DEVICE (int l_dof)
{
const int e = l_dof/elt_dofs;
const int i = l_dof%elt_dofs;
const int i_gm = e*elt_dofs + i;
const int i_L = d_gather_map[i_gm];
const int i_offset = d_offsets[i_L];
const int i_next_offset = d_offsets[i_L+1];
const int i_nbElts = i_next_offset - i_offset;
int *i_elts = &d_ij_elts(i_offset, 0);
for (int e_i = 0; e_i < i_nbElts; ++e_i)
{
const int i_E = d_indices[i_offset+e_i];
i_elts[e_i] = i_E/elt_dofs;
}
for (int j = 0; j < elt_dofs; j++)
{
const int j_gm = e*elt_dofs + j;
const int j_L = d_gather_map[j_gm];
const int j_offset = d_offsets[j_L];
const int j_next_offset = d_offsets[j_L+1];
const int j_nbElts = j_next_offset - j_offset;
if (i_nbElts == 1 || j_nbElts == 1) // no assembly required
{
GetAndIncrementNnzIndex(i_L, I);
}
else // assembly required
{
int *j_elts = &d_ij_elts(j_offset, 1);
for (int e_j = 0; e_j < j_nbElts; ++e_j)
{
const int j_E = d_indices[j_offset+e_j];
const int elt = j_E/elt_dofs;
j_elts[e_j] = elt;
}
int min_e = GetMinElt(i_elts, i_nbElts, j_elts, j_nbElts);
if (e == min_e) // add the nnz only once
{
GetAndIncrementNnzIndex(i_L, I);
}
}
}
});
// We need to sum the entries of I, we do it on CPU as it is very sequential.
auto h_I = mat.HostReadWriteI();
const int nTdofs = vd*all_dofs;
int sum = 0;
for (int i = 0; i < nTdofs; i++)
{
const int nnz = h_I[i];
h_I[i] = sum;
sum+=nnz;
}
h_I[nTdofs] = sum;
// We return the number of nnz
return h_I[nTdofs];
}
void ElementRestriction::FillJAndData(const Vector &ea_data,
SparseMatrix &mat) const
{
const int all_dofs = ndofs;
const int vd = vdim;
const int elt_dofs = dof;
auto I = mat.ReadWriteI();
auto J = mat.WriteJ();
auto Data = mat.WriteData();
auto d_offsets = offsets.Read();
auto d_indices = indices.Read();
auto d_gather_map = gather_map.Read();
auto mat_ea = Reshape(ea_data.Read(), elt_dofs, elt_dofs, ne);
Array<int> ij_B_el(indices.Size() * 4);
auto d_ij_B_el = Reshape(ij_B_el.Write(), indices.Size(), 4);
mfem::forall(ne*elt_dofs, [=] MFEM_HOST_DEVICE (int l_dof)
{
const int e = l_dof/elt_dofs;
const int i = l_dof%elt_dofs;
const int i_gm = e*elt_dofs + i;
const int i_L = d_gather_map[i_gm];
const int i_offset = d_offsets[i_L];
const int i_next_offset = d_offsets[i_L+1];
const int i_nbElts = i_next_offset - i_offset;
int *i_elts = &d_ij_B_el(i_offset, 0);
int *i_B = &d_ij_B_el(i_offset, 1);
for (int e_i = 0; e_i < i_nbElts; ++e_i)
{
const int i_E = d_indices[i_offset+e_i];
i_elts[e_i] = i_E/elt_dofs;
i_B[e_i] = i_E%elt_dofs;
}
for (int j = 0; j < elt_dofs; j++)
{
const int j_gm = e*elt_dofs + j;
const int j_L = d_gather_map[j_gm];
const int j_offset = d_offsets[j_L];
const int j_next_offset = d_offsets[j_L+1];
const int j_nbElts = j_next_offset - j_offset;
if (i_nbElts == 1 || j_nbElts == 1) // no assembly required
{
const int nnz = GetAndIncrementNnzIndex(i_L, I);
J[nnz] = j_L;
Data[nnz] = mat_ea(j,i,e);
}
else // assembly required
{
int *j_elts = &d_ij_B_el(j_offset, 2);
int *j_B = &d_ij_B_el(j_offset, 3);
for (int e_j = 0; e_j < j_nbElts; ++e_j)
{
const int j_E = d_indices[j_offset+e_j];
const int elt = j_E/elt_dofs;
j_elts[e_j] = elt;
j_B[e_j] = j_E%elt_dofs;
}
int min_e = GetMinElt(i_elts, i_nbElts, j_elts, j_nbElts);
if (e == min_e) // add the nnz only once
{
real_t val = 0.0;
for (int k = 0; k < i_nbElts; k++)
{
const int e_i = i_elts[k];
const int i_Bloc = i_B[k];
for (int l = 0; l < j_nbElts; l++)
{
const int e_j = j_elts[l];
const int j_Bloc = j_B[l];
if (e_i == e_j)
{
val += mat_ea(j_Bloc, i_Bloc, e_i);
}
}
}
const int nnz = GetAndIncrementNnzIndex(i_L, I);
J[nnz] = j_L;
Data[nnz] = val;
}
}
}
});
// We need to shift again the entries of I, we do it on CPU as it is very
// sequential.
auto h_I = mat.HostReadWriteI();
const int size = vd*all_dofs;
for (int i = 0; i < size; i++)
{
h_I[size-i] = h_I[size-(i+1)];
}
h_I[0] = 0;
}
L2ElementRestriction::L2ElementRestriction(const FiniteElementSpace &fes)
: ne(fes.GetNE()),
vdim(fes.GetVDim()),
byvdim(fes.GetOrdering() == Ordering::byVDIM),
ndof(fes.GetTypicalFE()->GetDof()),
ndofs(fes.GetNDofs())
{
height = vdim*ne*ndof;
width = vdim*ne*ndof;
}
void L2ElementRestriction::Mult(const Vector &x, Vector &y) const
{
const int nd = ndof;
const int vd = vdim;
const bool t = byvdim;
auto d_x = Reshape(x.Read(), t?vd:ndofs, t?ndofs:vd);
auto d_y = Reshape(y.Write(), nd, vd, ne);
mfem::forall(ndofs, [=] MFEM_HOST_DEVICE (int i)
{
const int idx = i;
const int dof = idx % nd;
const int e = idx / nd;
for (int c = 0; c < vd; ++c)
{
d_y(dof, c, e) = d_x(t?c:idx, t?idx:c);
}
});
}
template <bool ADD>
void L2ElementRestriction::TAddMultTranspose(const Vector &x, Vector &y) const
{
const int nd = ndof;
const int vd = vdim;
const bool t = byvdim;
auto d_x = Reshape(x.Read(), nd, vd, ne);
auto d_y = Reshape(ADD ? y.ReadWrite() : y.Write(), t?vd:ndofs, t?ndofs:vd);
mfem::forall(ndofs, [=] MFEM_HOST_DEVICE (int i)
{
const int idx = i;
const int dof = idx % nd;
const int e = idx / nd;
for (int c = 0; c < vd; ++c)
{
if (ADD) { d_y(t?c:idx,t?idx:c) += d_x(dof, c, e); }
else { d_y(t?c:idx,t?idx:c) = d_x(dof, c, e); }
}
});
}
void L2ElementRestriction::MultTranspose(const Vector &x, Vector &y) const
{
constexpr bool ADD = false;
TAddMultTranspose<ADD>(x, y);
}
void L2ElementRestriction::AddMultTranspose(const Vector &x, Vector &y,
const real_t a) const
{
MFEM_VERIFY(a == 1.0, "General coefficient case is not yet supported!");
constexpr bool ADD = true;
TAddMultTranspose<ADD>(x, y);
}
void L2ElementRestriction::FillI(SparseMatrix &mat) const
{
const int elem_dofs = ndof;
const int vd = vdim;
auto I = mat.WriteI();
const int isize = mat.Height() + 1;
const int interior_dofs = ne*elem_dofs*vd;
mfem::forall(isize, [=] MFEM_HOST_DEVICE (int dof)
{
I[dof] = dof<interior_dofs ? elem_dofs : 0;
});
}
static MFEM_HOST_DEVICE int AddNnz(const int iE, int *I, const int dofs)
{
int val = AtomicAdd(I[iE],dofs);
return val;
}
void L2ElementRestriction::FillJAndData(const Vector &ea_data,
SparseMatrix &mat) const
{
const int elem_dofs = ndof;
const int vd = vdim;
auto I = mat.ReadWriteI();
auto J = mat.WriteJ();
auto Data = mat.WriteData();
auto mat_ea = Reshape(ea_data.Read(), elem_dofs, elem_dofs, ne);
mfem::forall(ne*elem_dofs*vd, [=] MFEM_HOST_DEVICE (int iE)
{
const int offset = AddNnz(iE,I,elem_dofs);
const int e = iE/elem_dofs;
const int i = iE%elem_dofs;
for (int j = 0; j < elem_dofs; j++)
{
J[offset+j] = e*elem_dofs+j;
Data[offset+j] = mat_ea(j,i,e);
}
});
}
ConformingFaceRestriction::ConformingFaceRestriction(
const FiniteElementSpace &fes,
const ElementDofOrdering f_ordering,
const FaceType type,
bool build)
: fes(fes),
nf(fes.GetNFbyType(type)),
vdim(fes.GetVDim()),
byvdim(fes.GetOrdering() == Ordering::byVDIM),
face_dofs(nf > 0 ? fes.GetFaceElement(0)->GetDof() : 0),
elem_dofs(fes.GetTypicalFE()->GetDof()),
nfdofs(nf*face_dofs),
ndofs(fes.GetNDofs()),
scatter_indices(nf*face_dofs),
gather_offsets(ndofs+1),
gather_indices(nf*face_dofs),
face_map(face_dofs)
{
height = vdim*nf*face_dofs;
width = fes.GetVSize();
if (nf==0) { return; }
CheckFESpace(f_ordering);
// Get the mapping from lexicographic DOF ordering to native ordering.
const TensorBasisElement* el =
dynamic_cast<const TensorBasisElement*>(fes.GetTypicalFE());
const Array<int> &dof_map_ = el->GetDofMap();
if (dof_map_.Size() > 0)
{
vol_dof_map.MakeRef(dof_map_);
}
else
{
// For certain types of elements dof_map_ is empty. In this case, that
// means the element is already ordered lexicographically, so the
// permutation is the identity.
vol_dof_map.SetSize(elem_dofs);
for (int i = 0; i < elem_dofs; ++i) { vol_dof_map[i] = i; }
}
if (!build) { return; }
ComputeScatterIndicesAndOffsets(f_ordering, type);
ComputeGatherIndices(f_ordering,type);
}
ConformingFaceRestriction::ConformingFaceRestriction(
const FiniteElementSpace &fes,
const ElementDofOrdering f_ordering,
const FaceType type)
: ConformingFaceRestriction(fes, f_ordering, type, true)
{ }
void ConformingFaceRestriction::MultInternal(const Vector& x, Vector& y,
const bool useAbs) const
{
if (nf==0) { return; }
// Assumes all elements have the same number of dofs
const int nface_dofs = face_dofs;
const int vd = vdim;
const bool t = byvdim;
auto d_indices = scatter_indices.Read();
auto d_x = Reshape(x.Read(), t?vd:ndofs, t?ndofs:vd);
auto d_y = Reshape(y.Write(), nface_dofs, vd, nf);
mfem::forall(nfdofs, [=] MFEM_HOST_DEVICE (int i)
{
const int s_idx = d_indices[i];
const int sgn = (useAbs || s_idx >= 0) ? 1 : -1;
const int idx = (s_idx >= 0) ? s_idx : -1 - s_idx;
const int dof = i % nface_dofs;
const int face = i / nface_dofs;
for (int c = 0; c < vd; ++c)
{
d_y(dof, c, face) = sgn*d_x(t?c:idx, t?idx:c);
}
});
}
static void ConformingFaceRestriction_AddMultTranspose(
const int ndofs,
const int face_dofs,
const int nf,
const int vdim,
const bool by_vdim,
const Array<int> &gather_offsets,
const Array<int> &gather_indices,
const Vector &x,
Vector &y,
bool use_signs,
const real_t a)
{
MFEM_VERIFY(a == 1.0, "General coefficient case is not yet supported!");
if (nf==0) { return; }
// Assumes all elements have the same number of dofs
auto d_offsets = gather_offsets.Read();
auto d_indices = gather_indices.Read();
auto d_x = Reshape(x.Read(), face_dofs, vdim, nf);
auto d_y = Reshape(y.ReadWrite(), by_vdim?vdim:ndofs, by_vdim?ndofs:vdim);
mfem::forall(ndofs, [=] MFEM_HOST_DEVICE (int i)
{
const int offset = d_offsets[i];
const int next_offset = d_offsets[i + 1];
for (int c = 0; c < vdim; ++c)
{
real_t dof_value = 0;
for (int j = offset; j < next_offset; ++j)
{
const int s_idx_j = d_indices[j];
const real_t sgn = (s_idx_j >= 0 || !use_signs) ? 1.0 : -1.0;
const int idx_j = (s_idx_j >= 0) ? s_idx_j : -1 - s_idx_j;
dof_value += sgn*d_x(idx_j % face_dofs, c, idx_j / face_dofs);
}
d_y(by_vdim?c:i,by_vdim?i:c) += dof_value;
}
});
}
void ConformingFaceRestriction::AddMultTranspose(
const Vector& x, Vector& y, const real_t a) const
{
ConformingFaceRestriction_AddMultTranspose(
ndofs, face_dofs, nf, vdim, byvdim, gather_offsets, gather_indices, x, y,
true, a);
}
void ConformingFaceRestriction::AddAbsMultTranspose(
const Vector& x, Vector& y, const real_t a) const
{
ConformingFaceRestriction_AddMultTranspose(
ndofs, face_dofs, nf, vdim, byvdim, gather_offsets, gather_indices, x, y,
false, a);
}
void ConformingFaceRestriction::CheckFESpace(const ElementDofOrdering
f_ordering)
{
#ifdef MFEM_USE_MPI
// If the underlying finite element space is parallel, ensure the face
// neighbor information is generated.
if (const ParFiniteElementSpace *pfes
= dynamic_cast<const ParFiniteElementSpace*>(&fes))
{
pfes->GetParMesh()->ExchangeFaceNbrData();
}
#endif
#ifdef MFEM_DEBUG
const FiniteElement *fe0 = fes.GetTypicalFE();
const TensorBasisElement *tfe = dynamic_cast<const TensorBasisElement*>(fe0);
MFEM_VERIFY(tfe != NULL,
"ConformingFaceRestriction only supports TensorBasisElements");
MFEM_VERIFY(tfe->GetBasisType()==BasisType::GaussLobatto ||
tfe->GetBasisType()==BasisType::Positive,
"ConformingFaceRestriction only supports Gauss-Lobatto and Bernstein bases");
// Assuming all finite elements are using Gauss-Lobatto.
const bool dof_reorder = (f_ordering == ElementDofOrdering::LEXICOGRAPHIC);
if (dof_reorder && nf > 0)
{
for (int f = 0; f < fes.GetNF(); ++f)
{
const FiniteElement *fe = fes.GetFaceElement(f);
const TensorBasisElement* el =
dynamic_cast<const TensorBasisElement*>(fe);
if (el) { continue; }
MFEM_ABORT("Finite element not suitable for lexicographic ordering");
}
}
#endif
}
void ConformingFaceRestriction::ComputeScatterIndicesAndOffsets(
const ElementDofOrdering f_ordering,
const FaceType type)
{
Mesh &mesh = *fes.GetMesh();
// Initialization of the offsets
for (int i = 0; i <= ndofs; ++i)
{
gather_offsets[i] = 0;
}
// Computation of scatter indices and offsets
int f_ind = 0;
for (int f = 0; f < fes.GetNF(); ++f)
{
Mesh::FaceInformation face = mesh.GetFaceInformation(f);
if ( face.IsNonconformingCoarse() )
{
// We skip nonconforming coarse faces as they are treated
// by the corresponding nonconforming fine faces.
continue;
}
else if ( face.IsOfFaceType(type) )
{
SetFaceDofsScatterIndices(face, f_ind, f_ordering);
f_ind++;
}
}
MFEM_VERIFY(f_ind==nf, "Unexpected number of faces.");
// Summation of the offsets
for (int i = 1; i <= ndofs; ++i)
{
gather_offsets[i] += gather_offsets[i - 1];
}
}
void ConformingFaceRestriction::ComputeGatherIndices(
const ElementDofOrdering f_ordering,
const FaceType type)
{
Mesh &mesh = *fes.GetMesh();
// Computation of gather_indices
int f_ind = 0;
for (int f = 0; f < fes.GetNF(); ++f)
{
Mesh::FaceInformation face = mesh.GetFaceInformation(f);
if ( face.IsNonconformingCoarse() )
{
// We skip nonconforming coarse faces as they are treated
// by the corresponding nonconforming fine faces.
continue;
}
else if ( face.IsOfFaceType(type) )
{
SetFaceDofsGatherIndices(face, f_ind, f_ordering);
f_ind++;
}
}
MFEM_VERIFY(f_ind==nf, "Unexpected number of faces.");
// Reset offsets to their initial value
for (int i = ndofs; i > 0; --i)
{
gather_offsets[i] = gather_offsets[i - 1];
}
gather_offsets[0] = 0;
}
void ConformingFaceRestriction::SetFaceDofsScatterIndices(
const Mesh::FaceInformation &face,
const int face_index,
const ElementDofOrdering f_ordering)
{
MFEM_ASSERT(!(face.IsNonconformingCoarse()),
"This method should not be used on nonconforming coarse faces.");
MFEM_ASSERT(face.element[0].orientation==0,
"FaceRestriction used on degenerated mesh.");
MFEM_VERIFY(f_ordering == ElementDofOrdering::LEXICOGRAPHIC,
"NATIVE ordering is not supported yet");
fes.GetTypicalFE()->GetFaceMap(face.element[0].local_face_id, face_map);
const Table& e2dTable = fes.GetElementToDofTable();
const int* elem_map = e2dTable.GetJ();
const int elem_index = face.element[0].index;
for (int face_dof = 0; face_dof < face_dofs; ++face_dof)
{
const int lex_volume_dof = face_map[face_dof];
const int s_volume_dof = AsConst(vol_dof_map)[lex_volume_dof]; // signed
const int volume_dof = UnsignIndex(s_volume_dof);
const int s_global_dof = elem_map[elem_index*elem_dofs + volume_dof];
const int global_dof = UnsignIndex(s_global_dof);
const int restriction_dof = face_dofs*face_index + face_dof;
scatter_indices[restriction_dof] = s_global_dof;
++gather_offsets[global_dof + 1];
}
}
void ConformingFaceRestriction::SetFaceDofsGatherIndices(
const Mesh::FaceInformation &face,
const int face_index,
const ElementDofOrdering f_ordering)
{
MFEM_ASSERT(!(face.IsNonconformingCoarse()),
"This method should not be used on nonconforming coarse faces.");
MFEM_VERIFY(f_ordering == ElementDofOrdering::LEXICOGRAPHIC,
"NATIVE ordering is not supported yet");
fes.GetTypicalFE()->GetFaceMap(face.element[0].local_face_id, face_map);
const Table& e2dTable = fes.GetElementToDofTable();
const int* elem_map = e2dTable.GetJ();
const int elem_index = face.element[0].index;
for (int face_dof = 0; face_dof < face_dofs; ++face_dof)
{
const int lex_volume_dof = face_map[face_dof];
const int s_volume_dof = AsConst(vol_dof_map)[lex_volume_dof];
const int volume_dof = UnsignIndex(s_volume_dof);
const int s_global_dof = elem_map[elem_index*elem_dofs + volume_dof];
const int sgn = (s_global_dof >= 0) ? 1 : -1;
const int global_dof = UnsignIndex(s_global_dof);
const int restriction_dof = face_dofs*face_index + face_dof;
const int s_restriction_dof = (sgn >= 0) ? restriction_dof : -1 -
restriction_dof;
gather_indices[gather_offsets[global_dof]++] = s_restriction_dof;
}
}
// Permute dofs or quads on a face for e2 to match with the ordering of e1
int PermuteFaceL2(const int dim, const int face_id1,
const int face_id2, const int orientation,
const int size1d, const int index)
{
switch (dim)
{
case 1:
return 0;
case 2:
return internal::PermuteFace2D(face_id1, face_id2, orientation, size1d, index);
case 3:
return internal::PermuteFace3D(face_id1, face_id2, orientation, size1d, index);
default:
MFEM_ABORT("Unsupported dimension.");
return 0;
}
}
L2FaceRestriction::L2FaceRestriction(const FiniteElementSpace &fes,
const ElementDofOrdering f_ordering,
const FaceType type,
const L2FaceValues m,
bool build)
: fes(fes),
ordering(f_ordering),
nf(fes.GetNFbyType(type)),
ne(fes.GetNE()),
vdim(fes.GetVDim()),
byvdim(fes.GetOrdering() == Ordering::byVDIM),
face_dofs(fes.GetTypicalTraceElement()->GetDof()),
elem_dofs(fes.GetTypicalFE()->GetDof()),
nfdofs(nf*face_dofs),
ndofs(fes.GetNDofs()),
type(type),
m(m),
scatter_indices1(nf*face_dofs),
scatter_indices2(m==L2FaceValues::DoubleValued?nf*face_dofs:0),
gather_offsets(ndofs+1),
gather_indices((m==L2FaceValues::DoubleValued? 2 : 1)*nf*face_dofs),
face_map(face_dofs)
{
height = (m==L2FaceValues::DoubleValued? 2 : 1)*vdim*nf*face_dofs;
width = fes.GetVSize();
if (!build) { return; }
CheckFESpace();
ComputeScatterIndicesAndOffsets();
ComputeGatherIndices();
}
L2FaceRestriction::L2FaceRestriction(const FiniteElementSpace &fes,
const ElementDofOrdering f_ordering,
const FaceType type,
const L2FaceValues m)
: L2FaceRestriction(fes, f_ordering, type, m, true)
{ }
void L2FaceRestriction::SingleValuedConformingMult(const Vector& x,
Vector& y) const
{
if (nf == 0) { return; }
MFEM_ASSERT(
m == L2FaceValues::SingleValued,
"This method should be called when m == L2FaceValues::SingleValued.");
// Assumes all elements have the same number of dofs
const int nface_dofs = face_dofs;
const int vd = vdim;
const bool t = byvdim;
auto d_indices1 = scatter_indices1.Read();
auto d_x = Reshape(x.Read(), t?vd:ndofs, t?ndofs:vd);
auto d_y = Reshape(y.Write(), nface_dofs, vd, nf);
mfem::forall(nfdofs, [=] MFEM_HOST_DEVICE (int i)
{
const int dof = i % nface_dofs;
const int face = i / nface_dofs;
const int idx1 = d_indices1[i];
for (int c = 0; c < vd; ++c)
{
d_y(dof, c, face) = d_x(t?c:idx1, t?idx1:c);
}
});
}
void L2FaceRestriction::DoubleValuedConformingMult(const Vector& x,
Vector& y) const
{
MFEM_ASSERT(
m == L2FaceValues::DoubleValued,
"This method should be called when m == L2FaceValues::DoubleValued.");
// Assumes all elements have the same number of dofs
const int nface_dofs = face_dofs;
const int vd = vdim;
const bool t = byvdim;
auto d_indices1 = scatter_indices1.Read();
auto d_indices2 = scatter_indices2.Read();
auto d_x = Reshape(x.Read(), t?vd:ndofs, t?ndofs:vd);
auto d_y = Reshape(y.Write(), nface_dofs, vd, 2, nf);
mfem::forall(nfdofs, [=] MFEM_HOST_DEVICE (int i)
{
const int dof = i % nface_dofs;
const int face = i / nface_dofs;
const int idx1 = d_indices1[i];
for (int c = 0; c < vd; ++c)
{
d_y(dof, c, 0, face) = d_x(t?c:idx1, t?idx1:c);
}
const int idx2 = d_indices2[i];
for (int c = 0; c < vd; ++c)
{
d_y(dof, c, 1, face) = idx2==-1 ? 0.0 : d_x(t?c:idx2, t?idx2:c);
}
});
}
void L2FaceRestriction::Mult(const Vector& x, Vector& y) const
{
if (nf==0) { return; }
if (m==L2FaceValues::DoubleValued)
{
DoubleValuedConformingMult(x, y);
}
else
{
SingleValuedConformingMult(x, y);
}
}
void L2FaceRestriction::SingleValuedConformingAddMultTranspose(
const Vector& x, Vector& y) const
{
// Assumes all elements have the same number of dofs
const int nface_dofs = face_dofs;
const int vd = vdim;
const bool t = byvdim;
auto d_offsets = gather_offsets.Read();
auto d_indices = gather_indices.Read();
auto d_x = Reshape(x.Read(), nface_dofs, vd, nf);
auto d_y = Reshape(y.ReadWrite(), t?vd:ndofs, t?ndofs:vd);
mfem::forall(ndofs, [=] MFEM_HOST_DEVICE (int i)
{
const int offset = d_offsets[i];
const int next_offset = d_offsets[i + 1];
for (int c = 0; c < vd; ++c)
{
real_t dof_value = 0;
for (int j = offset; j < next_offset; ++j)
{
int idx_j = d_indices[j];
dof_value += d_x(idx_j % nface_dofs, c, idx_j / nface_dofs);
}
d_y(t?c:i,t?i:c) += dof_value;
}
});
}
void L2FaceRestriction::DoubleValuedConformingAddMultTranspose(
const Vector& x, Vector& y) const
{
// Assumes all elements have the same number of dofs
const int nface_dofs = face_dofs;
const int vd = vdim;
const bool t = byvdim;
const int dofs = nfdofs;
auto d_offsets = gather_offsets.Read();
auto d_indices = gather_indices.Read();
auto d_x = Reshape(x.Read(), nface_dofs, vd, 2, nf);
auto d_y = Reshape(y.ReadWrite(), t?vd:ndofs, t?ndofs:vd);
mfem::forall(ndofs, [=] MFEM_HOST_DEVICE (int i)
{
const int offset = d_offsets[i];
const int next_offset = d_offsets[i + 1];
for (int c = 0; c < vd; ++c)
{
real_t dof_value = 0;
for (int j = offset; j < next_offset; ++j)
{
int idx_j = d_indices[j];
bool isE1 = idx_j < dofs;
idx_j = isE1 ? idx_j : idx_j - dofs;
dof_value += isE1 ?
d_x(idx_j % nface_dofs, c, 0, idx_j / nface_dofs)
:d_x(idx_j % nface_dofs, c, 1, idx_j / nface_dofs);
}
d_y(t?c:i,t?i:c) += dof_value;
}
});
}
void L2FaceRestriction::AddMultTranspose(const Vector& x, Vector& y,
const real_t a) const
{
MFEM_VERIFY(a == 1.0, "General coefficient case is not yet supported!");
if (nf==0) { return; }
if (m == L2FaceValues::DoubleValued)
{
DoubleValuedConformingAddMultTranspose(x, y);
}
else
{
SingleValuedConformingAddMultTranspose(x, y);
}
}
void L2FaceRestriction::FillI(SparseMatrix &mat,
const bool keep_nbr_block) const
{
const int nface_dofs = face_dofs;
auto d_indices1 = scatter_indices1.Read();
auto d_indices2 = scatter_indices2.Read();
auto I = mat.ReadWriteI();
mfem::forall(nf*nface_dofs, [=] MFEM_HOST_DEVICE (int fdof)
{
const int iE1 = d_indices1[fdof];
const int iE2 = d_indices2[fdof];
AddNnz(iE1,I,nface_dofs);
AddNnz(iE2,I,nface_dofs);
});
}
void L2FaceRestriction::FillJAndData(const Vector &fea_data,
SparseMatrix &mat,
const bool keep_nbr_block) const
{
const int nface_dofs = face_dofs;
auto d_indices1 = scatter_indices1.Read();
auto d_indices2 = scatter_indices2.Read();
auto I = mat.ReadWriteI();
auto mat_fea = Reshape(fea_data.Read(), nface_dofs, nface_dofs, 2, nf);
auto J = mat.WriteJ();
auto Data = mat.WriteData();
mfem::forall(nf*nface_dofs, [=] MFEM_HOST_DEVICE (int fdof)
{
const int f = fdof/nface_dofs;
const int iF = fdof%nface_dofs;
const int iE1 = d_indices1[f*nface_dofs+iF];
const int iE2 = d_indices2[f*nface_dofs+iF];
const int offset1 = AddNnz(iE1,I,nface_dofs);
const int offset2 = AddNnz(iE2,I,nface_dofs);
for (int jF = 0; jF < nface_dofs; jF++)
{
const int jE1 = d_indices1[f*nface_dofs+jF];
const int jE2 = d_indices2[f*nface_dofs+jF];
J[offset2+jF] = jE1;
J[offset1+jF] = jE2;
Data[offset2+jF] = mat_fea(jF,iF,0,f);
Data[offset1+jF] = mat_fea(jF,iF,1,f);
}
});
}
void L2FaceRestriction::AddFaceMatricesToElementMatrices(const Vector &fea_data,
Vector &ea_data) const
{
const int nface_dofs = face_dofs;
const int nelem_dofs = elem_dofs;
const int NE = ne;
if (m==L2FaceValues::DoubleValued)
{
auto d_indices1 = scatter_indices1.Read();
auto d_indices2 = scatter_indices2.Read();
auto mat_fea = Reshape(fea_data.Read(), nface_dofs, nface_dofs, 2, nf);
auto mat_ea = Reshape(ea_data.ReadWrite(), nelem_dofs, nelem_dofs, ne);
mfem::forall(nf, [=] MFEM_HOST_DEVICE (int f)
{
const int e1 = d_indices1[f*nface_dofs]/nelem_dofs;
const int e2 = d_indices2[f*nface_dofs]/nelem_dofs;
for (int j = 0; j < nface_dofs; j++)
{
const int jB1 = d_indices1[f*nface_dofs+j]%nelem_dofs;
for (int i = 0; i < nface_dofs; i++)
{
const int iB1 = d_indices1[f*nface_dofs+i]%nelem_dofs;
AtomicAdd(mat_ea(iB1,jB1,e1), mat_fea(i,j,0,f));
}
}
if (e2 < NE)
{
for (int j = 0; j < nface_dofs; j++)
{
const int jB2 = d_indices2[f*nface_dofs+j]%nelem_dofs;
for (int i = 0; i < nface_dofs; i++)
{
const int iB2 = d_indices2[f*nface_dofs+i]%nelem_dofs;
AtomicAdd(mat_ea(iB2,jB2,e2), mat_fea(i,j,1,f));
}
}
}
});
}
else
{
auto d_indices = scatter_indices1.Read();
auto mat_fea = Reshape(fea_data.Read(), nface_dofs, nface_dofs, nf);
auto mat_ea = Reshape(ea_data.ReadWrite(), nelem_dofs, nelem_dofs, ne);
mfem::forall(nf, [=] MFEM_HOST_DEVICE (int f)
{
const int e = d_indices[f*nface_dofs]/nelem_dofs;
for (int j = 0; j < nface_dofs; j++)
{
const int jE = d_indices[f*nface_dofs+j]%nelem_dofs;
for (int i = 0; i < nface_dofs; i++)
{
const int iE = d_indices[f*nface_dofs+i]%nelem_dofs;
AtomicAdd(mat_ea(iE,jE,e), mat_fea(i,j,f));
}
}
});
}
}
void L2FaceRestriction::CheckFESpace()
{
#ifdef MFEM_USE_MPI
// If the underlying finite element space is parallel, ensure the face
// neighbor information is generated.
if (const ParFiniteElementSpace *pfes
= dynamic_cast<const ParFiniteElementSpace*>(&fes))
{
pfes->GetParMesh()->ExchangeFaceNbrData();
}
#endif
#ifdef MFEM_DEBUG
// If fespace == L2
const FiniteElement *fe0 = fes.GetTypicalFE();
const TensorBasisElement *tfe = dynamic_cast<const TensorBasisElement*>(fe0);
MFEM_VERIFY(tfe != NULL &&
(tfe->GetBasisType()==BasisType::GaussLobatto ||
tfe->GetBasisType()==BasisType::Positive),
"Only Gauss-Lobatto and Bernstein basis are supported in "
"L2FaceRestriction.");
if (nf==0) { return; }
const bool dof_reorder = (ordering == ElementDofOrdering::LEXICOGRAPHIC);
if (!dof_reorder)
{
MFEM_ABORT("Non-Tensor L2FaceRestriction not yet implemented.");
}
if (dof_reorder && nf > 0)
{
for (int f = 0; f < fes.GetNF(); ++f)
{
const FiniteElement *fe = fes.GetTypicalTraceElement();
const TensorBasisElement* el = dynamic_cast<const TensorBasisElement*>(fe);
if (el) { continue; }
MFEM_ABORT("Finite element not suitable for lexicographic ordering");
}
}
#endif
}
void L2FaceRestriction::ComputeScatterIndicesAndOffsets()
{
Mesh &mesh = *fes.GetMesh();
// Initialization of the offsets
for (int i = 0; i <= ndofs; ++i)
{
gather_offsets[i] = 0;
}
// Computation of scatter indices and offsets
int f_ind=0;
for (int f = 0; f < fes.GetNF(); ++f)
{
Mesh::FaceInformation face = mesh.GetFaceInformation(f);
MFEM_ASSERT(!face.IsShared(),
"Unexpected shared face in L2FaceRestriction.");
if ( face.IsOfFaceType(type) )
{
SetFaceDofsScatterIndices1(face,f_ind);
if ( m==L2FaceValues::DoubleValued )
{
if ( type==FaceType::Interior && face.IsInterior() )
{
PermuteAndSetFaceDofsScatterIndices2(face,f_ind);
}
else if ( type==FaceType::Boundary && face.IsBoundary() )
{
SetBoundaryDofsScatterIndices2(face,f_ind);
}
}
f_ind++;
}
}
MFEM_VERIFY(f_ind==nf, "Unexpected number of faces.");
// Summation of the offsets
for (int i = 1; i <= ndofs; ++i)
{
gather_offsets[i] += gather_offsets[i - 1];
}
}
void L2FaceRestriction::ComputeGatherIndices()
{
Mesh &mesh = *fes.GetMesh();
// Computation of gather_indices
int f_ind = 0;
for (int f = 0; f < fes.GetNF(); ++f)
{
Mesh::FaceInformation face = mesh.GetFaceInformation(f);
MFEM_ASSERT(!face.IsShared(),
"Unexpected shared face in L2FaceRestriction.");
if ( face.IsOfFaceType(type) )
{
SetFaceDofsGatherIndices1(face,f_ind);
if ( m==L2FaceValues::DoubleValued &&
type==FaceType::Interior &&
face.IsLocal())
{
PermuteAndSetFaceDofsGatherIndices2(face,f_ind);
}
f_ind++;
}
}
MFEM_VERIFY(f_ind==nf, "Unexpected number of faces.");
// Reset offsets to their correct value
for (int i = ndofs; i > 0; --i)
{
gather_offsets[i] = gather_offsets[i - 1];
}
gather_offsets[0] = 0;
}
void L2FaceRestriction::SetFaceDofsScatterIndices1(
const Mesh::FaceInformation &face,
const int face_index)
{
MFEM_ASSERT(!(face.IsNonconformingCoarse()),
"This method should not be used on nonconforming coarse faces.");
const Table& e2dTable = fes.GetElementToDofTable();
const int* elem_map = e2dTable.GetJ();
const int face_id1 = face.element[0].local_face_id;
const int elem_index = face.element[0].index;
fes.GetTypicalFE()->GetFaceMap(face_id1, face_map);
for (int face_dof_elem1 = 0; face_dof_elem1 < face_dofs; ++face_dof_elem1)
{
const int volume_dof_elem1 = face_map[face_dof_elem1];
const int global_dof_elem1 = elem_map[elem_index*elem_dofs + volume_dof_elem1];
const int restriction_dof_elem1 = face_dofs*face_index + face_dof_elem1;
scatter_indices1[restriction_dof_elem1] = global_dof_elem1;
++gather_offsets[global_dof_elem1 + 1];
}
}
void L2FaceRestriction::PermuteAndSetFaceDofsScatterIndices2(
const Mesh::FaceInformation &face,
const int face_index)
{
MFEM_ASSERT(face.IsLocal(),
"This method should only be used on local faces.");
const Table& e2dTable = fes.GetElementToDofTable();
const int* elem_map = e2dTable.GetJ();
const int elem_index = face.element[1].index;
const int face_id1 = face.element[0].local_face_id;
const int face_id2 = face.element[1].local_face_id;
const int orientation = face.element[1].orientation;
const int dim = fes.GetMesh()->Dimension();
const int dof1d = fes.GetTypicalFE()->GetOrder()+1;
fes.GetTypicalFE()->GetFaceMap(face_id2, face_map);
for (int face_dof_elem1 = 0; face_dof_elem1 < face_dofs; ++face_dof_elem1)
{
const int face_dof_elem2 = PermuteFaceL2(dim, face_id1, face_id2,
orientation, dof1d,
face_dof_elem1);
const int volume_dof_elem2 = face_map[face_dof_elem2];
const int global_dof_elem2 = elem_map[elem_index*elem_dofs + volume_dof_elem2];
const int restriction_dof_elem2 = face_dofs*face_index + face_dof_elem1;
scatter_indices2[restriction_dof_elem2] = global_dof_elem2;
++gather_offsets[global_dof_elem2 + 1];
}
}
void L2FaceRestriction::PermuteAndSetSharedFaceDofsScatterIndices2(
const Mesh::FaceInformation &face,
const int face_index)
{
#ifdef MFEM_USE_MPI
MFEM_ASSERT(face.IsShared(),
"This method should only be used on shared faces.");
const int elem_index = face.element[1].index;
const int face_id1 = face.element[0].local_face_id;
const int face_id2 = face.element[1].local_face_id;
const int orientation = face.element[1].orientation;
const int dim = fes.GetMesh()->Dimension();
const int dof1d = fes.GetTypicalFE()->GetOrder()+1;
fes.GetTypicalFE()->GetFaceMap(face_id2, face_map);
Array<int> face_nbr_dofs;
const ParFiniteElementSpace &pfes =
static_cast<const ParFiniteElementSpace&>(this->fes);
pfes.GetFaceNbrElementVDofs(elem_index, face_nbr_dofs);
for (int face_dof_elem1 = 0; face_dof_elem1 < face_dofs; ++face_dof_elem1)
{
const int face_dof_elem2 = PermuteFaceL2(dim, face_id1, face_id2,
orientation, dof1d, face_dof_elem1);
const int volume_dof_elem2 = face_map[face_dof_elem2];
const int global_dof_elem2 = face_nbr_dofs[volume_dof_elem2];
const int restriction_dof_elem2 = face_dofs*face_index + face_dof_elem1;
// Trick to differentiate dof location inter/shared
scatter_indices2[restriction_dof_elem2] = ndofs+global_dof_elem2;
}
#endif
}
void L2FaceRestriction::SetBoundaryDofsScatterIndices2(
const Mesh::FaceInformation &face,
const int face_index)
{
MFEM_ASSERT(face.IsBoundary(),
"This method should only be used on boundary faces.");
for (int d = 0; d < face_dofs; ++d)
{
const int restriction_dof_elem2 = face_dofs*face_index + d;
scatter_indices2[restriction_dof_elem2] = -1;
}
}
void L2FaceRestriction::SetFaceDofsGatherIndices1(
const Mesh::FaceInformation &face,
const int face_index)
{
MFEM_ASSERT(!(face.IsNonconformingCoarse()),
"This method should not be used on nonconforming coarse faces.");
const Table& e2dTable = fes.GetElementToDofTable();
const int* elem_map = e2dTable.GetJ();
const int face_id1 = face.element[0].local_face_id;
const int elem_index = face.element[0].index;
fes.GetTypicalFE()->GetFaceMap(face_id1, face_map);
for (int face_dof_elem1 = 0; face_dof_elem1 < face_dofs; ++face_dof_elem1)
{
const int volume_dof_elem1 = face_map[face_dof_elem1];
const int global_dof_elem1 = elem_map[elem_index*elem_dofs + volume_dof_elem1];
const int restriction_dof_elem1 = face_dofs*face_index + face_dof_elem1;
// We don't shift restriction_dof_elem1 to express that it's elem1 of the face
gather_indices[gather_offsets[global_dof_elem1]++] = restriction_dof_elem1;
}
}
void L2FaceRestriction::PermuteAndSetFaceDofsGatherIndices2(
const Mesh::FaceInformation &face,
const int face_index)
{
MFEM_ASSERT(face.IsLocal(),
"This method should only be used on local faces.");
const Table& e2dTable = fes.GetElementToDofTable();
const int* elem_map = e2dTable.GetJ();
const int elem_index = face.element[1].index;
const int face_id1 = face.element[0].local_face_id;
const int face_id2 = face.element[1].local_face_id;
const int orientation = face.element[1].orientation;
const int dim = fes.GetMesh()->Dimension();
const int dof1d = fes.GetTypicalFE()->GetOrder()+1;
fes.GetTypicalFE()->GetFaceMap(face_id2, face_map);
for (int face_dof_elem1 = 0; face_dof_elem1 < face_dofs; ++face_dof_elem1)
{
const int face_dof_elem2 = PermuteFaceL2(dim, face_id1, face_id2,
orientation, dof1d,
face_dof_elem1);
const int volume_dof_elem2 = face_map[face_dof_elem2];
const int global_dof_elem2 = elem_map[elem_index*elem_dofs + volume_dof_elem2];
const int restriction_dof_elem2 = face_dofs*face_index + face_dof_elem1;
// We shift restriction_dof_elem2 to express that it's elem2 of the face
gather_indices[gather_offsets[global_dof_elem2]++] = nfdofs +
restriction_dof_elem2;
}
}
void L2FaceRestriction::NormalDerivativeMult(const Vector &x, Vector &y) const
{
EnsureNormalDerivativeRestriction();
normal_deriv_restr->Mult(x, y);
}
void L2FaceRestriction::NormalDerivativeAddMultTranspose(const Vector &x,
Vector &y) const
{
EnsureNormalDerivativeRestriction();
normal_deriv_restr->AddMultTranspose(x, y);
}
void L2FaceRestriction::EnsureNormalDerivativeRestriction() const
{
if (!normal_deriv_restr)
{
normal_deriv_restr.reset(
new L2NormalDerivativeFaceRestriction(fes, ordering, type));
}
}
InterpolationManager::InterpolationManager(const FiniteElementSpace &fes_,
ElementDofOrdering ordering_,
FaceType type)
: fes(fes_),
ordering(ordering_),
interp_config(fes.GetNFbyType(type)),
nc_cpt(0)
{ }
void InterpolationManager::RegisterFaceConformingInterpolation(
const Mesh::FaceInformation &face,
int face_index)
{
interp_config[face_index] = InterpConfig();
}
void InterpolationManager::RegisterFaceCoarseToFineInterpolation(
const Mesh::FaceInformation &face,
int face_index)
{
MFEM_ASSERT(!face.IsConforming(),
"Registering face as nonconforming even though it is not.");
const DenseMatrix* ptMat = face.point_matrix;
// In the case of nonconforming slave shared face the master face is elem1.
const int master_side =
face.element[0].conformity == Mesh::ElementConformity::Superset ? 0 : 1;
const int face_key = (master_side == 0 ? 1000 : 0) +
face.element[0].local_face_id +
6*face.element[1].local_face_id +
36*face.element[1].orientation ;
// Unfortunately we can't trust uniqueness of the ptMat to identify the
// transformation.
Key key(ptMat, face_key);
auto itr = interp_map.find(key);
if ( itr == interp_map.end() )
{
const DenseMatrix* interpolator =
GetCoarseToFineInterpolation(face,ptMat);
interp_map[key] = {nc_cpt, interpolator};
interp_config[face_index] = {master_side, nc_cpt};
nc_cpt++;
}
else
{
interp_config[face_index] = {master_side, itr->second.first};
}
}
const DenseMatrix* InterpolationManager::GetCoarseToFineInterpolation(
const Mesh::FaceInformation &face,
const DenseMatrix* ptMat)
{
MFEM_VERIFY(ordering == ElementDofOrdering::LEXICOGRAPHIC,
"The following interpolation operator is only implemented for"
"lexicographic ordering.");
MFEM_VERIFY(!face.IsConforming(),
"This method should not be called on conforming faces.")
const int face_id1 = face.element[0].local_face_id;
const int face_id2 = face.element[1].local_face_id;
const bool is_ghost_slave =
face.element[0].conformity == Mesh::ElementConformity::Superset;
const int master_face_id = is_ghost_slave ? face_id1 : face_id2;
// Computation of the interpolation matrix from master
// (coarse) face to slave (fine) face.
// Assumes all trace elements are the same.
const FiniteElement *trace_fe = fes.GetTypicalTraceElement();
const int face_dofs = trace_fe->GetDof();
const TensorBasisElement* el =
dynamic_cast<const TensorBasisElement*>(trace_fe);
const auto dof_map = el->GetDofMap();
DenseMatrix* interpolator = new DenseMatrix(face_dofs,face_dofs);
Vector shape(face_dofs);
IsoparametricTransformation isotr;
isotr.SetIdentityTransformation(trace_fe->GetGeomType());
isotr.SetPointMat(*ptMat);
DenseMatrix native_interpolator(face_dofs,face_dofs);
trace_fe->GetLocalInterpolation(isotr, native_interpolator);
if (trace_fe->GetMapType() == FiniteElement::INTEGRAL)
{
// Handle potentially inverted Jacobian matrix
isotr.SetIntPoint(&Geometries.GetCenter(trace_fe->GetGeomType()));
native_interpolator *= (isotr.Weight() >= 0) ? 1.0 : -1.0;
}
const int dim = trace_fe->GetDim()+1;
const int dof1d = trace_fe->GetOrder()+1;
int orientation_i = face.element[1].orientation;
const int orientation_j = face.element[1].orientation;
// In 2D, need to flip orientation of the segments`
if (trace_fe->GetGeomType() == Geometry::SEGMENT && !is_ghost_slave)
{
orientation_i = 1;
}
for (int i = 0; i < face_dofs; i++)
{
const int ni = (dof_map.Size()==0) ? i : dof_map[i];
int li = ToLexOrdering(dim, master_face_id, dof1d, i);
if ( !is_ghost_slave )
{
// master side is elem 2, so we permute to order dofs as elem 1.
li = PermuteFaceL2(dim, face_id2, face_id1,
orientation_i, dof1d, li);
}
for (int j = 0; j < face_dofs; j++)
{
int lj = ToLexOrdering(dim, master_face_id, dof1d, j);
if ( !is_ghost_slave )
{
// master side is elem 2, so we permute to order dofs as elem 1.
lj = PermuteFaceL2(dim, face_id2, face_id1,
orientation_j, dof1d, lj);
}
const int nj = (dof_map.Size()==0) ? j : dof_map[j];
(*interpolator)(li,lj) = native_interpolator(ni,nj);
}
}
return interpolator;
}
void InterpolationManager::LinearizeInterpolatorMapIntoVector()
{
// Assumes all trace elements are the same.
const FiniteElement *trace_fe = fes.GetTypicalTraceElement();
const int face_dofs = trace_fe->GetDof();
const int nc_size = static_cast<int>(interp_map.size());
MFEM_VERIFY(nc_cpt==nc_size, "Unexpected number of interpolators.");
interpolators.SetSize(face_dofs*face_dofs*nc_size);
auto d_interp = Reshape(interpolators.HostWrite(),face_dofs,face_dofs,nc_size);
for (auto val : interp_map)
{
const int idx = val.second.first;
const DenseMatrix &interpolator = *val.second.second;
for (int i = 0; i < face_dofs; i++)
{
for (int j = 0; j < face_dofs; j++)
{
d_interp(i,j,idx) = interpolator(i,j);
}
}
delete val.second.second;
}
interp_map.clear();
}
void InterpolationManager::InitializeNCInterpConfig()
{
// Count nonconforming faces
int num_nc_faces = 0;
for (int i = 0; i < interp_config.Size(); i++)
{
if ( interp_config[i].is_non_conforming )
{
num_nc_faces++;
}
}
// Set nc_interp_config
nc_interp_config.SetSize(num_nc_faces);
int nc_index = 0;
for (int i = 0; i < interp_config.Size(); i++)
{
auto & config = interp_config[i];
if ( config.is_non_conforming )
{
nc_interp_config[nc_index] = NCInterpConfig(i, config);
nc_index++;
}
}
}
NCL2FaceRestriction::NCL2FaceRestriction(const FiniteElementSpace &fes,
const ElementDofOrdering f_ordering,
const FaceType type,
const L2FaceValues m,
bool build)
: L2FaceRestriction(fes, f_ordering, type, m, false),
interpolations(fes.GetInterpolationManager(ordering, type))
{
if (!build) { return; }
x_interp.UseDevice(true);
CheckFESpace();
ComputeScatterIndicesAndOffsets();
ComputeGatherIndices();
}
NCL2FaceRestriction::NCL2FaceRestriction(const FiniteElementSpace &fes,
const ElementDofOrdering f_ordering,
const FaceType type,
const L2FaceValues m)
: NCL2FaceRestriction(fes, f_ordering, type, m, true)
{ }
void NCL2FaceRestriction::DoubleValuedNonconformingMult(
const Vector& x, Vector& y) const
{
DoubleValuedConformingMult(x, y);
DoubleValuedNonconformingInterpolation(y);
}
void NCL2FaceRestriction::DoubleValuedNonconformingInterpolation(
Vector& y) const
{
if (nf == 0) { return; }
// Assumes all elements have the same number of dofs
const int nface_dofs = face_dofs;
const int vd = vdim;
auto d_y = Reshape(y.ReadWrite(), nface_dofs, vd, 2, nf);
auto &nc_interp_config = interpolations.GetNCFaceInterpConfig();
const int num_nc_faces = nc_interp_config.Size();
if ( num_nc_faces == 0 ) { return; }
auto interp_config_ptr = nc_interp_config.Read();
const int nc_size = interpolations.GetNumInterpolators();
auto d_interp = Reshape(interpolations.GetInterpolators().Read(),
nface_dofs, nface_dofs, nc_size);
static constexpr int max_nd = 16*16;
MFEM_VERIFY(nface_dofs<=max_nd, "Too many degrees of freedom.");
mfem::forall_2D(num_nc_faces, nface_dofs, 1, [=] MFEM_HOST_DEVICE (int nc_face)
{
MFEM_SHARED real_t dof_values[max_nd];
const NCInterpConfig conf = interp_config_ptr[nc_face];
if ( conf.is_non_conforming )
{
const int master_side = conf.master_side;
const int interp_index = conf.index;
const int face = conf.face_index;
for (int c = 0; c < vd; ++c)
{
MFEM_FOREACH_THREAD(dof,x,nface_dofs)
{
dof_values[dof] = d_y(dof, c, master_side, face);
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(dof_out,x,nface_dofs)
{
real_t res = 0.0;
for (int dof_in = 0; dof_in<nface_dofs; dof_in++)
{
res += d_interp(dof_out, dof_in, interp_index)*dof_values[dof_in];
}
d_y(dof_out, c, master_side, face) = res;
}
MFEM_SYNC_THREAD;
}
}
});
}
void NCL2FaceRestriction::Mult(const Vector& x, Vector& y) const
{
if ( type==FaceType::Interior && m==L2FaceValues::DoubleValued )
{
DoubleValuedNonconformingMult(x, y);
}
else if ( type==FaceType::Boundary && m==L2FaceValues::DoubleValued )
{
DoubleValuedConformingMult(x, y);
}
else // Single valued (assumes no nonconforming master on elem1)
{
SingleValuedConformingMult(x, y);
}
}
void NCL2FaceRestriction::SingleValuedNonconformingTransposeInterpolation(
const Vector& x) const
{
MFEM_ASSERT(
m == L2FaceValues::SingleValued,
"This method should be called when m == L2FaceValues::SingleValued.");
if (x_interp.Size()==0)
{
x_interp.SetSize(x.Size());
}
x_interp = x;
SingleValuedNonconformingTransposeInterpolationInPlace(x_interp);
}
void NCL2FaceRestriction::SingleValuedNonconformingTransposeInterpolationInPlace(
Vector& x) const
{
// Assumes all elements have the same number of dofs
const int nface_dofs = face_dofs;
const int vd = vdim;
// Interpolation
auto d_x = Reshape(x_interp.ReadWrite(), nface_dofs, vd, nf);
auto &nc_interp_config = interpolations.GetNCFaceInterpConfig();
const int num_nc_faces = nc_interp_config.Size();
if ( num_nc_faces == 0 ) { return; }
auto interp_config_ptr = nc_interp_config.Read();
auto interpolators = interpolations.GetInterpolators().Read();
const int nc_size = interpolations.GetNumInterpolators();
auto d_interp = Reshape(interpolators, nface_dofs, nface_dofs, nc_size);
static constexpr int max_nd = 16*16;
MFEM_VERIFY(nface_dofs<=max_nd, "Too many degrees of freedom.");
mfem::forall_2D(num_nc_faces, nface_dofs, 1, [=] MFEM_HOST_DEVICE (int nc_face)
{
MFEM_SHARED real_t dof_values[max_nd];
const NCInterpConfig conf = interp_config_ptr[nc_face];
const int master_side = conf.master_side;
const int interp_index = conf.index;
const int face = conf.face_index;
if ( conf.is_non_conforming && master_side==0 )
{
// Interpolation from fine to coarse
for (int c = 0; c < vd; ++c)
{
MFEM_FOREACH_THREAD(dof,x,nface_dofs)
{
dof_values[dof] = d_x(dof, c, face);
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(dof_out,x,nface_dofs)
{
real_t res = 0.0;
for (int dof_in = 0; dof_in<nface_dofs; dof_in++)
{
res += d_interp(dof_in, dof_out, interp_index)*dof_values[dof_in];
}
d_x(dof_out, c, face) = res;
}
MFEM_SYNC_THREAD;
}
}
});
}
void NCL2FaceRestriction::DoubleValuedNonconformingTransposeInterpolation(
const Vector& x) const
{
MFEM_ASSERT(
m == L2FaceValues::DoubleValued,
"This method should be called when m == L2FaceValues::DoubleValued.");
if (x_interp.Size()==0)
{
x_interp.SetSize(x.Size());
}
x_interp = x;
DoubleValuedNonconformingTransposeInterpolationInPlace(x_interp);
}
void NCL2FaceRestriction::DoubleValuedNonconformingTransposeInterpolationInPlace(
Vector& x) const
{
// Assumes all elements have the same number of dofs
const int nface_dofs = face_dofs;
const int vd = vdim;
// Interpolation
auto d_x = Reshape(x.ReadWrite(), nface_dofs, vd, 2, nf);
auto &nc_interp_config = interpolations.GetNCFaceInterpConfig();
const int num_nc_faces = nc_interp_config.Size();
if ( num_nc_faces == 0 ) { return; }
auto interp_config_ptr = nc_interp_config.Read();
auto interpolators = interpolations.GetInterpolators().Read();
const int nc_size = interpolations.GetNumInterpolators();
auto d_interp = Reshape(interpolators, nface_dofs, nface_dofs, nc_size);
static constexpr int max_nd = 16*16;
MFEM_VERIFY(nface_dofs<=max_nd, "Too many degrees of freedom.");
mfem::forall_2D(num_nc_faces, nface_dofs, 1, [=] MFEM_HOST_DEVICE (int nc_face)
{
MFEM_SHARED real_t dof_values[max_nd];
const NCInterpConfig conf = interp_config_ptr[nc_face];
const int master_side = conf.master_side;
const int interp_index = conf.index;
const int face = conf.face_index;
if ( conf.is_non_conforming )
{
// Interpolation from fine to coarse
for (int c = 0; c < vd; ++c)
{
MFEM_FOREACH_THREAD(dof,x,nface_dofs)
{
dof_values[dof] = d_x(dof, c, master_side, face);
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(dof_out,x,nface_dofs)
{
real_t res = 0.0;
for (int dof_in = 0; dof_in<nface_dofs; dof_in++)
{
res += d_interp(dof_in, dof_out, interp_index)*dof_values[dof_in];
}
d_x(dof_out, c, master_side, face) = res;
}
MFEM_SYNC_THREAD;
}
}
});
}
void NCL2FaceRestriction::AddMultTranspose(const Vector& x, Vector& y,
const real_t a) const
{
MFEM_VERIFY(a == 1.0, "General coefficient case is not yet supported!");
if (nf==0) { return; }
if (type==FaceType::Interior)
{
if ( m==L2FaceValues::DoubleValued )
{
DoubleValuedNonconformingTransposeInterpolation(x);
DoubleValuedConformingAddMultTranspose(x_interp, y);
}
else if ( m==L2FaceValues::SingleValued )
{
SingleValuedNonconformingTransposeInterpolation(x);
SingleValuedConformingAddMultTranspose(x_interp, y);
}
}
else
{
if ( m==L2FaceValues::DoubleValued )
{
DoubleValuedConformingAddMultTranspose(x, y);
}
else if ( m==L2FaceValues::SingleValued )
{
SingleValuedConformingAddMultTranspose(x, y);
}
}
}
void NCL2FaceRestriction::AddMultTransposeInPlace(Vector& x, Vector& y) const
{
if (nf==0) { return; }
if (type==FaceType::Interior)
{
if ( m==L2FaceValues::DoubleValued )
{
DoubleValuedNonconformingTransposeInterpolationInPlace(x);
DoubleValuedConformingAddMultTranspose(x, y);
}
else if ( m==L2FaceValues::SingleValued )
{
SingleValuedNonconformingTransposeInterpolationInPlace(x);
SingleValuedConformingAddMultTranspose(x, y);
}
}
else
{
if ( m==L2FaceValues::DoubleValued )
{
DoubleValuedConformingAddMultTranspose(x, y);
}
else if ( m==L2FaceValues::SingleValued )
{
SingleValuedConformingAddMultTranspose(x, y);
}
}
}
void NCL2FaceRestriction::FillI(SparseMatrix &mat,
const bool keep_nbr_block) const
{
const int nface_dofs = face_dofs;
auto d_indices1 = scatter_indices1.Read();
auto d_indices2 = scatter_indices2.Read();
auto I = mat.ReadWriteI();
mfem::forall(nf*nface_dofs, [=] MFEM_HOST_DEVICE (int fdof)
{
const int iE1 = d_indices1[fdof];
const int iE2 = d_indices2[fdof];
AddNnz(iE1,I,nface_dofs);
AddNnz(iE2,I,nface_dofs);
});
}
void NCL2FaceRestriction::FillJAndData(const Vector &fea_data,
SparseMatrix &mat,
const bool keep_nbr_block) const
{
const int nface_dofs = face_dofs;
auto d_indices1 = scatter_indices1.Read();
auto d_indices2 = scatter_indices2.Read();
auto I = mat.ReadWriteI();
auto mat_fea = Reshape(fea_data.Read(), nface_dofs, nface_dofs, 2, nf);
auto J = mat.WriteJ();
auto Data = mat.WriteData();
auto interp_config_ptr = interpolations.GetFaceInterpConfig().Read();
auto interpolators = interpolations.GetInterpolators().Read();
const int nc_size = interpolations.GetNumInterpolators();
auto d_interp = Reshape(interpolators, nface_dofs, nface_dofs, nc_size);
mfem::forall(nf*nface_dofs, [=] MFEM_HOST_DEVICE (int fdof)
{
const int f = fdof/nface_dofs;
const InterpConfig conf = interp_config_ptr[f];
const int master_side = conf.master_side;
const int interp_index = conf.index;
const int iF = fdof%nface_dofs;
const int iE1 = d_indices1[f*nface_dofs+iF];
const int iE2 = d_indices2[f*nface_dofs+iF];
const int offset1 = AddNnz(iE1,I,nface_dofs);
const int offset2 = AddNnz(iE2,I,nface_dofs);
for (int jF = 0; jF < nface_dofs; jF++)
{
const int jE1 = d_indices1[f*nface_dofs+jF];
const int jE2 = d_indices2[f*nface_dofs+jF];
J[offset2+jF] = jE1;
J[offset1+jF] = jE2;
real_t val1 = 0.0;
real_t val2 = 0.0;
if ( conf.is_non_conforming && master_side==0 )
{
for (int kF = 0; kF < nface_dofs; kF++)
{
val1 += mat_fea(kF,iF,0,f) * d_interp(kF, jF, interp_index);
val2 += d_interp(kF, iF, interp_index) * mat_fea(jF,kF,1,f);
}
}
else if ( conf.is_non_conforming && master_side==1 )
{
for (int kF = 0; kF < nface_dofs; kF++)
{
val1 += d_interp(kF, iF, interp_index) * mat_fea(jF,kF,0,f);
val2 += mat_fea(kF,iF,1,f) * d_interp(kF, jF, interp_index);
}
}
else
{
val1 = mat_fea(jF,iF,0,f);
val2 = mat_fea(jF,iF,1,f);
}
Data[offset2+jF] = val1;
Data[offset1+jF] = val2;
}
});
}
void NCL2FaceRestriction::AddFaceMatricesToElementMatrices(
const Vector &fea_data,
Vector &ea_data)
const
{
const int nface_dofs = face_dofs;
const int nelem_dofs = elem_dofs;
const int NE = ne;
if (m==L2FaceValues::DoubleValued)
{
auto d_indices1 = scatter_indices1.Read();
auto d_indices2 = scatter_indices2.Read();
auto mat_fea = Reshape(fea_data.Read(), nface_dofs, nface_dofs, 2, nf);
auto mat_ea = Reshape(ea_data.ReadWrite(), nelem_dofs, nelem_dofs, ne);
auto interp_config_ptr = interpolations.GetFaceInterpConfig().Read();
auto interpolators = interpolations.GetInterpolators().Read();
const int nc_size = interpolations.GetNumInterpolators();
auto d_interp = Reshape(interpolators, nface_dofs, nface_dofs, nc_size);
mfem::forall(nf, [=] MFEM_HOST_DEVICE (int f)
{
const InterpConfig conf = interp_config_ptr[f];
const int master_side = conf.master_side;
const int interp_index = conf.index;
const int e1 = d_indices1[f*nface_dofs]/nelem_dofs;
const int e2 = d_indices2[f*nface_dofs]/nelem_dofs;
for (int j = 0; j < nface_dofs; j++)
{
const int jB1 = d_indices1[f*nface_dofs+j]%nelem_dofs;
for (int i = 0; i < nface_dofs; i++)
{
const int iB1 = d_indices1[f*nface_dofs+i]%nelem_dofs;
real_t val = 0.0;
if ( conf.is_non_conforming && master_side==0 )
{
for (int k = 0; k < nface_dofs; k++)
{
for (int l = 0; l < nface_dofs; l++)
{
val += d_interp(l, j, interp_index)
* mat_fea(k,l,0,f)
* d_interp(k, i, interp_index);
}
}
}
else
{
val = mat_fea(i,j,0,f);
}
AtomicAdd(mat_ea(iB1,jB1,e1), val);
}
}
if (e2 < NE)
{
for (int j = 0; j < nface_dofs; j++)
{
const int jB2 = d_indices2[f*nface_dofs+j]%nelem_dofs;
for (int i = 0; i < nface_dofs; i++)
{
const int iB2 = d_indices2[f*nface_dofs+i]%nelem_dofs;
real_t val = 0.0;
if ( conf.is_non_conforming && master_side==1 )
{
for (int k = 0; k < nface_dofs; k++)
{
for (int l = 0; l < nface_dofs; l++)
{
val += d_interp(l, j, interp_index)
* mat_fea(k,l,1,f)
* d_interp(k, i, interp_index);
}
}
}
else
{
val = mat_fea(i,j,1,f);
}
AtomicAdd(mat_ea(iB2,jB2,e2), val);
}
}
}
});
}
else
{
auto d_indices = scatter_indices1.Read();
auto mat_fea = Reshape(fea_data.Read(), nface_dofs, nface_dofs, nf);
auto mat_ea = Reshape(ea_data.ReadWrite(), nelem_dofs, nelem_dofs, ne);
auto interp_config_ptr = interpolations.GetFaceInterpConfig().Read();
auto interpolators = interpolations.GetInterpolators().Read();
const int nc_size = interpolations.GetNumInterpolators();
auto d_interp = Reshape(interpolators, nface_dofs, nface_dofs, nc_size);
mfem::forall(nf, [=] MFEM_HOST_DEVICE (int f)
{
const InterpConfig conf = interp_config_ptr[f];
const int master_side = conf.master_side;
const int interp_index = conf.index;
const int e = d_indices[f*nface_dofs]/nelem_dofs;
for (int j = 0; j < nface_dofs; j++)
{
const int jE = d_indices[f*nface_dofs+j]%nelem_dofs;
for (int i = 0; i < nface_dofs; i++)
{
const int iE = d_indices[f*nface_dofs+i]%nelem_dofs;
real_t val = 0.0;
if ( conf.is_non_conforming && master_side==0 )
{
for (int k = 0; k < nface_dofs; k++)
{
for (int l = 0; l < nface_dofs; l++)
{
val += d_interp(l, j, interp_index)
* mat_fea(k,l,f)
* d_interp(k, i, interp_index);
}
}
}
else
{
val = mat_fea(i,j,f);
}
AtomicAdd(mat_ea(iE,jE,e), val);
}
}
});
}
}
int ToLexOrdering(const int dim, const int face_id, const int size1d,
const int index)
{
switch (dim)
{
case 1:
return 0;
case 2:
return internal::ToLexOrdering2D(face_id, size1d, index);
case 3:
return internal::ToLexOrdering3D(face_id, size1d, index%size1d, index/size1d);
default:
MFEM_ABORT("Unsupported dimension.");
return 0;
}
}
void NCL2FaceRestriction::ComputeScatterIndicesAndOffsets()
{
Mesh &mesh = *fes.GetMesh();
// Initialization of the offsets
for (int i = 0; i <= ndofs; ++i)
{
gather_offsets[i] = 0;
}
// Computation of scatter and offsets indices
int f_ind=0;
for (int f = 0; f < fes.GetNF(); ++f)
{
Mesh::FaceInformation face = mesh.GetFaceInformation(f);
if ( face.IsNonconformingCoarse() )
{
// We skip nonconforming coarse faces as they are treated
// by the corresponding nonconforming fine faces.
continue;
}
else if ( type==FaceType::Interior && face.IsInterior() )
{
SetFaceDofsScatterIndices1(face,f_ind);
if ( m==L2FaceValues::DoubleValued )
{
PermuteAndSetFaceDofsScatterIndices2(face,f_ind);
}
f_ind++;
}
else if ( type==FaceType::Boundary && face.IsBoundary() )
{
SetFaceDofsScatterIndices1(face,f_ind);
if ( m==L2FaceValues::DoubleValued )
{
SetBoundaryDofsScatterIndices2(face,f_ind);
}
f_ind++;
}
}
MFEM_VERIFY(f_ind==nf, "Unexpected number of " <<
(type==FaceType::Interior? "interior" : "boundary") <<
" faces: " << f_ind << " vs " << nf );
// Summation of the offsets
for (int i = 1; i <= ndofs; ++i)
{
gather_offsets[i] += gather_offsets[i - 1];
}
}
void NCL2FaceRestriction::ComputeGatherIndices()
{
Mesh &mesh = *fes.GetMesh();
// Computation of gather_indices
int f_ind = 0;
for (int f = 0; f < fes.GetNF(); ++f)
{
Mesh::FaceInformation face = mesh.GetFaceInformation(f);
MFEM_ASSERT(!face.IsShared(),
"Unexpected shared face in NCL2FaceRestriction.");
if ( face.IsNonconformingCoarse() )
{
// We skip nonconforming coarse faces as they are treated
// by the corresponding nonconforming fine faces.
continue;
}
else if ( face.IsOfFaceType(type) )
{
SetFaceDofsGatherIndices1(face,f_ind);
if ( m==L2FaceValues::DoubleValued &&
type==FaceType::Interior &&
face.IsInterior() )
{
PermuteAndSetFaceDofsGatherIndices2(face,f_ind);
}
f_ind++;
}
}
MFEM_VERIFY(f_ind==nf, "Unexpected number of " <<
(type==FaceType::Interior? "interior" : "boundary") <<
" faces: " << f_ind << " vs " << nf );
// Switch back offsets to their correct value
for (int i = ndofs; i > 0; --i)
{
gather_offsets[i] = gather_offsets[i - 1];
}
gather_offsets[0] = 0;
}
static int GetSharedVSize(const FiniteElementSpace &fes)
{
#ifdef MFEM_USE_MPI
if (auto pfes = dynamic_cast<const ParFiniteElementSpace*>(&fes))
{
const_cast<ParFiniteElementSpace*>(pfes)->ExchangeFaceNbrData();
return pfes->GetFaceNbrVSize();
}
#endif
return 0;
}
L2InterfaceFaceRestriction::L2InterfaceFaceRestriction(
const FiniteElementSpace& fes_,
const ElementDofOrdering ordering_,
const FaceType type_)
: fes(fes_),
ordering(ordering_),
type(type_),
nfaces(fes.GetNFbyType(type)),
vdim(fes.GetVDim()),
byvdim(fes.GetOrdering() == Ordering::byVDIM),
face_dofs(fes.GetTypicalTraceElement()->GetDof()),
nfdofs(face_dofs*nfaces),
ndofs(fes.GetNDofs()),
nsdofs(GetSharedVSize(fes))
{
height = nfdofs;
width = ndofs;
#ifdef MFEM_USE_MPI
auto pfes = dynamic_cast<const ParFiniteElementSpace*>(&fes);
#endif
const Table &face2dof = fes.GetFaceToDofTable();
const Mesh &mesh = *fes.GetMesh();
int face_idx = 0;
scatter_map.SetSize(nfdofs);
gather_map.SetSize(ndofs + nsdofs);
gather_map = -1;
Array<int> dofs;
for (int f = 0; f < mesh.GetNumFacesWithGhost(); ++f)
{
Mesh::FaceInformation face = mesh.GetFaceInformation(f);
if (!face.IsOfFaceType(type) || face.IsNonconformingCoarse()) { continue; }
if (f < mesh.GetNumFaces())
{
// Local face
face2dof.GetRow(f, dofs);
for (int i = 0; i < face_dofs; ++i)
{
scatter_map[i + face_idx*face_dofs] = dofs[i];
gather_map[dofs[i]] = i + face_idx*face_dofs;
}
}
else
{
// Shared (non-conforming) ghost face
#ifdef MFEM_USE_MPI
MFEM_ASSERT(pfes != nullptr, "");
pfes->GetFaceNbrFaceVDofs(f, dofs);
for (int i = 0; i < face_dofs; ++i)
{
scatter_map[i + face_idx*face_dofs] = ndofs + dofs[i];
gather_map[ndofs + dofs[i]] = i + face_idx*face_dofs;
}
#endif
}
++face_idx;
}
}
void L2InterfaceFaceRestriction::Mult(const Vector &x, Vector &y) const
{
const int NDOFS = ndofs;
const int nd = face_dofs;
const int nf = nfaces;
const int vd = vdim;
const bool t = byvdim;
const int *map = scatter_map.Read();
Vector face_nbr_data = GetLVectorFaceNbrData(fes, x, type);
MFEM_ASSERT(face_nbr_data.Size() / vd == nsdofs, "");
const auto d_x = Reshape(x.Read(), t?vd:ndofs, t?ndofs:vd);
const auto d_x_shared = Reshape(face_nbr_data.Read(),
t?vd:nsdofs, t?nsdofs:vd);
auto d_y = Reshape(y.Write(), nd, vd, nf);
mfem::forall(nd*nf, [=] MFEM_HOST_DEVICE (int i)
{
const int j = map[i];
for (int c = 0; c < vd; ++c)
{
if (j < NDOFS) { d_y(i % nd, c, i / nd) = d_x(t?c:j, t?j:c); }
else { d_y(i % nd, c, i / nd) = d_x_shared(t?c:(j-NDOFS), t?(j-NDOFS):c); }
}
});
}
void L2InterfaceFaceRestriction::AddMultTranspose(
const Vector &x, Vector &y, const real_t a) const
{
const int nd = face_dofs;
const int nf = nfaces;
const int vd = vdim;
const bool t = byvdim;
const int *map = gather_map.Read();
const auto d_x = Reshape(x.Read(), nd, vd, nf);
auto d_y = Reshape(y.ReadWrite(), t?vd:ndofs, t?ndofs:vd);
mfem::forall(ndofs, [=] MFEM_HOST_DEVICE (int i)
{
const int j = map[i];
if (j < 0) { return; }
for (int c = 0; c < vd; ++c)
{
d_y(t?c:i, t?i:c) += a*d_x(j % nd, c, j / nd);
}
});
}
void L2InterfaceFaceRestriction::MultTransposeShared(
const Vector &x, Vector &y) const
{
const int nd = face_dofs;
const int nf = nfaces;
const int vd = vdim;
const bool t = byvdim;
const int *map = gather_map.Read();
const auto d_x = Reshape(x.Read(), nd, vd, nf);
auto d_y = Reshape(y.Write(), t?vd:(ndofs+nsdofs), t?(ndofs+nsdofs):vd);
y = 0.0;
mfem::forall(ndofs + nsdofs, [=] MFEM_HOST_DEVICE (int i)
{
const int j = map[i];
if (j < 0) { return; }
for (int c = 0; c < vd; ++c)
{
d_y(t?c:i, t?i:c) = d_x(j % nd, c, j / nd);
}
});
}
const Array<int> &L2InterfaceFaceRestriction::GatherMap() const
{
return gather_map;
}
const Array<int> &L2InterfaceFaceRestriction::ScatterMap() const
{
return scatter_map;
}
Vector GetLVectorFaceNbrData(
const FiniteElementSpace &fes, const Vector &x, FaceType ftype)
{
#ifdef MFEM_USE_MPI
if (ftype == FaceType::Interior)
{
if (auto *pfes = const_cast<ParFiniteElementSpace*>
(dynamic_cast<const ParFiniteElementSpace*>(&fes)))
{
if (auto *x_gf = const_cast<ParGridFunction*>
(dynamic_cast<const ParGridFunction*>(&x)))
{
Vector &gf_face_nbr = x_gf->FaceNbrData();
if (gf_face_nbr.Size() == 0) { x_gf->ExchangeFaceNbrData(); }
gf_face_nbr.Read();
return Vector(gf_face_nbr, 0, gf_face_nbr.Size());
}
else
{
ParGridFunction gf(pfes, const_cast<Vector&>(x));
gf.ExchangeFaceNbrData();
x.SyncMemory(gf);
return std::move(gf.FaceNbrData());
}
}
}
#endif
return Vector();
}
} // namespace mfem