https://mooseframework.inl.gov
ExplicitDynamicsContactConstraint.C
Go to the documentation of this file.
1 //* This file is part of the MOOSE framework
2 //* https://mooseframework.inl.gov
3 //*
4 //* All rights reserved, see COPYRIGHT for full restrictions
5 //* https://github.com/idaholab/moose/blob/master/COPYRIGHT
6 //*
7 //* Licensed under LGPL 2.1, please see LICENSE for details
8 //* https://www.gnu.org/licenses/lgpl-2.1.html
9 
10 // MOOSE includes
12 #include "FEProblem.h"
13 #include "DisplacedProblem.h"
14 #include "AuxiliarySystem.h"
15 #include "PenetrationLocator.h"
16 #include "NearestNodeLocator.h"
17 #include "SystemBase.h"
18 #include "Assembly.h"
19 #include "MooseMesh.h"
20 #include "MathUtils.h"
21 #include "Executioner.h"
22 #include "AddVariableAction.h"
23 #include "ContactLineSearchBase.h"
25 
26 #include "libmesh/id_types.h"
27 #include "libmesh/string_to_enum.h"
28 #include "libmesh/sparse_matrix.h"
29 
31 
33 
36 {
40 
41  params.addRequiredParam<BoundaryName>("boundary", "The primary boundary");
42  params.addParam<BoundaryName>("secondary", "The secondary boundary");
43  params.addRequiredParam<unsigned int>("component",
44  "An integer corresponding to the direction "
45  "the variable this constraint acts on. (0 for x, "
46  "1 for y, 2 for z)");
47  params.addCoupledVar(
48  "displacements",
49  "The displacements appropriate for the simulation geometry and coordinate system");
50  params.addCoupledVar("secondary_gap_offset", "offset to the gap distance from secondary side");
51  params.addCoupledVar("mapped_primary_gap_offset",
52  "offset to the gap distance mapped from primary side");
53  params.set<bool>("use_displaced_mesh") = true;
54  params.addParam<Real>("penalty",
55  1e8,
56  "The penalty to apply. Its optimal value can vary depending on the "
57  "stiffness of the materials");
58  params.addParam<Real>("friction_coefficient", 0, "The friction coefficient");
59  params.addParam<Real>("tangential_tolerance",
60  "Tangential distance to extend edges of contact surfaces");
61  params.addParam<bool>(
62  "print_contact_nodes", false, "Whether to print the number of nodes in contact.");
63 
64  params.addClassDescription(
65  "Apply non-penetration constraints on the mechanical deformation in explicit dynamics "
66  "using a node on face formulation by solving uncoupled momentum-balance equations.");
67  return params;
68 }
69 
71  const InputParameters & parameters)
72  : NodeFaceConstraint(parameters),
73  TwoMaterialPropertyInterface(this, Moose::EMPTY_BLOCK_IDS, buildBoundaryIDs()),
74  _displaced_problem(parameters.get<FEProblemBase *>("_fe_problem_base")->getDisplacedProblem()),
75  _component(getParam<unsigned int>("component")),
76  _model(getParam<MooseEnum>("model").getEnum<ExplicitDynamicsContactModel>()),
77  _update_stateful_data(true),
78  _mesh_dimension(_mesh.dimension()),
79  _vars(3, libMesh::invalid_uint),
80  _var_objects(3, nullptr),
81  _has_secondary_gap_offset(isCoupled("secondary_gap_offset")),
82  _secondary_gap_offset_var(_has_secondary_gap_offset ? getVar("secondary_gap_offset", 0)
83  : nullptr),
84  _has_mapped_primary_gap_offset(isCoupled("mapped_primary_gap_offset")),
85  _mapped_primary_gap_offset_var(
86  _has_mapped_primary_gap_offset ? getVar("mapped_primary_gap_offset", 0) : nullptr),
87  _penalty(getParam<Real>("penalty")),
88  _print_contact_nodes(getParam<bool>("print_contact_nodes")),
89  _residual_copy(_sys.residualGhosted()),
90  _gap_rate(&writableVariable("gap_rate")),
91  _neighbor_vel_x(isCoupled("vel_x") ? coupledNeighborValue("vel_x") : _zero),
92  _neighbor_vel_y(isCoupled("vel_y") ? coupledNeighborValue("vel_y") : _zero),
93  _neighbor_vel_z((_mesh.dimension() == 3 && isCoupled("vel_z")) ? coupledNeighborValue("vel_z")
94  : _zero)
95 {
97 
98  if (isParamValid("displacements"))
99  {
100  // modern parameter scheme for displacements
101  for (unsigned int i = 0; i < coupledComponents("displacements"); ++i)
102  {
103  _vars[i] = coupled("displacements", i);
104  _var_objects[i] = getVar("displacements", i);
105  }
106  }
107 
108  if (parameters.isParamValid("tangential_tolerance"))
109  _penetration_locator.setTangentialTolerance(getParam<Real>("tangential_tolerance"));
110 
111  if (parameters.isParamValid("normal_smoothing_distance"))
112  _penetration_locator.setNormalSmoothingDistance(getParam<Real>("normal_smoothing_distance"));
113 
114  if (parameters.isParamValid("normal_smoothing_method"))
116  parameters.get<std::string>("normal_smoothing_method"));
117 
119  {
120  bool is_correct =
121  (isCoupled("vel_x") && isCoupled("vel_y") && _mesh.dimension() == 2) ||
122  (isCoupled("vel_x") && isCoupled("vel_y") && isCoupled("vel_z") && _mesh.dimension() == 3);
123 
124  if (!is_correct)
125  paramError("vel_x",
126  "Velocities vel_x and vel_y (also vel_z in three dimensions) need to be provided "
127  "for the 'balance' option of solving normal contact in explicit dynamics.");
128  }
129 }
130 
131 void
133 {
134  if (_component == 0)
135  {
136  updateContactStatefulData(/* beginning_of_step = */ true);
137  _update_stateful_data = false;
138  _dof_to_position.clear();
139  }
140 }
141 
142 void
144 {
145  for (auto & [secondary_node_num, pinfo] : _penetration_locator._penetration_info)
146  {
147  if (!pinfo)
148  continue;
149 
150  const Node & node = _mesh.nodeRef(secondary_node_num);
151  if (node.n_comp(_sys.number(), _vars[_component]) < 1)
152  continue;
153 
154  if (beginning_of_step)
155  {
157  {
158  pinfo->_contact_force_old = pinfo->_contact_force;
159  pinfo->_accumulated_slip_old = pinfo->_accumulated_slip;
160  pinfo->_frictional_energy_old = pinfo->_frictional_energy;
161  pinfo->_mech_status_old = pinfo->_mech_status;
162  }
163  else if (pinfo->_mech_status_old == PenetrationInfo::MS_NO_CONTACT &&
164  pinfo->_mech_status != PenetrationInfo::MS_NO_CONTACT)
165  {
166  mooseWarning("Previous step did not converge. Check results");
167  // The penetration info object could be based on a bad state so delete it
168  delete pinfo;
169  pinfo = nullptr;
170  continue;
171  }
172 
173  pinfo->_starting_elem = pinfo->_elem;
174  pinfo->_starting_side_num = pinfo->_side_num;
175  pinfo->_starting_closest_point_ref = pinfo->_closest_point_ref;
176  }
177  pinfo->_incremental_slip_prev_iter = pinfo->_incremental_slip;
178  }
179 }
180 
181 bool
183 {
184  if (_current_node->processor_id() != _fe_problem.processor_id())
185  return false;
186 
187  bool in_contact = false;
188 
189  std::map<dof_id_type, PenetrationInfo *>::iterator found =
191  if (found != _penetration_locator._penetration_info.end())
192  {
193  PenetrationInfo * pinfo = found->second;
194  if (pinfo != nullptr)
195  {
196  // This computes the contact force once per constraint, rather than once per quad point
197  // and for both primary and secondary cases.
198  if (_component == 0)
199  computeContactForce(*_current_node, pinfo, true);
200 
201  if (pinfo->isCaptured())
202  in_contact = true;
203  }
204  }
205 
206  return in_contact;
207 }
208 
209 void
211  PenetrationInfo * pinfo,
212  bool update_contact_set)
213 {
214  RealVectorValue distance_vec(node - pinfo->_closest_point);
215 
216  // Adding current velocity to the distance vector to ensure proper contact check
217  dof_id_type dof_x = node.dof_number(_sys.number(), _var_objects[0]->number(), 0);
218  dof_id_type dof_y = node.dof_number(_sys.number(), _var_objects[1]->number(), 0);
219  dof_id_type dof_z =
220  _mesh.dimension() == 3 ? node.dof_number(_sys.number(), _var_objects[2]->number(), 0) : 0;
221  RealVectorValue udotvec = {(*_sys.solutionUDot())(dof_x)*_dt,
222  (*_sys.solutionUDot())(dof_y)*_dt,
223  _mesh.dimension() == 3 ? (*_sys.solutionUDot())(dof_z)*_dt : 0};
224  distance_vec += udotvec;
225 
226  if (distance_vec.norm() != 0)
227  distance_vec += gapOffset(node) * pinfo->_normal;
228 
229  const Real gap_size = -1.0 * pinfo->_normal * distance_vec;
230 
231  // This is for preventing an increment of pinfo->_locked_this_step for nodes that are
232  // captured and released in this function
233  bool newly_captured = false;
234 
235  // Capture nodes that are newly in contact
236  if (update_contact_set && !pinfo->isCaptured() &&
237  MooseUtils::absoluteFuzzyGreaterEqual(gap_size, 0.0, 0.0))
238  {
239  newly_captured = true;
240  pinfo->capture();
241  }
242 
243  if (!pinfo->isCaptured())
244  return;
245 
246  switch (_model)
247  {
249  {
250  const Real penalty = getPenalty(node);
251  RealVectorValue pen_force(penalty * distance_vec);
252  pinfo->_contact_force = pinfo->_normal * (pinfo->_normal * pen_force);
253  break;
254  }
256  solveImpactEquations(node, pinfo, distance_vec);
257  break;
258  default:
259  mooseError("Invalid or unavailable contact model");
260  break;
261  }
262 
263  if (update_contact_set && pinfo->isCaptured() && !newly_captured)
264  {
265  const Real contact_pressure = -(pinfo->_normal * pinfo->_contact_force);
266  if (-contact_pressure >= 0.0)
267  {
268  pinfo->release();
269  pinfo->_contact_force.zero();
270  }
271  }
272 }
273 
274 void
276  PenetrationInfo * pinfo,
277  const RealVectorValue & /*distance_gap*/)
278 {
279  // Momentum balance, uncoupled normal pressure
280  // See Heinstein et al, 2000, Contact-impact modeling in explicit transient dynamics.
281 
282  dof_id_type dof_x = node.dof_number(_sys.number(), _var_objects[0]->number(), 0);
283  dof_id_type dof_y = node.dof_number(_sys.number(), _var_objects[1]->number(), 0);
284  dof_id_type dof_z =
285  _mesh_dimension == 3 ? node.dof_number(_sys.number(), _var_objects[2]->number(), 0) : 0;
286 
287  auto & u_dot = *_sys.solutionUDot();
288 
289  // Get lumped mass value
290  const auto & diag = _sys.getVector("mass_matrix_diag_inverted");
291 
292  Real mass_node = 1.0 / diag(dof_x);
293  Real mass_face = computeFaceMass(diag);
294 
295  Real mass_eff = (mass_face * mass_node) / (mass_face + mass_node);
296 
297  // Include effects of other forces:
298  // Initial guess: v_{n-1/2} + dt * M^{-1} * (F^{ext} - F^{int})
299  Real velocity_x = u_dot(dof_x) + _dt / mass_node * -1 * _residual_copy(dof_x);
300  Real velocity_y = u_dot(dof_y) + _dt / mass_node * -1 * _residual_copy(dof_y);
301  Real velocity_z = u_dot(dof_z) + _dt / mass_node * -1 * _residual_copy(dof_z);
302 
303  Real n_velocity_x = _neighbor_vel_x[0];
304  Real n_velocity_y = _neighbor_vel_y[0];
305  Real n_velocity_z = _neighbor_vel_z[0];
306 
307  RealVectorValue secondary_velocity(
308  velocity_x, velocity_y, _mesh.dimension() == 3 ? velocity_z : 0.0);
309  RealVectorValue closest_point_velocity(
310  n_velocity_x, n_velocity_y, _mesh.dimension() == 3 ? n_velocity_z : 0.0);
311  // Compute initial gap rate
312  Real gap_rate = pinfo->_normal * (secondary_velocity - closest_point_velocity);
313 
314  // Compute the force increment needed to set gap rate to 0
315  RealVectorValue impulse_force = pinfo->_normal * (gap_rate * mass_eff) / _dt;
316  pinfo->_contact_force = impulse_force;
317 
318  // recalculate velocity to determine gap rate
319  velocity_x -= _dt / mass_eff * impulse_force(0);
320  velocity_y -= _dt / mass_eff * impulse_force(1);
321  velocity_z -= _dt / mass_eff * impulse_force(2);
322 
323  // Recalculate gap rate for backwards compatibility
324  secondary_velocity = {velocity_x, velocity_y, _mesh.dimension() == 3 ? velocity_z : 0.0};
325  gap_rate = pinfo->_normal * (secondary_velocity - closest_point_velocity);
326  // gap rate is now always near "0"
327  _gap_rate->setNodalValue(gap_rate);
328 }
329 
330 Real
332 {
333  // Not used in current implementation.
334  return _u_secondary[_qp];
335 }
336 
337 Real
339 {
340  // We use kinematic contact. But adding the residual helps avoid element inversion.
342  Real resid = pinfo->_contact_force(_component);
343 
344  switch (type)
345  {
346  case Moose::Secondary:
347  return _test_secondary[_i][_qp] * resid;
348 
349  case Moose::Primary:
350  return _test_primary[_i][_qp] * -resid;
351  }
352 
353  return 0.0;
354 }
355 
356 Real
358 {
359  Real val = 0;
360 
363 
366 
367  return val;
368 }
369 
370 Real
372 {
373  // TODO: Include normalized penalty values.
374  return _penalty;
375 }
376 
377 Real
378 ExplicitDynamicsContactConstraint::computeFaceMass(const NumericVector<Real> & lumped_mass)
379 {
380  // Initialize face mass to zero
381  Real mass_face(0.0);
382 
383  // Get the primary side of the current contact
384  const auto primary_side = _current_primary->side_ptr(_assembly.neighborSide());
385 
386  // Get the dofs on the primary (face) side of the contact
387  std::vector<dof_id_type> face_dofs;
388  _primary_var.getDofIndices(primary_side.get(), face_dofs);
389 
390  // Get average mass of face
391  for (const auto dof : face_dofs)
392  mass_face += 1.0 / lumped_mass(dof);
393 
394  mass_face /= face_dofs.size();
395 
396  return mass_face;
397 }
MooseMesh & _mesh
virtual bool isCoupled(const std::string &var_name, unsigned int i=0) const
const unsigned int & neighborSide() const
virtual unsigned int coupled(const std::string &var_name, unsigned int comp=0) const
ConstraintType
auto norm() const -> decltype(std::norm(Real()))
const unsigned int invalid_uint
RealVectorValue _normal
void addParam(const std::string &name, const std::initializer_list< typename T::value_type > &value, const std::string &doc_string)
void setNormalSmoothingDistance(Real normal_smoothing_distance)
Real computeFaceMass(const NumericVector< Real > &lumped_mass)
std::vector< std::pair< R1, R2 > > get(const std::string &param1, const std::string &param2) const
MooseVariable & _primary_var
const Elem *const & _current_primary
virtual void setNodalValue(const Real &value, unsigned int idx=0)=0
void solveImpactEquations(const Node &node, PenetrationInfo *pinfo, const RealVectorValue &distance_gap)
Determine "Lagrange multipliers" from the iterative solution of the impact problem.
T & set(const std::string &name, bool quiet_mode=false)
static const std::string velocity_z
Definition: NS.h:48
static InputParameters validParams()
ExplicitDynamicsContactConstraint(const InputParameters &parameters)
MooseVariable * getVar(const std::string &var_name, unsigned int comp)
static const std::string velocity_x
Definition: NS.h:46
unsigned int _i
The following methods are specializations for using the Parallel::packed_range_* routines for a vecto...
const VariableTestValue & _test_primary
std::map< dof_id_type, PenetrationInfo *> & _penetration_info
void setTangentialTolerance(Real tangential_tolerance)
virtual const Node & nodeRef(const dof_id_type i) const
void mooseWarning(Args &&... args) const
void addRequiredParam(const std::string &name, const std::string &doc_string)
static InputParameters commonParameters()
Define parameters used by multiple contact objects.
const Node *const & _current_node
bool isParamValid(const std::string &name) const
std::unordered_map< dof_id_type, Real > _dof_to_position
SystemBase & _sys
virtual void updateContactStatefulData(bool beginning_of_step)
void computeContactForce(const Node &node, PenetrationInfo *pinfo, bool update_contact_set)
virtual void getDofIndices(const Elem *elem, std::vector< dof_id_type > &dof_indices) const override
virtual unsigned int dimension() const
virtual NumericVector< Number > * solutionUDot()
bool isCaptured() const
const ExplicitDynamicsContactModel _model
registerMooseObject("ContactApp", ExplicitDynamicsContactConstraint)
FEProblemBase & _fe_problem
const std::string & type() const
const VariableValue & _u_secondary
static const std::string velocity_y
Definition: NS.h:47
void paramError(const std::string &param, Args... args) const
unsigned int number() const
A ExplicitDynamicsContactConstraint does mechanical contact for explicit dynamics simulations...
RealVectorValue _contact_force
Executioner * getExecutioner() const
MooseWritableVariable * _gap_rate
Nodal gap rate (output for debugging or analyst perusal)
Assembly & _assembly
void addCoupledVar(const std::string &name, const std::string &doc_string)
OutputData getNodalValue(const Node &node) const
VariableTestValue _test_secondary
unsigned int coupledComponents(const std::string &var_name) const
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
const std::set< SubdomainID > EMPTY_BLOCK_IDS
const VariableValue & _neighbor_vel_z
Z component of velocity at the closest point.
static InputParameters validParams()
bool absoluteFuzzyGreaterEqual(const T &var1, const T2 &var2, const T3 &tol=libMesh::TOLERANCE *libMesh::TOLERANCE)
void mooseError(Args &&... args) const
void addClassDescription(const std::string &doc_string)
const InputParameters & parameters() const
bool _overwrite_secondary_residual
const VariableValue & _neighbor_vel_y
Y component of velocity at the closest point.
const VariableValue & _neighbor_vel_x
X component of velocity at the closest point.
void setNormalSmoothingMethod(std::string nsmString)
virtual bool lastSolveConverged() const=0
virtual Real computeQpResidual(Moose::ConstraintType type) override
PenetrationLocator & _penetration_locator
processor_id_type processor_id() const
virtual NumericVector< Number > & getVector(const std::string &name)
unsigned int _qp
void ErrorVector unsigned int
const Elem & get(const ElemType type_in)
uint8_t dof_id_type
bool isParamValid(const std::string &name) const