https://mooseframework.inl.gov
Loading...
Searching...
No Matches
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"
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
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
131void
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
142void
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
181bool
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
209void
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
274void
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
330Real
332{
333 // Not used in current implementation.
334 return _u_secondary[_qp];
335}
336
337Real
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
356Real
358{
359 Real val = 0;
360
363
366
367 return val;
368}
369
370Real
372{
373 // TODO: Include normalized penalty values.
374 return _penalty;
375}
376
377Real
378ExplicitDynamicsContactConstraint::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}
registerMooseObject("ContactApp", ExplicitDynamicsContactConstraint)
void ErrorVector unsigned int
const unsigned int & neighborSide() const
unsigned int _qp
unsigned int _i
MooseVariable * getVar(const std::string &var_name, unsigned int comp)
unsigned int coupledComponents(const std::string &var_name) const
virtual unsigned int coupled(const std::string &var_name, unsigned int comp=0) const
virtual bool isCoupled(const std::string &var_name, unsigned int i=0) const
virtual bool lastSolveConverged() const=0
static InputParameters commonParameters()
Define parameters used by multiple contact objects.
A ExplicitDynamicsContactConstraint does mechanical contact for explicit dynamics simulations.
MooseWritableVariable * _gap_rate
Nodal gap rate (output for debugging or analyst perusal)
virtual Real computeQpResidual(Moose::ConstraintType type) override
std::unordered_map< dof_id_type, Real > _dof_to_position
void solveImpactEquations(const Node &node, PenetrationInfo *pinfo, const RealVectorValue &distance_gap)
Determine "Lagrange multipliers" from the iterative solution of the impact problem.
Real computeFaceMass(const NumericVector< Real > &lumped_mass)
virtual void updateContactStatefulData(bool beginning_of_step)
ExplicitDynamicsContactConstraint(const InputParameters &parameters)
const VariableValue & _neighbor_vel_y
Y component of velocity at the closest point.
void computeContactForce(const Node &node, PenetrationInfo *pinfo, bool update_contact_set)
const VariableValue & _neighbor_vel_z
Z component of velocity at the closest point.
const VariableValue & _neighbor_vel_x
X component of velocity at the closest point.
void addRequiredParam(const std::string &name, const std::string &doc_string)
void addParam(const std::string &name, const std::initializer_list< typename T::value_type > &value, const std::string &doc_string)
std::vector< std::pair< R1, R2 > > get(const std::string &param1, const std::string &param2) const
void addClassDescription(const std::string &doc_string)
T & set(const std::string &name, bool quiet_mode=false)
void addCoupledVar(const std::string &name, const std::string &doc_string)
bool isParamValid(const std::string &name) const
Executioner * getExecutioner() const
const InputParameters & parameters() const
const std::string & type() const
void paramError(const std::string &param, Args... args) const
void mooseError(Args &&... args) const
void mooseWarning(Args &&... args) const
bool isParamValid(const std::string &name) const
virtual const Node & nodeRef(const dof_id_type i) const
virtual unsigned int dimension() const
DofValue getNodalValue(const Node &node) const
virtual void getDofIndices(const Elem *elem, std::vector< dof_id_type > &dof_indices) const override
virtual void setNodalValue(const OutputType &value, unsigned int idx=0)=0
PenetrationLocator & _penetration_locator
const Node *const & _current_node
VariableTestValue _test_secondary
const Elem *const & _current_primary
const VariableTestValue & _test_primary
bool _overwrite_secondary_residual
static InputParameters validParams()
const VariableValue & _u_secondary
MooseVariable & _primary_var
RealVectorValue _contact_force
bool isCaptured() const
RealVectorValue _normal
void setNormalSmoothingMethod(std::string nsmString)
void setTangentialTolerance(Real tangential_tolerance)
void setNormalSmoothingDistance(Real normal_smoothing_distance)
std::map< dof_id_type, PenetrationInfo * > & _penetration_info
MooseMesh & _mesh
Assembly & _assembly
SystemBase & _sys
FEProblemBase & _fe_problem
virtual NumericVector< Number > * solutionUDot()
unsigned int number() const
virtual NumericVector< Number > & getVector(const std::string &name)
static InputParameters validParams()
processor_id_type processor_id() const
ConstraintType
The following methods are specializations for using the Parallel::packed_range_* routines for a vecto...