MeshFields
GPU accelerated mesh-based fields
MeshField_Element.hpp
1 #ifndef MESHFIELD_ELEMENT_H
2 #define MESHFIELD_ELEMENT_H
3 
4 #include <Kokkos_Core.hpp>
5 #include <MeshField_Defines.hpp>
6 #include <MeshField_Fail.hpp>
7 #include <MeshField_Shape.hpp>
8 #include <MeshField_Utility.hpp> // getLastValue
9 #include <iostream>
10 #include <type_traits> // has_static_size helper
11 
12 namespace {
13 Kokkos::View<MeshField::LO *> getOffsets(MeshField::LO numItems,
14  MeshField::LO numEntriesPerItem) {
15  Kokkos::View<MeshField::LO *> offsets("offsets", numItems + 1);
16  auto first = Kokkos::subview(offsets, 0);
17  Kokkos::deep_copy(first, 0); // write 0 to the first item
18  Kokkos::parallel_for(
19  numItems, KOKKOS_LAMBDA(const int i) {
20  offsets(i + 1) = (i + 1) * numEntriesPerItem;
21  });
22  return offsets;
23 }
24 
25 // chatgpt prompt 2/20/2025:
26 // c++ static assert that checks that a type
27 // provides a function named size
28 template <typename T> class has_size_method {
29 private:
30  template <typename U>
31  static auto test(int) -> decltype(std::declval<U>().size(), std::true_type());
32  template <typename> static std::false_type test(...);
33 
34 public:
35  static constexpr bool value = decltype(test<T>(0))::value;
36 };
37 
38 template <typename T> class has_static_rank {
39 private:
40  template <typename U>
41  static auto test(int) -> decltype(U::rank(), std::true_type());
42  template <typename> static std::false_type test(...);
43 
44 public:
45  static constexpr bool value = decltype(test<T>(0))::value;
46 };
47 
48 template <typename T> class has_extent_method {
49 private:
50  template <typename U>
51  static auto test(int)
52  -> decltype(std::declval<U>().extent(std::declval<std::size_t>()),
53  std::true_type());
54  template <typename> static std::false_type test(...);
55 
56 public:
57  static constexpr bool value = decltype(test<T>(0))::value;
58 };
59 
60 // FIXME - addTensorProduct(...) likely needs performance improvements
62 template <typename VecA, typename VecB, typename Matrix>
63 KOKKOS_INLINE_FUNCTION auto addTensorProduct(VecA const &a, VecB const &b,
64  Matrix &A) {
65  static_assert(has_size_method<VecA>::value,
66  "VecA must have a size() method.");
67  static_assert(has_size_method<VecB>::value,
68  "VecB must have a size() method.");
69  static_assert(has_static_rank<VecA>::value,
70  "VecA must have a static rank() method.");
71  static_assert(has_static_rank<VecB>::value,
72  "VecB must have a static rank() method.");
73  static_assert(
74  std::is_same_v<typename VecA::value_type, typename VecB::value_type>);
75  static_assert(VecA::rank() == 1);
76  static_assert(VecB::rank() == 1);
77  static_assert(has_extent_method<Matrix>::value,
78  "Matrix must have an extent(size_t) method.");
79  const auto M = a.size();
80  const auto N = b.size();
81  for (std::size_t i = 0; i < M; ++i) {
82  for (std::size_t j = 0; j < N; ++j) {
83  A(i, j) += b[j] * a[i];
84  }
85  }
86 }
87 } // namespace
88 
89 namespace MeshField {
90 
91 
92 //
93 
104  LO node;
105 
111 
115  LO entity;
116 
120  Mesh_Topology topo;
121 };
122 
146 template <typename FieldAccessor, typename ShapeType,
147  typename ElementDofHolderAccessor>
148 struct FieldElement {
149  const size_t numMeshEnts;
150  const FieldAccessor field;
151  ShapeType shapeFn;
152  ElementDofHolderAccessor elm2dof;
153 
154  static const size_t MeshEntDim = ShapeType::meshEntDim;
155  FieldElement(size_t numMeshEntsIn, const FieldAccessor &fieldIn,
156  const ShapeType shapeFnIn,
157  const ElementDofHolderAccessor elm2dofIn)
158  : numMeshEnts(numMeshEntsIn), field(fieldIn), shapeFn(shapeFnIn),
159  elm2dof(elm2dofIn) {}
160  /* general template for baseType which simply sets type
161  */
162  template <typename T> struct baseType {
163  using type = T;
164  };
165  /* template specialization to recursively strip type to get base type
166  * Example: int[5][6] => int[6] => int
167  */
168  template <typename T, size_t N> struct baseType<T[N]> {
169  using type = typename baseType<T>::type;
170  };
171  using ValArray =
172  Kokkos::Array<typename baseType<typename FieldAccessor::BaseType>::type,
173  FieldAccessor::numComp>;
174  static const size_t NumComponents = FieldAccessor::numComp;
175 
191  KOKKOS_INLINE_FUNCTION ValArray
192  getValue(int ent, Kokkos::Array<Real, MeshEntDim> localCoord) const {
193  assert(ent >= 0);
194  assert(static_cast<size_t>(ent) < numMeshEnts);
195  ValArray c;
196  const auto shapeValues = shapeFn.getValues(localCoord);
197  for (size_t ci = 0; ci < NumComponents; ++ci)
198  c[ci] = 0;
199  for (auto topo : elm2dof.getTopology()) { // element topology
200  for (size_t ni = 0; ni < shapeFn.numNodes; ++ni) {
201  for (size_t ci = 0; ci < NumComponents; ++ci) {
202  auto map = elm2dof(ni, ci, ent, topo);
203  const auto fval =
204  field(map.entity, map.node, map.component, map.topo);
205  c[ci] += fval * shapeValues[ni];
206  }
207  }
208  }
209  return c;
210  }
211 
212  using NodeArray =
213  Kokkos::Array<typename baseType<typename FieldAccessor::BaseType>::type,
214  ShapeType::meshEntDim * ShapeType::numNodes>;
215  KOKKOS_INLINE_FUNCTION NodeArray getNodeValues(int ent) const {
216  NodeArray c;
217  for (auto topo : elm2dof.getTopology()) { // element topology
218  for (size_t ni = 0; ni < ShapeType::numNodes; ++ni) {
219  for (size_t d = 0; d < ShapeType::meshEntDim; ++d) {
220  auto map = elm2dof(ni, d, ent, topo);
221  const auto fval =
222  field(map.entity, map.node, map.component, map.topo);
223  c[ni * ShapeType::meshEntDim + d] = fval;
224  }
225  }
226  }
227  return c;
228  }
229 
242  KOKKOS_INLINE_FUNCTION Real getJacobian1d(int ent) const {
243  assert(ent >= 0);
244  assert(static_cast<size_t>(ent) < numMeshEnts);
245  Vector1 ignored;
246  const auto nodalGradients = shapeFn.getLocalGradients(ignored);
247  const auto nodeValues = getNodeValues(ent);
248  auto g = nodalGradients[0] * nodeValues[0];
249  for (size_t i = 1; i < shapeFn.numNodes; ++i) {
250  g = g + nodalGradients[i] * nodeValues[i];
251  }
252  return g;
253  }
254 
259  template <typename Matrices>
260  Kokkos::View<Real *> getJacobianDeterminants(Matrices const &J) const {
261  static_assert(has_static_rank<Matrices>::value,
262  "Matrices must have a static rank() method.");
263  static_assert(has_extent_method<Matrices>::value,
264  "Matrices must have an extent(size_t) method.");
265  static_assert(Matrices::rank() == 3); // array of rank two matrices
266  if (J.extent(1) != J.extent(2)) {
267  fail("getJacobianDeterminant only supports square matrices. "
268  "The given matrices have dimension %lu x %lu \n",
269  J.extent(1), J.extent(2));
270  }
271  const auto dimension = J.extent(1);
272  if (dimension > 3 || dimension < 1) {
273  fail("getJacobianDeterminant: invalid dimension of input matrix. "
274  "The given matrices have dimension %lu x %lu \n",
275  J.extent(0), J.extent(1));
276  }
277  if (dimension == 3) {
278  /* det(J) is also the triple product of the
279  "tangent vectors" in 3D, the volume of their
280  parallelpiped, which is the differential volume
281  of the coordinate field */
282  Kokkos::View<Real *> determinants("3dJacobianDeterminants", J.extent(0));
283  Kokkos::parallel_for(
284  J.extent(0), KOKKOS_LAMBDA(const int i) {
285  const auto Ji = Kokkos::subview(J, i, Kokkos::ALL(), Kokkos::ALL());
286  const auto cofactorSum =
287  Ji(0, 0) * (Ji(1, 1) * Ji(2, 2) - Ji(1, 2) * Ji(2, 1)) -
288  Ji(0, 1) * (Ji(1, 0) * Ji(2, 2) - Ji(1, 2) * Ji(2, 0)) +
289  Ji(0, 2) * (Ji(1, 0) * Ji(2, 1) - Ji(1, 1) * Ji(2, 0));
290  determinants(i) = cofactorSum;
291  });
292  return determinants;
293  }
294  if (dimension == 2) {
295  if (J.extent(1) != 2) {
296  fail("getJacobianDeterminant only supports 2x2 matrices in 2d. "
297  "The given matrices have dimension %lu x %lu \n",
298  J.extent(1), J.extent(2));
299  }
300  /* |\frac{\partial x}{\partial s}\times
301  \frac{\partial x}{\partial t}|,
302  the area spanned by the tangent vectors
303  at this point, surface integral. */
304  Kokkos::View<Real *> determinants("2dJacobianDeterminants", J.extent(0));
305  // compute the cross product of the 2x2 jacobian matrix
306  Kokkos::parallel_for(
307  J.extent(0), KOKKOS_LAMBDA(const int i) {
308  // TODO use nested parallel for?
309  auto Ji = Kokkos::subview(J, i, Kokkos::ALL(), Kokkos::ALL());
310  const auto cross = Ji(0, 0) * Ji(1, 1) - Ji(1, 0) * Ji(0, 1);
311  const auto magnitude = Kokkos::fabs(cross);
312  determinants(i) = magnitude;
313  });
314  return determinants;
315  }
316  if (dimension == 1) {
317  fail("getJacobianDeterminant doesn't yet support 1d. "
318  "The given matrices have dimension %lu x %lu \n",
319  J.extent(0), J.extent(1));
320  }
321  // assuming at this point dimension=1
322  /* \|\vec{x}_{,\xi}\| the length
323  of the tangent vector at this point.
324  line integral:
325  ds = sqrt(dx^2 + dy^2 + dz^2) */
326  return Kokkos::View<Real *>("foo", J.extent(0));
327  }
328 
344  Kokkos::View<Real ***> getJacobians(Kokkos::View<Real **> localCoords,
345  Kokkos::View<LO *> offsets) const {
346  if (Debug) {
347  // check input parametric coords are positive and sum to one
348  // TODO move this to helper function
349  LO numErrors = 0;
350  Kokkos::parallel_reduce(
351  "checkCoords", numMeshEnts,
352  KOKKOS_LAMBDA(const int &ent, LO &lerrors) {
353  Real sum = 0;
354  LO isError = 0;
355  for (size_t i = 0; i < localCoords.extent(1); i++) {
356  if (localCoords(ent, i) < 0)
357  isError++;
358  sum += localCoords(ent, i);
359  }
360  if (sum > 1.0)
361  isError++;
362  lerrors += isError;
363  },
364  numErrors);
365  if (numErrors) {
366  fail("One or more of the parametric coordinates passed "
367  "to evaluate(...) were invalid\n");
368  }
369  }
370  if (localCoords.extent(0) < numMeshEnts) {
371  fail("The size of dimension 0 of the local coordinates input array "
372  "must be at least %zu.\n",
373  numMeshEnts);
374  }
375  if (localCoords.extent(1) != MeshEntDim) {
376  fail("Dimension 1 of the input array of local coordinates "
377  "must have size = %zu.\n",
378  MeshEntDim);
379  }
380  if (offsets.size() != numMeshEnts + 1) {
381  fail("The input array of offsets must have size = %zu\n",
382  numMeshEnts + 1);
383  }
384  if (MeshEntDim != 1 && MeshEntDim != 2 && MeshEntDim != 3) {
385  fail("getJacobians only currently supports 1d, 2d, and 3d meshes. Input "
386  "mesh "
387  "has %zu dimensions.\n",
388  numMeshEnts);
389  }
390  if constexpr (MeshEntDim == 1) {
391  const auto numPts = MeshFieldUtil::getLastValue(offsets);
392  Kokkos::View<Real ***> res("result", numPts, 1, 1);
393  Kokkos::parallel_for(
394  numMeshEnts, KOKKOS_CLASS_LAMBDA(const int ent) {
395  // TODO use nested parallel for?
396  for (auto pt = offsets(ent); pt < offsets(ent + 1); pt++) {
397  const auto val = getJacobian1d(ent);
398  res(pt, 0, 0) = val;
399  }
400  });
401  return res;
402  } else if constexpr (MeshEntDim == 2 || MeshEntDim == 3) {
403  const auto numPts = MeshFieldUtil::getLastValue(offsets);
404  // one matrix per point
405  Kokkos::View<Real ***> res("result", numPts, MeshEntDim, MeshEntDim);
406  Kokkos::deep_copy(res, 0.0); // initialize all entries to zero
407 
408  // fill the views of node coordinates and node gradients
409  Kokkos::View<Real * [ShapeType::numNodes][MeshEntDim]> nodeCoords(
410  "nodeCoords", numPts);
411  Kokkos::View<Real * [ShapeType::numNodes][MeshEntDim]> nodalGradients(
412  "nodalGradients", numPts);
413  Kokkos::parallel_for(
414  numMeshEnts, KOKKOS_CLASS_LAMBDA(const int ent) {
415  const auto vals = getNodeValues(ent);
416  assert(vals.size() == MeshEntDim * ShapeType::numNodes);
417  for (auto pt = offsets(ent); pt < offsets(ent + 1); pt++) {
418  Kokkos::Array<Real, MeshEntDim> xi;
419  for (size_t d = 0; d < MeshEntDim; d++)
420  xi[d] = localCoords(pt, d);
421  const auto grad = shapeFn.getLocalGradients(xi);
422  for (size_t node = 0; node < ShapeType::numNodes; node++) {
423  for (size_t d = 0; d < MeshEntDim; d++) {
424  nodeCoords(pt, node, d) = vals[node * MeshEntDim + d];
425  nodalGradients(pt, node, d) = grad[node * MeshEntDim + d];
426  }
427  }
428  }
429  });
430 
431  Kokkos::parallel_for(
432  numMeshEnts, KOKKOS_LAMBDA(const int ent) {
433  // TODO use nested parallel for?
434  for (auto pt = offsets(ent); pt < offsets(ent + 1); pt++) {
435  auto A = Kokkos::subview(res, pt, Kokkos::ALL(), Kokkos::ALL());
436  for (size_t node = 0; node < ShapeType::numNodes; node++) {
437  auto a =
438  Kokkos::subview(nodalGradients, pt, node, Kokkos::ALL());
439  auto b = Kokkos::subview(nodeCoords, pt, node, Kokkos::ALL());
440  addTensorProduct(a, b, A);
441  }
442  }
443  });
444  return res;
445  }
446  }
447 };
448 
467 template <typename FieldElement>
468 Kokkos::View<Real *[FieldElement::NumComponents]>
469 evaluate(FieldElement &fes, Kokkos::View<Real **> localCoords,
470  Kokkos::View<LO *> offsets) {
471  if (Debug) {
472  // check input parametric coords are positive and sum to one
473  LO numErrors = 0;
474  Kokkos::parallel_reduce(
475  "checkCoords", fes.numMeshEnts,
476  KOKKOS_LAMBDA(const int &ent, LO &lerrors) {
477  Real sum = 0;
478  LO isError = 0;
479  for (size_t i = 0; i < localCoords.extent(1); i++) {
480  if (localCoords(ent, i) < 0)
481  isError++;
482  sum += localCoords(ent, i);
483  }
484  if (sum > 1.0)
485  isError++;
486  lerrors += isError;
487  },
488  numErrors);
489  if (numErrors) {
490  fail("One or more of the parametric coordinates passed "
491  "to evaluate(...) were invalid\n");
492  }
493  }
494 
495  if (localCoords.extent(1) != fes.MeshEntDim) {
496  fail("Dimension 1 of the input array of local coordinates "
497  "must have size = %zu.\n",
498  fes.MeshEntDim);
499  }
500  if (offsets.size() != fes.numMeshEnts + 1) {
501  fail("The input array of offsets must have size = %zu\n",
502  fes.numMeshEnts + 1);
503  }
504  LO numLocalCoords;
505  Kokkos::deep_copy(numLocalCoords,
506  Kokkos::subview(offsets, offsets.size() - 1));
507  if (localCoords.extent(0) != static_cast<size_t>(numLocalCoords)) {
508  fail("The size of dimension 0 of the local coordinates input array (%zu) "
509  "does not match the last entry of the offsets array (%d).\n",
510  localCoords.extent(0), numLocalCoords);
511  }
512 
513  constexpr const auto numComponents = FieldElement::ValArray::size();
514  const auto numPts = MeshFieldUtil::getLastValue(offsets);
515  Kokkos::View<Real *[numComponents]> res("result", numPts);
516  Kokkos::parallel_for(
517  fes.numMeshEnts, KOKKOS_LAMBDA(const int ent) {
518  Kokkos::Array<Real, FieldElement::MeshEntDim> lc;
519  // TODO use nested parallel for?
520  for (auto pt = offsets(ent); pt < offsets(ent + 1); pt++) {
521  for (size_t i = 0; i < localCoords.extent(1); i++) // better way?
522  lc[i] = localCoords(pt, i);
523  const auto val = fes.getValue(ent, lc);
524  for (size_t i = 0; i < numComponents; i++)
525  res(pt, i) = val[i];
526  }
527  });
528  return res;
529 }
530 
539 template <typename FieldElement>
540 Kokkos::View<Real *[FieldElement::NumComponents]> evaluate(
541  FieldElement &fes, Kokkos::View<Real **> localCoords) {
542  const auto numPtsPerElement = 1;
543  return evaluate(fes, localCoords, numPtsPerElement);
544 }
545 
557 template <typename FieldElement>
558 Kokkos::View<Real *[FieldElement::NumComponents]> evaluate(
559  FieldElement &fes, Kokkos::View<Real **> localCoords,
560  size_t numPtsPerElement) {
561  const auto offsets = getOffsets(fes.numMeshEnts, numPtsPerElement);
562  return evaluate(fes, localCoords, offsets);
563 }
564 
579 template <typename FieldElement>
580 Kokkos::View<Real ***> getJacobians(FieldElement &fes,
581  Kokkos::View<Real **> localCoords,
582  size_t numPtsPerElement) {
583  const auto offsets = getOffsets(fes.numMeshEnts, numPtsPerElement);
584  return fes.getJacobians(localCoords, offsets);
585 }
586 
597 template <typename FieldElement, typename Matrices>
598 Kokkos::View<Real *> getJacobianDeterminants(FieldElement &fes, Matrices J) {
599  return fes.getJacobianDeterminants(J);
600 }
601 
602 } // namespace MeshField
603 #endif
Shape function definitions for finite element analysis.
Kokkos::Array< Real, 1 > Vector1
1D parametric coordinate vector
Supports mapping between mesh (i.e., Omega_h) ordering and MeshFields ordering.
Supports the evaluation of a field, and other per-element operations, given the definition of the ele...
Kokkos::View< Real * > getJacobianDeterminants(Matrices const &J) const
Kokkos::View< Real *** > getJacobians(Kokkos::View< Real ** > localCoords, Kokkos::View< LO * > offsets) const
Given an array of parametric coordinates 'localCoords', one per mesh element, compute the jacobian wi...
KOKKOS_INLINE_FUNCTION Real getJacobian1d(int ent) const
compute the Jacobian of an edge
KOKKOS_INLINE_FUNCTION ValArray getValue(int ent, Kokkos::Array< Real, MeshEntDim > localCoord) const
evaluate the field in the specified element at the specified parametric/local/area coordinate