// 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. #ifndef MFEM_TEMPLATE_FESPACE #define MFEM_TEMPLATE_FESPACE #include "../config/tconfig.hpp" #include "../general/tassign.hpp" #include "../linalg/ttensor.hpp" #include "tfe.hpp" // for TFiniteElementSpace_simple #include "fespace.hpp" namespace mfem { // Templated finite element space classes, cf. fespace.?pp and fe_coll.?pp // Index types // IndexType must define: // - constructor IndexType(const FE &fe, const FiniteElementSpace &fes) // - copy constructor // - void SetElement(int elem_idx) // - int map(int loc_dof_idx, int elem_offset) const --> glob_dof_idx, for // single component; elem_offset is relative to the currently set element. // Index type based on an array listing the dofs for each element where all // elements are assumed to have the same number of dofs. Such an array is // constructed from the J array of an element-to-dof Table with optional local // renumbering to ensure tensor-product local dof ordering when needed. template class ElementDofIndexer { protected: const int *el_dof_list, *loc_dof_list; bool own_list; public: typedef FE FE_type; ElementDofIndexer(const FE &fe, const FiniteElementSpace &fes) { const Array *loc_dof_map = fe.GetDofMap(); // fes.BuildElementToDofTable(); const Table &el_dof = fes.GetElementToDofTable(); MFEM_ASSERT(el_dof.Size_of_connections() == el_dof.Size() * FE::dofs, "the element-to-dof Table is not compatible with this FE!"); int num_dofs = el_dof.Size() * FE::dofs; if (!loc_dof_map) { // no local dof reordering el_dof_list = el_dof.GetJ(); own_list = false; } else { // reorder the local dofs according to loc_dof_map int *el_dof_list_ = new int[num_dofs]; const int *loc_dof_map_ = loc_dof_map->GetData(); for (int i = 0; i < el_dof.Size(); i++) { MFEM_ASSERT(el_dof.RowSize(i) == FE::dofs, "incompatible element-to-dof Table!"); for (int j = 0; j < FE::dofs; j++) { el_dof_list_[j+FE::dofs*i] = el_dof.GetJ()[loc_dof_map_[j]+FE::dofs*i]; } } el_dof_list = el_dof_list_; own_list = true; } loc_dof_list = el_dof_list; // point to element 0 } // Shallow copy constructor inline MFEM_ALWAYS_INLINE ElementDofIndexer(const ElementDofIndexer &orig) : el_dof_list(orig.el_dof_list), loc_dof_list(orig.loc_dof_list), own_list(false) { } inline MFEM_ALWAYS_INLINE ~ElementDofIndexer() { if (own_list) { delete [] el_dof_list; } } inline MFEM_ALWAYS_INLINE void SetElement(int elem_idx) { loc_dof_list = el_dof_list + elem_idx * FE::dofs; } inline MFEM_ALWAYS_INLINE int map(int loc_dof_idx, int elem_offset) const { return loc_dof_list[loc_dof_idx + elem_offset * FE::dofs]; } }; // Simple template Finite Element Space, built using an IndexType. For a // description of the requirements on IndexType, see above. template class TFiniteElementSpace_simple { public: typedef FE FE_type; typedef IndexType index_type; static const int dofs = FE::dofs; protected: index_type ind; int num_elems, remain_elems; public: TFiniteElementSpace_simple(const FE &fe, const FiniteElementSpace &fes) : ind(fe, fes), num_elems(fes.GetNE()), remain_elems(num_elems) { } // default copy constructor int GetNE() const { return num_elems; } void SetElement(int el) { ind.SetElement(el); remain_elems = num_elems-el; } #if 0 // Multi-element Extract: // Extract dofs for multiple elements starting with the current element. // The number of elements to extract is given by the second dimension of // dof_layout_t: dof_layout is (DOFS x NumElems). template inline MFEM_ALWAYS_INLINE void Extract(const glob_dof_data_t &glob_dof_data, const dof_layout_t &dof_layout, dof_data_t &dof_data) const { const int SS = sizeof(dof_data[0])/sizeof(dof_data[0][0]); const int NE = dof_layout_t::dim_2; MFEM_STATIC_ASSERT(FE::dofs == dof_layout_t::dim_1, "invalid number of dofs"); for (int j = 0; j < NE; j++) { for (int i = 0; i < FE::dofs; i++) { for (int s = 0; s < SS; s++) { Assign(dof_data[dof_layout.ind(i,j)][s], glob_dof_data[ind.map(i,s+SS*j)]); } } } } template inline MFEM_ALWAYS_INLINE void Extract(const glob_dof_data_t &glob_dof_data, const dof_layout_t &dof_layout, dof_data_t &dof_data) const { Extract(glob_dof_data, dof_layout, dof_data); } // Multi-element assemble. template inline MFEM_ALWAYS_INLINE void Assemble(const dof_layout_t &dof_layout, const dof_data_t &dof_data, glob_dof_data_t &glob_dof_data) const { const int SS = sizeof(dof_data[0])/sizeof(dof_data[0][0]); const int NE = dof_layout_t::dim_2; MFEM_STATIC_ASSERT(FE::dofs == dof_layout_t::dim_1, "invalid number of dofs"); for (int j = 0; j < NE; j++) { for (int i = 0; i < FE::dofs; i++) { for (int s = 0; s < SS; s++) { Assign(glob_dof_data[ind.map(i,s+SS*j)], dof_data[dof_layout.ind(i,j)][s]); } } } } template inline MFEM_ALWAYS_INLINE void Assemble(const dof_layout_t &dof_layout, const dof_data_t &dof_data, glob_dof_data_t &glob_dof_data) const { Assemble(dof_layout, dof_data, glob_dof_data); } #endif // Multi-element VectorExtract: vdof_layout is (DOFS x NumComp x NumElems). template inline MFEM_ALWAYS_INLINE void VectorExtract(const vec_layout_t &vl, const glob_vdof_data_t &glob_vdof_data, const vdof_layout_t &vdof_layout, vdof_data_t &vdof_data) const { const int SS = sizeof(vdof_data[0])/sizeof(vdof_data[0][0]); const int NC = vdof_layout_t::dim_2; const int NE = vdof_layout_t::dim_3; MFEM_STATIC_ASSERT(FE::dofs == vdof_layout_t::dim_1, "invalid number of dofs"); MFEM_ASSERT(NC == vl.NumComponents(), "invalid number of components"); const int TE = std::min(SS*NE, remain_elems); // const int TE = SS*NE; for (int k = 0; k < NC; k++) { #if 0 for (int j = 0; j < NE; j++) { for (int i = 0; i < FE::dofs; i++) { for (int s = 0; s < SS; s++) { Assign(vdof_data[vdof_layout.ind(i,k,j)][s], glob_vdof_data[vl.ind(ind.map(i,s+SS*j), k)]); } } } #else for (int js = 0; js < TE; js++) { for (int i = 0; i < FE::dofs; i++) { const int s = js % SS, j = js / SS; Assign(vdof_data[vdof_layout.ind(i,k,j)][s], glob_vdof_data[vl.ind(ind.map(i,js), k)]); } } #endif } } template inline MFEM_ALWAYS_INLINE void VectorExtract(const vec_layout_t &vl, const glob_vdof_data_t &glob_vdof_data, const vdof_layout_t &vdof_layout, vdof_data_t &vdof_data) const { VectorExtract(vl, glob_vdof_data, vdof_layout, vdof_data); } // Multi-element VectorAssemble: vdof_layout is (DOFS x NumComp x NumElems). template inline MFEM_ALWAYS_INLINE void VectorAssemble(const vdof_layout_t &vdof_layout, const vdof_data_t &vdof_data, const vec_layout_t &vl, glob_vdof_data_t &glob_vdof_data) const { const int SS = sizeof(vdof_data[0])/sizeof(vdof_data[0][0]); const int NC = vdof_layout_t::dim_2; const int NE = vdof_layout_t::dim_3; MFEM_STATIC_ASSERT(FE::dofs == vdof_layout_t::dim_1, "invalid number of dofs"); MFEM_ASSERT(NC == vl.NumComponents(), "invalid number of components"); const int TE = std::min(SS*NE, remain_elems); // const int TE = SS*NE; for (int k = 0; k < NC; k++) { #if 0 for (int j = 0; j < NE; j++) { for (int i = 0; i < FE::dofs; i++) { for (int s = 0; s < SS; s++) { Assign(glob_vdof_data[vl.ind(ind.map(i,s+SS*j), k)], vdof_data[vdof_layout.ind(i,k,j)][s]); } } } #else for (int js = 0; js < TE; js++) { for (int i = 0; i < FE::dofs; i++) { const int s = js % SS, j = js / SS; Assign(glob_vdof_data[vl.ind(ind.map(i,js), k)], vdof_data[vdof_layout.ind(i,k,j)][s]); } } #endif } } template inline MFEM_ALWAYS_INLINE void VectorAssemble(const vdof_layout_t &vdof_layout, const vdof_data_t &vdof_data, const vec_layout_t &vl, glob_vdof_data_t &glob_vdof_data) const { VectorAssemble(vdof_layout, vdof_data, vl, glob_vdof_data); } // Extract a static number of consecutive components; vdof_layout is // (dofs x NC x NE), where NC is the number of components to extract. It is // assumed that: first_comp + NC <= vl.NumComponents(). template inline MFEM_ALWAYS_INLINE void ExtractComponents(int first_comp, const vec_layout_t &vl, const glob_vdof_data_t &glob_vdof_data, const vdof_layout_t &vdof_layout, vdof_data_t &vdof_data) const { const int SS = sizeof(vdof_data[0])/sizeof(vdof_data[0][0]); const int NC = vdof_layout_t::dim_2; const int NE = vdof_layout_t::dim_3; const int TE = std::min(SS*NE, remain_elems); MFEM_STATIC_ASSERT(FE::dofs == vdof_layout_t::dim_1, "invalid number of dofs"); MFEM_ASSERT(first_comp + NC <= vl.NumComponents(), "invalid number of components"); for (int k = 0; k < NC; k++) { for (int js = 0; js < TE; js++) { for (int i = 0; i < FE::dofs; i++) { const int s = js % SS, j = js / SS; Assign( vdof_data[vdof_layout.ind(i,k,j)][s], glob_vdof_data[vl.ind(ind.map(i,js), first_comp+k)]); } } } } // Assemble a static number of consecutive components; vdof_layout is // (dofs x NC x NE), where NC is the number of components to add. It is // assumed that: first_comp + NC <= vl.NumComponents(). template inline MFEM_ALWAYS_INLINE void AssembleComponents(int first_comp, const vdof_layout_t &vdof_layout, const vdof_data_t &vdof_data, const vec_layout_t &vl, glob_vdof_data_t &glob_vdof_data) const { const int SS = sizeof(vdof_data[0])/sizeof(vdof_data[0][0]); const int NC = vdof_layout_t::dim_2; const int NE = vdof_layout_t::dim_3; const int TE = std::min(SS*NE, remain_elems); MFEM_STATIC_ASSERT(FE::dofs == vdof_layout_t::dim_1, "invalid number of dofs"); MFEM_ASSERT(first_comp + NC <= vl.NumComponents(), "invalid number of components"); for (int k = 0; k < NC; k++) { for (int js = 0; js < TE; js++) { for (int i = 0; i < FE::dofs; i++) { const int s = js % SS, j = js / SS; Assign( glob_vdof_data[vl.ind(ind.map(i,js), first_comp+k)], vdof_data[vdof_layout.ind(i,k,j)][s]); } } } } template void Assemble(const TMatrix &m, SparseMatrix &M) const { const int SS = sizeof(m[0])/sizeof(m[0][0]); const int TE = std::min(SS, remain_elems); MFEM_FLOPS_ADD(FE::dofs*FE::dofs); for (int s = 0; s < TE; s++) { for (int i = 0; i < FE::dofs; i++) { M.SetColPtr(ind.map(i,s)); for (int j = 0; j < FE::dofs; j++) { M._Add_(ind.map(j,s), m(i,j)[s]); } M.ClearColPtr(); } } } template void AssembleBlock(int block_i, int block_j, const vec_layout_t &vl, const TMatrix &m, SparseMatrix &M) const { const int SS = sizeof(m[0])/sizeof(m[0][0]); const int TE = std::min(SS, remain_elems); MFEM_FLOPS_ADD(FE::dofs*FE::dofs); for (int s = 0; s < TE; s++) { for (int i = 0; i < FE::dofs; i++) { M.SetColPtr(vl.ind(ind.map(i,s), block_i)); for (int j = 0; j < FE::dofs; j++) { M._Add_(vl.ind(ind.map(j,s), block_j), m(i,j)[s]); } M.ClearColPtr(); } } } }; // H1 Finite Element Space template class H1_FiniteElementSpace : public TFiniteElementSpace_simple > { public: typedef FE FE_type; typedef TFiniteElementSpace_simple > base_class; H1_FiniteElementSpace(const FE &fe, const FiniteElementSpace &fes) : base_class(fe, fes) { } // default copy constructor static bool Matches(const FiniteElementSpace &fes) { const FiniteElementCollection *fec = fes.FEColl(); const H1_FECollection *h1_fec = dynamic_cast(fec); if (!h1_fec) { return false; } const FiniteElement *fe = h1_fec->FiniteElementForGeometry(FE_type::geom); if (fe->GetOrder() != FE_type::degree) { return false; } return true; } template static bool VectorMatches(const FiniteElementSpace &fes) { return Matches(fes) && vec_layout_t::Matches(fes); } }; // Simple index type for DG spaces, where the map method is given by: // glob_dof_idx = loc_dof_idx + elem_idx * num_dofs. template class DGIndexer { protected: int offset; public: typedef FE FE_type; DGIndexer(const FE &fe, const FiniteElementSpace &fes) { MFEM_ASSERT(fes.GetNDofs() == fes.GetNE() * FE::dofs, "the FE space is not compatible with this FE!"); offset = 0; } // default copy constructor inline MFEM_ALWAYS_INLINE void SetElement(int elem_idx) { offset = FE::dofs * elem_idx; } inline MFEM_ALWAYS_INLINE int map(int loc_dof_idx, int elem_offset) const { return offset + loc_dof_idx + elem_offset * FE::dofs; } }; // L2 Finite Element Space template class L2_FiniteElementSpace : public TFiniteElementSpace_simple > { public: typedef FE FE_type; typedef TFiniteElementSpace_simple > base_class; L2_FiniteElementSpace(const FE &fe, const FiniteElementSpace &fes) : base_class(fe, fes) { } // default copy constructor static bool Matches(const FiniteElementSpace &fes) { const FiniteElementCollection *fec = fes.FEColl(); const L2_FECollection *l2_fec = dynamic_cast(fec); if (!l2_fec) { return false; } const FiniteElement *fe = l2_fec->FiniteElementForGeometry(FE_type::geom); if (fe->GetOrder() != FE_type::degree) { return false; } return true; } template static bool VectorMatches(const FiniteElementSpace &fes) { return Matches(fes) && vec_layout_t::Matches(fes); } }; } // namespace mfem #endif // MFEM_TEMPLATE_FESPACE