https://mooseframework.inl.gov
RANFSNormalMechanicalContact.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 "PenetrationLocator.h"
12 #include "PenetrationInfo.h"
13 #include "SystemBase.h"
14 #include "Assembly.h"
15 #include "NearestNodeLocator.h"
16 #include "MooseVariableFE.h"
17 
18 #include "libmesh/numeric_vector.h"
19 #include "libmesh/enum_fe_family.h"
20 
22 
25 {
27  params.set<bool>("use_displaced_mesh") = true;
28 
29  MooseEnum component("x=0 y=1 z=2");
31  "component", component, "The force component constraint that this object is supplying");
32  params.addRequiredCoupledVar(
33  "displacements",
34  "The displacements appropriate for the simulation geometry and coordinate system");
35  params.addParam<bool>("ping_pong_protection",
36  false,
37  "Whether to protect against ping-ponging, e.g. the oscillation of the "
38  "secondary node between two "
39  "different primary faces, by tying the secondary node to the "
40  "edge between the involved primary faces");
41  params.addParam<Real>(
42  "normal_smoothing_distance",
43  "Distance from edge in parametric coordinates over which to smooth contact normal");
44  params.addClassDescription("Applies the Reduced Active Nonlinear Function Set scheme in which "
45  "the secondary node's non-linear residual function is replaced by the "
46  "zero penetration constraint equation when the constraint is active");
47  return params;
48 }
49 
51  : NodeFaceConstraint(parameters),
52  _component(getParam<MooseEnum>("component")),
53  _mesh_dimension(_mesh.dimension()),
54  _residual_copy(_sys.residualGhosted()),
55  _dof_number_to_value(coupledComponents("displacements")),
56  _disp_coupling(coupledComponents("displacements")),
57  _ping_pong_protection(getParam<bool>("ping_pong_protection"))
58 {
59  // modern parameter scheme for displacements
60  for (unsigned int i = 0; i < coupledComponents("displacements"); ++i)
61  {
62  _vars.push_back(coupled("displacements", i));
63  _var_objects.push_back(getVar("displacements", i));
64  }
65 
66  for (auto & var : _var_objects)
67  if (var->feType().family != LAGRANGE)
68  mooseError("This object only works when the displacement variables use a Lagrange basis");
69 
70  if (_vars.size() != _mesh_dimension)
71  mooseError("The number of displacement variables does not match the mesh dimension!");
72 
73  if (parameters.isParamValid("normal_smoothing_distance"))
74  _penetration_locator.setNormalSmoothingDistance(getParam<Real>("normal_smoothing_distance"));
75 }
76 
77 void
79 {
80  auto system_coupling_matrix = _subproblem.couplingMatrix(_sys.number());
81 
82  for (MooseIndex(_vars) i = 0; i < _vars.size(); ++i)
83  for (MooseIndex(_vars) j = 0; j < _vars.size(); ++j)
84  _disp_coupling(i, j) = (*system_coupling_matrix)(_vars[i], _vars[j]);
85 }
86 
87 void
89 {
92 }
93 
94 void
96 {
97  _node_to_contact_lm.clear();
98  _node_to_tied_lm.clear();
100 }
101 
102 bool
104 {
105  if (_tie_nodes)
106  return true;
107  else
108  return _largest_component == static_cast<unsigned int>(_component);
109 }
110 
111 bool
113 {
114  std::map<dof_id_type, PenetrationInfo *>::iterator found =
116  if (found != _penetration_locator._penetration_info.end())
117  {
118  _pinfo = found->second;
119  if (_pinfo)
120  {
121  // We overwrite the secondary residual when constraints are active so we cannot use the
122  // residual copy for determining the Lagrange multiplier when computing the Jacobian
124  {
125  // Build up residual vector corresponding to contact forces
126  _res_vec.zero();
127  for (unsigned int i = 0; i < _mesh_dimension; ++i)
128  {
129  dof_id_type dof_number = _current_node->dof_number(0, _vars[i], 0);
130  _res_vec(i) = _residual_copy(dof_number) / _var_objects[i]->scalingFactor();
131  }
132 
133  _node_to_contact_lm.insert(std::make_pair(_current_node->id(), _res_vec * _pinfo->_normal));
134  _node_to_tied_lm.insert(std::make_pair(_current_node->id(), _res_vec(_component)));
135  }
136  else
137  {
138  std::vector<dof_id_type> cols;
139  std::vector<Number> values;
140 
141  for (auto & d_to_v : _dof_number_to_value)
142  d_to_v.clear();
143 
144  mooseAssert(_vars.size() == _dof_number_to_value.size() &&
145  _vars.size() == _var_objects.size(),
146  "Somehow the sizes of our variable containers got out of sync");
147  for (MooseIndex(_var_objects) i = 0; i < _var_objects.size(); ++i)
148  {
149  auto secondary_dof_number = _current_node->dof_number(0, _vars[i], 0);
150 
151  _jacobian->get_row(secondary_dof_number, cols, values);
152  mooseAssert(cols.size() == values.size(),
153  "The size of the dof container and value container are different");
154 
155  for (MooseIndex(cols) j = 0; j < cols.size(); ++j)
156  _dof_number_to_value[i].insert(
157  std::make_pair(cols[j], values[j] / _var_objects[i]->scalingFactor()));
158  }
159  }
160 
161  mooseAssert(_node_to_contact_lm.find(_current_node->id()) != _node_to_contact_lm.end(),
162  "The node " << _current_node->id()
163  << " should map to a contact lagrange multiplier");
164  mooseAssert(_node_to_tied_lm.find(_current_node->id()) != _node_to_tied_lm.end(),
165  "The node " << _current_node->id()
166  << " should map to a tied lagrange multiplier");
169 
170  // Check to see whether we've locked a ping-ponging node
173  {
174  if (_contact_lm > -_pinfo->_distance)
175  {
176  // Ok, our math is telling us we should apply the constraint, but what if we are
177  // ping-ponging back and forth between different primary faces?
178 
179  // This only works for a basic line search! Write assertion here
181  {
182  auto & primary_elem_sequence = _node_to_primary_elem_sequence[_current_node->id()];
183  mooseAssert(
185  "The current primary element and the PenetrationInfo object's element should "
186  "be the same");
187  primary_elem_sequence.push_back(_pinfo->_elem);
188 
189  // 5 here is a heuristic choice. In testing, generally speaking, if a node ping-pongs
190  // back and forth 5 times then it will ping pong indefinitely. However, if it goes 3
191  // times for example, it is not guaranteed to ping-pong indefinitely and the Newton
192  // iteration may naturally resolve the correct face dofs that need to be involved in the
193  // constraint.
194  if (_ping_pong_protection && primary_elem_sequence.size() >= 5 &&
195  _pinfo->_elem == *(primary_elem_sequence.rbegin() + 2) &&
196  _pinfo->_elem == *(primary_elem_sequence.rbegin() + 4) &&
197  _pinfo->_elem != *(primary_elem_sequence.rbegin() + 1) &&
198  *(primary_elem_sequence.rbegin() + 1) == *(primary_elem_sequence.rbegin() + 3))
199  {
200  // Ok we are ping-ponging. Determine the primary node
201  auto primary_node =
203 
204  // Sanity checks
205  mooseAssert(_pinfo->_elem->get_node_index(primary_node) != libMesh::invalid_uint,
206  "The primary node is not on the current element");
207  mooseAssert((*(primary_elem_sequence.rbegin() + 1))->get_node_index(primary_node) !=
209  "The primary node is not on the other ping-ponging element");
210 
212  std::make_pair(_current_node->id(), primary_node));
213  }
214  }
215  }
216  else
217  // We have not locked the node into contact nor is the gap smaller than the Lagrange
218  // Multiplier so we should not apply
219  return false;
220  }
221 
222  // Determine whether we're going to apply the tied node equality constraint or the contact
223  // inequality constraint
226  {
227  _tie_nodes = true;
228  _nearest_node = it->second;
229  _primary_index = _current_primary->get_node_index(_nearest_node);
230  mooseAssert(_primary_index != libMesh::invalid_uint,
231  "nearest node not a node on the current primary element");
232  }
233  else
234  {
236  // Do this to make sure constraint equation has a positive on the diagonal
237  if (_pinfo->_normal(_component) > 0)
238  _distance *= -1;
239  _tie_nodes = false;
240 
241  // The contact constraint is active -> we're going to use our linear solve to ensure that
242  // the gap is driven to zero. We only have one zero-penetration constraint per node, so we
243  // choose to apply the zero penetration constraint only to the displacement component with
244  // the largest magnitude normal
245  auto largest_component_magnitude = std::abs(_pinfo->_normal(0));
246  _largest_component = 0;
247  for (MooseIndex(_mesh_dimension) i = 1; i < _mesh_dimension; ++i)
248  {
249  auto component_magnitude = std::abs(_pinfo->_normal(i));
250  if (component_magnitude > largest_component_magnitude)
251  {
252  largest_component_magnitude = component_magnitude;
253  _largest_component = i;
254  }
255  }
256  }
257 
258  return true;
259  }
260  }
261 
262  // If we're not applying the constraint then we can clear the node to primary elem sequence for
263  // this node
267  return false;
268 }
269 
270 Real
272 {
273  switch (type)
274  {
276  {
277  if (_tie_nodes)
278  return (*_current_node - *_nearest_node)(_component);
279  else
280  {
281  if (_largest_component == static_cast<unsigned int>(_component))
282  {
283  mooseAssert(_pinfo->_normal(_component) != 0,
284  "We should be selecting the largest normal component, hence it should be "
285  "impossible for this normal component to be zero");
286 
287  return _distance;
288  }
289 
290  else
291  // The normal points out of the primary face
292  return _contact_lm * -_pinfo->_normal(_component);
293  }
294  }
295 
297  {
298  if (_tie_nodes)
299  {
300  if (_i == _primary_index)
301  return _tied_lm;
302  else
303  return 0;
304  }
305  else
307  }
308 
309  default:
310  return 0;
311  }
312 }
313 
314 Real
316 {
317  switch (type)
318  {
319  case Moose::ConstraintJacobianType::SecondarySecondary:
320  {
321  if (_tie_nodes)
322  return _phi_secondary[_j][_qp];
323 
324  // doing contact
325  else
326  {
327  // corresponds to gap equation
328  if (_largest_component == static_cast<unsigned int>(_component))
329  // _phi_secondary has been set such that it is 1 when _j corresponds to the degree of
330  // freedom associated with the _current node and 0 otherwise
331  return std::abs(_pinfo->_normal(_component)) * _phi_secondary[_j][_qp];
332 
333  // corresponds to regular residual with Lagrange Multiplier applied
334  else
335  {
336  Real ret_val = 0;
337  for (MooseIndex(_disp_coupling) i = 0; i < _disp_coupling.size(); ++i)
338  if (_disp_coupling(_component, i))
339  {
340  mooseAssert(
342  _dof_number_to_value[i].end(),
343  "The connected dof index is not found in the _dof_number_to_value container. "
344  "This must mean that insufficient sparsity was allocated");
345  ret_val += -_pinfo->_normal(_component) * _pinfo->_normal(i) *
347  }
348  return ret_val;
349  }
350  }
351  }
352 
353  case Moose::ConstraintJacobianType::SecondaryPrimary:
354  {
355  if (_tie_nodes)
356  {
357  if (_primary_index == _j)
358  return -1;
359 
360  // We're tying the secondary node to only one node on the primary side (specified by
361  // _primary_index). If the current _j doesn't correspond to that tied primary node, then the
362  // secondary residual doesn't depend on it
363  else
364  return 0;
365  }
366  else
367  {
368  if (_largest_component == static_cast<unsigned int>(_component))
369  return -std::abs(_pinfo->_normal(_component)) * _phi_primary[_j][_qp];
370 
371  // If we're not applying the gap constraint equation on this _component, then we're
372  // applying a Lagrange multiplier, and consequently there is no dependence of the secondary
373  // residual on the primary dofs because the Lagrange multiplier is only a functon of the
374  // secondary residuals
375  else
376  return 0;
377  }
378  }
379 
380  case Moose::ConstraintJacobianType::PrimarySecondary:
381  {
382  if (_tie_nodes)
383  {
384  if (_i == _primary_index)
385  {
388  "The connected dof index is not found in the _dof_number_to_value container. "
389  "This must mean that insufficient sparsity was allocated");
391  }
392 
393  // We only apply the tied node Lagrange multiplier to the closest primary node
394  else
395  return 0;
396  }
397  else
398  {
399  Real ret_val = 0;
400  for (MooseIndex(_disp_coupling) i = 0; i < _disp_coupling.size(); ++i)
401  if (_disp_coupling(_component, i))
402  {
403  mooseAssert(
405  _dof_number_to_value[i].end(),
406  "The connected dof index is not found in the _dof_number_to_value container. "
407  "This must mean that insufficient sparsity was allocated");
408  ret_val += _test_primary[_i][_qp] * _pinfo->_normal(_component) * _pinfo->_normal(i) *
410  }
411  return ret_val;
412  }
413  }
414 
415  // The only primary-primary dependence would come from the dependence of the normal and also
416  // the location of the integration (quadrature) points. We assume (valid or not) that this
417  // dependence is weak
418  // case MooseConstraintJacobianType::PrimaryPrimary
419 
420  default:
421  return 0;
422  }
423 }
424 
425 void
427 {
428 }
429 
430 Real
432 {
433  mooseError(
434  "We overrode commputeSecondaryValue so computeQpSecondaryValue should never get called");
435 }
std::vector< MooseVariable * > _var_objects
LAGRANGE
registerMooseObject("ContactApp", RANFSNormalMechanicalContact)
virtual const libMesh::CouplingMatrix * couplingMatrix(const unsigned int nl_sys_num) const=0
NumericVector< Number > & _residual_copy
virtual unsigned int coupled(const std::string &var_name, unsigned int comp=0) const
virtual Real computeQpResidual(Moose::ConstraintType type) override
ConstraintType
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)
std::unordered_map< dof_id_type, std::vector< const Elem * > > _node_to_primary_elem_sequence
static const std::string component
Definition: NS.h:153
const Elem *const & _current_primary
T & set(const std::string &name, bool quiet_mode=false)
static InputParameters validParams()
MooseVariable * getVar(const std::string &var_name, unsigned int comp)
unsigned int _i
const VariableTestValue & _test_primary
std::unordered_map< dof_id_type, Real > _node_to_contact_lm
bool computingNonlinearResid() const
std::map< dof_id_type, PenetrationInfo *> & _penetration_info
void addRequiredParam(const std::string &name, const std::string &doc_string)
void residualSetup() override
const Node *const & _current_node
void computeSecondaryValue(NumericVector< Number > &solution) override
SystemBase & _sys
unsigned int _j
std::vector< std::unordered_map< dof_id_type, Number > > _dof_number_to_value
const Elem * _elem
SubProblem & _subproblem
virtual Real computeQpJacobian(Moose::ConstraintJacobianType type) override
const std::string & type() const
unsigned int number() const
RANFSNormalMechanicalContact(const InputParameters &parameters)
void addRequiredCoupledVar(const std::string &name, const std::string &doc_string)
unsigned int coupledComponents(const std::string &var_name) const
virtual void get_row(numeric_index_type i, std::vector< numeric_index_type > &indices, std::vector< Number > &values) const =0
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
std::unordered_map< dof_id_type, const Node * > _ping_pong_secondary_node_to_primary_node
const Node * nearestNode(dof_id_type node_id)
ConstraintJacobianType
void mooseError(Args &&... args) const
std::vector< dof_id_type > _connected_dof_indices
void addClassDescription(const std::string &doc_string)
const InputParameters & parameters() const
const VariablePhiValue & _phi_primary
static const std::complex< double > j(0, 1)
Complex number "j" (also known as "i")
const SparseMatrix< Number > * _jacobian
VariablePhiValue _phi_secondary
std::unordered_map< dof_id_type, Real > _node_to_tied_lm
const bool & currentlyComputingJacobian() const
PenetrationLocator & _penetration_locator
unsigned int _qp
virtual Real computeQpSecondaryValue() override
uint8_t dof_id_type
NearestNodeLocator & _nearest_node
bool isParamValid(const std::string &name) const