// 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 "../config/config.hpp" #ifdef MFEM_USE_MPI #include "restriction.hpp" #include "prestriction.hpp" #include "pgridfunc.hpp" #include "pfespace.hpp" #include "fespace.hpp" #include "../general/forall.hpp" namespace mfem { ParL2FaceRestriction::ParL2FaceRestriction(const ParFiniteElementSpace &fes, ElementDofOrdering e_ordering, FaceType type, L2FaceValues m) : L2FaceRestriction(fes, type, m) { if (nf==0) { return; } // If fespace == L2 const ParFiniteElementSpace &pfes = static_cast(this->fes); const FiniteElement *fe = pfes.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 " "ParL2FaceRestriction."); MFEM_VERIFY(pfes.GetMesh()->Conforming(), "Non-conforming meshes not yet supported with partial assembly."); // Assuming all finite elements are using Gauss-Lobatto dofs height = (m==L2FaceValues::DoubleValued? 2 : 1)*vdim*nf*dof; width = pfes.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 < pfes.GetNF(); ++f) { const FiniteElement *fe = pfes.GetTraceElement(f, pfes.GetMesh()->GetFaceBaseGeometry(f)); const TensorBasisElement* el = dynamic_cast(fe); if (el) { continue; } mfem_error("Finite element not suitable for lexicographic ordering"); } } const Table& e2dTable = pfes.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 = pfes.GetFE(0)->GetOrder()+1; const int elem_dofs = pfes.GetFE(0)->GetDof(); const int dim = pfes.GetMesh()->SpaceDimension(); // Computation of scatter indices int f_ind=0; for (int f = 0; f < pfes.GetNF(); ++f) { pfes.GetMesh()->GetFaceElements(f, &e1, &e2); pfes.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? orientation = 0; // suppress compiler warning face_id1 = face_id2 = 0; // suppress compiler warning } if (type==FaceType::Interior && (e2>=0 || (e2<0 && inf2>=0) )) // interior/shared face { 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) { if (e2>=0) // interior face { for (int d = 0; d < dof; ++d) { 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 (inf2>=0) // shared boundary { const int se2 = -1 - e2; Array sharedDofs; pfes.GetFaceNbrElementVDofs(se2, sharedDofs); for (int d = 0; d < dof; ++d) { 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 = sharedDofs[did]; const int lid = dof*f_ind + d; // Trick to differentiate dof location inter/shared scatter_indices2[lid] = ndofs+gid; } } } f_ind++; } else if (type==FaceType::Boundary && e2<0 && inf2<0) // true boundary { 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) { 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 < pfes.GetNF(); ++f) { pfes.GetMesh()->GetFaceElements(f, &e1, &e2); pfes.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 < pfes.GetNF(); ++f) { pfes.GetMesh()->GetFaceElements(f, &e1, &e2); pfes.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 ParL2FaceRestriction::Mult(const Vector& x, Vector& y) const { const ParFiniteElementSpace &pfes = static_cast(this->fes); ParGridFunction x_gf; x_gf.MakeRef(const_cast(&pfes), const_cast(x), 0); x_gf.ExchangeFaceNbrData(); // Assumes all elements have the same number of dofs const int nd = dof; const int vd = vdim; const bool t = byvdim; const int threshold = ndofs; 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_x_shared = Reshape(x_gf.FaceNbrData().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) { if (idx2>-1 && idx2=threshold) // shared boundary { d_y(dof, c, 1, face) = d_x_shared(t?c:(idx2-threshold), t?(idx2-threshold):c); } else // true boundary { d_y(dof, c, 1, face) = 0.0; } } }); } 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); } }); } } static MFEM_HOST_DEVICE int AddNnz(const int iE, int *I, const int dofs) { int val = AtomicAdd(I[iE],dofs); return val; } void ParL2FaceRestriction::FillI(SparseMatrix &mat, SparseMatrix &face_mat) const { const int face_dofs = dof; const int Ndofs = ndofs; auto d_indices1 = scatter_indices1.Read(); auto d_indices2 = scatter_indices2.Read(); auto I = mat.ReadWriteI(); auto I_face = face_mat.ReadWriteI(); MFEM_FORALL(i, ne*elemDofs*vdim+1, { I_face[i] = 0; }); MFEM_FORALL(fdof, nf*face_dofs, { const int f = fdof/face_dofs; const int iF = fdof%face_dofs; const int iE1 = d_indices1[f*face_dofs+iF]; if (iE1 < Ndofs) { for (int jF = 0; jF < face_dofs; jF++) { const int jE2 = d_indices2[f*face_dofs+jF]; if (jE2 < Ndofs) { AddNnz(iE1,I,1); } else { AddNnz(iE1,I_face,1); } } } const int iE2 = d_indices2[f*face_dofs+iF]; if (iE2 < Ndofs) { for (int jF = 0; jF < face_dofs; jF++) { const int jE1 = d_indices1[f*face_dofs+jF]; if (jE1 < Ndofs) { AddNnz(iE2,I,1); } else { AddNnz(iE2,I_face,1); } } } }); } void ParL2FaceRestriction::FillJAndData(const Vector &ea_data, SparseMatrix &mat, SparseMatrix &face_mat) const { const int face_dofs = dof; const int Ndofs = ndofs; auto d_indices1 = scatter_indices1.Read(); auto d_indices2 = scatter_indices2.Read(); auto mat_fea = Reshape(ea_data.Read(), face_dofs, face_dofs, 2, nf); auto I = mat.ReadWriteI(); auto I_face = face_mat.ReadWriteI(); auto J = mat.WriteJ(); auto J_face = face_mat.WriteJ(); auto Data = mat.WriteData(); auto Data_face = face_mat.WriteData(); MFEM_FORALL(fdof, nf*face_dofs, { const int f = fdof/face_dofs; const int iF = fdof%face_dofs; const int iE1 = d_indices1[f*face_dofs+iF]; if (iE1 < Ndofs) { for (int jF = 0; jF < face_dofs; jF++) { const int jE2 = d_indices2[f*face_dofs+jF]; if (jE2 < Ndofs) { const int offset = AddNnz(iE1,I,1); J[offset] = jE2; Data[offset] = mat_fea(jF,iF,1,f); } else { const int offset = AddNnz(iE1,I_face,1); J_face[offset] = jE2-Ndofs; Data_face[offset] = mat_fea(jF,iF,1,f); } } } const int iE2 = d_indices2[f*face_dofs+iF]; if (iE2 < Ndofs) { for (int jF = 0; jF < face_dofs; jF++) { const int jE1 = d_indices1[f*face_dofs+jF]; if (jE1 < Ndofs) { const int offset = AddNnz(iE2,I,1); J[offset] = jE1; Data[offset] = mat_fea(jF,iF,0,f); } else { const int offset = AddNnz(iE2,I_face,1); J_face[offset] = jE1-Ndofs; Data_face[offset] = mat_fea(jF,iF,0,f); } } } }); } } // namespace mfem #endif