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 axom::fmt::format(
"{} parameter spaces given in the template argument but {} input mfem spaces were supplied.",
62 sizeof...(InputSpaces), input_mfem_spaces.size()));
64 if constexpr (
sizeof...(InputSpaces) > 0) {
65 for_constexpr<
sizeof...(InputSpaces)>([&](
auto i) { trial_spaces[i] = input_mfem_spaces[i]; });
67 [&](
auto i) { vector_residual_trial_spaces[i + 1] = input_mfem_spaces[i]; });
70 const auto& shape_disp_space = mesh->shapeDisplacementSpace();
72 weak_form_ = std::make_unique<ShapeAwareFunctional<
ShapeDispSpace, OutputSpace(InputSpaces...)>>(
73 &shape_disp_space, &output_mfem_space, trial_spaces);
75 v_dot_weak_form_residual_ =
76 std::make_unique<ShapeAwareFunctional<
ShapeDispSpace, double(OutputSpace, InputSpaces...)>>(
77 &shape_disp_space, vector_residual_trial_spaces);
103 template <
int... active_parameters,
typename BodyIntegralType>
107 mesh_->domain(body_name));
109 v_dot_weak_form_residual_->AddDomainIntegral(
111 [integrand](
double t,
auto X,
auto V,
auto... inputs) {
112 auto orig_tuple = integrand(t, X, inputs...);
113 return serac::inner(get<VALUE>(V), get<VALUE>(orig_tuple)) +
114 serac::inner(get<DERIVATIVE>(V), get<DERIVATIVE>(orig_tuple));
116 mesh_->domain(body_name));
120 template <
typename BodyForceType>
123 addBodyIntegral(
DependsOn<>{}, body_name, body_integral);
146 template <
int... active_parameters,
typename BodyLoadType>
149 addBodyIntegral(depends_on, body_name, [load_function](
double t,
auto X,
auto... inputs) {
155 template <
int... active_parameters,
typename BodyLoadType>
158 return addBodySource(
DependsOn<>{}, body_name, load_function);
184 template <
int... active_parameters,
typename BoundaryIntegrandType>
188 mesh_->domain(boundary_name));
190 v_dot_weak_form_residual_->AddBoundaryIntegral(
192 [integrand](
double t,
auto X,
auto V,
auto... params) {
193 auto orig_surface_flux = integrand(t, X, params...);
196 mesh_->domain(boundary_name));
200 template <
typename BoundaryIntegrandType>
203 addBoundaryIntegral(
DependsOn<>{}, boundary_name, integrand);
227 template <
int... active_parameters,
typename BoundaryFluxType>
229 BoundaryFluxType flux_function)
231 addBoundaryIntegral(depends_on, boundary_name, [flux_function](
double t,
auto X,
auto... inputs) {
232 auto n =
cross(get<DERIVATIVE>(X));
233 return -flux_function(t, get<VALUE>(X),
normalize(n), get<VALUE>(inputs)...);
238 template <
typename BoundaryFluxType>
241 addBoundaryFlux(
DependsOn<>{}, boundary_name, integrand);
246 [[maybe_unused]]
const std::vector<ConstQuadratureFieldPtr>& quad_fields = {},
247 int block_row = 0)
const override
249 SLIC_ERROR_IF(block_row != 0,
"Invalid block row and column requested in fieldJacobian for FunctionalResidual");
251 auto ret = (*weak_form_)(time, *shape_disp, *fields[input_indices]...);
257 double time,
double dt,
ConstFieldPtr shape_disp,
const std::vector<ConstFieldPtr>& fields,
258 const std::vector<double>& jacobian_weights,
259 [[maybe_unused]]
const std::vector<ConstQuadratureFieldPtr>& quad_fields = {},
int block_row = 0)
const override
261 SLIC_ERROR_IF(block_row != 0,
"Invalid block row and column requested in fieldJacobian for FunctionalResidual");
265 std::unique_ptr<mfem::HypreParMatrix> J;
267 auto addToJ = [&J](
double factor, std::unique_ptr<mfem::HypreParMatrix> jac_contrib) {
269 SLIC_ERROR_IF(J->N() != jac_contrib->N(),
270 "Multiple nonzero jacobian weights are being used on inconsistently sized input arguments.");
271 SLIC_ERROR_IF(J->M() != jac_contrib->M(),
272 "Multiple nonzero jacobian weights are being used on inconsistently sized input arguments.");
273 J->Add(factor, *jac_contrib);
275 J.reset(jac_contrib.release());
276 if (factor != 1.0) (*J) *= factor;
281 jacobianFunctions(std::make_integer_sequence<
int,
sizeof...(input_indices)>{}, time, shape_disp, fields);
283 for (
size_t input_col = 0; input_col < jacobian_weights.size(); ++input_col) {
284 if (jacobian_weights[input_col] != 0.0) {
285 auto K = serac::get<DERIVATIVE>(jacs[input_col](time, shape_disp, fields));
286 addToJ(jacobian_weights[input_col], assemble(K));
294 void jvp(
double time,
double dt,
ConstFieldPtr shape_disp,
const std::vector<ConstFieldPtr>& fields,
295 [[maybe_unused]]
const std::vector<ConstQuadratureFieldPtr>& quad_fields,
296 [[maybe_unused]]
ConstFieldPtr v_shape_disp,
const std::vector<ConstFieldPtr>& v_fields,
297 [[maybe_unused]]
const std::vector<ConstQuadratureFieldPtr>& v_quad_fields,
298 const std::vector<DualFieldPtr>& jvp_reactions)
const override
300 SLIC_ERROR_IF(v_fields.size() != fields.size(),
301 "Invalid number of field sensitivities relative to the number of fields");
302 SLIC_ERROR_IF(jvp_reactions.size() != 1,
"FunctionalResidual nonlinear systems only supports 1 output residual");
306 jacobianFunctions(std::make_integer_sequence<
int,
sizeof...(input_indices)>{}, time, shape_disp, fields);
308 *jvp_reactions[0] = 0.0;
310 for (
size_t input_col = 0; input_col < fields.size(); ++input_col) {
311 if (v_fields[input_col] !=
nullptr) {
312 auto K = serac::get<DERIVATIVE>(jacs[input_col](time, shape_disp, fields));
313 K.AddMult(*v_fields[input_col], *jvp_reactions[0]);
319 void vjp(
double time,
double dt,
ConstFieldPtr shape_disp,
const std::vector<ConstFieldPtr>& fields,
320 [[maybe_unused]]
const std::vector<ConstQuadratureFieldPtr>& quad_fields,
321 const std::vector<ConstFieldPtr>& v_fields,
DualFieldPtr vjp_shape_disp_sensitivity,
322 const std::vector<DualFieldPtr>& vjp_sensitivities,
323 [[maybe_unused]]
const std::vector<QuadratureFieldPtr>& vjp_quad_field_sensitivities)
const override
325 SLIC_ERROR_IF(vjp_sensitivities.size() != fields.size(),
326 "Invalid number of field sensitivities relative to the number of fields");
327 SLIC_ERROR_IF(v_fields.size() != 1,
"FunctionalResidual nonlinear systems only supports 1 output residual");
330 auto vecJacs = vectorJacobianFunctions(std::make_integer_sequence<
int,
sizeof...(input_indices)>{}, time,
331 shape_disp, v_fields[0], fields);
333 auto shape_vjp = serac::get<DERIVATIVE>((*v_dot_weak_form_residual_)(
DifferentiateWRT<0>{}, time, *shape_disp,
334 *v_fields[0], *fields[input_indices]...));
335 auto shape_vjp_vector = assemble(shape_vjp);
336 *vjp_shape_disp_sensitivity += *shape_vjp_vector;
339 for (
size_t input_col = 0; input_col < fields.size(); ++input_col) {
340 if (vjp_sensitivities[input_col] !=
nullptr) {
341 auto vec_jac = serac::get<DERIVATIVE>(vecJacs[input_col](time, shape_disp, v_fields[0], fields));
342 auto vec_jac_mfem_vector = assemble(vec_jac);
343 *vjp_sensitivities[input_col] += *vec_jac_mfem_vector;
357 return *v_dot_weak_form_residual_;
364 const std::vector<ConstFieldPtr>& fs)
const
366 using JacFuncType = std::function<decltype((*weak_form_)(
DifferentiateWRT<1>{}, time, *shape_disp, *fs[i]...))(
368 return std::array<JacFuncType,
sizeof...(i)>{
369 [=](
double _time,
ConstFieldPtr _shape_disp,
const std::vector<ConstFieldPtr>& _fs) {
377 const std::vector<ConstFieldPtr>& fs)
const
380 std::function<decltype((*v_dot_weak_form_residual_)(
DifferentiateWRT<1>{}, time, *shape_disp, *v, *fs[i]...))(
382 return std::array<GradFuncType,
sizeof...(i)>{
403 inline std::vector<const mfem::ParFiniteElementSpace*>
getSpaces(
const std::vector<serac::FiniteElementState>& states)
405 std::vector<const mfem::ParFiniteElementSpace*> spaces;
406 for (
auto& f : states) {
407 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 ...
Serac mesh class which assists in constructing the appropriate parallel mfem meshes and registering a...
Accelerator functionality.
constexpr SERAC_HOST_DEVICE auto inner(const dual< S > &A, const dual< T > &B)
SERAC_HOST_DEVICE auto max(dual< gradient_type > a, double b)
Implementation of max for dual numbers.
FiniteElementState const * ConstFieldPtr
using
std::vector< const mfem::ParFiniteElementSpace * > getSpaces(const std::vector< serac::FiniteElementState > &states)
Helper function to construct vector of spaces from an existing vector of FiniteElementState.
auto cross(const tensor< T, 3, 2 > &A)
compute the cross product of the columns of A: A(:,1) x A(:,2)
SERAC_HOST_DEVICE auto normalize(const tensor< T, n... > &A)
Normalizes the tensor Each element is divided by the Frobenius norm of the tensor,...
Wrapper of serac::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 ...
This is a class that mimics most of std::tuple's interface, except that it is usable in CUDA kernels ...
A sentinel struct for eliding no-op tensor operations.