Smith  0.1
Smith is an implicit thermal structural mechanics simulation code.
solid_mechanics_contact.hpp
Go to the documentation of this file.
1 // Copyright (c) Lawrence Livermore National Security, LLC and
2 // other Smith 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 <charconv>
16 #include <memory>
17 #include <optional>
18 #include <string_view>
19 #include <system_error>
20 #include <unordered_map>
21 #include <vector>
22 
25 
26 namespace smith {
27 
28 template <int order, int dim, typename parameters = Parameters<>,
29  typename parameter_indices = std::make_integer_sequence<int, parameters::n>>
31 
41 template <int order, int dim, typename... parameter_space, int... parameter_indices>
42 class SolidMechanicsContact<order, dim, Parameters<parameter_space...>,
43  std::integer_sequence<int, parameter_indices...>>
44  : public SolidMechanics<order, dim, Parameters<parameter_space...>,
45  std::integer_sequence<int, parameter_indices...>> {
46  using SolidMechanicsBase =
47  SolidMechanics<order, dim, Parameters<parameter_space...>, std::integer_sequence<int, parameter_indices...>>;
48 
49  public:
66  const smith::TimesteppingOptions timestepping_opts, const std::string& physics_name,
67  std::shared_ptr<smith::Mesh> smith_mesh, std::vector<std::string> parameter_names = {},
68  int cycle = 0, double time = 0.0, bool checkpoint_to_disk = false, bool use_warm_start = true)
69  : SolidMechanicsContact(std::make_unique<EquationSolver>(nonlinear_opts, lin_opts, smith_mesh->getComm()),
70  timestepping_opts, physics_name, smith_mesh, parameter_names, cycle, time,
71  checkpoint_to_disk, use_warm_start)
72  {
73  }
74 
89  SolidMechanicsContact(std::unique_ptr<smith::EquationSolver> solver,
90  const smith::TimesteppingOptions timestepping_opts, const std::string& physics_name,
91  std::shared_ptr<smith::Mesh> smith_mesh, std::vector<std::string> parameter_names = {},
92  int cycle = 0, double time = 0.0, bool checkpoint_to_disk = false, bool use_warm_start = true)
93  : SolidMechanicsBase(std::move(solver), timestepping_opts, physics_name, smith_mesh, parameter_names, cycle, time,
94  checkpoint_to_disk, use_warm_start),
95  contact_(BasePhysics::mfemParMesh()),
96  forces_(StateManager::newDual(displacement_.space(), detail::addPrefix(physics_name, "contact_forces")))
97  {
98  forces_ = 0;
99  duals_.push_back(&forces_);
100  }
101 
111  SolidMechanicsContact(const SolidMechanicsInputOptions& input_options, const std::string& physics_name,
112  std::shared_ptr<smith::Mesh> smith_mesh, int cycle = 0, double time = 0.0)
113  : SolidMechanicsBase(input_options, physics_name, smith_mesh, cycle, time),
114  contact_(BasePhysics::mfemParMesh()),
115  forces_(StateManager::newDual(displacement_.space(), detail::addPrefix(physics_name, "contact_forces")))
116  {
117  forces_ = 0;
118  duals_.push_back(&forces_);
119  }
120 
122  void resetStates(int cycle = 0, double time = 0.0) override
123  {
124  SolidMechanicsBase::resetStates(cycle, time);
125  forces_ = 0.0;
126  for (auto& [_, force] : contact_interaction_forces_) {
127  if (force) {
128  *force = 0.0;
129  }
130  }
131  for (auto& [_, seed] : contact_interaction_force_adjoint_bcs_) {
132  if (seed) {
133  *seed = 0.0;
134  }
135  }
136  contact_.reset();
137  double dt = 0.0;
138  mfem::Vector p(contact_.numPressureDofs());
139  p = 0.0;
140  contact_.update(cycle, time, dt, BasePhysics::shapeDisplacement(), displacement_, p);
141  }
142 
144  std::unique_ptr<mfem_ext::StdFunctionOperator> buildQuasistaticOperator() override
145  {
146  auto residual_fn = [this](const mfem::Vector& u, mfem::Vector& r) {
147  const mfem::Vector u_blk(const_cast<mfem::Vector&>(u), 0, displacement_.Size());
148  const mfem::Vector res = (*residual_)(time_, BasePhysics::shapeDisplacement(), u_blk, acceleration_,
149  *parameters_[parameter_indices].state...);
150 
151  // NOTE this copy is required as the sundials solvers do not allow move assignments because of their memory
152  // tracking strategy
153  // See https://github.com/mfem/mfem/issues/3531
154  mfem::Vector r_blk(r, 0, displacement_.Size());
155  r_blk = res;
156 
157  contact_.residualFunction(BasePhysics::shapeDisplacement(), u, r);
158  r_blk.SetSubVector(bcs_.allEssentialTrueDofs(), 0.0);
159  };
160  // This if-block below breaks up building the Jacobian operator depending if there is Lagrange multiplier
161  // enforcement or not
162  if (contact_.haveLagrangeMultipliers()) {
163  // The quasistatic operator has blocks if any of the contact interactions are enforced using Lagrange multipliers.
164  // Jacobian operator is an mfem::BlockOperator
165  J_offsets_ = mfem::Array<int>({0, displacement_.Size(), displacement_.Size() + contact_.numPressureDofs()});
166  return std::make_unique<mfem_ext::StdFunctionOperator>(
167  displacement_.space().TrueVSize() + contact_.numPressureDofs(), residual_fn,
168  // gradient of residual function
169  [this](const mfem::Vector& u) -> mfem::Operator& {
170  const mfem::Vector u_blk(const_cast<mfem::Vector&>(u), 0, displacement_.Size());
171  auto [r, drdu] = (*residual_)(time_, BasePhysics::shapeDisplacement(), differentiate_wrt(u_blk),
172  acceleration_, *parameters_[parameter_indices].state...);
173 
174  // create block operator holding jacobian contributions
175  J_constraint_ = contact_.jacobianFunction(assemble(drdu));
176 
177  // take ownership of blocks
178  J_constraint_->owns_blocks = false;
179  J_ = std::unique_ptr<mfem::HypreParMatrix>(
180  static_cast<mfem::HypreParMatrix*>(&J_constraint_->GetBlock(0, 0)));
181  J_12_ = std::unique_ptr<mfem::HypreParMatrix>(
182  static_cast<mfem::HypreParMatrix*>(&J_constraint_->GetBlock(0, 1)));
183  J_21_ = std::unique_ptr<mfem::HypreParMatrix>(
184  static_cast<mfem::HypreParMatrix*>(&J_constraint_->GetBlock(1, 0)));
185  J_22_ = std::unique_ptr<mfem::HypreParMatrix>(
186  static_cast<mfem::HypreParMatrix*>(&J_constraint_->GetBlock(1, 1)));
187 
188  // eliminate bcs and compute eliminated blocks
189  J_e_ = bcs_.eliminateAllEssentialDofsFromMatrix(*J_);
190  J_e_21_ = std::unique_ptr<mfem::HypreParMatrix>(J_21_->EliminateCols(bcs_.allEssentialTrueDofs()));
191  J_12_->EliminateRows(bcs_.allEssentialTrueDofs());
192 
193  // create block operator for constraints
194  J_constraint_e_ = std::make_unique<mfem::BlockOperator>(J_offsets_);
195  J_constraint_e_->SetBlock(0, 0, J_e_.get());
196  J_constraint_e_->SetBlock(1, 0, J_e_21_.get());
197 
198  J_operator_ = J_constraint_.get();
199  return *J_constraint_;
200  });
201  } else {
202  // If all of the contact interactions are penalty, then there will be no blocks. Jacobian operator is a single
203  // mfem::HypreParMatrix
204  return std::make_unique<mfem_ext::StdFunctionOperator>(
205  displacement_.space().TrueVSize(), residual_fn, [this](const mfem::Vector& u) -> mfem::Operator& {
206  auto [r, drdu] = (*residual_)(time_, BasePhysics::shapeDisplacement(), differentiate_wrt(u), acceleration_,
207  *parameters_[parameter_indices].state...);
208 
209  // get 11-block holding Jacobian contributions
210  auto block_J = contact_.jacobianFunction(assemble(drdu));
211  block_J->owns_blocks = false;
212  J_ = std::unique_ptr<mfem::HypreParMatrix>(static_cast<mfem::HypreParMatrix*>(&block_J->GetBlock(0, 0)));
213 
214  J_e_ = bcs_.eliminateAllEssentialDofsFromMatrix(*J_);
215 
216  J_operator_ = J_.get();
217  return *J_;
218  });
219  }
220  }
221 
230  void addContactInteraction(int interaction_id, const std::set<int>& bdry_attr_surf1,
231  const std::set<int>& bdry_attr_surf2, ContactOptions contact_opts)
232  {
233  SLIC_ERROR_ROOT_IF(!is_quasistatic_, "Contact can only be applied to quasistatic problems.");
234  SLIC_ERROR_ROOT_IF(order > 1, "Contact can only be applied to linear (order = 1) meshes.");
235 
236  // Disallow duplicates for this physics module. Tribol ids are global, so reusing the same interaction_id is an
237  // error even if ContactData rejects it later.
238  SLIC_ERROR_ROOT_IF(
239  contact_interaction_forces_.find(interaction_id) != contact_interaction_forces_.end(),
240  std::format("Contact interaction id {} already exists for physics module '{}'.", interaction_id, name_));
241 
242  // Register with Tribol via ContactData (includes global uniqueness check).
243  contact_.addContactInteraction(interaction_id, bdry_attr_surf1, bdry_attr_surf2, contact_opts);
244 
245  const auto interaction_force_name = detail::addPrefix(name_, std::format("contact_force_{}", interaction_id));
246  auto interaction_force_dual =
247  std::make_unique<FiniteElementDual>(StateManager::newDual(displacement_.space(), interaction_force_name));
248  *interaction_force_dual = 0.0;
249  duals_.push_back(interaction_force_dual.get());
250  contact_interaction_forces_.emplace(interaction_id, std::move(interaction_force_dual));
251 
252  const auto force_adjoint_bcs_name =
253  detail::addPrefix(name_, std::format("contact_force_adjoint_bcs_{}", interaction_id));
254  auto force_adjoint_bcs = std::make_unique<FiniteElementState>(displacement_.space(), force_adjoint_bcs_name);
255  *force_adjoint_bcs = 0.0;
256  this->dual_adjoints_.push_back(force_adjoint_bcs.get());
257  contact_interaction_force_adjoint_bcs_.emplace(interaction_id, std::move(force_adjoint_bcs));
258  }
259 
261  std::vector<std::string> dualNames() const override
262  {
263  auto dual_names = SolidMechanicsBase::dualNames();
264  dual_names.push_back("contact_forces");
265 #ifdef SMITH_USE_TRIBOL
266  for (const auto& interaction : contact_.getContactInteractions()) {
267  dual_names.push_back(std::format("contact_force_{}", interaction.getInteractionId()));
268  }
269 #endif
270  return dual_names;
271  }
272 
274  const FiniteElementDual& dual(const std::string& dual_name) const override
275  {
276  if (dual_name == "contact_forces" || dual_name == detail::addPrefix(this->name_, "contact_forces")) {
277  return forces_;
278  }
279 
280  const auto interaction_id = parseContactInteractionForceId(dual_name);
281  if (interaction_id.has_value()) {
282  auto it = contact_interaction_forces_.find(*interaction_id);
283  if (it != contact_interaction_forces_.end()) {
284  return *it->second;
285  }
286  }
287 
288  return SolidMechanicsBase::dual(dual_name);
289  }
290 
292  const FiniteElementState& dualAdjoint(const std::string& dual_name) const override
293  {
294  const auto interaction_id = parseContactInteractionForceId(dual_name);
295  if (interaction_id.has_value()) {
296  auto it = contact_interaction_force_adjoint_bcs_.find(*interaction_id);
297  if (it != contact_interaction_force_adjoint_bcs_.end()) {
298  return *it->second;
299  }
300  }
301 
302  return SolidMechanicsBase::dualAdjoint(dual_name);
303  }
304 
309  {
310  // compute contact dof --> displacement dof prolongation operator
311  // if not previously computed
312  if (!contact_dof_prolongation_) {
313  contact_dof_prolongation_ = contact_.contactSubspaceTransferOperator();
314  }
315  }
316 
322  void completeSetup() override
323  {
324  double dt = 0.0;
325  mfem::Vector p = pressure();
326  contact_.update(cycle_, time_, dt, BasePhysics::shapeDisplacement(), displacement_, p);
327 
328  SolidMechanicsBase::completeSetup();
329  }
330 
336  mfem::HypreParVector pressure() const { return contact_.mergedPressures(); }
337 
338 #ifdef SMITH_USE_TRIBOL
345  const ContactInteraction& contactInteraction(int interaction_id) const
346  {
347  for (const auto& interaction : contact_.getContactInteractions()) {
348  if (interaction.getInteractionId() == interaction_id) {
349  return interaction;
350  }
351  }
352  SLIC_ERROR_ROOT(std::format("No contact interaction found with interaction_id={}", interaction_id));
353  return contact_.getContactInteractions().front();
354  }
355 #endif
356 
358  void setDualAdjointBcs(std::unordered_map<std::string, const smith::FiniteElementState&> bcs) override
359  {
360  SLIC_ERROR_ROOT_IF(bcs.size() == 0, "Adjoint load container size must be greater than 0 in SolidMechanicsContact.");
361 
362  auto reaction_adjoint_load = bcs.find("reactions");
363  if (reaction_adjoint_load != bcs.end()) {
364  SolidMechanicsBase::setDualAdjointBcs({{"reactions", reaction_adjoint_load->second}});
365  }
366 
367  for (const auto& [name, bc] : bcs) {
368  if (name == "reactions") {
369  continue;
370  }
371 
372  const auto interaction_id = parseContactInteractionForceId(name);
373  SLIC_ERROR_ROOT_IF(!interaction_id.has_value(),
374  std::format("Unknown dual adjoint BC '{}' for SolidMechanicsContact.", name));
375 
376  auto it = contact_interaction_force_adjoint_bcs_.find(*interaction_id);
377  SLIC_ERROR_ROOT_IF(it == contact_interaction_force_adjoint_bcs_.end(),
378  std::format("No contact force adjoint BC registered for interaction_id={}", *interaction_id));
379 
380  *it->second = bc;
381  }
382  }
383 
384  protected:
386  static std::optional<int> parseContactInteractionForceId(std::string_view dual_name)
387  {
388  constexpr std::string_view prefix = "contact_force_";
389 
390  // Accept both the bare name and the module-prefixed name, e.g. "solid_contact_force_0".
391  const auto idx = dual_name.rfind(prefix);
392  if (idx == std::string_view::npos) {
393  return std::nullopt;
394  }
395 
396  // This code converts everything after the prefix to a candidate id
397  const std::string_view id_str = dual_name.substr(idx + prefix.size());
398  int interaction_id = -1;
399  const auto* begin = id_str.data();
400  const auto* end = id_str.data() + id_str.size();
401  auto [ptr, ec] = std::from_chars(begin, end, interaction_id);
402  if (ec != std::errc{} || ptr != end) {
403  return std::nullopt;
404  }
405  return interaction_id;
406  }
407 
409  void quasiStaticSolve(double dt) override
410  {
411  // warm start must be called prior to the time update so that the previous Jacobians can be used consistently
412  // throughout. warm start for contact needs to include the previous stiffness terms associated with contact
413  // otherwise the system will interpenetrate instantly on warm-starting.
414  warmStartDisplacementContact(dt);
415  time_ += dt;
416 
417  // In general, the solution vector is a stacked (block) vector:
418  // | displacement |
419  // | contact pressure |
420  // Contact pressure is only active when solving a contact problem with Lagrange multipliers.
421  mfem::Vector augmented_solution(displacement_.Size() + contact_.numPressureDofs());
422  augmented_solution.SetVector(displacement_, 0);
423  augmented_solution.SetVector(contact_.mergedPressures(), displacement_.Size());
424 
425  // solve the non-linear system resid = 0 and pressure * gap = 0
426  nonlin_solver_->solve(augmented_solution);
427  displacement_.Set(1.0, mfem::Vector(augmented_solution, 0, displacement_.Size()));
428  forces_.SetVector(contact_.forces(), 0);
429 
430 #ifdef SMITH_USE_TRIBOL
431  for (const auto& interaction : contact_.getContactInteractions()) {
432  auto it = contact_interaction_forces_.find(interaction.getInteractionId());
433  if (it != contact_interaction_forces_.end()) {
434  it->second->SetVector(interaction.forces(), 0);
435  }
436  }
437 #endif
438  }
439 
474  {
476 
477  du_ = 0.0;
478  for (auto& bc : bcs_.essentials()) {
479  // apply the future boundary conditions, but use the most recent Jacobians stiffness.
480  bc.setDofs(du_, time_ + dt);
481  }
482 
483  auto& constrained_dofs = bcs_.allEssentialTrueDofs();
484  for (int i = 0; i < constrained_dofs.Size(); i++) {
485  int j = constrained_dofs[i];
486  du_[j] -= displacement_(j);
487  }
488 
489  auto amgf_prec = dynamic_cast<mfem::AMGFSolver*>(&nonlin_solver_->preconditioner());
490  if (amgf_prec) {
491  // compute contact_dof_prolongation
492  computeContactSubspaceTransferOperator();
493  // set AMGF subspace transfer operator
494  amgf_prec->SetFilteredSubspaceTransferOperator(*(contact_dof_prolongation_.get()));
495  // set the filteredsubspace solver component of AMGF
496  // better solution: retrieve print level from .preconditioner_print_level from linear_solver_options
497  int filter_solver_print_level = 0;
498  filter_solver_ =
499  std::make_unique<StrumpackSolver>(filter_solver_print_level, contact_dof_prolongation_->GetComm());
500  amgf_prec->SetFilteredSubspaceSolver(*filter_solver_.get());
501 
502  auto& lin_solver = nonlin_solver_->linearSolver();
503  auto iterative_solver = dynamic_cast<mfem::IterativeSolver*>(&lin_solver);
504  SLIC_WARNING_ROOT_IF(!iterative_solver,
505  "AMGFContact should only be used as a preconditioner for an iterative solver");
506  }
507 
508  if (use_warm_start_) {
509  // Update the system residual
510  mfem::Vector augmented_residual(displacement_.Size() + contact_.numPressureDofs());
511  augmented_residual = 0.0;
512  const mfem::Vector res = (*residual_)(time_ + dt, BasePhysics::shapeDisplacement(), displacement_, acceleration_,
513  *parameters_[parameter_indices].state...);
514 
515  mfem::Vector augmented_solution(displacement_.space().TrueVSize() + contact_.numPressureDofs());
516  augmented_solution = 0.0;
517  mfem::Vector du(augmented_solution, 0, displacement_.space().TrueVSize());
518  du = displacement_;
519  mfem::Vector p_blk(augmented_solution, displacement_.Size(), contact_.numPressureDofs());
520 
521  // Perform a single update for the warm start evaluation.
522  // Note: we use time_ to match the previous Jacobian evaluation point.
523  contact_.update(cycle_, time_, dt, BasePhysics::shapeDisplacement(), displacement_, p_blk);
524 
525  mfem::Vector r_blk(augmented_residual, 0, displacement_.space().TrueVSize());
526  r_blk = res;
527  r_blk += contact_.forces();
528 
529  mfem::Vector g_blk(augmented_residual, displacement_.Size(), contact_.numPressureDofs());
530  g_blk.Set(1.0, contact_.mergedGaps(true));
531 
532  r_blk.SetSubVector(bcs_.allEssentialTrueDofs(), 0.0);
533 
534  // use the most recently evaluated Jacobian
535  auto [_, drdu] = (*residual_)(time_, BasePhysics::shapeDisplacement(), differentiate_wrt(displacement_),
536  acceleration_, *parameters_[parameter_indices].previous_state...);
537 
538  if (contact_.haveLagrangeMultipliers()) {
539  J_offsets_ = mfem::Array<int>({0, displacement_.Size(), displacement_.Size() + contact_.numPressureDofs()});
540  J_constraint_ = contact_.jacobianFunction(assemble(drdu));
541 
542  // take ownership of blocks
543  J_constraint_->owns_blocks = false;
544  J_ = std::unique_ptr<mfem::HypreParMatrix>(static_cast<mfem::HypreParMatrix*>(&J_constraint_->GetBlock(0, 0)));
545  J_12_ =
546  std::unique_ptr<mfem::HypreParMatrix>(static_cast<mfem::HypreParMatrix*>(&J_constraint_->GetBlock(0, 1)));
547  J_21_ =
548  std::unique_ptr<mfem::HypreParMatrix>(static_cast<mfem::HypreParMatrix*>(&J_constraint_->GetBlock(1, 0)));
549  J_22_ =
550  std::unique_ptr<mfem::HypreParMatrix>(static_cast<mfem::HypreParMatrix*>(&J_constraint_->GetBlock(1, 1)));
551 
552  J_e_ = bcs_.eliminateAllEssentialDofsFromMatrix(*J_);
553  J_e_21_ = std::unique_ptr<mfem::HypreParMatrix>(J_21_->EliminateCols(bcs_.allEssentialTrueDofs()));
554  J_12_->EliminateRows(bcs_.allEssentialTrueDofs());
555 
556  J_operator_ = J_constraint_.get();
557  } else {
558  // get 11-block holding jacobian contributions
559  auto block_J = contact_.jacobianFunction(assemble(drdu));
560  block_J->owns_blocks = false;
561  J_ = std::unique_ptr<mfem::HypreParMatrix>(static_cast<mfem::HypreParMatrix*>(&block_J->GetBlock(0, 0)));
562 
563  J_e_ = bcs_.eliminateAllEssentialDofsFromMatrix(*J_);
564 
565  J_operator_ = J_.get();
566  }
567 
568  augmented_residual *= -1.0;
569 
570  du = du_;
571  mfem::EliminateBC(*J_, *J_e_, constrained_dofs, du, r_blk);
572  for (int i = 0; i < constrained_dofs.Size(); i++) {
573  int j = constrained_dofs[i];
574  r_blk[j] = du[j];
575  }
576 
577  auto& lin_solver = nonlin_solver_->linearSolver();
578  lin_solver.SetOperator(*J_operator_);
579  lin_solver.Mult(augmented_residual, augmented_solution);
580 
581  du_ = du;
582  }
583 
584  displacement_ += du_;
585  }
586 
588  void quasiStaticAdjointSolve(double /*dt*/) override
589  {
590  SLIC_ERROR_ROOT_IF(contact_.haveLagrangeMultipliers(),
591  "Lagrange multiplier contact does not currently support sensitivities/adjoints.");
592 
593  auto [_, drdu] = (*residual_)(time_, BasePhysics::shapeDisplacement(), differentiate_wrt(displacement_),
594  acceleration_, *parameters_[parameter_indices].state...);
595 
596  auto block_J = contact_.jacobianFunction(assemble(drdu));
597  block_J->owns_blocks = false;
598  auto jacobian = std::unique_ptr<mfem::HypreParMatrix>(static_cast<mfem::HypreParMatrix*>(&block_J->GetBlock(0, 0)));
599  auto J_T = std::unique_ptr<mfem::HypreParMatrix>(jacobian->Transpose());
600 
601  // If a QoI depends on per-interaction contact force duals, the dual-adjoint seeds define an additional contribution
602  // to the adjoint load:
603  // dJ/du += (df_i/du)^T * dJ/df_i
604  // Following SolidMechanics::setAdjointLoad() sign convention, displacement_adjoint_load_ stores the negative of the
605  // provided dJ/du, so we subtract these contributions here.
606 #ifdef SMITH_USE_TRIBOL
607  if (!contact_interaction_force_adjoint_bcs_.empty()) {
608  FiniteElementDual contact_force_load(displacement_.space(), "contact_force_dual_adjoint_load");
609  contact_force_load = 0.0;
610 
611  for (const auto& [interaction_id, force_seed] : contact_interaction_force_adjoint_bcs_) {
612  if (!force_seed) {
613  continue;
614  }
615 
616  // Only apply if the seed is nonzero.
617  if (force_seed->Norml2() == 0.0) {
618  continue;
619  }
620 
621  const auto interaction_J = contactInteraction(interaction_id).jacobianContribution();
622  auto* J00 = dynamic_cast<mfem::HypreParMatrix*>(&interaction_J->GetBlock(0, 0));
623  SLIC_ERROR_ROOT_IF(!J00, "Expected HypreParMatrix (0,0) block for contact interaction Jacobian.");
624 
625  FiniteElementDual tmp(displacement_.space(), "contact_force_dual_adjoint_load_tmp");
626  tmp = 0.0;
627  J00->MultTranspose(*force_seed, tmp);
628  contact_force_load.Add(1.0, tmp);
629  }
630 
631  displacement_adjoint_load_.Add(-1.0, contact_force_load);
632  }
633 #endif
634 
635  auto J_e_T = bcs_.eliminateAllEssentialDofsFromMatrix(*J_T);
636  auto& constrained_dofs = bcs_.allEssentialTrueDofs();
637 
638  mfem::EliminateBC(*J_T, *J_e_T, constrained_dofs, reactions_adjoint_bcs_, displacement_adjoint_load_);
639  for (int i = 0; i < constrained_dofs.Size(); i++) {
640  int j = constrained_dofs[i];
641  displacement_adjoint_load_[j] = reactions_adjoint_bcs_[j];
642  }
643 
644  auto& lin_solver = nonlin_solver_->linearSolver();
645  lin_solver.SetOperator(*J_T);
646  lin_solver.Mult(displacement_adjoint_load_, adjoint_displacement_);
647  }
648 
651  {
652  auto drdshape =
653  smith::get<DERIVATIVE>((*residual_)(time_end_step_, differentiate_wrt(BasePhysics::shapeDisplacement()),
654  displacement_, acceleration_, *parameters_[parameter_indices].state...));
655 
656  auto block_J = contact_.jacobianFunction(assemble(drdshape));
657  block_J->owns_blocks = false;
658  auto drdshape_mat =
659  std::unique_ptr<mfem::HypreParMatrix>(static_cast<mfem::HypreParMatrix*>(&block_J->GetBlock(0, 0)));
660 
661  drdshape_mat->MultTranspose(adjoint_displacement_, shape_displacement_dual_);
662 
664  }
665 
666  using BasePhysics::bcs_;
667  using BasePhysics::cycle_;
668  using BasePhysics::duals_;
670  using BasePhysics::mesh_;
671  using BasePhysics::name_;
673  using BasePhysics::states_;
674  using BasePhysics::time_;
675  using SolidMechanicsBase::acceleration_;
676  using SolidMechanicsBase::adjoint_displacement_;
677  using SolidMechanicsBase::d_residual_d_;
678  using SolidMechanicsBase::DERIVATIVE;
679  using SolidMechanicsBase::displacement_;
680  using SolidMechanicsBase::displacement_adjoint_load_;
681  using SolidMechanicsBase::du_;
682  using SolidMechanicsBase::J_;
683  using SolidMechanicsBase::J_e_;
684  using SolidMechanicsBase::nonlin_solver_;
685  using SolidMechanicsBase::reactions_adjoint_bcs_;
686  using SolidMechanicsBase::residual_;
687  using SolidMechanicsBase::residual_with_bcs_;
688  using SolidMechanicsBase::shape_displacement_dual_;
689  using SolidMechanicsBase::time_end_step_;
690  using SolidMechanicsBase::use_warm_start_;
691  using SolidMechanicsBase::warmStartDisplacement;
692 
694  mfem::Operator* J_operator_;
695 
697  std::unique_ptr<mfem::HypreParMatrix> J_21_;
698 
700  std::unique_ptr<mfem::HypreParMatrix> J_12_;
701 
703  std::unique_ptr<mfem::HypreParMatrix> J_22_;
704 
706  mfem::Array<int> J_offsets_;
707 
709  std::unique_ptr<mfem::BlockOperator> J_constraint_;
710 
712  std::unique_ptr<mfem::HypreParMatrix> J_e_21_;
713 
716  std::unique_ptr<mfem::BlockOperator> J_constraint_e_;
717 
720 
723 
725  std::unordered_map<int, std::unique_ptr<FiniteElementDual>> contact_interaction_forces_;
726 
728  std::unordered_map<int, std::unique_ptr<FiniteElementState>> contact_interaction_force_adjoint_bcs_;
729 
731  std::unique_ptr<mfem::HypreParMatrix> contact_dof_prolongation_;
732 
734  std::unique_ptr<mfem::Solver> filter_solver_;
735 };
736 
737 } // namespace smith
This is the abstract base class for a generic forward solver.
std::string name_
Name of the physics module.
std::shared_ptr< smith::Mesh > mesh_
The primary mesh.
std::vector< const smith::FiniteElementState * > states_
List of finite element primal states associated with this physics module.
int cycle_
Current cycle (forward pass time iteration count)
std::vector< const smith::FiniteElementDual * > duals_
List of finite element duals associated with this physics module.
std::vector< ParameterInfo > parameters_
A vector of the parameters associated with this physics module.
virtual const FiniteElementState & shapeDisplacement() const
Accessor for getting the shape displacement field from the physics modules.
double time_
Current time for the forward pass.
BoundaryConditionManager bcs_
Boundary condition manager instance.
bool is_quasistatic_
Whether the simulation is time-independent.
const FiniteElementDual & shapeDisplacementSensitivity() const
Internally used accessor for getting the shape displacement sensitivity from the physics modules.
const mfem::ParMesh & mfemParMesh() const
Returns a reference to the mfem ParMesh object.
This class stores all ContactInteractions for a problem, calls Tribol functions that act on all conta...
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.
std::unique_ptr< mfem::HypreParMatrix > J_12_
12 Jacobian block if using Lagrange multiplier contact (df/dp)
std::unordered_map< int, std::unique_ptr< FiniteElementState > > contact_interaction_force_adjoint_bcs_
per-interaction dual-adjoint (BC) fields for contact force duals
std::unique_ptr< mfem::HypreParMatrix > J_21_
21 Jacobian block if using Lagrange multiplier contact (dg/dx)
SolidMechanicsContact(const NonlinearSolverOptions nonlinear_opts, const LinearSolverOptions lin_opts, const smith::TimesteppingOptions timestepping_opts, const std::string &physics_name, std::shared_ptr< smith::Mesh > smith_mesh, std::vector< std::string > parameter_names={}, int cycle=0, double time=0.0, bool checkpoint_to_disk=false, bool use_warm_start=true)
Construct a new SolidMechanicsContact object.
std::unique_ptr< mfem::HypreParMatrix > J_e_21_
Columns of J_21_ that have been separated out because they are associated with essential boundary con...
std::vector< std::string > dualNames() const override
This is an overloaded member function, provided for convenience. It differs from the above function o...
SolidMechanicsContact(const SolidMechanicsInputOptions &input_options, const std::string &physics_name, std::shared_ptr< smith::Mesh > smith_mesh, int cycle=0, double time=0.0)
Construct a new Nonlinear SolidMechanicsContact Solver object.
std::unique_ptr< mfem::HypreParMatrix > J_22_
22 Jacobian block if using Lagrange multiplier contact (ones on diagonal for inactive t-dofs)
mfem::HypreParVector pressure() const
Get the contact pressures from all contact interactions, merged into a single HypreParVector.
static std::optional< int > parseContactInteractionForceId(std::string_view dual_name)
Converts a dual name into an interaction id (if it exists)
mfem::Operator * J_operator_
Pointer to the Jacobian operator (J_ if no Lagrange multiplier contact, J_constraint_ otherwise)
const FiniteElementDual & computeTimestepShapeSensitivity() override
This is an overloaded member function, provided for convenience. It differs from the above function o...
std::unique_ptr< mfem::BlockOperator > J_constraint_
Assembled sparse matrix for the Jacobian with constraint blocks.
void resetStates(int cycle=0, double time=0.0) override
This is an overloaded member function, provided for convenience. It differs from the above function o...
std::unordered_map< int, std::unique_ptr< FiniteElementDual > > contact_interaction_forces_
per-interaction contact forces for output
mfem::Array< int > J_offsets_
Block offsets for the J_constraint_ BlockOperator (must be owned outside J_constraint_)
const FiniteElementState & dualAdjoint(const std::string &dual_name) const override
This is an overloaded member function, provided for convenience. It differs from the above function o...
void warmStartDisplacementContact(double dt)
Sets the Dirichlet BCs for the current time and computes an initial guess for parameters and displace...
SolidMechanicsContact(std::unique_ptr< smith::EquationSolver > solver, const smith::TimesteppingOptions timestepping_opts, const std::string &physics_name, std::shared_ptr< smith::Mesh > smith_mesh, std::vector< std::string > parameter_names={}, int cycle=0, double time=0.0, bool checkpoint_to_disk=false, bool use_warm_start=true)
Construct a new SolidMechanicsContact object.
const FiniteElementDual & dual(const std::string &dual_name) const override
This is an overloaded member function, provided for convenience. It differs from the above function o...
std::unique_ptr< mfem_ext::StdFunctionOperator > buildQuasistaticOperator() override
Build the quasi-static operator corresponding to the total Lagrangian formulation.
void addContactInteraction(int interaction_id, const std::set< int > &bdry_attr_surf1, const std::set< int > &bdry_attr_surf2, ContactOptions contact_opts)
Add a mortar contact boundary condition.
void setDualAdjointBcs(std::unordered_map< std::string, const smith::FiniteElementState & > bcs) override
This is an overloaded member function, provided for convenience. It differs from the above function o...
Manages the lifetimes of FEState objects such that restarts are abstracted from physics modules.
static FiniteElementDual newDual(FunctionSpace space, const std::string &dual_name, const std::string &mesh_tag)
Factory method for creating a new FEDual object.
Class for storing contact data.
Accelerator functionality.
Definition: smith.cpp:36
dual(double, T) -> dual< T >
class template argument deduction guide for type dual.
auto differentiate_wrt(const mfem::Vector &v)
this function is intended to only be used in combination with smith::Functional::operator(),...
mfem::ParFiniteElementSpace & space(FieldState field)
Get the space from the primal field of a field states.
#define SMITH_MARK_FUNCTION
Definition: profiling.hpp:90
Tools for tagging a set of components of a vector field for boundary condition enforcement.
Stores the options for a contact pair.
Parameters for an iterative linear solution scheme.
Nonlinear solution scheme parameters.
a struct that is used in the physics modules to clarify which template arguments are user-controlled ...
Definition: common.hpp:45
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.