29 namespace solid_mechanics {
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);
51 .relative_tol = 1.0e-6,
52 .absolute_tol = 1.0e-16,
53 .max_iterations = 500,
57 #ifdef MFEM_USE_STRUMPACK
69 .relative_tol = 1.0e-4,
70 .absolute_tol = 1.0e-8,
83 template <
int order,
int dim,
typename parameters = Parameters<>,
84 typename parameter_indices = std::make_
integer_sequence<
int, parameters::n>>
97 template <
int order,
int dim,
typename... parameter_space,
int... parameter_indices>
102 static constexpr
int VALUE = 0, DERIVATIVE = 1;
103 static constexpr
int SHAPE = 0;
104 static constexpr
auto I = Identity<dim>();
109 static constexpr
auto NUM_STATE_VARS = 2;
113 template <
typename T>
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)
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)
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),
168 velocity_(
StateManager::newState(H1<order, dim>{}, detail::addPrefix(physics_name,
"velocity"), mesh_tag_)),
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)
185 SLIC_ERROR_ROOT_IF(mesh_.Dimension() != dim,
186 axom::fmt::format(
"Compile time dimension, {0}, and runtime mesh dimension, {1}, mismatch", dim,
189 SLIC_ERROR_ROOT_IF(!nonlin_solver_,
190 "EquationSolver argument is nullptr in SolidMechanics constructor. It is possible that it was "
191 "previously moved.");
195 ode2_.SetTimestepper(timestepping_opts.
timestepper);
197 is_quasistatic_ =
false;
199 is_quasistatic_ =
true;
202 states_.push_back(&displacement_);
203 if (!is_quasistatic_) {
204 states_.push_back(&velocity_);
205 states_.push_back(&acceleration_);
208 adjoints_.push_back(&adjoint_displacement_);
210 duals_.push_back(&reactions_);
213 mfem::ParFiniteElementSpace* test_space = &displacement_.space();
214 mfem::ParFiniteElementSpace* shape_space = &shape_displacement_.space();
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();
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()));
225 if constexpr (
sizeof...(parameter_space) > 0) {
226 tuple<parameter_space...> types{};
228 parameters_.emplace_back(mesh_, get<i>(types), detail::addPrefix(name_, parameter_names[i]));
230 trial_spaces[i + NUM_STATE_VARS] = &(parameters_[i].state->space());
234 residual_ = std::make_unique<ShapeAwareFunctional<shape_trial, test(trial, trial, parameter_space...)>>(
235 shape_space, test_space, trial_spaces);
239 auto* amg_prec =
dynamic_cast<mfem::HypreBoomerAMG*
>(nonlin_solver_->preconditioner());
246 amg_prec->SetSystemsOptions(dim,
true);
249 int true_size = velocity_.space().TrueVSize();
251 u_.SetSize(true_size);
252 v_.SetSize(true_size);
254 du_.SetSize(true_size);
255 dr_.SetSize(true_size);
256 predicted_displacement_.SetSize(true_size);
258 shape_displacement_ = 0.0;
259 initializeSolidMechanicsStates();
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,
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));
287 SLIC_ERROR_ROOT(
"J2 materials only work for 3D simulations");
291 if constexpr (dim == 3) {
294 createQuadratureDataBuffer(initial_state));
296 SLIC_ERROR_ROOT(
"J2Nonlinear materials only work for 3D simulations");
300 if constexpr (dim == 3) {
303 createQuadratureDataBuffer(initial_state));
305 SLIC_ERROR_ROOT(
"J2Nonlinear materials only work for 3D simulations");
308 SLIC_ERROR(
"Invalid material type.");
312 if (input_options.initial_displacement) {
313 displacement_.project(input_options.initial_displacement->constructVector(dim));
316 if (input_options.initial_velocity) {
317 velocity_.project(input_options.initial_velocity->constructVector(dim));
320 for (
const auto& [bc_name, bc] : input_options.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());
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);
333 }
else if (bc_name.find(
"traction") != std::string::npos) {
335 SLIC_ERROR(
"'traction' is not implemented yet in input files.");
336 }
else if (bc_name.find(
"traction_ref") != std::string::npos) {
338 SLIC_ERROR(
"'traction_ref' is not implemented yet in input files.");
339 }
else if (bc_name.find(
"pressure") != std::string::npos) {
341 SLIC_ERROR(
"'pressure' is not implemented yet in input files.");
342 }
else if (bc_name.find(
"pressure_ref") != std::string::npos) {
344 SLIC_ERROR(
"'pressure_ref' is not implemented yet in input files.");
346 SLIC_WARNING_ROOT(
"Ignoring boundary condition with unknown name: " << bc_name);
369 adjoint_displacement_ = 0.0;
370 displacement_adjoint_load_ = 0.0;
371 velocity_adjoint_load_ = 0.0;
372 acceleration_adjoint_load_ = 0.0;
374 implicit_sensitivity_displacement_start_of_step_ = 0.0;
375 implicit_sensitivity_velocity_start_of_step_ = 0.0;
383 predicted_displacement_ = 0.0;
385 if (!checkpoint_to_disk_) {
386 checkpoint_states_.clear();
388 checkpoint_states_[
"displacement"].push_back(displacement_);
389 checkpoint_states_[
"velocity"].push_back(velocity_);
390 checkpoint_states_[
"acceleration"].push_back(acceleration_);
403 initializeSolidMechanicsStates();
413 template <
typename T>
416 constexpr
auto Q = order + 1;
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{};
421 std::vector<mfem::Geometry::Type> geometries;
423 geometries = {mfem::Geometry::TRIANGLE, mfem::Geometry::SQUARE};
425 geometries = {mfem::Geometry::TETRAHEDRON, mfem::Geometry::CUBE};
428 for (
auto geom : geometries) {
432 return std::make_shared<QuadratureData<T>>(elems, qpts_per_elem, initial_state);
446 void setDisplacementBCs(
const std::set<int>& disp_bdr, std::function<
void(
const mfem::Vector&, mfem::Vector&)> disp)
449 disp_bdr_coef_ = std::make_shared<mfem::VectorFunctionCoefficient>(dim, disp);
451 bcs_.addEssential(disp_bdr, disp_bdr_coef_, displacement_.space());
466 std::function<
void(
const mfem::Vector&,
double, mfem::Vector&)> disp)
469 disp_bdr_coef_ = std::make_shared<mfem::VectorFunctionCoefficient>(dim, disp);
471 bcs_.addEssential(disp_bdr, disp_bdr_coef_, displacement_.space());
486 void setDisplacementBCs(
const std::set<int>& disp_bdr, std::function<
double(
const mfem::Vector& x)> disp,
490 component_disp_bdr_coef_ = std::make_shared<mfem::FunctionCoefficient>(disp);
492 bcs_.addEssential(disp_bdr, component_disp_bdr_coef_, displacement_.space(), component);
514 std::function<
void(
const mfem::Vector&,
double, mfem::Vector&)> disp)
516 disp_bdr_coef_ = std::make_shared<mfem::VectorFunctionCoefficient>(dim, disp);
518 bcs_.addEssential(true_dofs, disp_bdr_coef_, displacement_.space());
536 std::function<
void(
const mfem::Vector&, mfem::Vector&)> disp)
538 disp_bdr_coef_ = std::make_shared<mfem::VectorFunctionCoefficient>(dim, disp);
540 bcs_.addEssential(true_dofs, disp_bdr_coef_, displacement_.space());
558 std::function<
void(
const mfem::Vector&,
double, mfem::Vector&)> disp)
560 auto constrained_dofs = calculateConstrainedDofs(is_node_constrained);
562 setDisplacementBCsByDofList(constrained_dofs, disp);
580 std::function<
void(
const mfem::Vector&, mfem::Vector&)> disp)
582 auto constrained_dofs = calculateConstrainedDofs(is_node_constrained);
584 setDisplacementBCsByDofList(constrained_dofs, disp);
605 std::function<
double(
const mfem::Vector&,
double)> disp,
int component)
607 auto constrained_dofs = calculateConstrainedDofs(is_node_constrained, component);
609 auto vector_function = [disp, component](
const mfem::Vector& x,
double time, mfem::Vector& displacement) {
611 displacement(component) = disp(x, time);
614 setDisplacementBCsByDofList(constrained_dofs, vector_function);
635 std::function<
double(
const mfem::Vector& x)> disp,
int component)
637 auto constrained_dofs = calculateConstrainedDofs(is_node_constrained, component);
639 auto vector_function = [disp, component](
const mfem::Vector& x, mfem::Vector& displacement) {
641 displacement(component) = disp(x);
644 setDisplacementBCsByDofList(constrained_dofs, vector_function);
655 if (state_name ==
"displacement") {
656 return displacement_;
657 }
else if (state_name ==
"velocity") {
659 }
else if (state_name ==
"acceleration") {
660 return acceleration_;
663 SLIC_ERROR_ROOT(axom::fmt::format(
"State '{}' requested from solid mechanics module '{}', but it doesn't exist",
665 return displacement_;
679 if (state_name ==
"displacement") {
680 displacement_ = state;
681 if (!checkpoint_to_disk_) {
682 checkpoint_states_[
"displacement"][
static_cast<size_t>(cycle_)] = displacement_;
685 }
else if (state_name ==
"velocity") {
687 if (!checkpoint_to_disk_) {
688 checkpoint_states_[
"velocity"][
static_cast<size_t>(cycle_)] = velocity_;
693 SLIC_ERROR_ROOT(axom::fmt::format(
694 "setState for state named '{}' requested from solid mechanics module '{}', but it doesn't exist", state_name,
705 if (is_quasistatic_) {
706 return std::vector<std::string>{
"displacement"};
708 return std::vector<std::string>{
"displacement",
"velocity",
"acceleration"};
733 template <
int... active_parameters,
typename callable>
735 const std::optional<Domain>& optional_domain = std::nullopt)
751 if (state_name ==
"displacement") {
752 return adjoint_displacement_;
755 SLIC_ERROR_ROOT(axom::fmt::format(
"Adjoint '{}' requested from solid mechanics module '{}', but it doesn't exist",
757 return adjoint_displacement_;
765 std::vector<std::string>
adjointNames()
const override {
return std::vector<std::string>{{
"displacement"}}; }
796 template <
int... active_parameters,
typename callable,
typename StateType =
Nothing>
800 residual_->AddDomainIntegral(
Dimension<dim>{},
DependsOn<0, 1, active_parameters + NUM_STATE_VARS...>{}, qfunction,
828 template <
int... active_parameters,
typename MaterialType,
typename StateType =
Empty>
831 residual_->AddDomainIntegral(
834 active_parameters + NUM_STATE_VARS...>{},
838 [material, geom_nonlin = geom_nonlin_](
double ,
auto ,
auto& state,
auto displacement,
839 auto acceleration,
auto... params) {
840 auto du_dX = get<DERIVATIVE>(displacement);
841 auto d2u_dt2 = get<VALUE>(acceleration);
843 auto stress = material(state, du_dX, params...);
845 auto dx_dX = 0.0 * du_dX + I;
859 template <
typename MaterialType,
typename StateType = Empty>
870 void setDisplacement(std::function<
void(
const mfem::Vector& x, mfem::Vector& disp)> disp)
873 mfem::VectorFunctionCoefficient disp_coef(dim, disp);
874 displacement_.project(disp_coef);
885 void setVelocity(std::function<
void(
const mfem::Vector& x, mfem::Vector& vel)> vel)
888 mfem::VectorFunctionCoefficient vel_coef(dim, vel);
889 velocity_.project(vel_coef);
914 template <
int... active_parameters,
typename BodyForceType>
916 const std::optional<Domain>& optional_domain = std::nullopt)
920 residual_->AddDomainIntegral(
922 [body_force](
double t,
auto x,
auto ,
auto ,
auto... params) {
929 template <
typename BodyForceType>
930 void addBodyForce(BodyForceType body_force,
const std::optional<Domain>& optional_domain = std::nullopt)
932 addBodyForce(
DependsOn<>{}, body_force, optional_domain);
958 template <
int... active_parameters,
typename TractionType>
960 const std::optional<Domain>& optional_domain = std::nullopt)
964 residual_->AddBoundaryIntegral(
966 [traction_function](
double t,
auto X,
auto ,
auto ,
auto... params) {
967 auto n =
cross(get<DERIVATIVE>(X));
969 return -1.0 * traction_function(get<VALUE>(X),
normalize(n), t, params...);
975 template <
typename TractionType>
976 void setTraction(TractionType traction_function,
const std::optional<Domain>& optional_domain = std::nullopt)
978 setTraction(
DependsOn<>{}, traction_function, optional_domain);
1003 template <
int... active_parameters,
typename PressureType>
1005 const std::optional<Domain>& optional_domain = std::nullopt)
1009 residual_->AddBoundaryIntegral(
1011 [pressure_function, geom_nonlin = geom_nonlin_](
double t,
auto X,
auto displacement,
auto ,
1014 auto x = X + 0.0 * displacement;
1017 x = x + displacement;
1020 auto n =
cross(get<DERIVATIVE>(x));
1034 return pressure_function(get<VALUE>(X), t, params...) * (n /
norm(
cross(get<DERIVATIVE>(X))));
1040 template <
typename PressureType>
1041 void setPressure(PressureType pressure_function,
const std::optional<Domain>& optional_domain = std::nullopt)
1043 setPressure(
DependsOn<>{}, pressure_function, optional_domain);
1051 return std::make_unique<mfem_ext::StdFunctionOperator>(
1052 displacement_.space().TrueVSize(),
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...);
1063 r.SetSubVector(bcs_.allEssentialTrueDofs(), 0.0);
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_);
1087 std::pair<const mfem::HypreParMatrix&, const mfem::HypreParMatrix&>
stiffnessMatrix()
const
1089 SLIC_ERROR_ROOT_IF(!J_ || !J_e_,
"Stiffness matrix has not yet been assembled.");
1091 return {*J_, *J_e_};
1102 displacement_.space().BuildDofToArrays();
1104 if (is_quasistatic_) {
1105 residual_with_bcs_ = buildQuasistaticOperator();
1110 residual_with_bcs_ = std::make_unique<mfem_ext::StdFunctionOperator>(
1111 displacement_.space().TrueVSize(),
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...);
1122 r.SetSubVector(bcs_.allEssentialTrueDofs(), 0.0);
1125 [
this](
const mfem::Vector& d2u_dt2) -> mfem::Operator& {
1126 add(1.0, u_, c0_, d2u_dt2, predicted_displacement_);
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));
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));
1141 J_.reset(mfem::Add(1.0, *m_mat, c0_, *k_mat));
1142 J_e_ = bcs_.eliminateAllEssentialDofsFromMatrix(*J_);
1148 nonlin_solver_->setOperator(*residual_with_bcs_);
1150 if (checkpoint_to_disk_) {
1151 outputStateToDisk();
1153 checkpoint_states_.clear();
1155 checkpoint_states_[
"displacement"].push_back(displacement_);
1156 checkpoint_states_[
"velocity"].push_back(velocity_);
1157 checkpoint_states_[
"acceleration"].push_back(acceleration_);
1164 for (
const auto& essential : bcs_.essentials()) {
1165 field.SetSubVector(essential.getTrueDofList(), 0.0);
1175 ode_time_point_ = time_;
1179 warmStartDisplacement();
1181 nonlin_solver_->solve(displacement_);
1196 SLIC_ERROR_ROOT_IF(!residual_,
"completeSetup() must be called prior to advanceTimestep(dt) in SolidMechanics.");
1200 for (
auto& parameter : parameters_) {
1201 *parameter.previous_state = *parameter.state;
1205 if (is_quasistatic_) {
1206 quasiStaticSolve(dt);
1208 ode2_.Step(displacement_, velocity_, time_, dt);
1212 if (checkpoint_to_disk_) {
1213 outputStateToDisk();
1215 checkpoint_states_[
"displacement"].push_back(displacement_);
1216 checkpoint_states_[
"velocity"].push_back(velocity_);
1217 checkpoint_states_[
"acceleration"].push_back(acceleration_);
1225 residual_->updateQdata(
true);
1227 reactions_ = (*residual_)(ode_time_point_, shape_displacement_, displacement_, acceleration_,
1228 *parameters_[parameter_indices].state...);
1230 residual_->updateQdata(
false);
1233 if (cycle_ > max_cycle_) {
1234 timesteps_.push_back(dt);
1235 max_cycle_ = cycle_;
1254 virtual void setAdjointLoad(std::unordered_map<std::string, const serac::FiniteElementDual&> loads)
override
1256 SLIC_ERROR_ROOT_IF(loads.size() == 0,
"Adjoint load container size must be greater than 0 in the solid mechanics.");
1258 auto disp_adjoint_load = loads.find(
"displacement");
1260 SLIC_ERROR_ROOT_IF(disp_adjoint_load == loads.end(),
"Adjoint load for \"displacement\" not found.");
1262 displacement_adjoint_load_ = disp_adjoint_load->second;
1264 displacement_adjoint_load_ *= -1.0;
1266 auto velo_adjoint_load = loads.find(
"velocity");
1268 if (velo_adjoint_load != loads.end()) {
1269 velocity_adjoint_load_ = velo_adjoint_load->second;
1271 velocity_adjoint_load_ *= -1.0;
1274 auto accel_adjoint_load = loads.find(
"acceleration");
1276 if (accel_adjoint_load != loads.end()) {
1277 acceleration_adjoint_load_ = accel_adjoint_load->second;
1279 acceleration_adjoint_load_ *= -1.0;
1290 auto& lin_solver = nonlin_solver_->linearSolver();
1293 mfem::HypreParVector adjoint_essential(displacement_adjoint_load_);
1294 adjoint_essential = 0.0;
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());
1302 for (
const auto& bc : bcs_.essentials()) {
1303 bc.apply(*J_T, displacement_adjoint_load_, adjoint_essential);
1306 lin_solver.SetOperator(*J_T);
1307 lin_solver.Mult(displacement_adjoint_load_, adjoint_displacement_);
1310 nonlin_solver_->setOperator(*residual_with_bcs_);
1316 "Only Newmark implemented for transient adjoint solid mechanics.");
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");
1324 auto previous_states_n = getCheckpointedStates(cycle_);
1326 displacement_ = previous_states_n.at(
"displacement");
1327 velocity_ = previous_states_n.at(
"velocity");
1328 acceleration_ = previous_states_n.at(
"acceleration");
1331 double dt_np1 = getCheckpointedTimestep(cycle_);
1332 double dt_n = getCheckpointedTimestep(cycle_ - 1);
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));
1340 auto M = serac::get<DERIVATIVE>((*residual_)(ode_time_point_, shape_displacement_, displacement_,
1342 *parameters_[parameter_indices].state...));
1343 std::unique_ptr<mfem::HypreParMatrix> m_mat(assemble(M));
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);
1363 std::unordered_map<std::string, FiniteElementState> previous_states;
1365 if (checkpoint_to_disk_) {
1366 previous_states.emplace(
"displacement", displacement_);
1367 previous_states.emplace(
"velocity", velocity_);
1368 previous_states.emplace(
"acceleration", acceleration_);
1371 {previous_states.at(
"displacement"), previous_states.at(
"velocity"), previous_states.at(
"acceleration")});
1372 return previous_states;
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)]);
1381 return previous_states;
1395 SLIC_ASSERT_MSG(parameter_field <
sizeof...(parameter_indices),
1396 axom::fmt::format(
"Invalid parameter index '{}' requested for sensitivity."));
1400 auto drdparam = serac::get<DERIVATIVE>(d_residual_d_[parameter_field](ode_time_point_));
1401 auto drdparam_mat = assemble(drdparam);
1403 drdparam_mat->MultTranspose(adjoint_displacement_, *parameters_[parameter_field].sensitivity);
1405 return *parameters_[parameter_field].sensitivity;
1419 serac::get<DERIVATIVE>((*residual_)(ode_time_point_,
differentiate_wrt(shape_displacement_), displacement_,
1420 acceleration_, *parameters_[parameter_indices].state...));
1422 auto drdshape_mat = assemble(drdshape);
1424 drdshape_mat->MultTranspose(adjoint_displacement_, *shape_displacement_sensitivity_);
1426 return *shape_displacement_sensitivity_;
1438 return {{
"displacement", implicit_sensitivity_displacement_start_of_step_},
1439 {
"velocity", implicit_sensitivity_velocity_start_of_step_}};
1525 std::unique_ptr<mfem::HypreParMatrix>
J_;
1529 std::unique_ptr<mfem::HypreParMatrix>
J_e_;
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) {
1569 displacement_, acceleration_, *parameters_[parameter_indices].state...);
1582 std::optional<int> component = {})
1585 mfem::ParGridFunction nodal_positions(&displacement_.
space());
1586 mesh_.GetNodes(nodal_positions);
1588 const int num_nodes = nodal_positions.Size() / dim;
1589 mfem::Array<int> constrained_dofs;
1591 for (
int i = 0; i < num_nodes; i++) {
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++) {
1599 int local_vector_dof = mfem::Ordering::Map<mfem::Ordering::byNODES>(
1600 nodal_positions.FESpace()->GetNDofs(), nodal_positions.FESpace()->GetVDim(), i, d);
1603 node_coords(d) = nodal_positions(local_vector_dof);
1606 bool is_active_component =
true;
1608 if (*component != d) {
1609 is_active_component =
false;
1613 if (is_active_component) {
1615 node_dofs.Append(nodal_positions.ParFESpace()->GetLocalTDofNumber(local_vector_dof));
1620 if (is_node_constrained(node_coords)) {
1621 constrained_dofs.Append(node_dofs);
1625 node_dofs.DeleteAll();
1628 return constrained_dofs;
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_);
1643 for (
auto& bc : bcs_.essentials()) {
1644 bc.setDofs(du_, time_);
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);
1654 mfem::EliminateBC(*J_, *J_e_, constrained_dofs, du_, dr_);
1657 for (std::size_t parameter_index = 0; parameter_index < parameters_.size(); ++parameter_index) {
1660 parameter_difference -= *parameters_[parameter_index].previous_state;
1663 auto drdparam = serac::get<DERIVATIVE>(d_residual_d_[parameter_index](ode_time_point_));
1664 auto residual_update = drdparam(parameter_difference);
1668 residual_update *= -1.0;
1670 dr_ += residual_update;
1673 *parameters_[parameter_index].previous_state = *parameters_[parameter_index].state;
1676 for (
int i = 0; i < constrained_dofs.Size(); i++) {
1677 int j = constrained_dofs[i];
1681 auto& lin_solver = nonlin_solver_->linearSolver();
1683 lin_solver.SetOperator(*J_);
1685 lin_solver.Mult(dr_, du_);
1686 displacement_ += du_;
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...
FiniteElementState velocity_
The velocity finite element state.
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
FiniteElementState adjoint_displacement_
The displacement finite element adjoint state.
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
const serac::FiniteElementState & displacement() const
Get the displacement state.
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...
virtual void quasiStaticSolve(double dt)
Solve the Quasi-static Newton system.
const serac::FiniteElementState & acceleration() const
Get the acceleration state.
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.
void advanceTimestep(double dt) override
Advance the solid mechanics physics module in time.
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...
FiniteElementState acceleration_
The acceleration finite element state.
FiniteElementState displacement_
The displacement finite element state.
const serac::FiniteElementState & velocity() const
Get the velocity state.
mfem::Vector du_
vector used to store the change in essential bcs between timesteps
void reverseAdjointTimestep() override
Solve the adjoint problem.
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.
std::unique_ptr< mfem::HypreParMatrix > J_e_
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...
double c0_
coefficient used to calculate predicted displacement: u_p := u + c0 * d2u_dt2
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
FiniteElementDual reactions_
nodal forces
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.
virtual ~SolidMechanics()
Destroy the SolidMechanics Functional object.
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...
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.
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.
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"
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
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)
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,...
bool holds_alternative(const variant< T0, T1 > &v)
Checks whether a variant's active member is of a certain type.
Domain EntireDomain(const mfem::Mesh &mesh)
constructs a domain from all the elements in a mesh
GeometricNonlinearities
Enum to set the geometric nonlinearity flag.
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
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.
This file contains the declaration of the StateManager class.
Classes for defining an mfem::Operator with std::functions.
Compile-time alias for a dimension.
a class for representing a geometric region that can be used for integration
see Nothing for a complete description of this class and when to use it
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 ...
A class for storing and access user-defined types at quadrature points.
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 ...
A sentinel struct for eliding no-op tensor operations.