24 template <
int spatial_dim,
typename OutputSpace,
typename inputs = Parameters<>,
25 typename input_indices = std::make_
integer_sequence<
int, inputs::n>>
35 template <
int spatial_dim,
typename OutputSpace,
typename... InputSpaces,
int... input_indices>
37 std::integer_sequence<int, input_indices...>> :
public WeakForm {
39 using SpacesT = std::vector<const mfem::ParFiniteElementSpace*>;
52 const mfem::ParFiniteElementSpace& output_mfem_space,
const SpacesT& input_mfem_spaces)
53 :
WeakForm(physics_name), mesh_(mesh)
55 std::array<
const mfem::ParFiniteElementSpace*,
sizeof...(InputSpaces)> trial_spaces;
56 std::array<
const mfem::ParFiniteElementSpace*,
sizeof...(InputSpaces) + 1> vector_residual_trial_spaces{
60 sizeof...(InputSpaces) != input_mfem_spaces.size(),
61 std::format(
"{} parameter spaces given in the template argument but {} input mfem spaces were supplied.",
62 sizeof...(InputSpaces), input_mfem_spaces.size()));
65 validateSpace<OutputSpace>(output_mfem_space,
"output");
68 if constexpr (
sizeof...(InputSpaces) > 0) {
70 validateInputSpaces<0>(input_mfem_spaces);
72 for_constexpr<
sizeof...(InputSpaces)>([&](
auto i) { trial_spaces[i] = input_mfem_spaces[i]; });
74 [&](
auto i) { vector_residual_trial_spaces[i + 1] = input_mfem_spaces[i]; });
77 const auto& shape_disp_space = mesh->shapeDisplacementSpace();
79 weak_form_ = std::make_unique<ShapeAwareFunctional<
ShapeDispSpace, OutputSpace(InputSpaces...)>>(
80 &shape_disp_space, &output_mfem_space, trial_spaces);
82 v_dot_weak_form_residual_ =
83 std::make_unique<ShapeAwareFunctional<
ShapeDispSpace, double(OutputSpace, InputSpaces...)>>(
84 &shape_disp_space, vector_residual_trial_spaces);
108 template <
typename BodyIntegralType,
int... all_params>
109 void addBodyIntegralImpl(std::string body_name, BodyIntegralType integrand, std::integer_sequence<int, all_params...>)
111 const double* dt = &dt_;
112 const size_t* cycle = &cycle_;
114 weak_form_->AddDomainIntegral(
116 [dt, cycle, mode, integrand](
double time,
auto X,
auto... inputs) {
117 return integrand(
TimeInfo(time, *dt, *cycle, *mode), X, inputs...);
119 mesh_->domain(body_name));
121 v_dot_weak_form_residual_->AddDomainIntegral(
123 [dt, cycle, mode, integrand](
double time,
auto X,
auto V,
auto... inputs) {
124 auto orig_tuple = integrand(
TimeInfo(time, *dt, *cycle, *mode), X, inputs...);
125 return smith::inner(get<VALUE>(V), get<VALUE>(orig_tuple)) +
126 smith::inner(get<DERIVATIVE>(V), get<DERIVATIVE>(orig_tuple));
128 mesh_->domain(body_name));
132 template <
int... active_parameters,
typename BodyIntegralType>
135 addBodyIntegralImpl(body_name, integrand, std::integer_sequence<int, active_parameters...>{});
139 template <
typename BodyIntegralType>
142 addBodyIntegralImpl(body_name, integrand, std::make_integer_sequence<
int,
sizeof...(InputSpaces)>{});
162 template <
int... active_parameters,
typename BodyLoadType>
166 addBodyIntegral(depends_on, body_name, [load_function](
const TimeInfo& t_info,
auto X,
auto... inputs) {
172 template <
typename BodyLoadType>
175 addBodyIntegral(body_name, [load_function](
const TimeInfo& t_info,
auto X,
auto... inputs) {
201 template <
typename BoundaryIntegrandType,
int... all_params>
203 std::integer_sequence<int, all_params...>)
205 const double* dt = &dt_;
206 const size_t* cycle = &cycle_;
208 weak_form_->AddBoundaryIntegral(
210 [dt, cycle, mode, integrand](
double time,
auto X,
auto... params) {
211 return integrand(
TimeInfo(time, *dt, *cycle, *mode), X, params...);
213 mesh_->domain(boundary_name));
215 v_dot_weak_form_residual_->AddBoundaryIntegral(
217 [dt, cycle, mode, integrand](
double time,
auto X,
auto V,
auto... params) {
218 auto orig_surface_flux = integrand(
TimeInfo(time, *dt, *cycle, *mode), X, params...);
221 mesh_->domain(boundary_name));
225 template <
int... active_parameters,
typename BoundaryIntegrandType>
228 addBoundaryIntegralImpl(boundary_name, integrand, std::integer_sequence<int, active_parameters...>{});
232 template <
typename BoundaryIntegrandType>
235 addBoundaryIntegralImpl(boundary_name, integrand, std::make_integer_sequence<
int,
sizeof...(InputSpaces)>{});
254 template <
typename InteriorIntegrandType,
int... all_params>
256 std::integer_sequence<int, all_params...>)
258 const double* dt = &dt_;
259 const size_t* cycle = &cycle_;
261 weak_form_->AddInteriorFaceIntegral(
263 [dt, cycle, mode, integrand](
double time,
auto X,
auto... params) {
264 return integrand(
TimeInfo(time, *dt, *cycle, *mode), X, params...);
266 mesh_->domain(interior_name));
268 v_dot_weak_form_residual_->AddInteriorFaceIntegral(
270 [dt, cycle, mode, integrand](
double time,
auto X,
auto V,
auto... params) {
272 auto orig_surface_flux = integrand(
TimeInfo(time, *dt, *cycle, *mode), X, params...);
273 auto [flux_pos, flux_neg] = orig_surface_flux;
276 mesh_->domain(interior_name));
280 template <
int... active_parameters,
typename InteriorIntegrandType>
282 InteriorIntegrandType integrand)
284 addInteriorBoundaryIntegralImpl(interior_name, integrand, std::integer_sequence<int, active_parameters...>{});
288 template <
typename InteriorIntegrandType>
291 addInteriorBoundaryIntegralImpl(interior_name, integrand,
292 std::make_integer_sequence<
int,
sizeof...(InputSpaces)>{});
313 template <
int... active_parameters,
typename BoundaryFluxType>
316 BoundaryFluxType flux_function)
318 addBoundaryIntegral(depends_on, boundary_name, [flux_function](
const TimeInfo& t_info,
auto X,
auto... inputs) {
319 auto n =
cross(get<DERIVATIVE>(X));
320 return -flux_function(t_info, get<VALUE>(X),
normalize(n), get<VALUE>(inputs)...);
325 template <
typename BoundaryFluxType>
328 addBoundaryIntegral(boundary_name, [flux_function](
const TimeInfo& t_info,
auto X,
auto... inputs) {
329 auto n =
cross(get<DERIVATIVE>(X));
330 return -flux_function(t_info, get<VALUE>(X),
normalize(n), get<VALUE>(inputs)...);
336 [[maybe_unused]]
const std::vector<ConstQuadratureFieldPtr>& quad_fields = {})
const override
338 validateFields(fields,
"residual");
339 dt_ = time_info.
dt();
340 cycle_ = time_info.
cycle();
341 mode_ = time_info.
mode();
342 auto ret = (*weak_form_)(time_info.
time(), *shape_disp, *fields[input_indices]...);
349 const std::vector<double>& jacobian_weights,
350 [[maybe_unused]]
const std::vector<ConstQuadratureFieldPtr>& quad_fields = {})
const override
352 validateFields(fields,
"jacobian");
353 dt_ = time_info.
dt();
354 cycle_ = time_info.
cycle();
355 mode_ = time_info.
mode();
357 std::unique_ptr<mfem::HypreParMatrix> J;
359 auto addToJ = [&J](
double factor, std::unique_ptr<mfem::HypreParMatrix> jac_contrib) {
361 SLIC_ERROR_IF(J->N() != jac_contrib->N(),
362 "Multiple nonzero jacobian weights are being used on inconsistently sized input arguments.");
363 SLIC_ERROR_IF(J->M() != jac_contrib->M(),
364 "Multiple nonzero jacobian weights are being used on inconsistently sized input arguments.");
365 J->Add(factor, *jac_contrib);
367 J.reset(jac_contrib.release());
368 if (factor != 1.0) (*J) *= factor;
372 auto jacs = jacobianFunctions(std::make_integer_sequence<
int,
sizeof...(input_indices)>{}, time_info.
time(),
375 for (
size_t input_col = 0; input_col < jacobian_weights.size(); ++input_col) {
376 if (jacobian_weights[input_col] != 0.0) {
377 auto K = smith::get<DERIVATIVE>(jacs[input_col](time_info.
time(), shape_disp, fields));
378 addToJ(jacobian_weights[input_col], assemble(K));
387 [[maybe_unused]]
const std::vector<ConstQuadratureFieldPtr>& quad_fields,
388 [[maybe_unused]]
ConstFieldPtr v_shape_disp,
const std::vector<ConstFieldPtr>& v_fields,
389 [[maybe_unused]]
const std::vector<ConstQuadratureFieldPtr>& v_quad_fields,
392 validateFields(fields,
"jvp");
393 SLIC_ERROR_IF(v_fields.size() != fields.size(),
394 "Invalid number of field sensitivities relative to the number of fields");
396 dt_ = time_info.
dt();
397 cycle_ = time_info.
cycle();
398 mode_ = time_info.
mode();
400 auto jacs = jacobianFunctions(std::make_integer_sequence<
int,
sizeof...(input_indices)>{}, time_info.
time(),
405 for (
size_t input_col = 0; input_col < fields.size(); ++input_col) {
406 if (v_fields[input_col] !=
nullptr) {
407 auto K = smith::get<DERIVATIVE>(jacs[input_col](time_info.
time(), shape_disp, fields));
408 K.AddMult(*v_fields[input_col], *jvp_reaction);
415 [[maybe_unused]]
const std::vector<ConstQuadratureFieldPtr>& quad_fields,
ConstFieldPtr v_field,
416 DualFieldPtr vjp_shape_disp_sensitivity,
const std::vector<DualFieldPtr>& vjp_sensitivities,
417 [[maybe_unused]]
const std::vector<QuadratureFieldPtr>& vjp_quad_field_sensitivities)
const override
419 validateFields(fields,
"vjp");
420 SLIC_ERROR_IF(vjp_sensitivities.size() != fields.size(),
421 "Invalid number of field sensitivities relative to the number of fields");
423 dt_ = time_info.
dt();
424 cycle_ = time_info.
cycle();
425 mode_ = time_info.
mode();
427 auto vecJacs = vectorJacobianFunctions(std::make_integer_sequence<
int,
sizeof...(input_indices)>{},
428 time_info.
time(), shape_disp, v_field, fields);
430 auto shape_vjp = smith::get<DERIVATIVE>((*v_dot_weak_form_residual_)(
432 auto shape_vjp_vector = assemble(shape_vjp);
433 *vjp_shape_disp_sensitivity += *shape_vjp_vector;
436 for (
size_t input_col = 0; input_col < fields.size(); ++input_col) {
437 if (vjp_sensitivities[input_col] !=
nullptr) {
438 auto vec_jac = smith::get<DERIVATIVE>(vecJacs[input_col](time_info.
time(), shape_disp, v_field, fields));
439 auto vec_jac_mfem_vector = assemble(vec_jac);
440 *vjp_sensitivities[input_col] += *vec_jac_mfem_vector;
454 return *v_dot_weak_form_residual_;
458 template <
typename BodyIntegralType,
int... all_params>
461 std::integer_sequence<int, all_params...>)
463 addBodyIntegralImpl(body_name, integrand, std::integer_sequence<int, all_params...>{});
466 template <
typename BodyLoadType,
int... all_params>
469 std::integer_sequence<int, all_params...>)
471 (void)std::integer_sequence<int, all_params...>{};
472 addBodySource(body_name, load_function);
475 template <
typename BoundaryIntegrandType,
int... all_params>
478 std::integer_sequence<int, all_params...>)
480 addBoundaryIntegralImpl(boundary_name, integrand, std::integer_sequence<int, all_params...>{});
483 template <
typename BoundaryFluxType,
int... all_params>
486 std::integer_sequence<int, all_params...>)
488 (void)std::integer_sequence<int, all_params...>{};
489 addBoundaryFlux(boundary_name, flux_function);
492 template <
typename InteriorIntegrandType,
int... all_params>
495 std::integer_sequence<int, all_params...>)
497 addInteriorBoundaryIntegralImpl(interior_name, integrand, std::integer_sequence<int, all_params...>{});
504 if constexpr (I <
sizeof...(InputSpaces)) {
506 validateSpace<Space>(*input_mfem_spaces[I], axom::fmt::format(
"input[{}]", I));
507 validateInputSpaces<I + 1>(input_mfem_spaces);
515 if constexpr (I <
sizeof...(InputSpaces)) {
517 validateSpace<Space>(fields[I]->
space(),
518 axom::fmt::format(
"{}(): field[{}] ('{}')", method_name, I, fields[I]->name()));
519 validateFieldsRecursive<I + 1>(fields, method_name);
524 void validateFields(
const std::vector<ConstFieldPtr>& fields,
const std::string& method_name)
const
526 SLIC_ERROR_ROOT_IF(fields.size() !=
sizeof...(InputSpaces),
527 axom::fmt::format(
"{}(): fields.size()={} but weak form expects {} InputSpaces", method_name,
528 fields.size(),
sizeof...(InputSpaces)));
530 if constexpr (
sizeof...(InputSpaces) > 0) {
531 validateFieldsRecursive<0>(fields, method_name);
536 template <
typename Space>
537 static void validateSpace(
const mfem::ParFiniteElementSpace& mfem_space,
const std::string& space_name)
539 const auto* fec = mfem_space.FEColl();
543 if constexpr (Space::family == Family::H1) {
544 std::string fec_name = fec->Name();
546 (fec_name.find(
"H1") != std::string::npos || fec_name.find(
"ND_") != std::string::npos ||
547 fec_name.find(
"Linear") != std::string::npos);
548 SLIC_ERROR_ROOT_IF(!is_h1, axom::fmt::format(
"Space '{}': Template specifies H1 family but mfem space uses '{}'",
549 space_name, fec_name));
550 }
else if constexpr (Space::family == Family::L2) {
551 std::string fec_name = fec->Name();
552 bool is_l2 = (fec_name.find(
"L2") != std::string::npos || fec_name.find(
"DG") != std::string::npos ||
553 fec_name.find(
"Const") != std::string::npos);
555 !is_l2, axom::fmt::format(
"Space '{}': Template specifies L2/DG family but mfem space uses '{}'", space_name,
560 SLIC_ERROR_ROOT_IF(fec->GetOrder() != Space::order,
561 axom::fmt::format(
"Space '{}': Template specifies order {} but mfem space has order {}",
562 space_name, Space::order, fec->GetOrder()));
566 mfem_space.GetVDim() != Space::components,
567 axom::fmt::format(
"Space '{}': Template specifies {} components but mfem space has {} components (VDim)",
568 space_name, Space::components, mfem_space.GetVDim()));
574 const std::vector<ConstFieldPtr>& fs)
const
576 using JacFuncType = std::function<decltype((*weak_form_)(
DifferentiateWRT<1>{}, time, *shape_disp, *fs[i]...))(
578 return std::array<JacFuncType,
sizeof...(i)>{
579 [
this](
double _time,
ConstFieldPtr _shape_disp,
const std::vector<ConstFieldPtr>& _fs) {
587 const std::vector<ConstFieldPtr>& fs)
const
590 std::function<decltype((*v_dot_weak_form_residual_)(
DifferentiateWRT<1>{}, time, *shape_disp, *v, *fs[i]...))(
592 return std::array<GradFuncType,
sizeof...(i)>{
602 mutable size_t cycle_ = 0;
619 inline std::vector<const mfem::ParFiniteElementSpace*>
getSpaces(
const std::vector<smith::FiniteElementState>& states)
621 std::vector<const mfem::ParFiniteElementSpace*>
spaces;
622 for (
auto& f : states) {
623 spaces.push_back(&f.space());
Class for encapsulating the dual vector space of a finite element space (i.e. the space of linear for...
This contains a class that represents the dual of a finite element vector space, i....
This file contains the declaration of structure that manages the MFEM objects that make up the state ...
Smith mesh class which assists in constructing the appropriate parallel mfem meshes and registering a...
Accelerator functionality.
std::vector< const mfem::ParFiniteElementSpace * > getSpaces(const std::vector< smith::FiniteElementState > &states)
Helper function to construct vector of spaces from an existing vector of FiniteElementState.
std::vector< const mfem::ParFiniteElementSpace * > spaces(const std::vector< FieldState > &states, const std::vector< FieldState > ¶ms={})
Get the spaces from the primal fields of a vector of field states.
SMITH_HOST_DEVICE auto cross(const tensor< T, 3, 2 > &A)
compute the cross product of the columns of A: A(:,1) x A(:,2)
SMITH_HOST_DEVICE auto max(dual< gradient_type > a, double b)
Implementation of max for dual numbers.
mfem::future::tuple< T... > tuple
Expose MFEM tuple in the Smith namespace.
constexpr SMITH_HOST_DEVICE auto inner(const dual< S > &A, const dual< T > &B)
mfem::future::tuple_element< I, T > tuple_element
Alias for the MFEM tuple element trait.
mfem::ParFiniteElementSpace & space(FieldState field)
Get the space from the primal field of a field states.
SMITH_HOST_DEVICE auto normalize(const tensor< T, n... > &A)
Normalizes the tensor Each element is divided by the Frobenius norm of the tensor,...
FiniteElementState const * ConstFieldPtr
using
Wrapper of smith::Functional for evaluating integrals and derivatives of quantities with shape displa...
Compile-time alias for a dimension.
a struct that is used in the physics modules to clarify which template arguments are user-controlled ...
struct storing time and timestep information
double dt() const
accessor for dt
size_t cycle() const
accessor for cycle
EvaluationMode
Evaluation mode for the current step.
@ Regular
Normal evaluation step.
double time() const
accessor for the current time
EvaluationMode mode() const
accessor for residual evaluation mode.
A sentinel struct for eliding no-op tensor operations.