Files
mfem/fem/normal_deriv_restriction.cpp
T

829 lines
23 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 "normal_deriv_restriction.hpp"
#include "fespace.hpp"
#include "pgridfunc.hpp"
#include "fe/face_map_utils.hpp"
#include "../general/forall.hpp"
namespace mfem
{
/// Compute the face index to volume index map "face_to_vol" in 2D
static void NormalDerivativeSetupFaceIndexMap2D(
int nf, int d, const Array<int>& face_to_elem, Array<int>& face_to_vol)
{
const auto f2e = Reshape(face_to_elem.HostRead(), 2, 2, nf);
auto f2v = Reshape(face_to_vol.HostWrite(), d, 2, nf);
for (int f = 0; f < nf; ++f)
{
const int fid0 = f2e(0, 1, f);
const int fid1 = f2e(1, 1, f);
for (int side = 0; side < 2; ++side)
{
const int el = f2e(side, 0, f);
if (el < 0)
{
for (int p = 0; p < d; ++p)
{
f2v(p, side, f) = -1;
}
}
else
{
for (int p = 0; p < d; ++p)
{
int i, j;
internal::FaceIdxToVolIdx2D(p, d, fid0, fid1, side, i, j);
f2v(p, side, f) = i + d * j;
}
}
}
}
}
/// Compute the face index to volume index map "face_to_vol" in 3D
static void NormalDerivativeSetupFaceIndexMap3D(
int nf, int d, const Array<int>& face_to_elem, Array<int>& face_to_vol)
{
const auto f2e = Reshape(face_to_elem.HostRead(), 2, 3, nf);
auto f2v = Reshape(face_to_vol.HostWrite(), d*d, 2, nf);
for (int f = 0; f < nf; ++f)
{
const int fid0 = f2e(0, 1, f);
const int fid1 = f2e(1, 1, f);
for (int side = 0; side < 2; ++side)
{
const int el = f2e(side, 0, f);
const int orientation = f2e(side, 2, f);
if (el < 0)
{
for (int p = 0; p < d*d; ++p)
{
f2v(p, side, f) = -1;
}
}
else
{
for (int p = 0; p < d*d; ++p)
{
int i, j, k; // 3D lexicographic index of quad point p
internal::FaceIdxToVolIdx3D(p, d, fid0, fid1, side, orientation, i, j, k);
f2v(p, side, f) = i + d * (j + d * k);
}
}
}
}
}
L2NormalDerivativeFaceRestriction::L2NormalDerivativeFaceRestriction(
const FiniteElementSpace &fes_,
const ElementDofOrdering f_ordering,
const FaceType face_type_)
: fes(fes_),
face_type(face_type_),
dim(fes.GetMesh()->Dimension()),
nf(fes.GetNFbyType(face_type)),
ne(fes.GetNE())
{
MFEM_VERIFY(f_ordering == ElementDofOrdering::LEXICOGRAPHIC,
"Non-lexicographic ordering not currently supported in "
"L2NormalDerivativeFaceRestriction.");
Mesh &mesh = *fes.GetMesh();
const FiniteElement &fe = *fes.GetTypicalFE();
const int d = fe.GetDofToQuad(fe.GetNodes(), DofToQuad::TENSOR).ndof;
if (dim == 2)
{
// (el0, el1, fid0, fid1)
face_to_elem.SetSize(nf * 4);
face_to_vol.SetSize(2 * nf * d);
}
else if (dim == 3)
{
// (el0, el1, fid0, fid1, or0, or1)
face_to_elem.SetSize(nf * 6);
face_to_vol.SetSize(2 * nf * d * d);
}
else
{
MFEM_ABORT("Unsupported dimension.");
}
auto f2e = Reshape(face_to_elem.HostWrite(), 2, (dim == 2) ? 2 : 3, nf);
// Populate the face_to_elem array. The elem_indicator will be used to count
// the number of elements that are adjacent to faces of the given type.
Array<int> elem_indicator(ne);
elem_indicator = 0;
int f_ind = 0;
for (int f = 0; f < fes.GetNF(); ++f)
{
Mesh::FaceInformation face = mesh.GetFaceInformation(f);
if (face.IsOfFaceType(face_type))
{
f2e(0, 0, f_ind) = face.element[0].index;
f2e(0, 1, f_ind) = face.element[0].local_face_id;
if (dim == 3)
{
f2e(0, 2, f_ind) = face.element[0].orientation;
}
elem_indicator[face.element[0].index] = 1;
if (face_type == FaceType::Interior)
{
const int el_idx_1 = face.element[1].index;
if (face.IsShared())
{
// Indicate shared face by index >= ne
f2e(1, 0, f_ind) = ne + el_idx_1;
}
else
{
// Face is not shared
f2e(1, 0, f_ind) = el_idx_1;
elem_indicator[el_idx_1] = 1;
}
f2e(1, 1, f_ind) = face.element[1].local_face_id;
if (dim == 3)
{
f2e(1, 2, f_ind) = face.element[1].orientation;
}
}
else
{
f2e(1, 0, f_ind) = -1;
f2e(1, 1, f_ind) = -1;
if (dim == 3)
{
f2e(1, 2, f_ind) = -1;
}
}
f_ind++;
}
}
// evaluate face to vol map
if (dim == 2)
{
NormalDerivativeSetupFaceIndexMap2D(nf, d, face_to_elem, face_to_vol);
}
else if (dim == 3)
{
NormalDerivativeSetupFaceIndexMap3D(nf, d, face_to_elem, face_to_vol);
}
// Number of elements adjacent to faces of face_type
ne_type = elem_indicator.Sum();
// In 2D: (el, f0,f1,f2,f3, s0,s1,s2,s3)
// In 3D: (el, f0,f1,f2,f3,f4,f5, s0,s1,s2,s3,s4,s5)
const int elem_data_sz = (dim == 2) ? 9 : 13;
elem_to_face.SetSize(elem_data_sz * ne_type);
elem_to_face = -1;
auto e2f = Reshape(elem_to_face.HostWrite(), elem_data_sz, ne_type);
elem_indicator.PartialSum();
const int nsides = (face_type == FaceType::Interior) ? 2 : 1;
const int side_begin = (dim == 2) ? 5 : 7;
for (int f = 0; f < nf; ++f)
{
for (int side = 0; side < nsides; ++side)
{
const int el = f2e(side, 0, f);
// Skip shared faces
if (el < ne)
{
const int face_id = f2e(side, 1, f);
const int e = elem_indicator[el] - 1;
e2f(0, e) = el;
e2f(1 + face_id, e) = f;
e2f(side_begin + face_id, e) = side;
}
}
}
}
void L2NormalDerivativeFaceRestriction::Mult(const Vector &x, Vector &y) const
{
if (nf == 0) { return; }
switch (dim)
{
case 2:
{
const int d1d = fes.GetElementOrder(0) + 1;
switch (d1d)
{
case 1: Mult2D<1>(x, y); break;
case 2: Mult2D<2>(x, y); break;
case 3: Mult2D<3>(x, y); break;
case 4: Mult2D<4>(x, y); break;
case 5: Mult2D<5>(x, y); break;
case 6: Mult2D<6>(x, y); break;
case 7: Mult2D<7>(x, y); break;
case 8: Mult2D<8>(x, y); break;
default: Mult2D(x, y); break;
}
}
break;
case 3:
{
const int d1d = fes.GetElementOrder(0) + 1;
switch (d1d)
{
case 1: Mult3D<1>(x, y); break;
case 2: Mult3D<2>(x, y); break;
case 3: Mult3D<3>(x, y); break;
case 4: Mult3D<4>(x, y); break;
case 5: Mult3D<5>(x, y); break;
case 6: Mult3D<6>(x, y); break;
case 7: Mult3D<7>(x, y); break;
case 8: Mult3D<8>(x, y); break;
default: Mult3D(x, y); break; // fallback
}
break;
}
default: MFEM_ABORT("Dimension not supported."); break;
}
}
void L2NormalDerivativeFaceRestriction::AddMultTranspose(
const Vector &x, Vector &y, const real_t a) const
{
if (nf == 0) { return; }
switch (dim)
{
case 2:
{
const int d1d = fes.GetElementOrder(0) + 1;
switch (d1d)
{
case 1: AddMultTranspose2D<1>(x, y, a); break;
case 2: AddMultTranspose2D<2>(x, y, a); break;
case 3: AddMultTranspose2D<3>(x, y, a); break;
case 4: AddMultTranspose2D<4>(x, y, a); break;
case 5: AddMultTranspose2D<5>(x, y, a); break;
case 6: AddMultTranspose2D<6>(x, y, a); break;
case 7: AddMultTranspose2D<7>(x, y, a); break;
case 8: AddMultTranspose2D<8>(x, y, a); break;
default: AddMultTranspose2D(x, y, a); break;
}
}
break;
case 3:
{
const int d1d = fes.GetElementOrder(0) + 1;
switch (d1d)
{
case 1: AddMultTranspose3D<1>(x, y, a); break;
case 2: AddMultTranspose3D<2>(x, y, a); break;
case 3: AddMultTranspose3D<3>(x, y, a); break;
case 4: AddMultTranspose3D<4>(x, y, a); break;
case 5: AddMultTranspose3D<5>(x, y, a); break;
case 6: AddMultTranspose3D<6>(x, y, a); break;
case 7: AddMultTranspose3D<7>(x, y, a); break;
case 8: AddMultTranspose3D<8>(x, y, a); break;
default: AddMultTranspose3D(x, y, a); break; // fallback
}
break;
}
default: MFEM_ABORT("Not yet implemented"); break;
}
}
template <int T_D1D>
void L2NormalDerivativeFaceRestriction::Mult2D(const Vector &x, Vector &y) const
{
const int vd = fes.GetVDim();
const bool t = fes.GetOrdering() == Ordering::byVDIM;
const int num_elem = ne;
const FiniteElement &fe = *fes.GetTypicalFE();
const DofToQuad &maps = fe.GetDofToQuad(fe.GetNodes(), DofToQuad::TENSOR);
const int q = maps.nqpt;
const int d = maps.ndof;
Vector face_nbr_data = GetLVectorFaceNbrData(fes, x, face_type);
const int ne_shared = face_nbr_data.Size() / d / d / vd;
MFEM_VERIFY(q == d, "");
MFEM_VERIFY(T_D1D == d || T_D1D == 0, "");
// derivative of 1D basis function
const auto G_ = Reshape(maps.G.Read(), q, d);
// (el0, el1, fid0, fid1)
const auto f2e = Reshape(face_to_elem.Read(), 2, 2, nf);
const auto f2v = Reshape(face_to_vol.Read(), q, 2, nf);
// if byvdim, d_x has shape (vdim, nddof, nddof, ne)
// otherwise, d_x has shape (nddof, nddof, ne, vdim)
const auto d_x = Reshape(x.Read(), t?vd:d, d, t?d:ne, t?ne:vd);
const auto d_x_shared = Reshape(face_nbr_data.Read(),
t?vd:d, d, t?d:ne_shared, t?ne_shared:vd);
auto d_y = Reshape(y.Write(), q, vd, 2, nf);
mfem::forall_2D(nf, 2, q, [=] MFEM_HOST_DEVICE (int f) -> void
{
constexpr int MD = (T_D1D) ? T_D1D : DofQuadLimits::MAX_D1D;
MFEM_SHARED real_t G_s[MD*MD];
DeviceMatrix G(G_s, q, d);
MFEM_SHARED int E[2];
MFEM_SHARED int FID[2];
MFEM_SHARED int F2V[2][MD];
if (MFEM_THREAD_ID(x) == 0)
{
MFEM_FOREACH_THREAD(j, y, d)
{
for (int i = 0; i < q; ++i)
{
G(i, j) = G_(i, j);
}
}
}
MFEM_FOREACH_THREAD(side, x, 2)
{
if (MFEM_THREAD_ID(y) == 0)
{
E[side] = f2e(side, 0, f);
FID[side] = f2e(side, 1, f);
}
MFEM_FOREACH_THREAD(j, y, d)
{
F2V[side][j] = f2v(j, side, f);
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(side, x, 2)
{
const int el = E[side];
const bool shared = (el >= num_elem);
const auto &d_x_e = shared ? d_x_shared : d_x;
const int el_idx = shared ? el - num_elem : el;
const int face_id = FID[side];
MFEM_FOREACH_THREAD(p, y, q)
{
if (el < 0)
{
for (int c = 0; c < vd; ++c)
{
d_y(p, c, side, f) = 0.0;
}
}
else
{
const int ij = F2V[side][p];
const int i = ij % q;
const int j = ij / q;
for (int c=0; c < vd; ++c)
{
real_t grad_n = 0;
for (int kk=0; kk < d; ++kk)
{
const int k = (face_id == 0 || face_id == 2) ? i : kk;
const int l = (face_id == 0 || face_id == 2) ? kk : j;
const real_t g = (face_id == 0 || face_id == 2) ? G(j,l) : G(i,k);
grad_n += g * d_x_e(t?c:k, t?k:l, t?l:el_idx, t?el_idx:c);
}
d_y(p, c, side, f) = grad_n;
}
}
}
}
});
}
template <int T_D1D>
void L2NormalDerivativeFaceRestriction::Mult3D(const Vector &x, Vector &y) const
{
const int vd = fes.GetVDim();
const bool t = fes.GetOrdering() == Ordering::byVDIM;
const int num_elem = ne;
const FiniteElement &fe = *fes.GetTypicalFE();
const DofToQuad &maps = fe.GetDofToQuad(fe.GetNodes(), DofToQuad::TENSOR);
const int q = maps.nqpt;
const int d = maps.ndof;
const int q2d = q * q;
Vector face_nbr_data = GetLVectorFaceNbrData(fes, x, face_type);
const int ne_shared = face_nbr_data.Size() / d / d / d / vd;
MFEM_VERIFY(q == d, "");
MFEM_VERIFY(T_D1D == d || T_D1D == 0, "");
const auto G_ = Reshape(maps.G.Read(), q, d);
// (el0, el1, fid0, fid1, or0, or1)
const auto f2e = Reshape(face_to_elem.Read(), 2, 3, nf);
const auto f2v = Reshape(face_to_vol.Read(), q2d, 2, nf);
// t ? (vdim, d, d, d, ne) : (d, d, d, ne, vdim)
const auto d_x = Reshape(x.Read(), t?vd:d, d, d, t?d:ne, t?ne:vd);
const auto d_x_shared = Reshape(face_nbr_data.Read(),
t?vd:d, d, d, t?d:ne_shared, t?ne_shared:vd);
auto d_y = Reshape(y.Write(), q2d, vd, 2, nf);
mfem::forall_2D(nf, q2d, 2, [=] MFEM_HOST_DEVICE (int f) -> void
{
static constexpr int MD = T_D1D ? T_D1D : DofQuadLimits::MAX_D1D;
MFEM_SHARED real_t G_s[MD*MD];
DeviceMatrix G(G_s, d, q);
MFEM_SHARED int E[2];
MFEM_SHARED int FID[2];
MFEM_SHARED int F2V[2][MD*MD];
// Load G matrix into shared memory
if (MFEM_THREAD_ID(y) == 0)
{
MFEM_FOREACH_THREAD(j, x, d*q)
{
const int p = j % q;
const int k = j / q;
G(k, p) = G_(p, k);
}
}
MFEM_FOREACH_THREAD(side, y, 2)
{
if (MFEM_THREAD_ID(x) == 0)
{
E[side] = f2e(side, 0, f);
FID[side] = f2e(side, 1, f);
}
MFEM_FOREACH_THREAD(j, x, q2d)
{
F2V[side][j] = f2v(j, side, f);
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(side, y, 2)
{
const int el = E[side];
const bool shared = (el >= num_elem);
const auto &d_x_e = shared ? d_x_shared : d_x;
const int el_idx = shared ? el - num_elem : el;
const int face_id = FID[side];
// Is this face parallel to the x-y plane in reference coordinates?
const bool xy_plane = (face_id == 0 || face_id == 5);
const bool xz_plane = (face_id == 1 || face_id == 3);
const bool yz_plane = (face_id == 2 || face_id == 4);
MFEM_FOREACH_THREAD(p, x, q2d)
{
if (el_idx < 0)
{
for (int c = 0; c < vd; ++c)
{
d_y(p, c, side, f) = 0.0;
}
}
else
{
const int ijk = F2V[side][p];
const int k = ijk / q2d;
const int i = ijk % q;
const int j = (ijk - q2d*k) / q;
// the fixed 1D index of the normal component of the face
// quadrature point
const int g_row = yz_plane ? i : xz_plane ? j : k;
for (int c = 0; c < vd; ++c)
{
real_t grad_n = 0.0;
for (int kk = 0; kk < d; ++kk)
{
// (l, m, n) 3D lexicographic index of interior points used
// in evaluating normal derivatives
const int l = yz_plane ? kk : i;
const int m = xz_plane ? kk : j;
const int n = xy_plane ? kk : k;
const real_t g = G(kk, g_row);
grad_n += g * d_x_e(t?c:l, t?l:m, t?m:n, t?n:el_idx, t?el_idx:c);
}
d_y(p, c, side, f) = grad_n;
}
}
}
}
});
}
template <int T_D1D>
void L2NormalDerivativeFaceRestriction::AddMultTranspose2D(
const Vector &y, Vector &x, const real_t a) const
{
const int vd = fes.GetVDim();
const bool t = fes.GetOrdering() == Ordering::byVDIM;
const FiniteElement &fe = *fes.GetTypicalFE();
const DofToQuad &maps = fe.GetDofToQuad(fe.GetNodes(), DofToQuad::TENSOR);
const int q = maps.nqpt;
const int d = maps.ndof;
// derivative of 1D basis function
auto G_ = Reshape(maps.G.Read(), q, d);
// entries of e2f: (el,f0,f1,f2,f3,s0,s1,s2,s3)
auto e2f = Reshape(elem_to_face.Read(), 9, ne_type);
auto f2v = Reshape(face_to_vol.Read(), d, 2, nf);
// if byvdim, d_x has shape (vdim, nddof, nddof, ne)
// otherwise, d_x has shape (nddof, nddof, ne, vdim)
auto d_x = Reshape(x.ReadWrite(), t?vd:d, d, t?d:ne, t?ne:vd);
auto d_y = Reshape(y.Read(), q, vd, 2, nf);
mfem::forall_2D(ne_type, d, d, [=] MFEM_HOST_DEVICE (int e)
{
constexpr int MD = T_D1D ? T_D1D : DofQuadLimits::MAX_D1D;
MFEM_SHARED real_t y_s[MD];
MFEM_SHARED int pp[MD];
MFEM_SHARED int jj;
if (MFEM_THREAD_ID(x) == 0 && MFEM_THREAD_ID(y) == 0) { jj = 0; }
MFEM_SHARED real_t BG[MD*MD];
DeviceMatrix G(BG, q, d);
MFEM_SHARED real_t x_s[MD*MD];
DeviceMatrix xx(x_s, d, d);
MFEM_SHARED int el; // global element index
MFEM_SHARED int faces[4];
MFEM_SHARED int sides[4];
MFEM_FOREACH_THREAD(i,x,d)
{
MFEM_FOREACH_THREAD(p,y,q)
{
G(p,i) = a * G_(p,i);
xx(p,i) = 0.0;
}
}
if (MFEM_THREAD_ID(y) == 0)
{
if (MFEM_THREAD_ID(x) == 0)
{
el = e2f(0, e);
}
MFEM_FOREACH_THREAD(i, x, 4)
{
faces[i] = e2f(1 + i, e);
sides[i] = e2f(5 + i, e);
}
}
MFEM_SYNC_THREAD;
for (int face_id=0; face_id < 4; ++face_id)
{
const int f = faces[face_id];
if (f < 0) { continue; }
const int side = sides[face_id];
if (MFEM_THREAD_ID(y) == 0)
{
MFEM_FOREACH_THREAD(p,x,d)
{
y_s[p] = d_y(p, 0, side, f);
const int ij = f2v(p, side, f);
const int i = ij % q;
const int j = ij / q;
pp[(face_id == 0 || face_id == 2) ? i : j] = p;
if (MFEM_THREAD_ID(x) == 0)
{
jj = (face_id == 0 || face_id == 2) ? j : i;
}
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(k,x,d)
{
MFEM_FOREACH_THREAD(l,y,d)
{
const int p = (face_id == 0 || face_id == 2) ? pp[k] : pp[l];
const int kk = (face_id == 0 || face_id == 2) ? l : k;
const real_t g = G(jj, kk);
xx(k,l) += g * y_s[p];
}
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(k,x,d)
{
MFEM_FOREACH_THREAD(l,y,d)
{
const int c = 0;
d_x(t?c:k, t?k:l, t?l:el, t?el:c) += xx(k,l);
}
}
});
}
template <int T_D1D>
void L2NormalDerivativeFaceRestriction::AddMultTranspose3D(
const Vector &y, Vector &x, const real_t a) const
{
const int vd = fes.GetVDim();
const bool t = fes.GetOrdering() == Ordering::byVDIM;
MFEM_VERIFY(vd == 1, "vdim > 1 not supported.");
const FiniteElement &fe = *fes.GetTypicalFE();
const DofToQuad &maps = fe.GetDofToQuad(fe.GetNodes(), DofToQuad::TENSOR);
const int q = maps.nqpt;
const int d = maps.ndof;
const int q2d = q * q;
MFEM_VERIFY(q == d, "");
MFEM_VERIFY(T_D1D == d || T_D1D == 0, "");
auto G_ = Reshape(maps.G.Read(), q, d);
// (el, f0,f1,f2,f3,f4,f5, s0,s1,s2,s3,s4,s5)
auto e2f = Reshape(elem_to_face.Read(), 13, ne_type);
auto f2v = Reshape(face_to_vol.Read(), q2d, 2, nf);
auto d_x = Reshape(x.ReadWrite(), t?vd:d, d, d, t?d:ne, t?ne:vd);
const auto d_y = Reshape(y.Read(), q2d, vd, 2, nf);
mfem::forall_2D(ne_type, q, q, [=] MFEM_HOST_DEVICE (int e) -> void
{
static constexpr int MD = T_D1D ? T_D1D : DofQuadLimits::MAX_D1D;
MFEM_SHARED int pp[MD][MD];
MFEM_SHARED real_t y_s[MD*MD];
MFEM_SHARED int jj;
if (MFEM_THREAD_ID(x) == 0 && MFEM_THREAD_ID(y) == 0) { jj = 0; }
MFEM_SHARED real_t xx_s[MD*MD*MD];
auto xx = Reshape(xx_s, d, d, d);
MFEM_SHARED real_t G_s[MD*MD];
DeviceMatrix G(G_s, q, d);
MFEM_SHARED int el;
MFEM_SHARED int faces[6];
MFEM_SHARED int sides[6];
// Load G into shared memory
MFEM_FOREACH_THREAD(j, x, d)
{
MFEM_FOREACH_THREAD(i, y, q)
{
G(i, j) = a * G_(i, j);
G(i, j) = a * G_(i, j);
G(i, j) = a * G_(i, j);
}
}
if (MFEM_THREAD_ID(y) == 0)
{
if (MFEM_THREAD_ID(x) == 0)
{
el = e2f(0, e); // global element index
}
MFEM_FOREACH_THREAD(i, x, 6)
{
faces[i] = e2f(1 + i, e);
sides[i] = e2f(7 + i, e);
}
}
MFEM_FOREACH_THREAD(k, x, d)
{
MFEM_FOREACH_THREAD(j, y, d)
{
for (int i = 0; i < d; ++i)
{
xx(i, j, k) = 0.0;
}
}
}
MFEM_SYNC_THREAD;
for (int face_id = 0; face_id < 6; ++face_id)
{
const int f = faces[face_id];
if (f < 0)
{
continue;
}
const int side = sides[face_id];
// is this face parallel to the x-y plane in reference coordinates?
const bool xy_plane = (face_id == 0 || face_id == 5);
const bool xz_plane = (face_id == 1 || face_id == 3);
MFEM_FOREACH_THREAD(p1, x, q)
{
MFEM_FOREACH_THREAD(p2, y, q)
{
const int p = p1 + q * p2;
y_s[p] = d_y(p, 0, side, f);
const int ijk = f2v(p, side, f);
const int k = ijk / q2d;
const int i = ijk % q;
const int j = (ijk - q2d*k) / q;
pp[(xy_plane || xz_plane) ? i : j][(xy_plane) ? j : k] = p;
if (MFEM_THREAD_ID(x) == 0 && MFEM_THREAD_ID(y) == 0)
{
jj = (xy_plane) ? k : (xz_plane) ? j : i;
}
}
}
MFEM_SYNC_THREAD;
MFEM_FOREACH_THREAD(n, x, d)
{
MFEM_FOREACH_THREAD(m, y, d)
{
for (int l = 0; l < d; ++l)
{
const int p = (xy_plane) ? pp[l][m] : (xz_plane) ? pp[l][n] : pp[m][n];
const int kk = (xy_plane) ? n : (xz_plane) ? m : l;
const real_t g = G(jj, kk);
xx(l, m, n) += g * y_s[p];
}
}
}
}
MFEM_SYNC_THREAD;
// map back to global array
MFEM_FOREACH_THREAD(n, x, d)
{
MFEM_FOREACH_THREAD(m, y, d)
{
for (int l = 0; l < d; ++l)
{
const int c = 0;
d_x(t?c:l, t?l:m, t?m:n, t?n:el, t?el:c) += xx(l, m, n);
}
}
}
});
}
} // namespace mfem