Serac  0.1
Serac is an implicit thermal strucural mechanics simulation code.
dof_numbering.hpp
1 // Copyright (c) 2019-2024, Lawrence Livermore National Security, LLC and
2 // other Serac Project Developers. See the top-level LICENSE file for
3 // details.
4 //
5 // SPDX-License-Identifier: (BSD-3-Clause)
6 #pragma once
7 
8 #include "mfem.hpp"
9 
11 
12 #include "serac/numerics/functional/integral.hpp"
13 #include "serac/numerics/functional/element_restriction.hpp"
14 
15 namespace serac {
16 
25 struct ElemInfo {
26  uint32_t global_row_;
27  uint32_t global_col_;
28  uint32_t local_row_;
29  uint32_t local_col_;
30  uint32_t element_id_;
31  int sign_;
33 };
34 
40 inline bool operator<(const ElemInfo& x, const ElemInfo& y)
41 {
42  return (x.global_row_ < y.global_row_) || (x.global_row_ == y.global_row_ && x.global_col_ < y.global_col_);
43 }
44 
50 inline bool operator!=(const ElemInfo& x, const ElemInfo& y)
51 {
52  return (x.global_row_ != y.global_row_) || (x.global_col_ != y.global_col_);
53 }
54 
61 struct SignedIndex {
63  uint32_t index_;
64 
66  int sign_;
67 
69  operator uint32_t() { return index_; }
70 };
71 
79 {
80  return SignedIndex{static_cast<uint32_t>((i >= 0) ? i : -1 - i), (i >= 0) ? 1 : -1};
81 }
82 
88 inline bool isHcurl(const mfem::ParFiniteElementSpace& fes)
89 {
90  return (fes.FEColl()->GetContType() == mfem::FiniteElementCollection::TANGENTIAL);
91 }
92 
98 inline bool isL2(const mfem::ParFiniteElementSpace& fes)
99 {
100  return (fes.FEColl()->GetContType() == mfem::FiniteElementCollection::DISCONTINUOUS);
101 }
102 
109 inline bool compatibleWithFaceRestriction(const mfem::ParFiniteElementSpace& fes)
110 {
111  return !(isHcurl(fes) && fes.GetMesh()->Dimension() == 2) && !(isHcurl(fes) && fes.GetMesh()->Dimension() == 3) &&
112  !(isL2(fes)) && fes.GetMesh()->GetNBE() > 0;
113 }
114 
128 template <typename T, ExecutionSpace exec>
129 ExecArray<T, 3, exec> allocateMemoryForBdrElementGradients(const mfem::ParFiniteElementSpace& trial_fes,
130  const mfem::ParFiniteElementSpace& test_fes)
131 {
132  auto* test_BE = test_fes.GetBE(0);
133  auto* trial_BE = trial_fes.GetBE(0);
134  return {static_cast<size_t>(trial_fes.GetNFbyType(mfem::FaceType::Boundary)),
135  static_cast<size_t>(test_BE->GetDof() * test_fes.GetVDim()),
136  static_cast<size_t>(trial_BE->GetDof() * trial_fes.GetVDim())};
137 }
138 
140 template <typename T, ExecutionSpace exec>
141 ExecArray<T, 2, exec> allocateMemoryForBdrElementGradients(const mfem::ParFiniteElementSpace& fes)
142 {
144  auto* BE = fes.GetBE(0);
145  return {static_cast<size_t>(fes.GetNFbyType(mfem::FaceType::Boundary)),
146  static_cast<size_t>(BE->GetDof() * fes.GetVDim())};
147  } else {
148  return {0, 0};
149  }
150 }
151 
161 struct DofNumbering {
168  DofNumbering(const mfem::ParFiniteElementSpace& fespace)
169  : element_dofs_(static_cast<size_t>(fespace.GetNE()),
170  static_cast<size_t>(fespace.GetFE(0)->GetDof() * fespace.GetVDim())),
172  {
173  int dim = fespace.GetMesh()->Dimension();
174  mfem::Geometry::Type elem_geom[4] = {mfem::Geometry::INVALID, mfem::Geometry::SEGMENT, mfem::Geometry::SQUARE,
175  mfem::Geometry::CUBE};
176  ElementRestriction dofs(&fespace, elem_geom[dim]);
177  ElementRestriction boundary_dofs(&fespace, elem_geom[dim - 1], FaceType::BOUNDARY);
178 
179  {
180  auto elem_restriction = fespace.GetElementRestriction(mfem::ElementDofOrdering::LEXICOGRAPHIC);
181 
182  mfem::Vector iota(elem_restriction->Width());
183  mfem::Vector dof_ids(elem_restriction->Height());
184  dof_ids = 0.0;
185  for (int i = 0; i < iota.Size(); i++) {
186  iota[i] = i + 1; // note: 1-based index
187  }
188 
189  // we're using Mult() to reveal the locations nonzero entries
190  // in the restriction operator, since that information is not
191  // made available through its public interface
192  //
193  // TODO: investigate refactoring mfem's restriction operators
194  // to provide this information in more natural way.
195  elem_restriction->Mult(iota, dof_ids);
196  const double* dof_ids_h = dof_ids.HostRead();
197 
198  int index = 0;
199  for (axom::IndexType e = 0; e < element_dofs_.shape()[0]; e++) {
200  for (axom::IndexType i = 0; i < element_dofs_.shape()[1]; i++) {
201  uint32_t dof_id = static_cast<uint32_t>(fabs(dof_ids_h[index])); // note: 1-based index
202  int dof_sign = dof_ids[index] > 0 ? +1 : -1;
203  element_dofs_(e, i) = {dof_id - 1, dof_sign}; // subtract 1 to get back to 0-based index
204  index++;
205  }
206  }
207  }
208 
209  if (bdr_element_dofs_.size() > 0) {
210  auto face_restriction = fespace.GetFaceRestriction(mfem::ElementDofOrdering::LEXICOGRAPHIC,
211  mfem::FaceType::Boundary, mfem::L2FaceValues::SingleValued);
212 
213  mfem::Vector iota(face_restriction->Width());
214  mfem::Vector dof_ids(face_restriction->Height());
215  for (int i = 0; i < iota.Size(); i++) {
216  iota[i] = i + 1; // note: 1-based index
217  }
218 
219  face_restriction->Mult(iota, dof_ids);
220  const double* dof_ids_h = dof_ids.HostRead();
221 
222  int index = 0;
223  for (axom::IndexType e = 0; e < bdr_element_dofs_.shape()[0]; e++) {
224  for (axom::IndexType i = 0; i < bdr_element_dofs_.shape()[1]; i++) {
225  uint32_t dof_id = static_cast<uint32_t>(fabs(dof_ids_h[index])); // note: 1-based index
226  int dof_sign = dof_ids[index] > 0 ? +1 : -1;
227  bdr_element_dofs_(e, i) = {dof_id - 1, dof_sign}; // subtract 1 to get back to 0-based index
228  index++;
229  }
230  }
231  }
232  }
233 
236 
239 };
240 
255  struct Entry {
256  uint32_t row;
257  uint32_t column;
258 
260  bool operator<(const Entry& other) const
261  {
262  return (row < other.row) || ((row == other.row) && (column < other.column));
263  }
264 
266  bool operator==(const Entry& other) const { return (row == other.row && column == other.column); }
267 
269  struct Hasher {
271  std::size_t operator()(const Entry& k) const
272  {
273  std::size_t seed = std::hash<uint32_t>()(k.row);
274  seed ^= std::hash<uint32_t>()(k.column) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
275  return seed;
276  }
277  };
278  };
279 
288  const serac::BlockElementRestriction& block_trial_dofs)
289  {
290  // we start by having each element and boundary element emit the (i,j) entry that it
291  // touches in the global "stiffness matrix", and also keep track of some metadata about
292  // which element and which dof are associated with that particular nonzero entry
293  for (const auto& [geometry, trial_dofs] : block_trial_dofs.restrictions) {
294  const auto& test_dofs = block_test_dofs.restrictions.at(geometry);
295 
296  std::vector<DoF> test_vdofs(test_dofs.nodes_per_elem * test_dofs.components);
297  std::vector<DoF> trial_vdofs(trial_dofs.nodes_per_elem * trial_dofs.components);
298 
299  auto num_elements = static_cast<uint32_t>(trial_dofs.num_elements);
300  for (uint32_t e = 0; e < num_elements; e++) {
301  for (uint64_t i = 0; i < uint64_t(test_dofs.dof_info.shape()[1]); i++) {
302  auto test_dof = test_dofs.dof_info(e, i);
303 
304  for (uint64_t j = 0; j < uint64_t(trial_dofs.dof_info.shape()[1]); j++) {
305  auto trial_dof = trial_dofs.dof_info(e, j);
306 
307  for (uint64_t k = 0; k < test_dofs.components; k++) {
308  uint32_t test_global_id = uint32_t(test_dofs.GetVDof(test_dof, k).index());
309  for (uint64_t l = 0; l < trial_dofs.components; l++) {
310  uint32_t trial_global_id = uint32_t(trial_dofs.GetVDof(trial_dof, l).index());
311  nz_LUT[{test_global_id, trial_global_id}] = 0; // just store the keys initially
312  }
313  }
314  }
315  }
316  }
317  }
318  std::vector<Entry> entries(nz_LUT.size());
319 
320  uint32_t count = 0;
321  for (auto [key, value] : nz_LUT) {
322  entries[count++] = key;
323  }
324 
325  std::sort(entries.begin(), entries.end());
326 
327  nnz = static_cast<uint32_t>(nz_LUT.size());
328  row_ptr.resize(static_cast<size_t>(block_test_dofs.LSize() + 1));
329  col_ind.resize(nnz);
330 
331  row_ptr[0] = 0;
332  col_ind[0] = int(entries[0].column);
333 
334  for (uint32_t i = 1; i < nnz; i++) {
335  nz_LUT[entries[i]] = i;
336  col_ind[i] = int(entries[i].column);
337 
338  // if the new entry has a different row, then the row_ptr offsets must be set as well
339  for (uint32_t j = entries[i - 1].row; j < entries[i].row; j++) {
340  row_ptr[j + 1] = int(i);
341  }
342  }
343 
344  row_ptr.back() = static_cast<int>(nnz);
345  }
346 
352  uint32_t operator()(int i, int j) const { return nz_LUT.at({uint32_t(i), uint32_t(j)}); }
353 
355  uint32_t nnz;
356 
361  std::vector<int> row_ptr;
362 
364  std::vector<int> col_ind;
365 
370  std::unordered_map<Entry, uint32_t, Entry::Hasher> nz_LUT;
371 };
372 
373 } // namespace serac
This file contains the interface used for initializing/terminating any hardware accelerator-related f...
Accelerator functionality.
Definition: serac.cpp:38
bool isL2(const mfem::ParFiniteElementSpace &fes)
return whether or not the underlying function space is L2 or not
ExecArray< T, 3, exec > allocateMemoryForBdrElementGradients(const mfem::ParFiniteElementSpace &trial_fes, const mfem::ParFiniteElementSpace &test_fes)
this is a (hopefully) temporary measure to work around the fact that mfem's support for querying info...
axom::Array< T, dim, detail::execution_to_memory_v< space > > ExecArray
Alias for an Array corresponding to a particular ExecutionSpace.
bool compatibleWithFaceRestriction(const mfem::ParFiniteElementSpace &fes)
attempt to characterize which FiniteElementSpaces mfem::FaceRestriction actually works with
bool isHcurl(const mfem::ParFiniteElementSpace &fes)
return whether or not the underlying function space is Hcurl or not
bool operator<(const ElemInfo &x, const ElemInfo &y)
operator for sorting lexicographically by {global_row, global_col}
SignedIndex decodeSignedIndex(int i)
mfem will frequently encode {sign, index} into a single int32_t. This function decodes those values.
ExecutionSpace
enum used for signalling whether or not to perform certain calculations on the CPU or GPU
Definition: accelerator.hpp:69
ExecArray< T, dim, ExecutionSpace::CPU > CPUArray
Alias for an array on the CPU.
bool operator!=(const ElemInfo &x, const ElemInfo &y)
operator determining inequality by {global_row, global_col}
a generalization of mfem::ElementRestriction that works with multiple kinds of element geometries....
std::map< mfem::Geometry::Type, ElementRestriction > restrictions
the individual ElementRestriction operators for each element geometry
uint64_t LSize() const
the size of the "L-vector" associated with this restriction operator
this object extracts the dofs for each element in a FiniteElementSpace as a 2D array such that elemen...
CPUArray< SignedIndex, 2 > element_dofs_
element_dofs_(e, i) stores the ith dof of element e.
DofNumbering(const mfem::ParFiniteElementSpace &fespace)
create lookup tables of which degrees of freedom correspond to each element and boundary element
CPUArray< SignedIndex, 2 > bdr_element_dofs_
bdr_element_dofs_(b, i) stores the ith dof of boundary element b.
Type
enum describing what kind of elements are included in a Domain
Definition: domain.hpp:24
a (poorly named) tuple of quantities used to discover the sparsity pattern associated with element an...
uint32_t global_col_
The global column number.
uint32_t element_id_
The element ID.
Domain::Type type
Which kind of Integral this entry comes from.
int sign_
The orientation of the element.
uint32_t local_col_
The global column number.
uint32_t global_row_
The global row number.
uint32_t local_row_
The local row number.
hash functor required for use in std::unordered_map
std::size_t operator()(const Entry &k) const
a hash function implementation for Entry
a type for representing a nonzero entry in a sparse matrix
bool operator<(const Entry &other) const
operator< is used when sorting Entry. Lexicographical ordering
uint32_t row
row value for this nonzero Entry
bool operator==(const Entry &other) const
operator== is required for use in std::unordered_map
uint32_t column
column value for this nonzero Entry
this object figures out the sparsity pattern associated with a finite element discretization of the g...
GradientAssemblyLookupTables(const serac::BlockElementRestriction &block_test_dofs, const serac::BlockElementRestriction &block_trial_dofs)
create lookup tables describing which degrees of freedom correspond to each domain/boundary element
std::vector< int > row_ptr
array holding the offsets for a given row of the sparse matrix i.e. row r corresponds to the indices ...
std::vector< int > col_ind
array holding the column associated with each nonzero entry
uint32_t nnz
how many nonzero entries appear in the sparse matrix
std::unordered_map< Entry, uint32_t, Entry::Hasher > nz_LUT
nz_LUT returns the index of the col_ind / value CSR arrays corresponding to the (i,...
uint32_t operator()(int i, int j) const
return the index (into the nonzero entries) corresponding to entry (i,j)
this type explicitly stores sign (typically used conveying edge/face orientation) and index values
uint32_t index_
the actual index of some quantity
int sign_
whether or not the value associated with this index is positive or negative