https://mooseframework.inl.gov
Loading...
Searching...
No Matches
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");
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
77void
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
87void
93
94void
101
102bool
104{
105 if (_tie_nodes)
106 return true;
107 else
108 return _largest_component == static_cast<unsigned int>(_component);
109}
110
111bool
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
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 {
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;
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));
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;
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
270Real
272{
273 switch (type)
274 {
275 case Moose::ConstraintType::Secondary:
276 {
277 if (_tie_nodes)
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
293 }
294 }
295
296 case Moose::ConstraintType::Primary:
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
314Real
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)
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)
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
425void
429
430Real
432{
434 "We overrode commputeSecondaryValue so computeQpSecondaryValue should never get called");
435}
std::array< Real, 2 > values
registerMooseObject("ContactApp", RANFSNormalMechanicalContact)
unsigned int _qp
unsigned int _j
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
void addRequiredCoupledVar(const std::string &name, const std::string &doc_string)
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)
void addClassDescription(const std::string &doc_string)
T & set(const std::string &name, bool quiet_mode=false)
bool isParamValid(const std::string &name) const
const InputParameters & parameters() const
const std::string & type() const
void mooseError(Args &&... args) const
const Node * nearestNode(dof_id_type node_id)
PenetrationLocator & _penetration_locator
const Node *const & _current_node
void residualSetup() override
const Elem *const & _current_primary
std::vector< dof_id_type > _connected_dof_indices
const VariableTestValue & _test_primary
const SparseMatrix< Number > * _jacobian
static InputParameters validParams()
const VariablePhiValue & _phi_primary
VariablePhiValue _phi_secondary
const Elem * _elem
RealVectorValue _normal
void setNormalSmoothingDistance(Real normal_smoothing_distance)
std::map< dof_id_type, PenetrationInfo * > & _penetration_info
NearestNodeLocator & _nearest_node
virtual Real computeQpJacobian(Moose::ConstraintJacobianType type) override
virtual Real computeQpResidual(Moose::ConstraintType type) override
RANFSNormalMechanicalContact(const InputParameters &parameters)
std::unordered_map< dof_id_type, Real > _node_to_contact_lm
std::vector< MooseVariable * > _var_objects
virtual Real computeQpSecondaryValue() override
std::unordered_map< dof_id_type, const Node * > _ping_pong_secondary_node_to_primary_node
std::unordered_map< dof_id_type, std::vector< const Elem * > > _node_to_primary_elem_sequence
std::vector< std::unordered_map< dof_id_type, Number > > _dof_number_to_value
std::unordered_map< dof_id_type, Real > _node_to_tied_lm
void computeSecondaryValue(NumericVector< Number > &solution) override
SubProblem & _subproblem
SystemBase & _sys
const bool & currentlyComputingJacobian() const
bool computingNonlinearResid() const
virtual const libMesh::CouplingMatrix * couplingMatrix(const unsigned int nl_sys_num) const=0
unsigned int number() const
virtual void insert(const T *v, const std::vector< numeric_index_type > &dof_indices)
virtual void get_row(numeric_index_type i, std::vector< numeric_index_type > &indices, std::vector< T > &values) const=0
ConstraintType
ConstraintJacobianType
const unsigned int invalid_uint