// Copyright (c) 2010-2023, 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 { ParNCH1FaceRestriction::ParNCH1FaceRestriction(const ParFiniteElementSpace &fes, ElementDofOrdering f_ordering, FaceType type) : H1FaceRestriction(fes, f_ordering, type, false), type(type), interpolations(fes, f_ordering, type) { if (nf==0) { return; } x_interp.UseDevice(true); // Check that the space is H1 (not currently implemented for ND or RT spaces) const bool is_h1 = dynamic_cast(fes.FEColl()); MFEM_VERIFY(is_h1, "ParNCH1FaceRestriction is only implemented for H1 spaces.") CheckFESpace(f_ordering); ComputeScatterIndicesAndOffsets(f_ordering, type); ComputeGatherIndices(f_ordering, type); } void ParNCH1FaceRestriction::Mult(const Vector &x, Vector &y) const { H1FaceRestriction::Mult(x, y); NonconformingInterpolation(y); } void ParNCH1FaceRestriction::NonconformingInterpolation(Vector& y) const { // 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, 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 double dof_values[max_nd]; const NCInterpConfig conf = interp_config_ptr[nc_face]; if ( conf.is_non_conforming && conf.master_side == 0 ) { 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, face); } MFEM_SYNC_THREAD; MFEM_FOREACH_THREAD(dof_out,x,nface_dofs) { double res = 0.0; for (int dof_in = 0; dof_in 0; --i) { gather_offsets[i] = gather_offsets[i - 1]; } gather_offsets[0] = 0; } ParL2FaceRestriction::ParL2FaceRestriction(const ParFiniteElementSpace &fes, ElementDofOrdering f_ordering, FaceType type, L2FaceValues m, bool build) : L2FaceRestriction(fes, f_ordering, type, m, false) { if (!build) { return; } if (nf==0) { return; } CheckFESpace(f_ordering); ComputeScatterIndicesAndOffsets(f_ordering, type); ComputeGatherIndices(f_ordering, type); } ParL2FaceRestriction::ParL2FaceRestriction(const ParFiniteElementSpace &fes, ElementDofOrdering f_ordering, FaceType type, L2FaceValues m) : ParL2FaceRestriction(fes, f_ordering, type, m, true) { } void ParL2FaceRestriction::DoubleValuedConformingMult( const Vector& x, Vector& y) const { MFEM_ASSERT( m == L2FaceValues::DoubleValued, "This method should be called when m == L2FaceValues::DoubleValued."); const ParFiniteElementSpace &pfes = static_cast(this->fes); ParGridFunction x_gf; x_gf.MakeRef(const_cast(&pfes), const_cast(x), 0); // Face-neighbor information is only needed for interior faces. For boundary // faces, no communication is required. if (type == FaceType::Interior) { x_gf.ExchangeFaceNbrData(); } // Early return only after calling ParGridFunction::ExchangeFaceNbrData, // otherwise MPI communication can hang. 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; const int threshold = ndofs; const int nsdofs = pfes.GetFaceNbrVSize(); 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:nsdofs, t?nsdofs: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) { 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; } } }); } void ParL2FaceRestriction::Mult(const Vector& x, Vector& y) const { if (m==L2FaceValues::DoubleValued) { DoubleValuedConformingMult(x, y); } else { SingleValuedConformingMult(x, y); } } 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, const bool keep_nbr_block) const { if (keep_nbr_block) { return L2FaceRestriction::FillI(mat, keep_nbr_block); } const int nface_dofs = face_dofs; const int Ndofs = ndofs; 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 f = fdof/nface_dofs; const int iF = fdof%nface_dofs; const int iE1 = d_indices1[f*nface_dofs+iF]; if (iE1 < Ndofs) { AddNnz(iE1,I,nface_dofs); } const int iE2 = d_indices2[f*nface_dofs+iF]; if (iE2 < Ndofs) { AddNnz(iE2,I,nface_dofs); } }); } void ParL2FaceRestriction::FillI(SparseMatrix &mat, SparseMatrix &face_mat) const { const int nface_dofs = face_dofs; 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(ne*elem_dofs*vdim+1, [=] MFEM_HOST_DEVICE (int i) { I_face[i] = 0; }); 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]; if (iE1 < Ndofs) { for (int jF = 0; jF < nface_dofs; jF++) { const int jE2 = d_indices2[f*nface_dofs+jF]; if (jE2 < Ndofs) { AddNnz(iE1,I,1); } else { AddNnz(iE1,I_face,1); } } } const int iE2 = d_indices2[f*nface_dofs+iF]; if (iE2 < Ndofs) { for (int jF = 0; jF < nface_dofs; jF++) { const int jE1 = d_indices1[f*nface_dofs+jF]; if (jE1 < Ndofs) { AddNnz(iE2,I,1); } else { AddNnz(iE2,I_face,1); } } } }); } void ParL2FaceRestriction::FillJAndData(const Vector &ea_data, SparseMatrix &mat, const bool keep_nbr_block) const { if (keep_nbr_block) { return L2FaceRestriction::FillJAndData(ea_data, mat, keep_nbr_block); } const int nface_dofs = face_dofs; const int Ndofs = ndofs; auto d_indices1 = scatter_indices1.Read(); auto d_indices2 = scatter_indices2.Read(); auto mat_fea = Reshape(ea_data.Read(), nface_dofs, nface_dofs, 2, nf); auto I = mat.ReadWriteI(); 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]; if (iE1 < Ndofs) { const int offset = AddNnz(iE1,I,nface_dofs); for (int jF = 0; jF < nface_dofs; jF++) { const int jE2 = d_indices2[f*nface_dofs+jF]; J[offset+jF] = jE2; Data[offset+jF] = mat_fea(jF,iF,1,f); } } const int iE2 = d_indices2[f*nface_dofs+iF]; if (iE2 < Ndofs) { const int offset = AddNnz(iE2,I,nface_dofs); for (int jF = 0; jF < nface_dofs; jF++) { const int jE1 = d_indices1[f*nface_dofs+jF]; J[offset+jF] = jE1; Data[offset+jF] = mat_fea(jF,iF,0,f); } } }); } void ParL2FaceRestriction::FillJAndData(const Vector &ea_data, SparseMatrix &mat, SparseMatrix &face_mat) const { const int nface_dofs = face_dofs; const int Ndofs = ndofs; auto d_indices1 = scatter_indices1.Read(); auto d_indices2 = scatter_indices2.Read(); auto mat_fea = Reshape(ea_data.Read(), nface_dofs, nface_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(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]; if (iE1 < Ndofs) { for (int jF = 0; jF < nface_dofs; jF++) { const int jE2 = d_indices2[f*nface_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*nface_dofs+iF]; if (iE2 < Ndofs) { for (int jF = 0; jF < nface_dofs; jF++) { const int jE1 = d_indices1[f*nface_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); } } } }); } void ParL2FaceRestriction::ComputeScatterIndicesAndOffsets( const ElementDofOrdering f_ordering, const FaceType type) { Mesh &mesh = *fes.GetMesh(); const ParFiniteElementSpace &pfes = static_cast(this->fes); // 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 < pfes.GetNF(); ++f) { Mesh::FaceInformation face = mesh.GetFaceInformation(f); if (type==FaceType::Interior && face.IsInterior()) { SetFaceDofsScatterIndices1(face,f_ind); if (m==L2FaceValues::DoubleValued) { if (face.IsShared()) { PermuteAndSetSharedFaceDofsScatterIndices2(face,f_ind); } else { 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 faces."); // Summation of the offsets for (int i = 1; i <= ndofs; ++i) { gather_offsets[i] += gather_offsets[i - 1]; } } void ParL2FaceRestriction::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.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; } ParNCL2FaceRestriction::ParNCL2FaceRestriction(const ParFiniteElementSpace &fes, ElementDofOrdering f_ordering, FaceType type, L2FaceValues m) : L2FaceRestriction(fes, f_ordering, type, m, false), NCL2FaceRestriction(fes, f_ordering, type, m, false), ParL2FaceRestriction(fes, f_ordering, type, m, false) { if (nf==0) { return; } x_interp.UseDevice(true); CheckFESpace(f_ordering); ComputeScatterIndicesAndOffsets(f_ordering, type); ComputeGatherIndices(f_ordering, type); } void ParNCL2FaceRestriction::SingleValuedNonconformingMult( 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; const int threshold = ndofs; 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); 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); static constexpr int max_nd = 16*16; MFEM_VERIFY(nface_dofs<=max_nd, "Too many degrees of freedom."); mfem::forall_2D(nf, nface_dofs, 1, [=] MFEM_HOST_DEVICE (int face) { MFEM_SHARED double dof_values[max_nd]; const InterpConfig conf = interp_config_ptr[face]; const int master_side = conf.master_side; const int interp_index = conf.index; const int side = 0; if ( !conf.is_non_conforming || side!=master_side ) { MFEM_FOREACH_THREAD(dof,x,nface_dofs) { const int i = face*nface_dofs + dof; const int idx = d_indices1[i]; if (idx>-1 && idx-1 && idx 0; --i) { gather_offsets[i] = gather_offsets[i - 1]; } gather_offsets[0] = 0; } } // namespace mfem #endif