Serac  0.1
Serac is an implicit thermal strucural mechanics simulation code.
shape_aware_functional.hpp
Go to the documentation of this file.
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 
14 #pragma once
15 
17 
18 namespace serac {
19 
21 constexpr int SOURCE = 0;
22 constexpr int FLUX = 1;
23 constexpr int VALUE = 0;
24 constexpr int DERIVATIVE = 1;
26 
27 namespace detail {
28 
36 template <int dim, typename shape_type>
38 public:
45  : J_(Identity<dim>() + get<DERIVATIVE>(p)), detJ_(det(J_)), inv_J_(inv(J_)), inv_JT_(transpose(inv_J_))
46  {
47  }
48 
60  template <typename trial_type, typename space_type>
61  SERAC_HOST_DEVICE auto modify_trial_argument(space_type /* space */, const trial_type& u) const
62  {
63  if constexpr (space_type{}.family == Family::H1 || space_type{}.family == Family::L2) {
64  // Our updated spatial coordinate is x' = x + p. We want to calculate
65  // du/dx' = du/dx * dx/dx'
66  // = du/dx * (dx'/dx)^-1
67  // = du_dx * (J)^-1
68  auto trial_derivative = dot(get<DERIVATIVE>(u), inv_J_);
69 
70  return serac::tuple{get<VALUE>(u), trial_derivative};
71  }
72 
73  if constexpr (space_type{}.family == Family::HCURL) {
74  auto modified_val = dot(get<VALUE>(u), inv_J_);
75  if constexpr (dim == 2) {
76  auto modified_derivative = get<DERIVATIVE>(u) / detJ_;
77  return serac::tuple{modified_val, modified_derivative};
78  }
79  if constexpr (dim == 3) {
80  auto modified_derivative = dot(get<DERIVATIVE>(u), transpose(J_));
81  return serac::tuple{modified_val, modified_derivative};
82  }
83  }
84  }
85 
97  template <typename test_space, typename q_func_type>
98  SERAC_HOST_DEVICE auto modify_shape_aware_qf_return(test_space /*test*/, const q_func_type& v)
99  {
100  if constexpr (std::is_same_v<test_space, double>) {
101  return detJ_ * v;
102  } else {
103  if constexpr (test_space{}.family == Family::H1 || test_space{}.family == Family::L2) {
104  auto modified_flux = dot(get<FLUX>(v), inv_JT_) * detJ_;
105  auto modified_source = get<SOURCE>(v) * detJ_;
106 
107  return serac::tuple{modified_source, modified_flux};
108  }
109  if constexpr (test_space{}.family == Family::HCURL) {
110  auto modified_source = dot(get<SOURCE>(v), inv_JT_) * detJ_;
111  if constexpr (dim == 3) {
112  auto modified_flux = dot(get<FLUX>(v), J_);
113  return serac::tuple{modified_source, modified_flux};
114  } else {
115  return serac::tuple{modified_source, get<FLUX>(v)};
116  }
117  }
118  }
119  }
120 
121 private:
123  using jacobian_type = std::remove_reference_t<decltype(get<DERIVATIVE>(std::declval<shape_type>()))>;
124  using detJ_type = decltype(det(std::declval<jacobian_type>()));
125  using inv_J_type = decltype(inv(std::declval<jacobian_type>()));
126  using inv_JT_type = decltype(inv(transpose(std::declval<jacobian_type>())));
128 
130  jacobian_type J_;
131 
133  detJ_type detJ_;
134 
136  inv_J_type inv_J_;
137 
139  inv_JT_type inv_JT_;
140 };
141 
154 template <typename position_type, typename shape_type>
155 SERAC_HOST_DEVICE auto compute_boundary_area_correction(const position_type& X, const shape_type& shape)
156 {
157  auto x_prime = X + shape;
158 
159  auto n = cross(get<DERIVATIVE>(x_prime));
160 
161  // serac::Functional's boundary integrals multiply the q-function output by
162  // norm(cross(dX_dxi)) at that quadrature point, but if we impose a shape displacement
163  // then that weight needs to be corrected. The new weight should be
164  // norm(cross(dX_dxi + dp_dxi)), so we multiply by the ratio w_new / w_old
165  // to get
166  // q * area_correction * w_old
167  // = q * (w_new / w_old) * w_old
168  // = q * w_new
169 
170  auto area_correction = norm(n) / norm(cross(get<DERIVATIVE>(X)));
171 
172  return area_correction;
173 }
174 
199 template <typename lambda, typename coord_type, typename shape_type, typename space_types, typename trial_types,
200  typename correction_type, int... i>
201 SERAC_HOST_DEVICE auto apply_shape_aware_qf_helper(lambda&& qf, double t, const coord_type& position,
202  const shape_type& shape, const space_types& space_tuple,
203  const trial_types& arg_tuple, const correction_type& correction,
204  std::integer_sequence<int, i...>)
205 {
207  "Argument and finite element space tuples are not the same size.");
208 
209  auto x = serac::tuple{get<VALUE>(position) + get<VALUE>(shape),
210 
211  // x := X + u,
212  // so, dx/dxi = dX/dxi + du/dxi
213  // = dX/dxi + du/dX * dX/dxi
214  get<DERIVATIVE>(position) + get<DERIVATIVE>(shape) * get<DERIVATIVE>(position)};
215 
216  return qf(t, x, correction.modify_trial_argument(serac::get<i>(space_tuple), serac::get<i>(arg_tuple))...);
217 }
218 
245 template <typename lambda, typename coord_type, typename state_type, typename shape_type, typename space_types,
246  typename trial_types, typename correction_type, int... i>
247 SERAC_HOST_DEVICE auto apply_shape_aware_qf_helper_with_state(lambda&& qf, double t, const coord_type& position,
248  state_type& state, const shape_type& shape,
249  const space_types& space_tuple,
250  const trial_types& arg_tuple,
251  const correction_type& correction,
252  std::integer_sequence<int, i...>)
253 {
255  "Argument and finite element space tuples are not the same size.");
256 
257  auto x = serac::tuple{get<VALUE>(position) + get<VALUE>(shape),
258 
259  // x := X + u,
260  // so, dx/dxi = dX/dxi + du/dxi
261  // = dX/dxi + du/dX * dX/dxi
262  get<DERIVATIVE>(position) + get<DERIVATIVE>(shape) * get<DERIVATIVE>(position)};
263 
264  return qf(t, x, state, correction.modify_trial_argument(serac::get<i>(space_tuple), serac::get<i>(arg_tuple))...);
265 }
266 
267 } // namespace detail
268 
270 template <typename T1, typename T2, ExecutionSpace exec = serac::default_execution_space>
271 class ShapeAwareFunctional;
273 
291 template <typename test, typename shape, typename... trials, ExecutionSpace exec>
292 class ShapeAwareFunctional<shape, test(trials...), exec> {
294  static constexpr tuple<trials...> trial_spaces{};
295 
297  static constexpr test test_space{};
298 
300  static constexpr shape shape_space{};
301 
303  static constexpr uint32_t num_trial_spaces = sizeof...(trials);
304 
305 public:
316  template <typename test_type = test, typename = std::enable_if_t<!std::is_same_v<double, test_type>>>
317  ShapeAwareFunctional(const mfem::ParFiniteElementSpace* shape_fes, const mfem::ParFiniteElementSpace* test_fes,
318  std::array<const mfem::ParFiniteElementSpace*, num_trial_spaces> trial_fes)
319  {
320  static_assert(shape_space.family == Family::H1, "Only H1 spaces allowed for shape displacements");
321 
322  std::array<const mfem::ParFiniteElementSpace*, num_trial_spaces + 1> prepended_spaces;
323 
324  prepended_spaces[0] = shape_fes;
325 
326  for (uint32_t i = 0; i < num_trial_spaces; ++i) {
327  prepended_spaces[1 + i] = trial_fes[i];
328  }
329 
330  functional_ = std::make_unique<Functional<test(shape, trials...), exec>>(test_fes, prepended_spaces);
331  }
332 
342  template <typename test_type = test, typename = std::enable_if_t<std::is_same_v<double, test_type>>>
343  ShapeAwareFunctional(const mfem::ParFiniteElementSpace* shape_fes,
344  std::array<const mfem::ParFiniteElementSpace*, num_trial_spaces> trial_fes)
345  {
346  static_assert(shape_space.family == Family::H1, "Only H1 spaces allowed for shape displacements");
347 
348  std::array<const mfem::ParFiniteElementSpace*, num_trial_spaces + 1> prepended_spaces;
349 
350  prepended_spaces[0] = shape_fes;
351 
352  for (uint32_t i = 0; i < num_trial_spaces; ++i) {
353  prepended_spaces[1 + i] = trial_fes[i];
354  }
355 
356  functional_ = std::make_unique<Functional<double(shape, trials...), exec>>(prepended_spaces);
357  }
358 
375  template <int dim, int... args, typename lambda, typename domain_type, typename qpt_data_type = Nothing>
376  void AddDomainIntegral(Dimension<dim>, DependsOn<args...>, lambda&& integrand, domain_type& domain,
377  std::shared_ptr<QuadratureData<qpt_data_type>> qdata = NoQData)
378  {
379  if constexpr (std::is_same_v<qpt_data_type, Nothing>) {
380  functional_->AddDomainIntegral(
381  Dimension<dim>{}, DependsOn<0, (args + 1)...>{},
382  [integrand](double time, auto x, auto shape_val, auto... qfunc_args) {
383  auto qfunc_tuple = make_tuple(qfunc_args...);
384  auto reduced_trial_space_tuple = make_tuple(get<args>(trial_spaces)...);
385 
386  detail::ShapeCorrection shape_correction(Dimension<dim>{}, shape_val);
387 
388  auto unmodified_qf_return = detail::apply_shape_aware_qf_helper(
389  integrand, time, x, shape_val, reduced_trial_space_tuple, qfunc_tuple, shape_correction,
390  std::make_integer_sequence<int, sizeof...(qfunc_args)>{});
391  return shape_correction.modify_shape_aware_qf_return(test_space, unmodified_qf_return);
392  },
393  domain, qdata);
394  } else {
395  functional_->AddDomainIntegral(
396  Dimension<dim>{}, DependsOn<0, (args + 1)...>{},
397  [integrand](double time, auto x, auto& state, auto shape_val, auto... qfunc_args) {
398  auto qfunc_tuple = make_tuple(qfunc_args...);
399  auto reduced_trial_space_tuple = make_tuple(get<args>(trial_spaces)...);
400 
401  detail::ShapeCorrection shape_correction(Dimension<dim>{}, shape_val);
402 
403  auto unmodified_qf_return = detail::apply_shape_aware_qf_helper_with_state(
404  integrand, time, x, state, shape_val, reduced_trial_space_tuple, qfunc_tuple, shape_correction,
405  std::make_integer_sequence<int, sizeof...(qfunc_args)>{});
406  return shape_correction.modify_shape_aware_qf_return(test_space, unmodified_qf_return);
407  },
408  domain, qdata);
409  }
410  }
411 
426  template <int dim, int... args, typename lambda, typename domain_type>
427  void AddBoundaryIntegral(Dimension<dim>, DependsOn<args...>, lambda&& integrand, domain_type& domain)
428  {
429  functional_->AddBoundaryIntegral(
430  Dimension<dim>{}, DependsOn<0, (args + 1)...>{},
431  [integrand](double time, auto x, auto shape_val, auto... qfunc_args) {
432  auto unmodified_qf_return = integrand(time, x + shape_val, qfunc_args...);
433 
434  return unmodified_qf_return * detail::compute_boundary_area_correction(x, shape_val);
435  },
436  domain);
437  }
438 
454  template <uint32_t wrt, typename... T>
455  auto operator()(DifferentiateWRT<wrt>, double t, const T&... args)
456  {
457  return (*functional_)(DifferentiateWRT<wrt>{}, t, args...);
458  }
459 
473  template <typename... T>
474  auto operator()(double t, const T&... args)
475  {
476  return (*functional_)(t, args...);
477  }
478 
487  void updateQdata(bool update_flag) { functional_->updateQdata(update_flag); }
488 
489 private:
491  std::unique_ptr<Functional<test(shape, trials...), exec>> functional_;
492 };
493 
494 } // namespace serac
#define SERAC_HOST_DEVICE
Macro that evaluates to __host__ __device__ when compiling with nvcc and does nothing on a host compi...
Definition: accelerator.hpp:38
auto operator()(double t, const T &... args)
This function lets the user evaluate the serac::ShapeAwareFunctional with the given trial space value...
auto operator()(DifferentiateWRT< wrt >, double t, const T &... args)
This function lets the user evaluate the serac::ShapeAwareFunctional with the given trial space value...
ShapeAwareFunctional(const mfem::ParFiniteElementSpace *shape_fes, const mfem::ParFiniteElementSpace *test_fes, std::array< const mfem::ParFiniteElementSpace *, num_trial_spaces > trial_fes)
Constructs using mfem::ParFiniteElementSpace objects corresponding to the test/trial spaces.
void AddBoundaryIntegral(Dimension< dim >, DependsOn< args... >, lambda &&integrand, domain_type &domain)
Adds a boundary integral term to the weak formulation of the PDE.
void AddDomainIntegral(Dimension< dim >, DependsOn< args... >, lambda &&integrand, domain_type &domain, std::shared_ptr< QuadratureData< qpt_data_type >> qdata=NoQData)
Adds a domain integral term to the weak formulation of the PDE.
void updateQdata(bool update_flag)
A flag to update the quadrature data for this operator following the computation.
ShapeAwareFunctional(const mfem::ParFiniteElementSpace *shape_fes, std::array< const mfem::ParFiniteElementSpace *, num_trial_spaces > trial_fes)
Constructs a QOI functional using mfem::ParFiniteElementSpace objects corresponding to the trial spac...
Implementation of the quadrature-function-based functional enabling rapid development of FEM formulat...
Accelerator functionality.
Definition: serac.cpp:38
constexpr SERAC_HOST_DEVICE auto dot(const isotropic_tensor< S, m, m > &I, const tensor< T, m, n... > &A)
dot product between an isotropic and (nonisotropic) tensor
std::shared_ptr< QuadratureData< Nothing > > NoQData
a single instance of a QuadratureData container of Nothings, since they are all interchangeable
constexpr T & get(variant< T0, T1 > &v)
Returns the variant member of specified type.
Definition: variant.hpp:338
SERAC_HOST_DEVICE tuple< T... > make_tuple(const T &... args)
helper function for combining a list of values into a tuple
Definition: tuple.hpp:180
constexpr SERAC_HOST_DEVICE auto det(const isotropic_tensor< T, m, m > &I)
compute the determinant of an isotropic tensor
constexpr SERAC_HOST_DEVICE auto norm(const isotropic_tensor< T, m, m > &I)
compute the Frobenius norm (sqrt(tr(dot(transpose(I), I)))) of an isotropic tensor
auto cross(const tensor< T, 3, 2 > &A)
compute the cross product of the columns of A: A(:,1) x A(:,2)
Definition: tensor.hpp:906
constexpr SERAC_HOST_DEVICE auto inv(const isotropic_tensor< T, m, m > &I)
return the inverse of an isotropic tensor
constexpr SERAC_HOST_DEVICE auto transpose(const isotropic_tensor< T, m, m > &I)
return the transpose of an isotropic tensor
ExecutionSpace
enum used for signalling whether or not to perform certain calculations on the CPU or GPU
Definition: accelerator.hpp:69
constexpr SERAC_HOST_DEVICE isotropic_tensor< double, m, m > Identity()
return the identity matrix of the specified size
SERAC_HOST_DEVICE auto apply_shape_aware_qf_helper_with_state(lambda &&qf, double t, const coord_type &position, state_type &state, const shape_type &shape, const space_types &space_tuple, const trial_types &arg_tuple, const correction_type &correction, std::integer_sequence< int, i... >)
A helper function to modify all of the trial function input derivatives according to the given shape ...
SERAC_HOST_DEVICE auto apply_shape_aware_qf_helper(lambda &&qf, double t, const coord_type &position, const shape_type &shape, const space_types &space_tuple, const trial_types &arg_tuple, const correction_type &correction, std::integer_sequence< int, i... >)
A helper function to modify all of the trial function input derivatives according to the given shape ...
SERAC_HOST_DEVICE auto compute_boundary_area_correction(const position_type &X, const shape_type &shape)
Compute the boundary area correction term for boundary integrals with a shape displacement field.
Compile-time alias for a dimension.
Definition: geometry.hpp:11
these classes are a little confusing. These two special types represent the similar (but different) c...
A class for storing and access user-defined types at quadrature points.
A helper struct that contains the appropriate parent-to-physical and physical-to-parent transformatio...
SERAC_HOST_DEVICE auto modify_trial_argument(space_type, const trial_type &u) const
Modify the trial argument using the correct physical to reference to shape-adjusted transformation fo...
ShapeCorrection(Dimension< dim >, shape_type p)
Construct a new Shape Correction object with the appropriate transformations for a shape field.
SERAC_HOST_DEVICE auto modify_shape_aware_qf_return(test_space, const q_func_type &v)
Modify the quadrature function return value using the correct shape-adjusted to reference transformat...
This is a class that mimics most of std::tuple's interface, except that it is usable in CUDA kernels ...
Definition: tuple.hpp:28