// Copyright (c) 2010-2020, 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 "gridfunc.hpp" #include "fespace.hpp" #include "../general/forall.hpp" namespace mfem { L2ElementRestriction::L2ElementRestriction(const FiniteElementSpace &fes) : ne(fes.GetNE()), vdim(fes.GetVDim()), byvdim(fes.GetOrdering() == Ordering::byVDIM), ndof(ne > 0 ? fes.GetFE(0)->GetDof() : 0), 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(i, ndofs, { 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); } }); } void L2ElementRestriction::MultTranspose(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(y.Write(), t?vd:ndofs, t?ndofs:vd); MFEM_FORALL(i, ndofs, { const int idx = i; const int dof = idx % nd; const int e = idx / nd; for (int c = 0; c < vd; ++c) { d_y(t?c:idx,t?idx:c) = d_x(dof, c, e); } }); } 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(ne > 0 ? fes.GetFE(0)->GetDof() : 0), nedofs(ne*dof), offsets(ndofs+1), indices(ne*dof), gatherMap(ne*dof) { // Assuming all finite elements are the same. 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); const TensorBasisElement* el = dynamic_cast(fe); if (el) { continue; } mfem_error("Finite element not suitable for lexicographic ordering"); } const FiniteElement *fe = fes.GetFE(0); const TensorBasisElement* el = dynamic_cast(fe); const Array &fe_dof_map = el->GetDofMap(); MFEM_VERIFY(fe_dof_map.Size() > 0, "invalid dof map"); dof_map = fe_dof_map.GetData(); } const Table& e2dTable = fes.GetElementToDofTable(); const int* elementMap = 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 = elementMap[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 = elementMap[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); gatherMap[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_gatherMap = gatherMap.Read(); MFEM_FORALL(i, dof*ne, { const int gid = d_gatherMap[i]; const bool plus = gid >= 0; const int j = plus ? gid : -1-gid; for (int c = 0; c < vd; ++c) { const double dofValue = d_x(t?c:j, t?j:c); d_y(i % nd, c, i / nd) = plus ? dofValue : -dofValue; } }); } void ElementRestriction::MultUnsigned(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_gatherMap = gatherMap.Read(); MFEM_FORALL(i, dof*ne, { const int gid = d_gatherMap[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); } }); } void ElementRestriction::MultTranspose(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(i, ndofs, { const int offset = d_offsets[i]; const int nextOffset = d_offsets[i + 1]; for (int c = 0; c < vd; ++c) { double dofValue = 0; for (int j = offset; j < nextOffset; ++j) { const int idx_j = (d_indices[j] >= 0) ? d_indices[j] : -1 - d_indices[j]; dofValue += (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) = dofValue; } }); } void ElementRestriction::MultTransposeUnsigned(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(i, ndofs, { const int offset = d_offsets[i]; const int nextOffset = d_offsets[i + 1]; for (int c = 0; c < vd; ++c) { double dofValue = 0; for (int j = offset; j < nextOffset; ++j) { const int idx_j = (d_indices[j] >= 0) ? d_indices[j] : -1 - d_indices[j]; dofValue += d_x(idx_j % nd, c, idx_j / nd); } d_y(t?c:i,t?i:c) = dofValue; } }); } 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 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 nextOffset = d_offsets[i+1]; for (int c = 0; c < vd; ++c) { for (int j = offset; j < nextOffset; ++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; } } } } } /// Return the face degrees of freedom returned in Lexicographic order. void GetFaceDofs(const int dim, const int face_id, const int dof1d, Array &faceMap) { switch (dim) { case 1: switch (face_id) { case 0: // WEST faceMap[0] = 0; break; case 1: // EAST faceMap[0] = dof1d-1; break; } break; case 2: switch (face_id) { case 0: // SOUTH for (int i = 0; i < dof1d; ++i) { faceMap[i] = i; } break; case 1: // EAST for (int i = 0; i < dof1d; ++i) { faceMap[i] = dof1d-1 + i*dof1d; } break; case 2: // NORTH for (int i = 0; i < dof1d; ++i) { faceMap[i] = (dof1d-1)*dof1d + i; } break; case 3: // WEST for (int i = 0; i < dof1d; ++i) { faceMap[i] = i*dof1d; } break; } break; case 3: switch (face_id) { case 0: // BOTTOM for (int i = 0; i < dof1d; ++i) { for (int j = 0; j < dof1d; ++j) { faceMap[i+j*dof1d] = i + j*dof1d; } } break; case 1: // SOUTH for (int i = 0; i < dof1d; ++i) { for (int j = 0; j < dof1d; ++j) { faceMap[i+j*dof1d] = i + j*dof1d*dof1d; } } break; case 2: // EAST for (int i = 0; i < dof1d; ++i) { for (int j = 0; j < dof1d; ++j) { faceMap[i+j*dof1d] = dof1d-1 + i*dof1d + j*dof1d*dof1d; } } break; case 3: // NORTH for (int i = 0; i < dof1d; ++i) { for (int j = 0; j < dof1d; ++j) { faceMap[i+j*dof1d] = (dof1d-1)*dof1d + i + j*dof1d*dof1d; } } break; case 4: // WEST for (int i = 0; i < dof1d; ++i) { for (int j = 0; j < dof1d; ++j) { faceMap[i+j*dof1d] = i*dof1d + j*dof1d*dof1d; } } break; case 5: // TOP for (int i = 0; i < dof1d; ++i) { for (int j = 0; j < dof1d; ++j) { faceMap[i+j*dof1d] = (dof1d-1)*dof1d*dof1d + i + j*dof1d; } } break; } break; } } H1FaceRestriction::H1FaceRestriction(const FiniteElementSpace &fes, const ElementDofOrdering e_ordering, const FaceType type) : fes(fes), nf(fes.GetNFbyType(type)), vdim(fes.GetVDim()), byvdim(fes.GetOrdering() == Ordering::byVDIM), ndofs(fes.GetNDofs()), dof(nf > 0 ? fes.GetFaceElement(0)->GetDof() : 0), nfdofs(nf*dof), scatter_indices(nf*dof), offsets(ndofs+1), gather_indices(nf*dof) { if (nf==0) { return; } // If fespace == H1 const FiniteElement *fe = fes.GetFE(0); const TensorBasisElement *tfe = dynamic_cast(fe); MFEM_VERIFY(tfe != NULL && (tfe->GetBasisType()==BasisType::GaussLobatto || tfe->GetBasisType()==BasisType::Positive), "Only Gauss-Lobatto and Bernstein basis are supported in " "H1FaceRestriction."); MFEM_VERIFY(fes.GetMesh()->Conforming(), "Non-conforming meshes not yet supported with partial assembly."); // Assuming all finite elements are using Gauss-Lobatto. height = vdim*nf*dof; width = fes.GetVSize(); const bool dof_reorder = (e_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(fe); if (el) { continue; } mfem_error("Finite element not suitable for lexicographic ordering"); } const FiniteElement *fe = fes.GetFaceElement(0); const TensorBasisElement* el = dynamic_cast(fe); const Array &fe_dof_map = el->GetDofMap(); MFEM_VERIFY(fe_dof_map.Size() > 0, "invalid dof map"); } const TensorBasisElement* el = dynamic_cast(fe); const int *dof_map = el->GetDofMap().GetData(); const Table& e2dTable = fes.GetElementToDofTable(); const int* elementMap = e2dTable.GetJ(); Array faceMap(dof); int e1, e2; int inf1, inf2; int face_id; int orientation; const int dof1d = fes.GetFE(0)->GetOrder()+1; const int elem_dofs = fes.GetFE(0)->GetDof(); const int dim = fes.GetMesh()->SpaceDimension(); // Computation of scatter_indices int f_ind = 0; for (int f = 0; f < fes.GetNF(); ++f) { fes.GetMesh()->GetFaceElements(f, &e1, &e2); fes.GetMesh()->GetFaceInfos(f, &inf1, &inf2); orientation = inf1 % 64; face_id = inf1 / 64; if ((type==FaceType::Interior && (e2>=0 || (e2<0 && inf2>=0))) || (type==FaceType::Boundary && e2<0 && inf2<0) ) { // Assumes Gauss-Lobatto basis if (dof_reorder) { if (orientation != 0) { mfem_error("FaceRestriction used on degenerated mesh."); } GetFaceDofs(dim, face_id, dof1d, faceMap); // Only for hex } else { mfem_error("FaceRestriction not yet implemented for this type of " "element."); // TODO Something with GetFaceDofs? } for (int d = 0; d < dof; ++d) { const int face_dof = faceMap[d]; const int did = (!dof_reorder)?face_dof:dof_map[face_dof]; const int gid = elementMap[e1*elem_dofs + did]; const int lid = dof*f_ind + d; scatter_indices[lid] = gid; } f_ind++; } } MFEM_VERIFY(f_ind==nf, "Unexpected number of faces."); // Computation of gather_indices for (int i = 0; i <= ndofs; ++i) { offsets[i] = 0; } f_ind = 0; for (int f = 0; f < fes.GetNF(); ++f) { fes.GetMesh()->GetFaceElements(f, &e1, &e2); fes.GetMesh()->GetFaceInfos(f, &inf1, &inf2); orientation = inf1 % 64; face_id = inf1 / 64; if ((type==FaceType::Interior && (e2>=0 || (e2<0 && inf2>=0))) || (type==FaceType::Boundary && e2<0 && inf2<0) ) { GetFaceDofs(dim, face_id, dof1d, faceMap); for (int d = 0; d < dof; ++d) { const int face_dof = faceMap[d]; const int did = (!dof_reorder)?face_dof:dof_map[face_dof]; const int gid = elementMap[e1*elem_dofs + did]; ++offsets[gid + 1]; } f_ind++; } } MFEM_VERIFY(f_ind==nf, "Unexpected number of faces."); for (int i = 1; i <= ndofs; ++i) { offsets[i] += offsets[i - 1]; } f_ind = 0; for (int f = 0; f < fes.GetNF(); ++f) { fes.GetMesh()->GetFaceElements(f, &e1, &e2); fes.GetMesh()->GetFaceInfos(f, &inf1, &inf2); orientation = inf1 % 64; face_id = inf1 / 64; if ((type==FaceType::Interior && (e2>=0 || (e2<0 && inf2>=0))) || (type==FaceType::Boundary && e2<0 && inf2<0) ) { GetFaceDofs(dim, face_id, dof1d, faceMap); for (int d = 0; d < dof; ++d) { const int face_dof = faceMap[d]; const int did = (!dof_reorder)?face_dof:dof_map[face_dof]; const int gid = elementMap[e1*elem_dofs + did]; const int lid = dof*f_ind + d; gather_indices[offsets[gid]++] = lid; } f_ind++; } } MFEM_VERIFY(f_ind==nf, "Unexpected number of faces."); for (int i = ndofs; i > 0; --i) { offsets[i] = offsets[i - 1]; } offsets[0] = 0; } void H1FaceRestriction::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_indices = scatter_indices.Read(); auto d_x = Reshape(x.Read(), t?vd:ndofs, t?ndofs:vd); auto d_y = Reshape(y.Write(), nd, vd, nf); MFEM_FORALL(i, nfdofs, { const int idx = d_indices[i]; const int dof = i % nd; const int face = i / nd; for (int c = 0; c < vd; ++c) { d_y(dof, c, face) = d_x(t?c:idx, t?idx:c); } }); } void H1FaceRestriction::MultTranspose(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 = gather_indices.Read(); auto d_x = Reshape(x.Read(), nd, vd, nf); auto d_y = Reshape(y.Write(), t?vd:ndofs, t?ndofs:vd); MFEM_FORALL(i, ndofs, { const int offset = d_offsets[i]; const int nextOffset = d_offsets[i + 1]; for (int c = 0; c < vd; ++c) { double dofValue = 0; for (int j = offset; j < nextOffset; ++j) { const int idx_j = d_indices[j]; dofValue += d_x(idx_j % nd, c, idx_j / nd); } d_y(t?c:i,t?i:c) += dofValue; } }); } static int ToLexOrdering2D(const int face_id, const int size1d, const int i) { if (face_id==2 || face_id==3) { return size1d-1-i; } else { return i; } } static int PermuteFace2D(const int face_id1, const int face_id2, const int orientation, const int size1d, const int index) { int new_index; // Convert from lex ordering if (face_id1==2 || face_id1==3) { new_index = size1d-1-index; } else { new_index = index; } // Permute based on face orientations if (orientation==1) { new_index = size1d-1-new_index; } return ToLexOrdering2D(face_id2, size1d, new_index); } static int ToLexOrdering3D(const int face_id, const int size1d, const int i, const int j) { if (face_id==2 || face_id==1 || face_id==5) { return i + j*size1d; } else if (face_id==3 || face_id==4) { return (size1d-1-i) + j*size1d; } else // face_id==0 { return i + (size1d-1-j)*size1d; } } static int PermuteFace3D(const int face_id1, const int face_id2, const int orientation, const int size1d, const int index) { int i=0, j=0, new_i=0, new_j=0; i = index%size1d; j = index/size1d; // Convert from lex ordering if (face_id1==3 || face_id1==4) { i = size1d-1-i; } else if (face_id1==0) { j = size1d-1-j; } // Permute based on face orientations switch (orientation) { case 0: new_i = i; new_j = j; break; case 1: new_i = j; new_j = i; break; case 2: new_i = j; new_j = (size1d-1-i); break; case 3: new_i = (size1d-1-i); new_j = j; break; case 4: new_i = (size1d-1-i); new_j = (size1d-1-j); break; case 5: new_i = (size1d-1-j); new_j = (size1d-1-i); break; case 6: new_i = (size1d-1-j); new_j = i; break; case 7: new_i = i; new_j = (size1d-1-j); break; } return ToLexOrdering3D(face_id2, size1d, new_i, new_j); } /// 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 PermuteFace2D(face_id1, face_id2, orientation, size1d, index); case 3: return PermuteFace3D(face_id1, face_id2, orientation, size1d, index); default: mfem_error("Unsupported dimension."); return 0; } } L2FaceRestriction::L2FaceRestriction(const FiniteElementSpace &fes, const ElementDofOrdering e_ordering, const FaceType type, const L2FaceValues m) : fes(fes), nf(fes.GetNFbyType(type)), vdim(fes.GetVDim()), byvdim(fes.GetOrdering() == Ordering::byVDIM), ndofs(fes.GetNDofs()), dof(nf > 0 ? fes.GetTraceElement(0, fes.GetMesh()->GetFaceBaseGeometry(0))->GetDof() : 0), m(m), nfdofs(nf*dof), scatter_indices1(nf*dof), scatter_indices2(m==L2FaceValues::DoubleValued?nf*dof:0), offsets(ndofs+1), gather_indices((m==L2FaceValues::DoubleValued? 2 : 1)*nf*dof) { // If fespace == L2 const FiniteElement *fe = fes.GetFE(0); const TensorBasisElement *tfe = dynamic_cast(fe); MFEM_VERIFY(tfe != NULL && (tfe->GetBasisType()==BasisType::GaussLobatto || tfe->GetBasisType()==BasisType::Positive), "Only Gauss-Lobatto and Bernstein basis are supported in " "L2FaceRestriction."); MFEM_VERIFY(fes.GetMesh()->Conforming(), "Non-conforming meshes not yet supported with partial assembly."); if (nf==0) { return; } height = (m==L2FaceValues::DoubleValued? 2 : 1)*vdim*nf*dof; width = fes.GetVSize(); const bool dof_reorder = (e_ordering == ElementDofOrdering::LEXICOGRAPHIC); if (!dof_reorder) { mfem_error("Non-Tensor L2FaceRestriction not yet implemented."); } if (dof_reorder && nf > 0) { for (int f = 0; f < fes.GetNF(); ++f) { const FiniteElement *fe = fes.GetTraceElement(f, fes.GetMesh()->GetFaceBaseGeometry(f)); const TensorBasisElement* el = dynamic_cast(fe); if (el) { continue; } mfem_error("Finite element not suitable for lexicographic ordering"); } } const Table& e2dTable = fes.GetElementToDofTable(); const int* elementMap = e2dTable.GetJ(); Array faceMap1(dof), faceMap2(dof); int e1, e2; int inf1, inf2; int face_id1, face_id2; int orientation; const int dof1d = fes.GetFE(0)->GetOrder()+1; const int elem_dofs = fes.GetFE(0)->GetDof(); const int dim = fes.GetMesh()->SpaceDimension(); // Computation of scatter indices int f_ind=0; for (int f = 0; f < fes.GetNF(); ++f) { fes.GetMesh()->GetFaceElements(f, &e1, &e2); fes.GetMesh()->GetFaceInfos(f, &inf1, &inf2); if (dof_reorder) { orientation = inf1 % 64; face_id1 = inf1 / 64; GetFaceDofs(dim, face_id1, dof1d, faceMap1); // Only for hex orientation = inf2 % 64; face_id2 = inf2 / 64; GetFaceDofs(dim, face_id2, dof1d, faceMap2); // Only for hex } else { mfem_error("FaceRestriction not yet implemented for this type of " "element."); // TODO Something with GetFaceDofs? } if ((type==FaceType::Interior && e2>=0) || (type==FaceType::Boundary && e2<0)) { for (int d = 0; d < dof; ++d) { const int face_dof = faceMap1[d]; const int did = face_dof; const int gid = elementMap[e1*elem_dofs + did]; const int lid = dof*f_ind + d; scatter_indices1[lid] = gid; } if (m==L2FaceValues::DoubleValued) { for (int d = 0; d < dof; ++d) { if (type==FaceType::Interior && e2>=0) // interior face { const int pd = PermuteFaceL2(dim, face_id1, face_id2, orientation, dof1d, d); const int face_dof = faceMap2[pd]; const int did = face_dof; const int gid = elementMap[e2*elem_dofs + did]; const int lid = dof*f_ind + d; scatter_indices2[lid] = gid; } else if (type==FaceType::Boundary && e2<0) // true boundary face { const int lid = dof*f_ind + d; scatter_indices2[lid] = -1; } } } f_ind++; } } MFEM_VERIFY(f_ind==nf, "Unexpected number of faces."); // Computation of gather_indices for (int i = 0; i <= ndofs; ++i) { offsets[i] = 0; } f_ind = 0; for (int f = 0; f < fes.GetNF(); ++f) { fes.GetMesh()->GetFaceElements(f, &e1, &e2); fes.GetMesh()->GetFaceInfos(f, &inf1, &inf2); if ((type==FaceType::Interior && (e2>=0 || (e2<0 && inf2>=0))) || (type==FaceType::Boundary && e2<0 && inf2<0) ) { orientation = inf1 % 64; face_id1 = inf1 / 64; GetFaceDofs(dim, face_id1, dof1d, faceMap1); orientation = inf2 % 64; face_id2 = inf2 / 64; GetFaceDofs(dim, face_id2, dof1d, faceMap2); for (int d = 0; d < dof; ++d) { const int did = faceMap1[d]; const int gid = elementMap[e1*elem_dofs + did]; ++offsets[gid + 1]; } if (m==L2FaceValues::DoubleValued) { for (int d = 0; d < dof; ++d) { if (type==FaceType::Interior && e2>=0) // interior face { const int pd = PermuteFaceL2(dim, face_id1, face_id2, orientation, dof1d, d); const int did = faceMap2[pd]; const int gid = elementMap[e2*elem_dofs + did]; ++offsets[gid + 1]; } } } f_ind++; } } MFEM_VERIFY(f_ind==nf, "Unexpected number of faces."); for (int i = 1; i <= ndofs; ++i) { offsets[i] += offsets[i - 1]; } f_ind = 0; for (int f = 0; f < fes.GetNF(); ++f) { fes.GetMesh()->GetFaceElements(f, &e1, &e2); fes.GetMesh()->GetFaceInfos(f, &inf1, &inf2); if ((type==FaceType::Interior && (e2>=0 || (e2<0 && inf2>=0))) || (type==FaceType::Boundary && e2<0 && inf2<0) ) { orientation = inf1 % 64; face_id1 = inf1 / 64; GetFaceDofs(dim, face_id1, dof1d, faceMap1); orientation = inf2 % 64; face_id2 = inf2 / 64; GetFaceDofs(dim, face_id2, dof1d, faceMap2); for (int d = 0; d < dof; ++d) { const int did = faceMap1[d]; const int gid = elementMap[e1*elem_dofs + did]; const int lid = dof*f_ind + d; // We don't shift lid to express that it's e1 of f gather_indices[offsets[gid]++] = lid; } if (m==L2FaceValues::DoubleValued) { for (int d = 0; d < dof; ++d) { if (type==FaceType::Interior && e2>=0) // interior face { const int pd = PermuteFaceL2(dim, face_id1, face_id2, orientation, dof1d, d); const int did = faceMap2[pd]; const int gid = elementMap[e2*elem_dofs + did]; const int lid = dof*f_ind + d; // We shift lid to express that it's e2 of f gather_indices[offsets[gid]++] = nfdofs + lid; } } } f_ind++; } } MFEM_VERIFY(f_ind==nf, "Unexpected number of faces."); for (int i = ndofs; i > 0; --i) { offsets[i] = offsets[i - 1]; } offsets[0] = 0; } void L2FaceRestriction::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; if (m==L2FaceValues::DoubleValued) { 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(), nd, vd, 2, nf); MFEM_FORALL(i, nfdofs, { const int dof = i % nd; const int face = i / nd; 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); } }); } else { auto d_indices1 = scatter_indices1.Read(); auto d_x = Reshape(x.Read(), t?vd:ndofs, t?ndofs:vd); auto d_y = Reshape(y.Write(), nd, vd, nf); MFEM_FORALL(i, nfdofs, { const int dof = i % nd; const int face = i / nd; 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::MultTranspose(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; const int dofs = nfdofs; auto d_offsets = offsets.Read(); auto d_indices = gather_indices.Read(); if (m == L2FaceValues::DoubleValued) { auto d_x = Reshape(x.Read(), nd, vd, 2, nf); auto d_y = Reshape(y.Write(), t?vd:ndofs, t?ndofs:vd); MFEM_FORALL(i, ndofs, { const int offset = d_offsets[i]; const int nextOffset = d_offsets[i + 1]; for (int c = 0; c < vd; ++c) { double dofValue = 0; for (int j = offset; j < nextOffset; ++j) { int idx_j = d_indices[j]; bool isE1 = idx_j < dofs; idx_j = isE1 ? idx_j : idx_j - dofs; dofValue += isE1 ? d_x(idx_j % nd, c, 0, idx_j / nd) :d_x(idx_j % nd, c, 1, idx_j / nd); } d_y(t?c:i,t?i:c) += dofValue; } }); } else { auto d_x = Reshape(x.Read(), nd, vd, nf); auto d_y = Reshape(y.Write(), t?vd:ndofs, t?ndofs:vd); MFEM_FORALL(i, ndofs, { const int offset = d_offsets[i]; const int nextOffset = d_offsets[i + 1]; for (int c = 0; c < vd; ++c) { double dofValue = 0; for (int j = offset; j < nextOffset; ++j) { int idx_j = d_indices[j]; dofValue += d_x(idx_j % nd, c, idx_j / nd); } d_y(t?c:i,t?i:c) += dofValue; } }); } } int ToLexOrdering(const int dim, const int face_id, const int size1d, const int index) { switch (dim) { case 1: return 0; case 2: return ToLexOrdering2D(face_id, size1d, index); case 3: return ToLexOrdering3D(face_id, size1d, index%size1d, index/size1d); default: mfem_error("Unsupported dimension."); return 0; } } } // namespace mfem