Serac  0.1
Serac is an implicit thermal strucural mechanics simulation code.
solid_mechanics.hpp
Go to the documentation of this file.
1 // Copyright (c) 2019-2023, 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 
13 #pragma once
14 
15 #include "mfem.hpp"
16 
18 #include "serac/physics/common.hpp"
21 #include "serac/numerics/odes.hpp"
26 
27 namespace serac {
28 
29 namespace solid_mechanics {
30 
31 namespace detail {
35 void adjoint_integrate(double dt_n, double dt_np1, mfem::HypreParMatrix* m_mat, mfem::HypreParMatrix* k_mat,
36  mfem::HypreParVector& disp_adjoint_load_vector, mfem::HypreParVector& velo_adjoint_load_vector,
37  mfem::HypreParVector& accel_adjoint_load_vector, mfem::HypreParVector& adjoint_displacement_,
38  mfem::HypreParVector& implicit_sensitivity_displacement_start_of_step_,
39  mfem::HypreParVector& implicit_sensitivity_velocity_start_of_step_,
40  mfem::HypreParVector& adjoint_essential, BoundaryConditionManager& bcs_,
41  mfem::Solver& lin_solver);
42 } // namespace detail
43 
50  .preconditioner = Preconditioner::HypreAMG,
51  .relative_tol = 1.0e-6,
52  .absolute_tol = 1.0e-16,
53  .max_iterations = 500,
54  .print_level = 0};
55 
57 #ifdef MFEM_USE_STRUMPACK
59 #else
61 #endif
62 
69  .relative_tol = 1.0e-4,
70  .absolute_tol = 1.0e-8,
71  .max_iterations = 10,
72  .print_level = 1};
73 
76 
80 
81 } // namespace solid_mechanics
82 
83 template <int order, int dim, typename parameters = Parameters<>,
84  typename parameter_indices = std::make_integer_sequence<int, parameters::n>>
86 
97 template <int order, int dim, typename... parameter_space, int... parameter_indices>
98 class SolidMechanics<order, dim, Parameters<parameter_space...>, std::integer_sequence<int, parameter_indices...>>
99  : public BasePhysics {
100 public:
102  static constexpr int VALUE = 0, DERIVATIVE = 1;
103  static constexpr int SHAPE = 0;
104  static constexpr auto I = Identity<dim>();
106 
109  static constexpr auto NUM_STATE_VARS = 2;
110 
113  template <typename T>
114  using qdata_type = std::shared_ptr<QuadratureData<T>>;
115 
134  SolidMechanics(const NonlinearSolverOptions nonlinear_opts, const LinearSolverOptions lin_opts,
135  const serac::TimesteppingOptions timestepping_opts, const GeometricNonlinearities geom_nonlin,
136  const std::string& physics_name, std::string mesh_tag, std::vector<std::string> parameter_names = {},
137  int cycle = 0, double time = 0.0, bool checkpoint_to_disk = false)
138  : SolidMechanics(
139  std::make_unique<EquationSolver>(nonlinear_opts, lin_opts, StateManager::mesh(mesh_tag).GetComm()),
140  timestepping_opts, geom_nonlin, physics_name, mesh_tag, parameter_names, cycle, time, checkpoint_to_disk)
141  {
142  }
143 
161  SolidMechanics(std::unique_ptr<serac::EquationSolver> solver, const serac::TimesteppingOptions timestepping_opts,
162  const GeometricNonlinearities geom_nonlin, const std::string& physics_name, std::string mesh_tag,
163  std::vector<std::string> parameter_names = {}, int cycle = 0, double time = 0.0,
164  bool checkpoint_to_disk = false)
165  : BasePhysics(physics_name, mesh_tag, cycle, time, checkpoint_to_disk),
166  displacement_(
167  StateManager::newState(H1<order, dim>{}, detail::addPrefix(physics_name, "displacement"), mesh_tag_)),
168  velocity_(StateManager::newState(H1<order, dim>{}, detail::addPrefix(physics_name, "velocity"), mesh_tag_)),
169  acceleration_(
170  StateManager::newState(H1<order, dim>{}, detail::addPrefix(physics_name, "acceleration"), mesh_tag_)),
171  adjoint_displacement_(StateManager::newState(
172  H1<order, dim>{}, detail::addPrefix(physics_name, "adjoint_displacement"), mesh_tag_)),
173  displacement_adjoint_load_(displacement_.space(), detail::addPrefix(physics_name, "displacement_adjoint_load")),
174  velocity_adjoint_load_(displacement_.space(), detail::addPrefix(physics_name, "velocity_adjoint_load")),
175  acceleration_adjoint_load_(displacement_.space(), detail::addPrefix(physics_name, "acceleration_adjoint_load")),
176  implicit_sensitivity_displacement_start_of_step_(displacement_.space(), "total_deriv_wrt_displacement."),
177  implicit_sensitivity_velocity_start_of_step_(displacement_.space(), "total_deriv_wrt_velocity."),
178  reactions_(StateManager::newDual(H1<order, dim>{}, detail::addPrefix(physics_name, "reactions"), mesh_tag_)),
179  nonlin_solver_(std::move(solver)),
180  ode2_(displacement_.space().TrueVSize(),
181  {.time = ode_time_point_, .c0 = c0_, .c1 = c1_, .u = u_, .du_dt = v_, .d2u_dt2 = acceleration_},
182  *nonlin_solver_, bcs_),
183  geom_nonlin_(geom_nonlin)
184  {
185  SLIC_ERROR_ROOT_IF(mesh_.Dimension() != dim,
186  axom::fmt::format("Compile time dimension, {0}, and runtime mesh dimension, {1}, mismatch", dim,
187  mesh_.Dimension()));
188 
189  SLIC_ERROR_ROOT_IF(!nonlin_solver_,
190  "EquationSolver argument is nullptr in SolidMechanics constructor. It is possible that it was "
191  "previously moved.");
192 
193  // Check for dynamic mode
194  if (timestepping_opts.timestepper != TimestepMethod::QuasiStatic) {
195  ode2_.SetTimestepper(timestepping_opts.timestepper);
196  ode2_.SetEnforcementMethod(timestepping_opts.enforcement_method);
197  is_quasistatic_ = false;
198  } else {
199  is_quasistatic_ = true;
200  }
201 
202  states_.push_back(&displacement_);
203  if (!is_quasistatic_) {
204  states_.push_back(&velocity_);
205  states_.push_back(&acceleration_);
206  }
207 
208  adjoints_.push_back(&adjoint_displacement_);
209 
210  duals_.push_back(&reactions_);
211 
212  // Create a pack of the primal field and parameter finite element spaces
213  mfem::ParFiniteElementSpace* test_space = &displacement_.space();
214  mfem::ParFiniteElementSpace* shape_space = &shape_displacement_.space();
215 
216  std::array<const mfem::ParFiniteElementSpace*, NUM_STATE_VARS + sizeof...(parameter_space)> trial_spaces;
217  trial_spaces[0] = &displacement_.space();
218  trial_spaces[1] = &displacement_.space();
219 
220  SLIC_ERROR_ROOT_IF(
221  sizeof...(parameter_space) != parameter_names.size(),
222  axom::fmt::format("{} parameter spaces given in the template argument but {} parameter names were supplied.",
223  sizeof...(parameter_space), parameter_names.size()));
224 
225  if constexpr (sizeof...(parameter_space) > 0) {
226  tuple<parameter_space...> types{};
227  for_constexpr<sizeof...(parameter_space)>([&](auto i) {
228  parameters_.emplace_back(mesh_, get<i>(types), detail::addPrefix(name_, parameter_names[i]));
229 
230  trial_spaces[i + NUM_STATE_VARS] = &(parameters_[i].state->space());
231  });
232  }
233 
234  residual_ = std::make_unique<ShapeAwareFunctional<shape_trial, test(trial, trial, parameter_space...)>>(
235  shape_space, test_space, trial_spaces);
236 
237  // If the user wants the AMG preconditioner with a linear solver, set the pfes
238  // to be the displacement
239  auto* amg_prec = dynamic_cast<mfem::HypreBoomerAMG*>(nonlin_solver_->preconditioner());
240  if (amg_prec) {
241  // amg_prec->SetElasticityOptions(&displacement_.space());
242 
243  // TODO: The above call was seg faulting in the HYPRE_BoomerAMGSetInterpRefine(amg_precond, interp_refine)
244  // method as of Hypre version v2.26.0. Instead, we just set the system size for Hypre. This is a temporary work
245  // around as it will decrease the effectiveness of the preconditioner.
246  amg_prec->SetSystemsOptions(dim, true);
247  }
248 
249  int true_size = velocity_.space().TrueVSize();
250 
251  u_.SetSize(true_size);
252  v_.SetSize(true_size);
253 
254  du_.SetSize(true_size);
255  dr_.SetSize(true_size);
256  predicted_displacement_.SetSize(true_size);
257 
258  shape_displacement_ = 0.0;
259  initializeSolidMechanicsStates();
260  }
261 
271  SolidMechanics(const SolidMechanicsInputOptions& input_options, const std::string& physics_name, std::string mesh_tag,
272  int cycle = 0, double time = 0.0)
273  : SolidMechanics(input_options.nonlin_solver_options, input_options.lin_solver_options,
274  input_options.timestepping_options, input_options.geom_nonlin, physics_name, mesh_tag, {}, cycle,
275  time)
276  {
277  for (auto& mat : input_options.materials) {
278  if (std::holds_alternative<serac::solid_mechanics::NeoHookean>(mat)) {
279  setMaterial(std::get<serac::solid_mechanics::NeoHookean>(mat));
280  } else if (std::holds_alternative<serac::solid_mechanics::LinearIsotropic>(mat)) {
281  setMaterial(std::get<serac::solid_mechanics::LinearIsotropic>(mat));
282  } else if (std::holds_alternative<serac::solid_mechanics::J2>(mat)) {
283  if constexpr (dim == 3) {
284  solid_mechanics::J2::State initial_state{};
285  setMaterial(std::get<serac::solid_mechanics::J2>(mat), createQuadratureDataBuffer(initial_state));
286  } else {
287  SLIC_ERROR_ROOT("J2 materials only work for 3D simulations");
288  }
290  mat)) {
291  if constexpr (dim == 3) {
294  createQuadratureDataBuffer(initial_state));
295  } else {
296  SLIC_ERROR_ROOT("J2Nonlinear materials only work for 3D simulations");
297  }
299  mat)) {
300  if constexpr (dim == 3) {
303  createQuadratureDataBuffer(initial_state));
304  } else {
305  SLIC_ERROR_ROOT("J2Nonlinear materials only work for 3D simulations");
306  }
307  } else {
308  SLIC_ERROR("Invalid material type.");
309  }
310  }
311 
312  if (input_options.initial_displacement) {
313  displacement_.project(input_options.initial_displacement->constructVector(dim));
314  }
315 
316  if (input_options.initial_velocity) {
317  velocity_.project(input_options.initial_velocity->constructVector(dim));
318  }
319 
320  for (const auto& [bc_name, bc] : input_options.boundary_conditions) {
321  // FIXME: Better naming for boundary conditions?
322  if (bc_name.find("displacement") != std::string::npos) {
323  if (bc.coef_opts.isVector()) {
324  std::shared_ptr<mfem::VectorCoefficient> disp_coef(bc.coef_opts.constructVector(dim));
325  bcs_.addEssential(bc.attrs, disp_coef, displacement_.space());
326  } else {
327  SLIC_ERROR_ROOT_IF(
328  !bc.coef_opts.component,
329  "Component not specified with scalar coefficient when setting the displacement condition.");
330  std::shared_ptr<mfem::Coefficient> disp_coef(bc.coef_opts.constructScalar());
331  bcs_.addEssential(bc.attrs, disp_coef, displacement_.space(), *bc.coef_opts.component);
332  }
333  } else if (bc_name.find("traction") != std::string::npos) {
334  // TODO: Not implemented yet in input files
335  SLIC_ERROR("'traction' is not implemented yet in input files.");
336  } else if (bc_name.find("traction_ref") != std::string::npos) {
337  // TODO: Not implemented yet in input files
338  SLIC_ERROR("'traction_ref' is not implemented yet in input files.");
339  } else if (bc_name.find("pressure") != std::string::npos) {
340  // TODO: Not implemented yet in input files
341  SLIC_ERROR("'pressure' is not implemented yet in input files.");
342  } else if (bc_name.find("pressure_ref") != std::string::npos) {
343  // TODO: Not implemented yet in input files
344  SLIC_ERROR("'pressure_ref' is not implemented yet in input files.");
345  } else {
346  SLIC_WARNING_ROOT("Ignoring boundary condition with unknown name: " << bc_name);
347  }
348  }
349  }
350 
352  virtual ~SolidMechanics() {}
353 
361  {
362  c0_ = 0.0;
363  c1_ = 0.0;
364 
365  displacement_ = 0.0;
366  velocity_ = 0.0;
367  acceleration_ = 0.0;
368 
369  adjoint_displacement_ = 0.0;
370  displacement_adjoint_load_ = 0.0;
371  velocity_adjoint_load_ = 0.0;
372  acceleration_adjoint_load_ = 0.0;
373 
374  implicit_sensitivity_displacement_start_of_step_ = 0.0;
375  implicit_sensitivity_velocity_start_of_step_ = 0.0;
376 
377  reactions_ = 0.0;
378 
379  u_ = 0.0;
380  v_ = 0.0;
381  du_ = 0.0;
382  dr_ = 0.0;
383  predicted_displacement_ = 0.0;
384 
385  if (!checkpoint_to_disk_) {
386  checkpoint_states_.clear();
387 
388  checkpoint_states_["displacement"].push_back(displacement_);
389  checkpoint_states_["velocity"].push_back(velocity_);
390  checkpoint_states_["acceleration"].push_back(acceleration_);
391  }
392  }
393 
400  void resetStates(int cycle = 0, double time = 0.0) override
401  {
403  initializeSolidMechanicsStates();
404  }
405 
413  template <typename T>
415  {
416  constexpr auto Q = order + 1;
417 
418  std::array<uint32_t, mfem::Geometry::NUM_GEOMETRIES> elems = geometry_counts(mesh_);
419  std::array<uint32_t, mfem::Geometry::NUM_GEOMETRIES> qpts_per_elem{};
420 
421  std::vector<mfem::Geometry::Type> geometries;
422  if (dim == 2) {
423  geometries = {mfem::Geometry::TRIANGLE, mfem::Geometry::SQUARE};
424  } else {
425  geometries = {mfem::Geometry::TETRAHEDRON, mfem::Geometry::CUBE};
426  }
427 
428  for (auto geom : geometries) {
429  qpts_per_elem[size_t(geom)] = uint32_t(num_quadrature_points(geom, Q));
430  }
431 
432  return std::make_shared<QuadratureData<T>>(elems, qpts_per_elem, initial_state);
433  }
434 
446  void setDisplacementBCs(const std::set<int>& disp_bdr, std::function<void(const mfem::Vector&, mfem::Vector&)> disp)
447  {
448  // Project the coefficient onto the grid function
449  disp_bdr_coef_ = std::make_shared<mfem::VectorFunctionCoefficient>(dim, disp);
450 
451  bcs_.addEssential(disp_bdr, disp_bdr_coef_, displacement_.space());
452  }
453 
465  void setDisplacementBCs(const std::set<int>& disp_bdr,
466  std::function<void(const mfem::Vector&, double, mfem::Vector&)> disp)
467  {
468  // Project the coefficient onto the grid function
469  disp_bdr_coef_ = std::make_shared<mfem::VectorFunctionCoefficient>(dim, disp);
470 
471  bcs_.addEssential(disp_bdr, disp_bdr_coef_, displacement_.space());
472  }
473 
486  void setDisplacementBCs(const std::set<int>& disp_bdr, std::function<double(const mfem::Vector& x)> disp,
487  int component)
488  {
489  // Project the coefficient onto the grid function
490  component_disp_bdr_coef_ = std::make_shared<mfem::FunctionCoefficient>(disp);
491 
492  bcs_.addEssential(disp_bdr, component_disp_bdr_coef_, displacement_.space(), component);
493  }
494 
513  void setDisplacementBCsByDofList(const mfem::Array<int> true_dofs,
514  std::function<void(const mfem::Vector&, double, mfem::Vector&)> disp)
515  {
516  disp_bdr_coef_ = std::make_shared<mfem::VectorFunctionCoefficient>(dim, disp);
517 
518  bcs_.addEssential(true_dofs, disp_bdr_coef_, displacement_.space());
519  }
520 
535  void setDisplacementBCsByDofList(const mfem::Array<int> true_dofs,
536  std::function<void(const mfem::Vector&, mfem::Vector&)> disp)
537  {
538  disp_bdr_coef_ = std::make_shared<mfem::VectorFunctionCoefficient>(dim, disp);
539 
540  bcs_.addEssential(true_dofs, disp_bdr_coef_, displacement_.space());
541  }
542 
557  void setDisplacementBCs(std::function<bool(const mfem::Vector&)> is_node_constrained,
558  std::function<void(const mfem::Vector&, double, mfem::Vector&)> disp)
559  {
560  auto constrained_dofs = calculateConstrainedDofs(is_node_constrained);
561 
562  setDisplacementBCsByDofList(constrained_dofs, disp);
563  }
564 
579  void setDisplacementBCs(std::function<bool(const mfem::Vector&)> is_node_constrained,
580  std::function<void(const mfem::Vector&, mfem::Vector&)> disp)
581  {
582  auto constrained_dofs = calculateConstrainedDofs(is_node_constrained);
583 
584  setDisplacementBCsByDofList(constrained_dofs, disp);
585  }
586 
604  void setDisplacementBCs(std::function<bool(const mfem::Vector&)> is_node_constrained,
605  std::function<double(const mfem::Vector&, double)> disp, int component)
606  {
607  auto constrained_dofs = calculateConstrainedDofs(is_node_constrained, component);
608 
609  auto vector_function = [disp, component](const mfem::Vector& x, double time, mfem::Vector& displacement) {
610  displacement = 0.0;
611  displacement(component) = disp(x, time);
612  };
613 
614  setDisplacementBCsByDofList(constrained_dofs, vector_function);
615  }
616 
634  void setDisplacementBCs(std::function<bool(const mfem::Vector& x)> is_node_constrained,
635  std::function<double(const mfem::Vector& x)> disp, int component)
636  {
637  auto constrained_dofs = calculateConstrainedDofs(is_node_constrained, component);
638 
639  auto vector_function = [disp, component](const mfem::Vector& x, mfem::Vector& displacement) {
640  displacement = 0.0;
641  displacement(component) = disp(x);
642  };
643 
644  setDisplacementBCsByDofList(constrained_dofs, vector_function);
645  }
646 
653  const FiniteElementState& state(const std::string& state_name) const override
654  {
655  if (state_name == "displacement") {
656  return displacement_;
657  } else if (state_name == "velocity") {
658  return velocity_;
659  } else if (state_name == "acceleration") {
660  return acceleration_;
661  }
662 
663  SLIC_ERROR_ROOT(axom::fmt::format("State '{}' requested from solid mechanics module '{}', but it doesn't exist",
664  state_name, name_));
665  return displacement_;
666  }
667 
677  void setState(const std::string& state_name, const FiniteElementState& state) override
678  {
679  if (state_name == "displacement") {
680  displacement_ = state;
681  if (!checkpoint_to_disk_) {
682  checkpoint_states_["displacement"][static_cast<size_t>(cycle_)] = displacement_;
683  }
684  return;
685  } else if (state_name == "velocity") {
686  velocity_ = state;
687  if (!checkpoint_to_disk_) {
688  checkpoint_states_["velocity"][static_cast<size_t>(cycle_)] = velocity_;
689  }
690  return;
691  }
692 
693  SLIC_ERROR_ROOT(axom::fmt::format(
694  "setState for state named '{}' requested from solid mechanics module '{}', but it doesn't exist", state_name,
695  name_));
696  }
697 
703  std::vector<std::string> stateNames() const override
704  {
705  if (is_quasistatic_) {
706  return std::vector<std::string>{"displacement"};
707  } else {
708  return std::vector<std::string>{"displacement", "velocity", "acceleration"};
709  }
710  }
711 
733  template <int... active_parameters, typename callable>
735  const std::optional<Domain>& optional_domain = std::nullopt)
736  {
737  Domain domain = (optional_domain) ? *optional_domain : EntireBoundary(mesh_);
738 
739  residual_->AddBoundaryIntegral(Dimension<dim - 1>{}, DependsOn<0, 1, active_parameters + NUM_STATE_VARS...>{},
740  qfunction, domain);
741  }
742 
749  const FiniteElementState& adjoint(const std::string& state_name) const override
750  {
751  if (state_name == "displacement") {
752  return adjoint_displacement_;
753  }
754 
755  SLIC_ERROR_ROOT(axom::fmt::format("Adjoint '{}' requested from solid mechanics module '{}', but it doesn't exist",
756  state_name, name_));
757  return adjoint_displacement_;
758  }
759 
765  std::vector<std::string> adjointNames() const override { return std::vector<std::string>{{"displacement"}}; }
766 
796  template <int... active_parameters, typename callable, typename StateType = Nothing>
799  {
800  residual_->AddDomainIntegral(Dimension<dim>{}, DependsOn<0, 1, active_parameters + NUM_STATE_VARS...>{}, qfunction,
801  mesh_, qdata);
802  }
803 
828  template <int... active_parameters, typename MaterialType, typename StateType = Empty>
830  {
831  residual_->AddDomainIntegral(
832  Dimension<dim>{},
833  DependsOn<0, 1,
834  active_parameters + NUM_STATE_VARS...>{}, // the magic number "+ NUM_STATE_VARS" accounts for the
835  // fact that the displacement, acceleration, and shape
836  // fields are always-on and come first, so the `n`th
837  // parameter will actually be argument `n + NUM_STATE_VARS`
838  [material, geom_nonlin = geom_nonlin_](double /*t*/, auto /*x*/, auto& state, auto displacement,
839  auto acceleration, auto... params) {
840  auto du_dX = get<DERIVATIVE>(displacement);
841  auto d2u_dt2 = get<VALUE>(acceleration);
842 
843  auto stress = material(state, du_dX, params...);
844 
845  auto dx_dX = 0.0 * du_dX + I;
846 
847  if (geom_nonlin == GeometricNonlinearities::On) {
848  dx_dX += du_dX;
849  }
850 
851  auto flux = dot(stress, transpose(inv(dx_dX))) * det(dx_dX);
852 
853  return serac::tuple{material.density * d2u_dt2, flux};
854  },
855  mesh_, qdata);
856  }
857 
859  template <typename MaterialType, typename StateType = Empty>
860  void setMaterial(MaterialType material, std::shared_ptr<QuadratureData<StateType>> qdata = EmptyQData)
861  {
862  setMaterial(DependsOn<>{}, material, qdata);
863  }
864 
870  void setDisplacement(std::function<void(const mfem::Vector& x, mfem::Vector& disp)> disp)
871  {
872  // Project the coefficient onto the grid function
873  mfem::VectorFunctionCoefficient disp_coef(dim, disp);
874  displacement_.project(disp_coef);
875  }
876 
878  void setDisplacement(const FiniteElementState& temp) { displacement_ = temp; }
879 
885  void setVelocity(std::function<void(const mfem::Vector& x, mfem::Vector& vel)> vel)
886  {
887  // Project the coefficient onto the grid function
888  mfem::VectorFunctionCoefficient vel_coef(dim, vel);
889  velocity_.project(vel_coef);
890  }
891 
893  void setVelocity(const FiniteElementState& temp) { velocity_ = temp; }
894 
914  template <int... active_parameters, typename BodyForceType>
915  void addBodyForce(DependsOn<active_parameters...>, BodyForceType body_force,
916  const std::optional<Domain>& optional_domain = std::nullopt)
917  {
918  Domain domain = (optional_domain) ? *optional_domain : EntireDomain(mesh_);
919 
920  residual_->AddDomainIntegral(
921  Dimension<dim>{}, DependsOn<0, 1, active_parameters + NUM_STATE_VARS...>{},
922  [body_force](double t, auto x, auto /* displacement */, auto /* acceleration */, auto... params) {
923  return serac::tuple{-1.0 * body_force(x, t, params...), zero{}};
924  },
925  domain);
926  }
927 
929  template <typename BodyForceType>
930  void addBodyForce(BodyForceType body_force, const std::optional<Domain>& optional_domain = std::nullopt)
931  {
932  addBodyForce(DependsOn<>{}, body_force, optional_domain);
933  }
934 
958  template <int... active_parameters, typename TractionType>
959  void setTraction(DependsOn<active_parameters...>, TractionType traction_function,
960  const std::optional<Domain>& optional_domain = std::nullopt)
961  {
962  Domain domain = (optional_domain) ? *optional_domain : EntireBoundary(mesh_);
963 
964  residual_->AddBoundaryIntegral(
965  Dimension<dim - 1>{}, DependsOn<0, 1, active_parameters + NUM_STATE_VARS...>{},
966  [traction_function](double t, auto X, auto /* displacement */, auto /* acceleration */, auto... params) {
967  auto n = cross(get<DERIVATIVE>(X));
968 
969  return -1.0 * traction_function(get<VALUE>(X), normalize(n), t, params...);
970  },
971  domain);
972  }
973 
975  template <typename TractionType>
976  void setTraction(TractionType traction_function, const std::optional<Domain>& optional_domain = std::nullopt)
977  {
978  setTraction(DependsOn<>{}, traction_function, optional_domain);
979  }
980 
1003  template <int... active_parameters, typename PressureType>
1004  void setPressure(DependsOn<active_parameters...>, PressureType pressure_function,
1005  const std::optional<Domain>& optional_domain = std::nullopt)
1006  {
1007  Domain domain = (optional_domain) ? *optional_domain : EntireBoundary(mesh_);
1008 
1009  residual_->AddBoundaryIntegral(
1010  Dimension<dim - 1>{}, DependsOn<0, 1, active_parameters + NUM_STATE_VARS...>{},
1011  [pressure_function, geom_nonlin = geom_nonlin_](double t, auto X, auto displacement, auto /* acceleration */,
1012  auto... params) {
1013  // Calculate the position and normal in the shape perturbed deformed configuration
1014  auto x = X + 0.0 * displacement;
1015 
1016  if (geom_nonlin == GeometricNonlinearities::On) {
1017  x = x + displacement;
1018  }
1019 
1020  auto n = cross(get<DERIVATIVE>(x));
1021 
1022  // serac::Functional's boundary integrals multiply the q-function output by
1023  // norm(cross(dX_dxi)) at that quadrature point, but if we impose a shape displacement
1024  // then that weight needs to be corrected. The new weight should be
1025  // norm(cross(dX_dxi + du_dxi + dp_dxi)) where u is displacement and p is shape displacement. This implies:
1026  //
1027  // pressure * normalize(normal_new) * w_new
1028  // = pressure * normalize(normal_new) * (w_new / w_old) * w_old
1029  // = pressure * normalize(normal_new) * (norm(normal_new) / norm(normal_old)) * w_old
1030  // = pressure * (normal_new / norm(normal_new)) * (norm(normal_new) / norm(normal_old)) * w_old
1031  // = pressure * (normal_new / norm(normal_old)) * w_old
1032 
1033  // We always query the pressure function in the undeformed configuration
1034  return pressure_function(get<VALUE>(X), t, params...) * (n / norm(cross(get<DERIVATIVE>(X))));
1035  },
1036  domain);
1037  }
1038 
1040  template <typename PressureType>
1041  void setPressure(PressureType pressure_function, const std::optional<Domain>& optional_domain = std::nullopt)
1042  {
1043  setPressure(DependsOn<>{}, pressure_function, optional_domain);
1044  }
1045 
1047  virtual std::unique_ptr<mfem_ext::StdFunctionOperator> buildQuasistaticOperator()
1048  {
1049  // the quasistatic case is entirely described by the residual,
1050  // there is no ordinary differential equation
1051  return std::make_unique<mfem_ext::StdFunctionOperator>(
1052  displacement_.space().TrueVSize(),
1053 
1054  // residual function
1055  [this](const mfem::Vector& u, mfem::Vector& r) {
1056  const mfem::Vector res = (*residual_)(ode_time_point_, shape_displacement_, u, acceleration_,
1057  *parameters_[parameter_indices].state...);
1058 
1059  // TODO this copy is required as the sundials solvers do not allow move assignments because of their memory
1060  // tracking strategy
1061  // See https://github.com/mfem/mfem/issues/3531
1062  r = res;
1063  r.SetSubVector(bcs_.allEssentialTrueDofs(), 0.0);
1064  },
1065 
1066  // gradient of residual function
1067  [this](const mfem::Vector& u) -> mfem::Operator& {
1068  auto [r, drdu] = (*residual_)(ode_time_point_, shape_displacement_, differentiate_wrt(u), acceleration_,
1069  *parameters_[parameter_indices].state...);
1070  J_ = assemble(drdu);
1071  J_e_ = bcs_.eliminateAllEssentialDofsFromMatrix(*J_);
1072  return *J_;
1073  });
1074  }
1075 
1087  std::pair<const mfem::HypreParMatrix&, const mfem::HypreParMatrix&> stiffnessMatrix() const
1088  {
1089  SLIC_ERROR_ROOT_IF(!J_ || !J_e_, "Stiffness matrix has not yet been assembled.");
1090 
1091  return {*J_, *J_e_};
1092  }
1093 
1099  void completeSetup() override
1100  {
1101  // Build the dof array lookup tables
1102  displacement_.space().BuildDofToArrays();
1103 
1104  if (is_quasistatic_) {
1105  residual_with_bcs_ = buildQuasistaticOperator();
1106  } else {
1107  // the dynamic case is described by a residual function and a second order
1108  // ordinary differential equation. Here, we define the residual function in
1109  // terms of an acceleration.
1110  residual_with_bcs_ = std::make_unique<mfem_ext::StdFunctionOperator>(
1111  displacement_.space().TrueVSize(),
1112 
1113  [this](const mfem::Vector& d2u_dt2, mfem::Vector& r) {
1114  add(1.0, u_, c0_, d2u_dt2, predicted_displacement_);
1115  const mfem::Vector res = (*residual_)(ode_time_point_, shape_displacement_, predicted_displacement_,
1116  d2u_dt2, *parameters_[parameter_indices].state...);
1117 
1118  // TODO this copy is required as the sundials solvers do not allow move assignments because of their memory
1119  // tracking strategy
1120  // See https://github.com/mfem/mfem/issues/3531
1121  r = res;
1122  r.SetSubVector(bcs_.allEssentialTrueDofs(), 0.0);
1123  },
1124 
1125  [this](const mfem::Vector& d2u_dt2) -> mfem::Operator& {
1126  add(1.0, u_, c0_, d2u_dt2, predicted_displacement_);
1127 
1128  // K := dR/du
1129  auto K = serac::get<DERIVATIVE>((*residual_)(ode_time_point_, shape_displacement_,
1130  differentiate_wrt(predicted_displacement_), d2u_dt2,
1131  *parameters_[parameter_indices].state...));
1132  std::unique_ptr<mfem::HypreParMatrix> k_mat(assemble(K));
1133 
1134  // M := dR/da
1135  auto M = serac::get<DERIVATIVE>((*residual_)(ode_time_point_, shape_displacement_, predicted_displacement_,
1136  differentiate_wrt(d2u_dt2),
1137  *parameters_[parameter_indices].state...));
1138  std::unique_ptr<mfem::HypreParMatrix> m_mat(assemble(M));
1139 
1140  // J = M + c0 * K
1141  J_.reset(mfem::Add(1.0, *m_mat, c0_, *k_mat));
1142  J_e_ = bcs_.eliminateAllEssentialDofsFromMatrix(*J_);
1143 
1144  return *J_;
1145  });
1146  }
1147 
1148  nonlin_solver_->setOperator(*residual_with_bcs_);
1149 
1150  if (checkpoint_to_disk_) {
1151  outputStateToDisk();
1152  } else {
1153  checkpoint_states_.clear();
1154 
1155  checkpoint_states_["displacement"].push_back(displacement_);
1156  checkpoint_states_["velocity"].push_back(velocity_);
1157  checkpoint_states_["acceleration"].push_back(acceleration_);
1158  }
1159  }
1160 
1163  {
1164  for (const auto& essential : bcs_.essentials()) {
1165  field.SetSubVector(essential.getTrueDofList(), 0.0);
1166  }
1167  }
1168 
1170  virtual void quasiStaticSolve(double dt)
1171  {
1172  time_ += dt;
1173 
1174  // Set the ODE time point for the time-varying loads in quasi-static problems
1175  ode_time_point_ = time_;
1176 
1177  // this method is essentially equivalent to the 1-liner
1178  // u += dot(inv(J), dot(J_elim[:, dofs], (U(t + dt) - u)[dofs]));
1179  warmStartDisplacement();
1180 
1181  nonlin_solver_->solve(displacement_);
1182 
1183  cycle_ += 1;
1184  }
1185 
1194  void advanceTimestep(double dt) override
1195  {
1196  SLIC_ERROR_ROOT_IF(!residual_, "completeSetup() must be called prior to advanceTimestep(dt) in SolidMechanics.");
1197 
1198  // If this is the first call, initialize the previous parameter values as the initial values
1199  if (cycle_ == 0) {
1200  for (auto& parameter : parameters_) {
1201  *parameter.previous_state = *parameter.state;
1202  }
1203  }
1204 
1205  if (is_quasistatic_) {
1206  quasiStaticSolve(dt);
1207  } else {
1208  ode2_.Step(displacement_, velocity_, time_, dt);
1209 
1210  cycle_ += 1;
1211 
1212  if (checkpoint_to_disk_) {
1213  outputStateToDisk();
1214  } else {
1215  checkpoint_states_["displacement"].push_back(displacement_);
1216  checkpoint_states_["velocity"].push_back(velocity_);
1217  checkpoint_states_["acceleration"].push_back(acceleration_);
1218  }
1219  }
1220 
1221  {
1222  // after finding displacements that satisfy equilibrium,
1223  // compute the residual one more time, this time enabling
1224  // the material state buffers to be updated
1225  residual_->updateQdata(true);
1226 
1227  reactions_ = (*residual_)(ode_time_point_, shape_displacement_, displacement_, acceleration_,
1228  *parameters_[parameter_indices].state...);
1229 
1230  residual_->updateQdata(false);
1231  }
1232 
1233  if (cycle_ > max_cycle_) {
1234  timesteps_.push_back(dt);
1235  max_cycle_ = cycle_;
1236  max_time_ = time_;
1237  }
1238  }
1239 
1254  virtual void setAdjointLoad(std::unordered_map<std::string, const serac::FiniteElementDual&> loads) override
1255  {
1256  SLIC_ERROR_ROOT_IF(loads.size() == 0, "Adjoint load container size must be greater than 0 in the solid mechanics.");
1257 
1258  auto disp_adjoint_load = loads.find("displacement");
1259 
1260  SLIC_ERROR_ROOT_IF(disp_adjoint_load == loads.end(), "Adjoint load for \"displacement\" not found.");
1261 
1262  displacement_adjoint_load_ = disp_adjoint_load->second;
1263  // Add the sign correction to move the term to the RHS
1264  displacement_adjoint_load_ *= -1.0;
1265 
1266  auto velo_adjoint_load = loads.find("velocity");
1267 
1268  if (velo_adjoint_load != loads.end()) {
1269  velocity_adjoint_load_ = velo_adjoint_load->second;
1270  // Add the sign correction to move the term to the RHS
1271  velocity_adjoint_load_ *= -1.0;
1272  }
1273 
1274  auto accel_adjoint_load = loads.find("acceleration");
1275 
1276  if (accel_adjoint_load != loads.end()) {
1277  acceleration_adjoint_load_ = accel_adjoint_load->second;
1278  // Add the sign correction to move the term to the RHS
1279  acceleration_adjoint_load_ *= -1.0;
1280  }
1281  }
1282 
1288  void reverseAdjointTimestep() override
1289  {
1290  auto& lin_solver = nonlin_solver_->linearSolver();
1291 
1292  // By default, use a homogeneous essential boundary condition
1293  mfem::HypreParVector adjoint_essential(displacement_adjoint_load_);
1294  adjoint_essential = 0.0;
1295 
1296  if (is_quasistatic_) {
1297  auto [_, drdu] = (*residual_)(ode_time_point_, shape_displacement_, differentiate_wrt(displacement_),
1298  acceleration_, *parameters_[parameter_indices].state...);
1299  auto jacobian = assemble(drdu);
1300  auto J_T = std::unique_ptr<mfem::HypreParMatrix>(jacobian->Transpose());
1301 
1302  for (const auto& bc : bcs_.essentials()) {
1303  bc.apply(*J_T, displacement_adjoint_load_, adjoint_essential);
1304  }
1305 
1306  lin_solver.SetOperator(*J_T);
1307  lin_solver.Mult(displacement_adjoint_load_, adjoint_displacement_);
1308 
1309  // Reset the equation solver to use the full nonlinear residual operator. MRT, is this needed?
1310  nonlin_solver_->setOperator(*residual_with_bcs_);
1311 
1312  return;
1313  }
1314 
1315  SLIC_ERROR_ROOT_IF(ode2_.GetTimestepper() != TimestepMethod::Newmark,
1316  "Only Newmark implemented for transient adjoint solid mechanics.");
1317 
1318  SLIC_ERROR_ROOT_IF(cycle_ <= min_cycle_,
1319  "Maximum number of adjoint timesteps exceeded! The number of adjoint timesteps must equal the "
1320  "number of forward timesteps");
1321 
1322  // Load the end of step disp, velo, accel from the previous cycle
1323  {
1324  auto previous_states_n = getCheckpointedStates(cycle_);
1325 
1326  displacement_ = previous_states_n.at("displacement");
1327  velocity_ = previous_states_n.at("velocity");
1328  acceleration_ = previous_states_n.at("acceleration");
1329  }
1330 
1331  double dt_np1 = getCheckpointedTimestep(cycle_);
1332  double dt_n = getCheckpointedTimestep(cycle_ - 1);
1333 
1334  // K := dR/du
1335  auto K = serac::get<DERIVATIVE>((*residual_)(ode_time_point_, shape_displacement_, differentiate_wrt(displacement_),
1336  acceleration_, *parameters_[parameter_indices].state...));
1337  std::unique_ptr<mfem::HypreParMatrix> k_mat(assemble(K));
1338 
1339  // M := dR/da
1340  auto M = serac::get<DERIVATIVE>((*residual_)(ode_time_point_, shape_displacement_, displacement_,
1341  differentiate_wrt(acceleration_),
1342  *parameters_[parameter_indices].state...));
1343  std::unique_ptr<mfem::HypreParMatrix> m_mat(assemble(M));
1344 
1345  solid_mechanics::detail::adjoint_integrate(
1346  dt_n, dt_np1, m_mat.get(), k_mat.get(), displacement_adjoint_load_, velocity_adjoint_load_,
1347  acceleration_adjoint_load_, adjoint_displacement_, implicit_sensitivity_displacement_start_of_step_,
1348  implicit_sensitivity_velocity_start_of_step_, adjoint_essential, bcs_, lin_solver);
1349 
1350  time_ -= dt_n;
1351  cycle_--;
1352  }
1353 
1361  std::unordered_map<std::string, FiniteElementState> getCheckpointedStates(int cycle_to_load) const override
1362  {
1363  std::unordered_map<std::string, FiniteElementState> previous_states;
1364 
1365  if (checkpoint_to_disk_) {
1366  previous_states.emplace("displacement", displacement_);
1367  previous_states.emplace("velocity", velocity_);
1368  previous_states.emplace("acceleration", acceleration_);
1370  cycle_to_load,
1371  {previous_states.at("displacement"), previous_states.at("velocity"), previous_states.at("acceleration")});
1372  return previous_states;
1373  } else {
1374  previous_states.emplace("displacement",
1375  checkpoint_states_.at("displacement")[static_cast<size_t>(cycle_to_load)]);
1376  previous_states.emplace("velocity", checkpoint_states_.at("velocity")[static_cast<size_t>(cycle_to_load)]);
1377  previous_states.emplace("acceleration",
1378  checkpoint_states_.at("acceleration")[static_cast<size_t>(cycle_to_load)]);
1379  }
1380 
1381  return previous_states;
1382  }
1383 
1393  FiniteElementDual& computeTimestepSensitivity(size_t parameter_field) override
1394  {
1395  SLIC_ASSERT_MSG(parameter_field < sizeof...(parameter_indices),
1396  axom::fmt::format("Invalid parameter index '{}' requested for sensitivity."));
1397 
1398  // TODO: the time is likely not being handled correctly on the reverse pass, but we don't
1399  // have tests to confirm.
1400  auto drdparam = serac::get<DERIVATIVE>(d_residual_d_[parameter_field](ode_time_point_));
1401  auto drdparam_mat = assemble(drdparam);
1402 
1403  drdparam_mat->MultTranspose(adjoint_displacement_, *parameters_[parameter_field].sensitivity);
1404 
1405  return *parameters_[parameter_field].sensitivity;
1406  }
1407 
1417  {
1418  auto drdshape =
1419  serac::get<DERIVATIVE>((*residual_)(ode_time_point_, differentiate_wrt(shape_displacement_), displacement_,
1420  acceleration_, *parameters_[parameter_indices].state...));
1421 
1422  auto drdshape_mat = assemble(drdshape);
1423 
1424  drdshape_mat->MultTranspose(adjoint_displacement_, *shape_displacement_sensitivity_);
1425 
1426  return *shape_displacement_sensitivity_;
1427  }
1428 
1436  const std::unordered_map<std::string, const serac::FiniteElementDual&> computeInitialConditionSensitivity() override
1437  {
1438  return {{"displacement", implicit_sensitivity_displacement_start_of_step_},
1439  {"velocity", implicit_sensitivity_velocity_start_of_step_}};
1440  }
1441 
1447  const serac::FiniteElementState& displacement() const { return displacement_; };
1448 
1454  const serac::FiniteElementState& velocity() const { return velocity_; };
1455 
1461  const serac::FiniteElementState& acceleration() const { return acceleration_; };
1462 
1464  const serac::FiniteElementDual& reactions() { return reactions_; };
1465 
1466 protected:
1469 
1472 
1476 
1479 
1482 
1485 
1486  // In the case of transient dynamics, this is more like an adjoint_acceleration
1489 
1492 
1495 
1498 
1501 
1504 
1507 
1509  std::unique_ptr<ShapeAwareFunctional<shape_trial, test(trial, trial, parameter_space...)>> residual_;
1510 
1512  std::unique_ptr<mfem_ext::StdFunctionOperator> residual_with_bcs_;
1513 
1515  std::unique_ptr<EquationSolver> nonlin_solver_;
1516 
1523 
1525  std::unique_ptr<mfem::HypreParMatrix> J_;
1526 
1529  std::unique_ptr<mfem::HypreParMatrix> J_e_;
1530 
1533 
1535  mfem::Vector du_;
1536 
1538  mfem::Vector dr_;
1539 
1541  mfem::Vector u_;
1542 
1544  mfem::Vector v_;
1545 
1547  double c0_;
1548 
1550  double c1_;
1551 
1554 
1556  std::shared_ptr<mfem::VectorCoefficient> disp_bdr_coef_;
1557 
1559  std::shared_ptr<mfem::Coefficient> component_disp_bdr_coef_;
1560 
1564  std::array<std::function<decltype((*residual_)(DifferentiateWRT<1>{}, 0.0, shape_displacement_, displacement_,
1565  acceleration_, *parameters_[parameter_indices].state...))(double)>,
1566  sizeof...(parameter_indices)>
1567  d_residual_d_ = {[&](double t) {
1568  return (*residual_)(DifferentiateWRT<NUM_STATE_VARS + 1 + parameter_indices>{}, t, shape_displacement_,
1569  displacement_, acceleration_, *parameters_[parameter_indices].state...);
1570  }...};
1571 
1581  mfem::Array<int> calculateConstrainedDofs(std::function<bool(const mfem::Vector&)> is_node_constrained,
1582  std::optional<int> component = {})
1583  {
1584  // Get the nodal positions for the displacement vector in grid function form
1585  mfem::ParGridFunction nodal_positions(&displacement_.space());
1586  mesh_.GetNodes(nodal_positions);
1587 
1588  const int num_nodes = nodal_positions.Size() / dim;
1589  mfem::Array<int> constrained_dofs;
1590 
1591  for (int i = 0; i < num_nodes; i++) {
1592  // Determine if this "local" node (L-vector node) is in the local true vector. I.e. ensure this node is not a
1593  // shared node owned by another processor
1594  if (nodal_positions.ParFESpace()->GetLocalTDofNumber(i) >= 0) {
1595  mfem::Vector node_coords(dim);
1596  mfem::Array<int> node_dofs;
1597  for (int d = 0; d < dim; d++) {
1598  // Get the local dof number for the prescribed component
1599  int local_vector_dof = mfem::Ordering::Map<mfem::Ordering::byNODES>(
1600  nodal_positions.FESpace()->GetNDofs(), nodal_positions.FESpace()->GetVDim(), i, d);
1601 
1602  // Save the spatial position for this coordinate dof
1603  node_coords(d) = nodal_positions(local_vector_dof);
1604 
1605  // Check if this component of the displacement vector is constrained
1606  bool is_active_component = true;
1607  if (component) {
1608  if (*component != d) {
1609  is_active_component = false;
1610  }
1611  }
1612 
1613  if (is_active_component) {
1614  // Add the true dof for this component to the related dof list
1615  node_dofs.Append(nodal_positions.ParFESpace()->GetLocalTDofNumber(local_vector_dof));
1616  }
1617  }
1618 
1619  // Do the user-defined spatial query to determine if these dofs should be constrained
1620  if (is_node_constrained(node_coords)) {
1621  constrained_dofs.Append(node_dofs);
1622  }
1623 
1624  // Reset the temporary container for the dofs associated with a particular node
1625  node_dofs.DeleteAll();
1626  }
1627  }
1628  return constrained_dofs;
1629  }
1630 
1635  {
1636  // Update the linearized Jacobian matrix
1637  auto [r, drdu] = (*residual_)(ode_time_point_, shape_displacement_, differentiate_wrt(displacement_), acceleration_,
1638  *parameters_[parameter_indices].state...);
1639  J_ = assemble(drdu);
1640  J_e_ = bcs_.eliminateAllEssentialDofsFromMatrix(*J_);
1641 
1642  du_ = 0.0;
1643  for (auto& bc : bcs_.essentials()) {
1644  bc.setDofs(du_, time_);
1645  }
1646 
1647  auto& constrained_dofs = bcs_.allEssentialTrueDofs();
1648  for (int i = 0; i < constrained_dofs.Size(); i++) {
1649  int j = constrained_dofs[i];
1650  du_[j] -= displacement_(j);
1651  }
1652 
1653  dr_ = 0.0;
1654  mfem::EliminateBC(*J_, *J_e_, constrained_dofs, du_, dr_);
1655 
1656  // Update the initial guess for changes in the parameters if this is not the first solve
1657  for (std::size_t parameter_index = 0; parameter_index < parameters_.size(); ++parameter_index) {
1658  // Compute the change in parameters parameter_diff = parameter_new - parameter_old
1659  serac::FiniteElementState parameter_difference = *parameters_[parameter_index].state;
1660  parameter_difference -= *parameters_[parameter_index].previous_state;
1661 
1662  // Compute a linearized estimate of the residual forces due to this change in parameter
1663  auto drdparam = serac::get<DERIVATIVE>(d_residual_d_[parameter_index](ode_time_point_));
1664  auto residual_update = drdparam(parameter_difference);
1665 
1666  // Flip the sign to get the RHS of the Newton update system
1667  // J^-1 du = - residual
1668  residual_update *= -1.0;
1669 
1670  dr_ += residual_update;
1671 
1672  // Save the current parameter value for the next timestep
1673  *parameters_[parameter_index].previous_state = *parameters_[parameter_index].state;
1674  }
1675 
1676  for (int i = 0; i < constrained_dofs.Size(); i++) {
1677  int j = constrained_dofs[i];
1678  dr_[j] = du_[j];
1679  }
1680 
1681  auto& lin_solver = nonlin_solver_->linearSolver();
1682 
1683  lin_solver.SetOperator(*J_);
1684 
1685  lin_solver.Mult(dr_, du_);
1686  displacement_ += du_;
1687  }
1688 };
1689 
1690 } // namespace serac
The base interface class for a generic PDE solver.
This is the abstract base class for a generic forward solver.
void initializeBasePhysicsStates(int cycle, double time)
Protected, non-virtual method to reset physics states to zero. This does not reset design parameters ...
Class for encapsulating the dual vector space of a finite element space (i.e. the space of linear for...
Class for encapsulating the critical MFEM components of a primal finite element field.
Class for encapsulating the data associated with a vector derived from a MFEM finite element space....
mfem::ParFiniteElementSpace & space()
Returns a non-owning reference to the internal FESpace.
void addCustomBoundaryIntegral(DependsOn< active_parameters... >, callable qfunction, const std::optional< Domain > &optional_domain=std::nullopt)
register a custom boundary integral calculation as part of the residual
mfem_ext::SecondOrderODE ode2_
the ordinary differential equation that describes how to solve for the second time derivative of disp...
std::unique_ptr< mfem::HypreParMatrix > J_
Assembled sparse matrix for the Jacobian df/du (11 block if using Lagrange multiplier contact)
void setPressure(DependsOn< active_parameters... >, PressureType pressure_function, const std::optional< Domain > &optional_domain=std::nullopt)
Set the pressure boundary condition.
mfem::Vector u_
used to communicate the ODE solver's predicted displacement to the residual operator
void setDisplacementBCsByDofList(const mfem::Array< int > true_dofs, std::function< void(const mfem::Vector &, mfem::Vector &)> disp)
Set the displacement essential boundary conditions on a set of true degrees of freedom.
void zeroEssentials(FiniteElementVector &field) const
Set field to zero wherever their are essential boundary conditions applies.
void setMaterial(DependsOn< active_parameters... >, MaterialType material, qdata_type< StateType > qdata=EmptyQData)
Set the material stress response and mass properties for the physics module.
FiniteElementDual implicit_sensitivity_velocity_start_of_step_
The total/implicit sensitivity of the qoi with respect to the start of the previous timestep's veloci...
std::unique_ptr< mfem_ext::StdFunctionOperator > residual_with_bcs_
mfem::Operator that calculates the residual after applying essential boundary conditions
void setPressure(PressureType pressure_function, const std::optional< Domain > &optional_domain=std::nullopt)
This is an overloaded member function, provided for convenience. It differs from the above function o...
void setDisplacementBCs(std::function< bool(const mfem::Vector &)> is_node_constrained, std::function< void(const mfem::Vector &, mfem::Vector &)> disp)
Set the displacement boundary conditions on a set of nodes within a spatially-defined area.
SolidMechanics(std::unique_ptr< serac::EquationSolver > solver, const serac::TimesteppingOptions timestepping_opts, const GeometricNonlinearities geom_nonlin, const std::string &physics_name, std::string mesh_tag, std::vector< std::string > parameter_names={}, int cycle=0, double time=0.0, bool checkpoint_to_disk=false)
Construct a new SolidMechanics object.
void setMaterial(MaterialType material, std::shared_ptr< QuadratureData< StateType >> qdata=EmptyQData)
This is an overloaded member function, provided for convenience. It differs from the above function o...
void setDisplacementBCsByDofList(const mfem::Array< int > true_dofs, std::function< void(const mfem::Vector &, double, mfem::Vector &)> disp)
Set the displacement essential boundary conditions on a set of true degrees of freedom.
void resetStates(int cycle=0, double time=0.0) override
Method to reset physics states to zero. This does not reset design parameters or shape.
mfem::Vector dr_
vector used to store forces arising from du_ when applying time-dependent bcs
void addCustomDomainIntegral(DependsOn< active_parameters... >, callable qfunction, qdata_type< StateType > qdata=NoQData)
register a custom domain integral calculation as part of the residual
void setDisplacementBCs(const std::set< int > &disp_bdr, std::function< double(const mfem::Vector &x)> disp, int component)
Set the displacement essential boundary conditions on a single component.
void addBodyForce(DependsOn< active_parameters... >, BodyForceType body_force, const std::optional< Domain > &optional_domain=std::nullopt)
Set the body forcefunction.
virtual void setAdjointLoad(std::unordered_map< std::string, const serac::FiniteElementDual & > loads) override
Set the loads for the adjoint reverse timestep solve.
void completeSetup() override
Complete the initialization and allocation of the data structures.
FiniteElementDual velocity_adjoint_load_
The adjoint load (RHS) for the velocity adjoint system solve (downstream -dQOI/d velocity)
void addBodyForce(BodyForceType body_force, const std::optional< Domain > &optional_domain=std::nullopt)
This is an overloaded member function, provided for convenience. It differs from the above function o...
std::shared_ptr< mfem::Coefficient > component_disp_bdr_coef_
Coefficient containing the essential boundary values.
mfem::Vector predicted_displacement_
an intermediate variable used to store the predicted end-step displacement
mfem::Vector v_
used to communicate the ODE solver's predicted velocity to the residual operator
void initializeSolidMechanicsStates()
Non virtual method to reset thermal states to zero. This does not reset design parameters or shape.
virtual std::unique_ptr< mfem_ext::StdFunctionOperator > buildQuasistaticOperator()
Build the quasi-static operator corresponding to the total Lagrangian formulation.
void setDisplacementBCs(std::function< bool(const mfem::Vector &)> is_node_constrained, std::function< void(const mfem::Vector &, double, mfem::Vector &)> disp)
Set the displacement boundary conditions on a set of nodes within a spatially-defined area.
std::unordered_map< std::string, FiniteElementState > getCheckpointedStates(int cycle_to_load) const override
Accessor for getting named finite element state primal solution from the physics modules at a given c...
void setDisplacementBCs(std::function< bool(const mfem::Vector &)> is_node_constrained, std::function< double(const mfem::Vector &, double)> disp, int component)
Set the displacement boundary conditions on a set of nodes within a spatially-defined area for a sing...
void setDisplacement(const FiniteElementState &temp)
This is an overloaded member function, provided for convenience. It differs from the above function o...
void setState(const std::string &state_name, const FiniteElementState &state) override
Set the primal solution field (displacement, velocity) for the underlying solid mechanics solver.
double c1_
coefficient used to calculate predicted velocity: dudt_p := dudt + c1 * d2u_dt2
std::shared_ptr< mfem::VectorCoefficient > disp_bdr_coef_
Coefficient containing the essential boundary values.
SolidMechanics(const SolidMechanicsInputOptions &input_options, const std::string &physics_name, std::string mesh_tag, int cycle=0, double time=0.0)
Construct a new Nonlinear SolidMechanics Solver object.
const FiniteElementState & state(const std::string &state_name) const override
Accessor for getting named finite element state fields from the physics modules.
void setTraction(DependsOn< active_parameters... >, TractionType traction_function, const std::optional< Domain > &optional_domain=std::nullopt)
Set the traction boundary condition.
const serac::FiniteElementDual & reactions()
getter for nodal forces (before zeroing-out essential dofs)
std::vector< std::string > adjointNames() const override
Get a vector of the finite element state solution variable names.
std::pair< const mfem::HypreParMatrix &, const mfem::HypreParMatrix & > stiffnessMatrix() const
Return the assembled stiffness matrix.
void setTraction(TractionType traction_function, const std::optional< Domain > &optional_domain=std::nullopt)
This is an overloaded member function, provided for convenience. It differs from the above function o...
FiniteElementDual implicit_sensitivity_displacement_start_of_step_
The total/implicit sensitivity of the qoi with respect to the start of the previous timestep's displa...
void setDisplacementBCs(const std::set< int > &disp_bdr, std::function< void(const mfem::Vector &, double, mfem::Vector &)> disp)
Set essential displacement boundary conditions (strongly enforced)
void warmStartDisplacement()
Sets the Dirichlet BCs for the current time and computes an initial guess for parameters and displace...
FiniteElementDual & computeTimestepSensitivity(size_t parameter_field) override
Compute the implicit sensitivity of the quantity of interest used in defining the load for the adjoin...
qdata_type< T > createQuadratureDataBuffer(T initial_state)
Create a shared ptr to a quadrature data buffer for the given material type.
void setDisplacementBCs(const std::set< int > &disp_bdr, std::function< void(const mfem::Vector &, mfem::Vector &)> disp)
Set essential displacement boundary conditions (strongly enforced)
void setDisplacementBCs(std::function< bool(const mfem::Vector &x)> is_node_constrained, std::function< double(const mfem::Vector &x)> disp, int component)
Set the displacement boundary conditions on a set of nodes within a spatially-defined area for a sing...
void setVelocity(std::function< void(const mfem::Vector &x, mfem::Vector &vel)> vel)
Set the underlying finite element state to a prescribed velocity.
mfem::Array< int > calculateConstrainedDofs(std::function< bool(const mfem::Vector &)> is_node_constrained, std::optional< int > component={})
Calculate a list of constrained dofs in the true displacement vector from a function that returns tru...
FiniteElementDual & computeTimestepShapeSensitivity() override
Compute the implicit sensitivity of the quantity of interest used in defining the load for the adjoin...
FiniteElementDual displacement_adjoint_load_
The adjoint load (RHS) for the displacement adjoint system solve (downstream -dQOI/d displacement)
std::unique_ptr< ShapeAwareFunctional< shape_trial, test(trial, trial, parameter_space...)> > residual_
serac::Functional that is used to calculate the residual and its derivatives
void setVelocity(const FiniteElementState &temp)
This is an overloaded member function, provided for convenience. It differs from the above function o...
SolidMechanics(const NonlinearSolverOptions nonlinear_opts, const LinearSolverOptions lin_opts, const serac::TimesteppingOptions timestepping_opts, const GeometricNonlinearities geom_nonlin, const std::string &physics_name, std::string mesh_tag, std::vector< std::string > parameter_names={}, int cycle=0, double time=0.0, bool checkpoint_to_disk=false)
Construct a new SolidMechanics object.
std::vector< std::string > stateNames() const override
Get a vector of the finite element state solution variable names.
const FiniteElementState & adjoint(const std::string &state_name) const override
Accessor for getting named finite element state adjoint solution from the physics modules.
FiniteElementDual acceleration_adjoint_load_
The adjoint load (RHS) for the adjoint system solve (downstream -dQOI/d acceleration)
std::unique_ptr< EquationSolver > nonlin_solver_
the specific methods and tolerances specified to solve the nonlinear residual equations
std::shared_ptr< QuadratureData< T > > qdata_type
a container holding quadrature point data of the specified type
void setDisplacement(std::function< void(const mfem::Vector &x, mfem::Vector &disp)> disp)
Set the underlying finite element state to a prescribed displacement.
const std::unordered_map< std::string, const serac::FiniteElementDual & > computeInitialConditionSensitivity() override
Compute the implicit sensitivity of the quantity of interest with respect to the initial temperature.
GeometricNonlinearities geom_nonlin_
A flag denoting whether to compute geometric nonlinearities in the residual.
static void loadCheckpointedStates(int cycle_to_load, std::vector< std::reference_wrapper< FiniteElementState >> states_to_load)
loads the finite element states from a previously checkpointed cycle
static FiniteElementState newState(FunctionSpace space, const std::string &state_name, const std::string &mesh_tag)
Factory method for creating a new FEState object.
static FiniteElementDual newDual(FunctionSpace space, const std::string &dual_name, const std::string &mesh_tag)
Factory method for creating a new FEDual object.
static mfem::ParMesh & mesh(const std::string &mesh_tag)
Returns a non-owning reference to mesh held by StateManager.
SecondOrderODE is a class wrapping mfem::SecondOrderTimeDependentOperator so that the user can use st...
Definition: odes.hpp:34
A file defining some enums and structs that are used by the different physics modules.
A function intended to be used as part of a driver to initialize common libraries.
constexpr SERAC_HOST_DEVICE void for_constexpr(lambda &&f)
multidimensional loop tool that evaluates the lambda body inside the innermost loop.
constexpr auto get(std::integer_sequence< int, n... >)
return the Ith integer in {n...}
const TimesteppingOptions default_timestepping_options
default implicit dynamic timestepping options for solid mechanics
const LinearSolverOptions direct_linear_options
the default direct solver option for solving the linear stiffness equations
const NonlinearSolverOptions default_nonlinear_options
default iteration limits, tolerances and verbosity for solving the systems of nonlinear equations tha...
const TimesteppingOptions default_quasistatic_options
default quasistatic timestepping options for solid mechanics
const LinearSolverOptions default_linear_options
default method and tolerances for solving the systems of linear equations that show up in implicit so...
Accelerator functionality.
Definition: serac.cpp:38
constexpr int num_quadrature_points(mfem::Geometry::Type g, int Q)
return the number of quadrature points in a Gauss-Legendre rule with parameter "Q"
Definition: geometry.hpp:25
std::shared_ptr< QuadratureData< Empty > > EmptyQData
a single instance of a QuadratureData container of Emptys, since they are all interchangeable
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
Domain EntireBoundary(const mfem::Mesh &mesh)
constructs a domain from all the boundary elements in a mesh
Definition: domain.cpp:418
std::shared_ptr< QuadratureData< Nothing > > NoQData
a single instance of a QuadratureData container of Nothings, since they are all interchangeable
auto differentiate_wrt(const mfem::Vector &v)
this function is intended to only be used in combination with serac::Functional::operator(),...
constexpr SERAC_HOST_DEVICE auto det(const isotropic_tensor< T, m, m > &I)
compute the determinant of an isotropic tensor
tuple(T...) -> tuple< T... >
Class template argument deduction rule for tuples.
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
SERAC_HOST_DEVICE auto normalize(const tensor< T, n... > &A)
Normalizes the tensor Each element is divided by the Frobenius norm of the tensor,...
Definition: tensor.hpp:1062
bool holds_alternative(const variant< T0, T1 > &v)
Checks whether a variant's active member is of a certain type.
Definition: variant.hpp:381
Domain EntireDomain(const mfem::Mesh &mesh)
constructs a domain from all the elements in a mesh
Definition: domain.cpp:382
GeometricNonlinearities
Enum to set the geometric nonlinearity flag.
Definition: common.hpp:32
std::array< uint32_t, mfem::Geometry::NUM_GEOMETRIES > geometry_counts(const mfem::Mesh &mesh)
count the number of elements of each geometry in a mesh
Definition: geometry.hpp:70
Wrapper classes for using MFEM's ODE solver objects.
Wrapper of serac::Functional for evaluating integrals and derivatives of quantities with shape displa...
The material and load types for the solid functional physics module.
An object containing all input file options for the solver for total Lagrangian finite deformation so...
This file contains the declaration of the StateManager class.
Classes for defining an mfem::Operator with std::functions.
Compile-time alias for a dimension.
Definition: geometry.hpp:11
a class for representing a geometric region that can be used for integration
Definition: domain.hpp:21
see Nothing for a complete description of this class and when to use it
H1 elements of order p.
Parameters for an iterative linear solution scheme.
LinearSolver linear_solver
Linear solver selection.
Nonlinear solution scheme parameters.
NonlinearSolver nonlin_solver
Nonlinear solver selection.
these classes are a little confusing. These two special types represent the similar (but different) c...
a struct that is used in the physics modules to clarify which template arguments are user-controlled ...
Definition: common.hpp:23
A class for storing and access user-defined types at quadrature points.
Stores all information held in the input file that is used to configure the solver.
A timestep and boundary condition enforcement method for a dynamic solver.
TimestepMethod timestepper
The timestepping method to be applied.
DirichletEnforcementMethod enforcement_method
The essential boundary enforcement method to use.
variables required to characterize the hysteresis response
J2 material with nonlinear isotropic hardening.
This is a class that mimics most of std::tuple's interface, except that it is usable in CUDA kernels ...
Definition: tuple.hpp:28
A sentinel struct for eliding no-op tensor operations.
Definition: tensor.hpp:123