Serac  0.1
Serac is an implicit thermal strucural 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 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 
17 
18 namespace serac {
19 
20 template <int order, int dim, typename parameters = Parameters<>,
21  typename parameter_indices = std::make_integer_sequence<int, parameters::n>>
23 
33 template <int order, int dim, typename... parameter_space, int... parameter_indices>
34 class SolidMechanicsContact<order, dim, Parameters<parameter_space...>,
35  std::integer_sequence<int, parameter_indices...>>
36  : public SolidMechanics<order, dim, Parameters<parameter_space...>,
37  std::integer_sequence<int, parameter_indices...>> {
38  using SolidMechanicsBase =
39  SolidMechanics<order, dim, Parameters<parameter_space...>, std::integer_sequence<int, parameter_indices...>>;
40 
41  public:
58  const serac::TimesteppingOptions timestepping_opts, const std::string& physics_name,
59  std::shared_ptr<serac::Mesh> serac_mesh, std::vector<std::string> parameter_names = {},
60  int cycle = 0, double time = 0.0, bool checkpoint_to_disk = false, bool use_warm_start = true)
61  : SolidMechanicsContact(std::make_unique<EquationSolver>(nonlinear_opts, lin_opts, serac_mesh->getComm()),
62  timestepping_opts, physics_name, serac_mesh, parameter_names, cycle, time,
63  checkpoint_to_disk, use_warm_start)
64  {
65  }
66 
81  SolidMechanicsContact(std::unique_ptr<serac::EquationSolver> solver,
82  const serac::TimesteppingOptions timestepping_opts, const std::string& physics_name,
83  std::shared_ptr<serac::Mesh> serac_mesh, std::vector<std::string> parameter_names = {},
84  int cycle = 0, double time = 0.0, bool checkpoint_to_disk = false, bool use_warm_start = true)
85  : SolidMechanicsBase(std::move(solver), timestepping_opts, physics_name, serac_mesh, parameter_names, cycle, time,
86  checkpoint_to_disk, use_warm_start),
87  contact_(BasePhysics::mfemParMesh()),
88  forces_(StateManager::newDual(displacement_.space(), detail::addPrefix(physics_name, "contact_forces")))
89  {
90  forces_ = 0;
91  duals_.push_back(&forces_);
92  }
93 
103  SolidMechanicsContact(const SolidMechanicsInputOptions& input_options, const std::string& physics_name,
104  std::shared_ptr<serac::Mesh> serac_mesh, int cycle = 0, double time = 0.0)
105  : SolidMechanicsBase(input_options, physics_name, serac_mesh, cycle, time),
106  contact_(BasePhysics::mesh()),
107  forces_(StateManager::newDual(displacement_.space(), detail::addPrefix(physics_name, "contact_forces")))
108  {
109  forces_ = 0;
110  duals_.push_back(&forces_);
111  }
112 
114  void resetStates(int cycle = 0, double time = 0.0) override
115  {
116  SolidMechanicsBase::resetStates(cycle, time);
117  forces_ = 0.0;
118  contact_.setDisplacements(BasePhysics::shapeDisplacement(), displacement_);
119  contact_.reset();
120  double dt = 0.0;
121  contact_.update(cycle, time, dt);
122  }
123 
125  std::unique_ptr<mfem_ext::StdFunctionOperator> buildQuasistaticOperator() override
126  {
127  auto residual_fn = [this](const mfem::Vector& u, mfem::Vector& r) {
128  const mfem::Vector u_blk(const_cast<mfem::Vector&>(u), 0, displacement_.Size());
129  const mfem::Vector res = (*residual_)(time_, BasePhysics::shapeDisplacement(), u_blk, acceleration_,
130  *parameters_[parameter_indices].state...);
131 
132  // NOTE this copy is required as the sundials solvers do not allow move assignments because of their memory
133  // tracking strategy
134  // See https://github.com/mfem/mfem/issues/3531
135  mfem::Vector r_blk(r, 0, displacement_.Size());
136  r_blk = res;
137 
138  contact_.residualFunction(BasePhysics::shapeDisplacement(), u, r);
139  r_blk.SetSubVector(bcs_.allEssentialTrueDofs(), 0.0);
140  };
141  // This if-block below breaks up building the Jacobian operator depending if there is Lagrange multiplier
142  // enforcement or not
143  if (contact_.haveLagrangeMultipliers()) {
144  // The quasistatic operator has blocks if any of the contact interactions are enforced using Lagrange multipliers.
145  // Jacobian operator is an mfem::BlockOperator
146  J_offsets_ = mfem::Array<int>({0, displacement_.Size(), displacement_.Size() + contact_.numPressureDofs()});
147  return std::make_unique<mfem_ext::StdFunctionOperator>(
148  displacement_.space().TrueVSize() + contact_.numPressureDofs(), residual_fn,
149  // gradient of residual function
150  [this](const mfem::Vector& u) -> mfem::Operator& {
151  const mfem::Vector u_blk(const_cast<mfem::Vector&>(u), 0, displacement_.Size());
152  auto [r, drdu] = (*residual_)(time_, BasePhysics::shapeDisplacement(), differentiate_wrt(u_blk),
153  acceleration_, *parameters_[parameter_indices].state...);
154  J_ = assemble(drdu);
155 
156  // create block operator holding jacobian contributions
157  J_constraint_ = contact_.jacobianFunction(J_.release());
158 
159  // take ownership of blocks
160  J_constraint_->owns_blocks = false;
161  J_ = std::unique_ptr<mfem::HypreParMatrix>(
162  static_cast<mfem::HypreParMatrix*>(&J_constraint_->GetBlock(0, 0)));
163  J_12_ = std::unique_ptr<mfem::HypreParMatrix>(
164  static_cast<mfem::HypreParMatrix*>(&J_constraint_->GetBlock(0, 1)));
165  J_21_ = std::unique_ptr<mfem::HypreParMatrix>(
166  static_cast<mfem::HypreParMatrix*>(&J_constraint_->GetBlock(1, 0)));
167  J_22_ = std::unique_ptr<mfem::HypreParMatrix>(
168  static_cast<mfem::HypreParMatrix*>(&J_constraint_->GetBlock(1, 1)));
169 
170  // eliminate bcs and compute eliminated blocks
171  J_e_ = bcs_.eliminateAllEssentialDofsFromMatrix(*J_);
172  J_e_21_ = std::unique_ptr<mfem::HypreParMatrix>(J_21_->EliminateCols(bcs_.allEssentialTrueDofs()));
173  J_12_->EliminateRows(bcs_.allEssentialTrueDofs());
174 
175  // create block operator for constraints
176  J_constraint_e_ = std::make_unique<mfem::BlockOperator>(J_offsets_);
177  J_constraint_e_->SetBlock(0, 0, J_e_.get());
178  J_constraint_e_->SetBlock(1, 0, J_e_21_.get());
179 
180  J_operator_ = J_constraint_.get();
181  return *J_constraint_;
182  });
183  } else {
184  // If all of the contact interactions are penalty, then there will be no blocks. Jacobian operator is a single
185  // mfem::HypreParMatrix
186  return std::make_unique<mfem_ext::StdFunctionOperator>(
187  displacement_.space().TrueVSize(), residual_fn, [this](const mfem::Vector& u) -> mfem::Operator& {
188  auto [r, drdu] = (*residual_)(time_, BasePhysics::shapeDisplacement(), differentiate_wrt(u), acceleration_,
189  *parameters_[parameter_indices].state...);
190  J_ = assemble(drdu);
191 
192  // get 11-block holding jacobian contributions
193  auto block_J = contact_.jacobianFunction(J_.release());
194  block_J->owns_blocks = false;
195  J_ = std::unique_ptr<mfem::HypreParMatrix>(static_cast<mfem::HypreParMatrix*>(&block_J->GetBlock(0, 0)));
196 
197  J_e_ = bcs_.eliminateAllEssentialDofsFromMatrix(*J_);
198 
199  J_operator_ = J_.get();
200  return *J_;
201  });
202  }
203  }
204 
213  void addContactInteraction(int interaction_id, const std::set<int>& bdry_attr_surf1,
214  const std::set<int>& bdry_attr_surf2, ContactOptions contact_opts)
215  {
216  SLIC_ERROR_ROOT_IF(!is_quasistatic_, "Contact can only be applied to quasistatic problems.");
217  SLIC_ERROR_ROOT_IF(order > 1, "Contact can only be applied to linear (order = 1) meshes.");
218  contact_.addContactInteraction(interaction_id, bdry_attr_surf1, bdry_attr_surf2, contact_opts);
219  }
220 
226  void completeSetup() override
227  {
228  double dt = 0.0;
229  contact_.update(cycle_, time_, dt);
230 
231  SolidMechanicsBase::completeSetup();
232  }
233 
239  mfem::HypreParVector pressure() const { return contact_.mergedPressures(); }
240 
241  protected:
243  void quasiStaticSolve(double dt) override
244  {
245  // warm start must be called prior to the time update so that the previous Jacobians can be used consistently
246  // throughout. warm start for contact needs to include the previous stiffness terms associated with contact
247  // otherwise the system will interpenetrate instantly on warm-starting.
248  warmStartDisplacementContact(dt);
249  time_ += dt;
250 
251  // In general, the solution vector is a stacked (block) vector:
252  // | displacement |
253  // | contact pressure |
254  // Contact pressure is only active when solving a contact problem with Lagrange multipliers.
255  mfem::Vector augmented_solution(displacement_.Size() + contact_.numPressureDofs());
256  augmented_solution.SetVector(displacement_, 0);
257  augmented_solution.SetVector(contact_.mergedPressures(), displacement_.Size());
258 
259  // solve the non-linear system resid = 0 and pressure * gap = 0
260  nonlin_solver_->solve(augmented_solution);
261  displacement_.Set(1.0, mfem::Vector(augmented_solution, 0, displacement_.Size()));
262  contact_.setPressures(mfem::Vector(augmented_solution, displacement_.Size(), contact_.numPressureDofs()));
263  contact_.update(cycle_, time_, dt);
264  forces_.SetVector(contact_.forces(), 0);
265  }
266 
301  {
303 
304  du_ = 0.0;
305  for (auto& bc : bcs_.essentials()) {
306  // apply the future boundary conditions, but use the most recent Jacobians stiffness.
307  bc.setDofs(du_, time_ + dt);
308  }
309 
310  auto& constrained_dofs = bcs_.allEssentialTrueDofs();
311  for (int i = 0; i < constrained_dofs.Size(); i++) {
312  int j = constrained_dofs[i];
313  du_[j] -= displacement_(j);
314  }
315 
316  if (use_warm_start_) {
317  // Update the system residual
318  mfem::Vector augmented_residual(displacement_.Size() + contact_.numPressureDofs());
319  augmented_residual = 0.0;
320  const mfem::Vector res = (*residual_)(time_ + dt, BasePhysics::shapeDisplacement(), displacement_, acceleration_,
321  *parameters_[parameter_indices].state...);
322 
323  contact_.setPressures(mfem::Vector(augmented_residual, displacement_.Size(), contact_.numPressureDofs()));
324  contact_.update(cycle_, time_, dt);
325  mfem::Vector r_blk(augmented_residual, 0, displacement_.space().TrueVSize());
326  r_blk = res;
327 
328  mfem::Vector augmented_solution(displacement_.space().TrueVSize() + contact_.numPressureDofs());
329  augmented_solution = 0.0;
330  mfem::Vector du(augmented_solution, 0, displacement_.space().TrueVSize());
331  du = displacement_;
332 
333  contact_.residualFunction(BasePhysics::shapeDisplacement(), augmented_solution, augmented_residual);
334  r_blk.SetSubVector(bcs_.allEssentialTrueDofs(), 0.0);
335 
336  // use the most recently evaluated Jacobian
337  auto [_, drdu] = (*residual_)(time_, BasePhysics::shapeDisplacement(), differentiate_wrt(displacement_),
338  acceleration_, *parameters_[parameter_indices].previous_state...);
339  J_.reset();
340  J_ = assemble(drdu);
341 
342  contact_.update(cycle_, time_, dt);
343  if (contact_.haveLagrangeMultipliers()) {
344  J_offsets_ = mfem::Array<int>({0, displacement_.Size(), displacement_.Size() + contact_.numPressureDofs()});
345  J_constraint_ = contact_.jacobianFunction(J_.release());
346 
347  // take ownership of blocks
348  J_constraint_->owns_blocks = false;
349  J_ = std::unique_ptr<mfem::HypreParMatrix>(static_cast<mfem::HypreParMatrix*>(&J_constraint_->GetBlock(0, 0)));
350  J_12_ =
351  std::unique_ptr<mfem::HypreParMatrix>(static_cast<mfem::HypreParMatrix*>(&J_constraint_->GetBlock(0, 1)));
352  J_21_ =
353  std::unique_ptr<mfem::HypreParMatrix>(static_cast<mfem::HypreParMatrix*>(&J_constraint_->GetBlock(1, 0)));
354  J_22_ =
355  std::unique_ptr<mfem::HypreParMatrix>(static_cast<mfem::HypreParMatrix*>(&J_constraint_->GetBlock(1, 1)));
356 
357  J_e_.reset();
358  J_e_ = bcs_.eliminateAllEssentialDofsFromMatrix(*J_);
359  J_e_21_ = std::unique_ptr<mfem::HypreParMatrix>(J_21_->EliminateCols(bcs_.allEssentialTrueDofs()));
360  J_12_->EliminateRows(bcs_.allEssentialTrueDofs());
361 
362  J_operator_ = J_constraint_.get();
363  } else {
364  // get 11-block holding jacobian contributions
365  auto block_J = contact_.jacobianFunction(J_.release());
366  block_J->owns_blocks = false;
367  J_ = std::unique_ptr<mfem::HypreParMatrix>(static_cast<mfem::HypreParMatrix*>(&block_J->GetBlock(0, 0)));
368 
369  J_e_ = bcs_.eliminateAllEssentialDofsFromMatrix(*J_);
370 
371  J_operator_ = J_.get();
372  }
373 
374  augmented_residual *= -1.0;
375 
376  du = du_;
377  mfem::EliminateBC(*J_, *J_e_, constrained_dofs, du, r_blk);
378  for (int i = 0; i < constrained_dofs.Size(); i++) {
379  int j = constrained_dofs[i];
380  r_blk[j] = du[j];
381  }
382 
383  auto& lin_solver = nonlin_solver_->linearSolver();
384 
385  lin_solver.SetOperator(*J_operator_);
386 
387  lin_solver.Mult(augmented_residual, augmented_solution);
388 
389  du_ = du;
390  }
391 
392  displacement_ += du_;
393  }
394 
396  void quasiStaticAdjointSolve(double /*dt*/) override
397  {
398  SLIC_ERROR_ROOT_IF(contact_.haveLagrangeMultipliers(),
399  "Lagrange multiplier contact does not currently support sensitivities/adjoints.");
400 
401  // By default, use a homogeneous essential boundary condition
402  mfem::HypreParVector adjoint_essential(displacement_adjoint_load_);
403  adjoint_essential = 0.0;
404 
405  auto [_, drdu] = (*residual_)(time_, BasePhysics::shapeDisplacement(), differentiate_wrt(displacement_),
406  acceleration_, *parameters_[parameter_indices].state...);
407  auto jacobian = assemble(drdu);
408 
409  auto block_J = contact_.jacobianFunction(jacobian.release());
410  block_J->owns_blocks = false;
411  jacobian = std::unique_ptr<mfem::HypreParMatrix>(static_cast<mfem::HypreParMatrix*>(&block_J->GetBlock(0, 0)));
412  auto J_T = std::unique_ptr<mfem::HypreParMatrix>(jacobian->Transpose());
413 
414  for (const auto& bc : bcs_.essentials()) {
415  bc.apply(*J_T, displacement_adjoint_load_, adjoint_essential);
416  }
417 
418  auto& lin_solver = nonlin_solver_->linearSolver();
419  lin_solver.SetOperator(*J_T);
420  lin_solver.Mult(displacement_adjoint_load_, adjoint_displacement_);
421  }
422 
425  {
426  auto drdshape =
427  serac::get<DERIVATIVE>((*residual_)(time_end_step_, differentiate_wrt(BasePhysics::shapeDisplacement()),
428  displacement_, acceleration_, *parameters_[parameter_indices].state...));
429 
430  auto drdshape_mat = assemble(drdshape);
431 
432  auto block_J = contact_.jacobianFunction(drdshape_mat.release());
433  block_J->owns_blocks = false;
434  drdshape_mat = std::unique_ptr<mfem::HypreParMatrix>(static_cast<mfem::HypreParMatrix*>(&block_J->GetBlock(0, 0)));
435 
436  drdshape_mat->MultTranspose(adjoint_displacement_, shape_displacement_dual_);
437 
439  }
440 
441  using BasePhysics::bcs_;
442  using BasePhysics::cycle_;
443  using BasePhysics::duals_;
445  using BasePhysics::mesh_;
446  using BasePhysics::name_;
448  using BasePhysics::states_;
449  using BasePhysics::time_;
450  using SolidMechanicsBase::acceleration_;
451  using SolidMechanicsBase::adjoint_displacement_;
452  using SolidMechanicsBase::d_residual_d_;
453  using SolidMechanicsBase::DERIVATIVE;
454  using SolidMechanicsBase::displacement_;
455  using SolidMechanicsBase::displacement_adjoint_load_;
456  using SolidMechanicsBase::du_;
457  using SolidMechanicsBase::J_;
458  using SolidMechanicsBase::J_e_;
459  using SolidMechanicsBase::nonlin_solver_;
460  using SolidMechanicsBase::residual_;
461  using SolidMechanicsBase::residual_with_bcs_;
462  using SolidMechanicsBase::shape_displacement_dual_;
463  using SolidMechanicsBase::time_end_step_;
464  using SolidMechanicsBase::use_warm_start_;
465  using SolidMechanicsBase::warmStartDisplacement;
466 
468  mfem::Operator* J_operator_;
469 
471  std::unique_ptr<mfem::HypreParMatrix> J_21_;
472 
474  std::unique_ptr<mfem::HypreParMatrix> J_12_;
475 
477  std::unique_ptr<mfem::HypreParMatrix> J_22_;
478 
480  mfem::Array<int> J_offsets_;
481 
483  std::unique_ptr<mfem::BlockOperator> J_constraint_;
484 
486  std::unique_ptr<mfem::HypreParMatrix> J_e_21_;
487 
490  std::unique_ptr<mfem::BlockOperator> J_constraint_e_;
491 
494 
497 };
498 
499 } // namespace serac
This is the abstract base class for a generic forward solver.
std::vector< const serac::FiniteElementDual * > duals_
List of finite element duals associated with this physics module.
const mfem::ParMesh & mfemParMesh() const
Returns a reference to the mfem ParMesh object.
int cycle_
Current cycle (forward pass time iteration count)
std::vector< const serac::FiniteElementState * > states_
List of finite element primal states associated with this physics module.
double time_
Current time for the forward pass.
virtual const FiniteElementState & shapeDisplacement() const
Accessor for getting the shape displacement field from the physics modules.
std::shared_ptr< serac::Mesh > mesh_
The primary mesh.
BoundaryConditionManager bcs_
Boundary condition manager instance.
bool is_quasistatic_
Whether the simulation is time-independent.
std::vector< ParameterInfo > parameters_
A vector of the parameters associated with this physics module.
std::string name_
Name of the physics module.
const FiniteElementDual & shapeDisplacementSensitivity() const
Internally used accessor for getting the shape displacement sensitivity from the physics modules.
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...
void warmStartDisplacementContact(double dt)
Sets the Dirichlet BCs for the current time and computes an initial guess for parameters and displace...
mfem::HypreParVector pressure() const
Get the contact pressures from all contact interactions, merged into a single HypreParVector.
SolidMechanicsContact(const NonlinearSolverOptions nonlinear_opts, const LinearSolverOptions lin_opts, const serac::TimesteppingOptions timestepping_opts, const std::string &physics_name, std::shared_ptr< serac::Mesh > serac_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::BlockOperator > J_constraint_
Assembled sparse matrix for the Jacobian with constraint blocks.
std::unique_ptr< mfem_ext::StdFunctionOperator > buildQuasistaticOperator() override
Build the quasi-static operator corresponding to the total Lagrangian formulation.
SolidMechanicsContact(std::unique_ptr< serac::EquationSolver > solver, const serac::TimesteppingOptions timestepping_opts, const std::string &physics_name, std::shared_ptr< serac::Mesh > serac_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.
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...
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::unique_ptr< mfem::HypreParMatrix > J_21_
21 Jacobian block if using Lagrange multiplier contact (dg/dx)
mfem::Array< int > J_offsets_
Block offsets for the J_constraint_ BlockOperator (must be owned outside J_constraint_)
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...
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.
SolidMechanicsContact(const SolidMechanicsInputOptions &input_options, const std::string &physics_name, std::shared_ptr< serac::Mesh > serac_mesh, int cycle=0, double time=0.0)
Construct a new Nonlinear SolidMechanicsContact Solver object.
std::unique_ptr< mfem::HypreParMatrix > J_12_
12 Jacobian block if using Lagrange multiplier contact (df/dp)
std::unique_ptr< mfem::HypreParMatrix > J_22_
22 Jacobian block if using Lagrange multiplier contact (ones on diagonal for inactive t-dofs)
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: serac.cpp:36
auto differentiate_wrt(const mfem::Vector &v)
this function is intended to only be used in combination with serac::Functional::operator(),...
#define SERAC_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:21
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.