https://mooseframework.inl.gov
ComputeFrictionalForceCartesianLMMechanicalContact.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 
11 #include "MortarContactUtils.h"
12 #include "DisplacedProblem.h"
13 #include "Assembly.h"
15 #include "metaphysicl/metaphysicl_version.h"
16 #include "metaphysicl/dualsemidynamicsparsenumberarray.h"
17 #include "metaphysicl/parallel_dualnumber.h"
18 #if METAPHYSICL_MAJOR_VERSION < 2
19 #include "metaphysicl/parallel_dynamic_std_array_wrapper.h"
20 #else
21 #include "metaphysicl/parallel_dynamic_array_wrapper.h"
22 #endif
23 #include "metaphysicl/parallel_semidynamicsparsenumberarray.h"
24 #include "timpi/parallel_sync.h"
25 
27 
28 namespace
29 {
30 const InputParameters &
31 assignVarsInParamsFriction(const InputParameters & params_in)
32 {
33  InputParameters & ret = const_cast<InputParameters &>(params_in);
34  const auto & disp_x_name = ret.get<std::vector<VariableName>>("disp_x");
35  if (disp_x_name.size() != 1)
36  mooseError("We require that the disp_x parameter have exactly one coupled name");
37 
38  // We do this so we don't get any variable errors during MortarConstraint(Base) construction
39  ret.set<VariableName>("secondary_variable") = disp_x_name[0];
40  ret.set<VariableName>("primary_variable") = disp_x_name[0];
41 
42  return ret;
43 }
44 }
45 
48 {
50  params.addClassDescription("Computes mortar frictional forces.");
51  params.addParam<Real>("c_t", 1e0, "Numerical parameter for tangential constraints");
52  params.addParam<Real>(
53  "epsilon",
54  1.0e-7,
55  "Minimum value of contact pressure that will trigger frictional enforcement");
56  params.addRangeCheckedParam<Real>(
57  "mu", "mu > 0", "The friction coefficient for the Coulomb friction law");
58  return params;
59 }
60 
63  : ComputeWeightedGapCartesianLMMechanicalContact(assignVarsInParamsFriction(parameters)),
64  _c_t(getParam<Real>("c_t")),
65  _secondary_x_dot(adCoupledDot("disp_x")),
66  _primary_x_dot(adCoupledNeighborValueDot("disp_x")),
67  _secondary_y_dot(adCoupledDot("disp_y")),
68  _primary_y_dot(adCoupledNeighborValueDot("disp_y")),
69  _secondary_z_dot(_has_disp_z ? &adCoupledDot("disp_z") : nullptr),
70  _primary_z_dot(_has_disp_z ? &adCoupledNeighborValueDot("disp_z") : nullptr),
71  _mu(getParam<Real>("mu")),
72  _epsilon(getParam<Real>("epsilon"))
73 {
74 }
75 
76 void
78 {
80 
81  // Trim derivatives
82  const auto & primary_ip_lowerd_map = amg().getPrimaryIpToLowerElementMap(
84  const auto & secondary_ip_lowerd_map =
86 
87  std::array<const MooseVariable *, 3> var_array{{_disp_x_var, _disp_y_var, _disp_z_var}};
88  std::array<ADReal, 3> primary_disp_dot{
89  {_primary_x_dot[_qp], _primary_y_dot[_qp], _has_disp_z ? (*_primary_z_dot)[_qp] : 0}};
90  std::array<ADReal, 3> secondary_disp_dot{
91  {_secondary_x_dot[_qp], _secondary_y_dot[_qp], _has_disp_z ? (*_secondary_z_dot)[_qp] : 0}};
92 
93  trimInteriorNodeDerivatives(primary_ip_lowerd_map, var_array, primary_disp_dot, false);
94  trimInteriorNodeDerivatives(secondary_ip_lowerd_map, var_array, secondary_disp_dot, true);
95 
96  const ADReal & prim_x_dot = primary_disp_dot[0];
97  const ADReal & prim_y_dot = primary_disp_dot[1];
98  const ADReal * prim_z_dot = nullptr;
99  if (_has_disp_z)
100  prim_z_dot = &primary_disp_dot[2];
101 
102  const ADReal & sec_x_dot = secondary_disp_dot[0];
103  const ADReal & sec_y_dot = secondary_disp_dot[1];
104  const ADReal * sec_z_dot = nullptr;
105  if (_has_disp_z)
106  sec_z_dot = &secondary_disp_dot[2];
107 
108  // Build relative velocity vector
109  ADRealVectorValue relative_velocity;
110 
111  if (_has_disp_z)
112  relative_velocity = {sec_x_dot - prim_x_dot, sec_y_dot - prim_y_dot, *sec_z_dot - *prim_z_dot};
113  else
114  relative_velocity = {sec_x_dot - prim_x_dot, sec_y_dot - prim_y_dot, 0.0};
115 
116  _qp_tangential_velocity_nodal = relative_velocity * (_JxW_msm[_qp] * _coord[_qp]);
117 }
118 
119 void
121 {
123 
124  const auto & nodal_tangents = amg().getNodalTangents(*_lower_secondary_elem);
125  // Get the _dof_to_weighted_tangential_velocity map
126  const DofObject * const dof =
127  _lm_vars[0]->isNodal() ? static_cast<const DofObject *>(_lower_secondary_elem->node_ptr(_i))
128  : static_cast<const DofObject *>(_lower_secondary_elem);
129 
131  _test[_i][_qp] * _qp_tangential_velocity_nodal * nodal_tangents[0][_i];
132 
133  // Get the _dof_to_weighted_tangential_velocity map for a second direction
134  if (_has_disp_z)
136  _test[_i][_qp] * _qp_tangential_velocity_nodal * nodal_tangents[1][_i];
137 }
138 
139 void
141 {
144 }
145 
146 void
148 {
153 
154  // Enforce frictional complementarity constraints
155  for (const auto & pr : _dof_to_weighted_tangential_velocity)
156  {
157  const DofObject * const dof = pr.first;
158 
159  if (dof->processor_id() != this->processor_id())
160  continue;
161 
162  auto & weighted_gap_pr = _dof_to_weighted_gap[dof];
163  _weighted_gap_ptr = &weighted_gap_pr.first;
164  _normalization_ptr = &weighted_gap_pr.second;
165  _tangential_vel_ptr[0] = &(pr.second[0]);
166 
167  if (_has_disp_z)
168  _tangential_vel_ptr[1] = &(pr.second[1]);
169 
171  }
172 }
173 
174 void
176  const std::unordered_set<const Node *> & inactive_lm_nodes)
177 {
182 
183  // Enforce frictional complementarity constraints
184  for (const auto & pr : _dof_to_weighted_tangential_velocity)
185  {
186  const DofObject * const dof = pr.first;
187 
188  // If node inactive, skip
189  if ((inactive_lm_nodes.find(static_cast<const Node *>(dof)) != inactive_lm_nodes.end()) ||
190  (dof->processor_id() != this->processor_id()))
191  continue;
192 
195  _tangential_vel_ptr[0] = &pr.second[0];
196 
197  if (_has_disp_z)
198  _tangential_vel_ptr[1] = &pr.second[1];
199 
201  }
202 }
203 
204 void
206  const DofObject * const dof)
207 {
208  using std::abs, std::sqrt, std::max, std::min;
209 
210  const auto & weighted_gap = *_weighted_gap_ptr;
211  const Real c = _normalize_c ? _c / *_normalization_ptr : _c;
212  const Real c_t = _normalize_c ? _c_t / *_normalization_ptr : _c_t;
213 
214  const auto dof_index_x = dof->dof_number(_sys.number(), _lm_vars[0]->number(), 0);
215  const auto dof_index_y = dof->dof_number(_sys.number(), _lm_vars[1]->number(), 0);
216  const Real scaling_factor_x = _lm_vars[0]->scalingFactor();
217  const Real scaling_factor_y = _lm_vars[1]->scalingFactor();
218  Real scaling_factor_z = 1;
219 
220  ADReal lm_x = (*_sys.currentSolution())(dof_index_x);
221  ADReal lm_y = (*_sys.currentSolution())(dof_index_y);
222 
223  Moose::derivInsert(lm_x.derivatives(), dof_index_x, 1.);
224  Moose::derivInsert(lm_y.derivatives(), dof_index_y, 1.);
225 
226  dof_id_type dof_index_z(-1);
227  ADReal lm_z;
228  if (_has_disp_z)
229  {
230  dof_index_z = dof->dof_number(_sys.number(), _lm_vars[2]->number(), 0);
231  lm_z = (*_sys.currentSolution())(dof_index_z);
232  Moose::derivInsert(lm_z.derivatives(), dof_index_z, 1.);
233  scaling_factor_z = _lm_vars[2]->scalingFactor();
234  }
235 
236  ADReal normal_pressure_value =
237  lm_x * _dof_to_normal_vector[dof](0) + lm_y * _dof_to_normal_vector[dof](1);
238  ADReal tangential_pressure_value =
239  lm_x * _dof_to_tangent_vectors[dof][0](0) + lm_y * _dof_to_tangent_vectors[dof][0](1);
240 
241  ADReal tangential_pressure_value_dir;
242 
243  if (_has_disp_z)
244  {
245  normal_pressure_value += lm_z * _dof_to_normal_vector[dof](2);
246  tangential_pressure_value += lm_z * _dof_to_tangent_vectors[dof][0](2);
247  tangential_pressure_value_dir = lm_x * _dof_to_tangent_vectors[dof][1](0) +
248  lm_y * _dof_to_tangent_vectors[dof][1](1) +
249  lm_z * _dof_to_tangent_vectors[dof][1](2);
250  }
251 
252  ADReal normal_dof_residual = min(normal_pressure_value, weighted_gap * c);
253  ADReal tangential_dof_residual;
254  ADReal tangential_dof_residual_dir;
255 
256  const ADReal & tangential_vel = *_tangential_vel_ptr[0];
257 
258  // Primal-dual active set strategy (PDASS)
259  if (!_has_disp_z)
260  {
261  if (normal_pressure_value.value() < _epsilon)
262  tangential_dof_residual = tangential_pressure_value;
263  else
264  {
265  const auto term_1 = max(_mu * (normal_pressure_value + c * weighted_gap),
266  abs(tangential_pressure_value + c_t * tangential_vel * _dt)) *
267  tangential_pressure_value;
268  const auto term_2 = _mu * max(0.0, normal_pressure_value + c * weighted_gap) *
269  (tangential_pressure_value + c_t * tangential_vel * _dt);
270 
271  tangential_dof_residual = term_1 - term_2;
272  }
273  }
274  else
275  {
276  if (normal_pressure_value.value() < _epsilon)
277  {
278  tangential_dof_residual = tangential_pressure_value;
279  tangential_dof_residual_dir = tangential_pressure_value_dir;
280  }
281  else
282  {
283  const Real epsilon_sqrt = 1.0e-48;
284 
285  const auto lamdba_plus_cg = normal_pressure_value + c * weighted_gap;
286 
287  std::array<ADReal, 2> lambda_t_plus_ctu;
288  lambda_t_plus_ctu[0] = tangential_pressure_value + c_t * (*_tangential_vel_ptr[0]) * _dt;
289  lambda_t_plus_ctu[1] = tangential_pressure_value_dir + c_t * (*_tangential_vel_ptr[1]) * _dt;
290 
291  const auto term_1_x = max(_mu * lamdba_plus_cg,
292  sqrt(lambda_t_plus_ctu[0] * lambda_t_plus_ctu[0] +
293  lambda_t_plus_ctu[1] * lambda_t_plus_ctu[1] + epsilon_sqrt)) *
294  tangential_pressure_value;
295 
296  const auto term_1_y = max(_mu * lamdba_plus_cg,
297  sqrt(lambda_t_plus_ctu[0] * lambda_t_plus_ctu[0] +
298  lambda_t_plus_ctu[1] * lambda_t_plus_ctu[1] + epsilon_sqrt)) *
299  tangential_pressure_value_dir;
300 
301  const auto term_2_x = _mu * max(0.0, lamdba_plus_cg) * lambda_t_plus_ctu[0];
302 
303  const auto term_2_y = _mu * max(0.0, lamdba_plus_cg) * lambda_t_plus_ctu[1];
304 
305  tangential_dof_residual = term_1_x - term_2_x;
306  tangential_dof_residual_dir = term_1_y - term_2_y;
307  }
308  }
309 
310  // Compute the friction coefficient (constant or function)
311 
312  // Get index for normal constraint.
313  // We do this to get a decent Jacobian structure, which is key for the use of iterative solvers.
314  // Using old normal vector to avoid changes in the Jacobian structure within one time step
315 
316  Real ny, nz;
317  // Intially, use the current normal vector
318  if (_dof_to_old_normal_vector[dof].norm() < TOLERANCE)
319  {
320  ny = _dof_to_normal_vector[dof](1);
321  nz = _dof_to_normal_vector[dof](2);
322  }
323  else
324  {
325  ny = _dof_to_old_normal_vector[dof](1);
326  nz = _dof_to_old_normal_vector[dof](2);
327  }
328 
329  unsigned int component_normal = 0;
330 
331  // Consider constraint orientation to improve Jacobian structure
332  const Real threshold_for_Jacobian = _has_disp_z ? 1.0 / sqrt(3.0) : 1.0 / sqrt(2.0);
333 
334  if (abs(ny) > threshold_for_Jacobian)
335  component_normal = 1;
336  else if (abs(nz) > threshold_for_Jacobian)
337  component_normal = 2;
338 
340  _assembly,
341  std::array<ADReal, 1>{{normal_dof_residual}},
342  std::array<dof_id_type, 1>{{component_normal == 0
343  ? dof_index_x
344  : (component_normal == 1 ? dof_index_y : dof_index_z)}},
345  component_normal == 0 ? scaling_factor_x
346  : (component_normal == 1 ? scaling_factor_y : scaling_factor_z));
347 
349  _assembly,
350  std::array<ADReal, 1>{{tangential_dof_residual}},
351  std::array<dof_id_type, 1>{
352  {(component_normal == 0 || component_normal == 2) ? dof_index_y : dof_index_x}},
353  (component_normal == 0 || component_normal == 2) ? scaling_factor_y : scaling_factor_x);
354 
355  if (_has_disp_z)
357  _assembly,
358  std::array<ADReal, 1>{{tangential_dof_residual_dir}},
359  std::array<dof_id_type, 1>{
360  {(component_normal == 0 || component_normal == 1) ? dof_index_z : dof_index_x}},
361  (component_normal == 0 || component_normal == 1) ? scaling_factor_z : scaling_factor_x);
362 }
MooseMesh & _mesh
MetaPhysicL::DualNumber< V, D, asd > abs(const MetaPhysicL::DualNumber< V, D, asd > &a)
const ADVariableValue & _secondary_y_dot
y-velocity on the secondary face
virtual const NumericVector< Number > *const & currentSolution() const=0
virtual void computeQpProperties()
Computes properties that are functions only of the current quadrature point (_qp), e.g.
const MooseVariable *const _disp_z_var
The z displacement variable.
void addResidualsAndJacobian(Assembly &assembly, const Residuals &residuals, const Indices &dof_indices, Real scaling_factor)
void communicateVelocities(std::unordered_map< const DofObject *, T > &dof_map, const MooseMesh &mesh, const bool nodal, const Parallel::Communicator &communicator, const bool send_data_back)
This function is used to communicate velocities across processes.
Computes the weighted gap that will later be used to enforce the zero-penetration mechanical contact ...
void addParam(const std::string &name, const std::initializer_list< typename T::value_type > &value, const std::string &doc_string)
const bool _has_disp_z
For 2D mortar contact no displacement will be specified, so const pointers used.
std::unordered_map< const DofObject *, std::pair< ADReal, Real > > _dof_to_weighted_gap
A map from node to weighted gap and normalization (if requested)
void mooseError(Args &&... args)
std::map< unsigned int, unsigned int > getPrimaryIpToLowerElementMap(const Elem &primary_elem, const Elem &primary_elem_ip, const Elem &lower_secondary_elem) const
std::vector< std::pair< R1, R2 > > get(const std::string &param1, const std::string &param2) const
const Real _c_t
Numerical factor used in the tangential constraints for convergence purposes.
const MooseVariable *const _disp_y_var
The y displacement variable.
T & set(const std::string &name, bool quiet_mode=false)
virtual void computeQpIProperties()
Computes properties that are functions both of _qp and _i, for example the weighted gap...
Elem const *const & _lower_primary_elem
const ADVariableValue & _primary_x_dot
x-velocity on the primary face
const std::vector< Real > & _JxW_msm
const Parallel::Communicator & _communicator
unsigned int _i
const bool _nodal
Whether the dof objects are nodal; if they&#39;re not, then they&#39;re elemental.
virtual void computeQpProperties() override
Computes properties that are functions only of the current quadrature point (_qp), e.g.
bool _normalize_c
Whether to normalize weighted gap by weighting function norm.
DualNumber< Real, DNDerivativeType, true > ADReal
auto max(const L &left, const R &right)
const VariableTestValue & _test
SystemBase & _sys
void communicateGaps(std::unordered_map< const DofObject *, std::pair< ADReal, Real >> &dof_to_weighted_gap, const MooseMesh &mesh, bool nodal, bool normalize_c, const Parallel::Communicator &communicator, bool send_data_back)
This function is used to communicate gaps across processes.
Elem const *const & _lower_secondary_elem
const AutomaticMortarGeneration & amg() const
std::unordered_map< const DofObject *, std::array< RealVectorValue, 2 > > _dof_to_tangent_vectors
A map from node to tangent vector (2D for now)
static void trimInteriorNodeDerivatives(const std::map< unsigned int, unsigned int > &primary_ip_lowerd_map, const Variables &moose_var, DualNumbers &ad_vars, const bool is_secondary)
const Real _epsilon
Small contact pressure value to trigger computation of frictional forces.
std::array< const ADReal *, 2 > _tangential_vel_ptr
An array of two pointers to avoid copies.
virtual void computeQpIProperties() override
Computes properties that are functions both of _qp and _i, for example the weighted gap...
std::array< MooseUtils::SemidynamicVector< Point, 9 >, 2 > getNodalTangents(const Elem &secondary_elem) const
std::map< unsigned int, unsigned int > getSecondaryIpToLowerElementMap(const Elem &lower_secondary_elem) const
std::unordered_map< const DofObject *, RealVectorValue > _dof_to_normal_vector
A map from node to normal vector (2D)
unsigned int number() const
auto norm(const T &a) -> decltype(std::abs(a))
Computes the weighted gap that will later be used to enforce the zero-penetration mechanical contact ...
const ADVariableValue & _primary_y_dot
y-velocity on the primary face
std::unordered_map< const DofObject *, std::array< ADReal, 2 > > _dof_to_weighted_tangential_velocity
A map from node to two weighted tangential velocities.
Assembly & _assembly
void incorrectEdgeDroppingPost(const std::unordered_set< const Node *> &inactive_lm_nodes) override
Copy of the post routine but that skips assembling inactive nodes.
std::vector< MooseVariable * > _lm_vars
Cartesian Lagrange multipliers for mechanical contact.
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
registerMooseObject("ContactApp", ComputeFrictionalForceCartesianLMMechanicalContact)
const MooseVariable *const _disp_x_var
The x displacement variable.
CTSub CT_OPERATOR_BINARY CTMul CTCompareLess CTCompareGreater CTCompareEqual _arg template * sqrt(_arg)) *_arg.template D< dtag >()) CT_SIMPLE_UNARY_FUNCTION(tanh
ADRealVectorValue _qp_tangential_velocity_nodal
The value of the tangential velocity vectors at the current node.
void addClassDescription(const std::string &doc_string)
void derivInsert(SemiDynamicSparseNumberArray< Real, libMesh::dof_id_type, NWrapper< N >> &derivs, libMesh::dof_id_type index, Real value)
const ADVariableValue & _secondary_x_dot
x-velocity on the secondary face
void addRangeCheckedParam(const std::string &name, const T &value, const std::string &parsed_function, const std::string &doc_string)
const MooseArray< Real > & _coord
std::unordered_map< const DofObject *, RealVectorValue > _dof_to_old_normal_vector
A map from node to normal vector (2D) - old.
processor_id_type processor_id() const
auto min(const L &left, const R &right)
unsigned int _qp
virtual void enforceConstraintOnDof(const DofObject *const dof) override
Method called from post().
uint8_t dof_id_type
const ADReal * _weighted_gap_ptr
A pointer members that can be used to help avoid copying ADReals.