https://mooseframework.inl.gov
Loading...
Searching...
No Matches
PenaltyWeightedGapUserObject.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
12#include "MooseVariableFE.h"
13#include "SystemBase.h"
14
16
18
21{
23 // The weighted-gap object supplies penalty contact's nodal-normal point-neighbor matrix coupling.
25 params.suppressParameter<bool>("ghost_point_neighbors");
26 params.addClassDescription("Computes the mortar normal contact force via a penalty approach.");
27 params.addRequiredParam<Real>("penalty", "The penalty factor");
28 params.addRangeCheckedParam<Real>(
29 "penalty_multiplier",
30 1.0,
31 "penalty_multiplier > 0",
32 "The penalty growth factor between augmented Lagrange "
33 "iterations if weighted gap does not get closed fast enough. For frictional simulations, "
34 "values smaller than 100 are recommended, e.g. 5.");
35 params.addParam<bool>(
36 "use_physical_gap",
37 false,
38 "Whether to use the physical normal gap (not scaled by mortar integration) and normalize the "
39 "penalty coefficient with a representative lower-dimensional volume assigned to the node in "
40 "the contacting boundary. This parameter is defaulted to 'true' in the contact action.");
41 params.addRangeCheckedParam<Real>(
42 "penetration_tolerance",
43 "penetration_tolerance > 0",
44 "Acceptable penetration distance at which augmented Lagrange iterations can be stopped");
45 params.addRangeCheckedParam<Real>(
46 "max_penalty_multiplier",
47 1.0e3,
48 "max_penalty_multiplier >= 1.0",
49 "Maximum multiplier applied to penalty factors when adaptivity is used in an augmented "
50 "Lagrange setting. The penalty factor supplied by the user is used as a reference.");
51 MooseEnum adaptivity_penalty_normal("SIMPLE BUSSETTA", "SIMPLE");
52 adaptivity_penalty_normal.addDocumentation(
53 "SIMPLE", "Keep multiplying by the penalty multiplier between AL iterations");
54 adaptivity_penalty_normal.addDocumentation(
55 "BUSSETTA",
56 "Modify the penalty using an algorithm from Bussetta et al, 2012, Comput Mech 49:259-275 "
57 "between AL iterations.");
58 params.addParam<MooseEnum>(
59 "adaptivity_penalty_normal",
60 adaptivity_penalty_normal,
61 "The augmented Lagrange update strategy used on the normal penalty coefficient.");
62 params.addCoupledVar(
63 "aux_lm",
64 "Auxiliary variable that is utilized together with the "
65 "penalty approach to interpolate the resulting contact tractions using dual bases.");
66 params.addParamNamesToGroup("penalty_multiplier penetration_tolerance max_penalty_multiplier",
67 "Augmented Lagrange");
68
69 return params;
70}
71
73 : WeightedGapUserObject(parameters),
75 _penalty(getParam<Real>("penalty")),
76 _penalty_multiplier(getParam<Real>("penalty_multiplier")),
77 _penetration_tolerance(
78 isParamValid("penetration_tolerance") ? getParam<Real>("penetration_tolerance") : 0.0),
79 _augmented_lagrange_problem(
80 dynamic_cast<AugmentedLagrangianContactProblemInterface *>(&_fe_problem)),
81 _lagrangian_iteration_number(_augmented_lagrange_problem
82 ? _augmented_lagrange_problem->getLagrangianIterationNumber()
83 : _no_iterations),
84 _t_step_old_normal(declareRestartableData<int>("t_step_old_normal", 0)),
85 _dt(_fe_problem.dt()),
86 _use_physical_gap(getParam<bool>("use_physical_gap")),
87 _aux_lm_var(isCoupled("aux_lm") ? getVar("aux_lm", 0) : nullptr),
88 _max_penalty_multiplier(getParam<Real>("max_penalty_multiplier")),
89 _adaptivity_normal(
90 getParam<MooseEnum>("adaptivity_penalty_normal").getEnum<AdaptivityNormalPenalty>())
91{
92 auto check_type = [this](const auto & var, const auto & var_name)
93 {
94 if (!var.isNodal())
95 paramError(var_name,
96 "The displacement variables must have degrees of freedom exclusively on "
97 "nodes, e.g. they should probably be of finite element type 'Lagrange'.");
98 };
99 check_type(*_disp_x_var, "disp_x");
100 check_type(*_disp_y_var, "disp_y");
101 if (_has_disp_z)
102 check_type(*_disp_z_var, "disp_z");
103
104 if (!_augmented_lagrange_problem == isParamValid("penetration_tolerance"))
105 paramError("penetration_tolerance",
106 "This parameter must be supplied if and only if an augmented Lagrange problem "
107 "object is used.");
108}
109
110const VariableTestValue &
115
116const VariableTestValue &
118{
119 // Penalty contact interpolates traction and weighted gap with the same basis.
120 return test();
121}
122
123const ADVariableValue &
128
129void
131{
132 for (auto & dof_pn : _dof_to_normal_pressure)
133 dof_pn.second = 0.0;
134}
135
136void
142
143void
145{
146 using std::min;
147
148 // compute new normal pressure for each node
149 for (const auto & [dof_object, wgap] : _dof_to_weighted_gap)
150 {
151 auto penalty = findValue(_dof_to_local_penalty, dof_object, _penalty);
152
153 // If _use_physical_gap is true we "normalize" the penalty parameter with the surrounding area.
154 auto gap = _use_physical_gap ? adPhysicalGap(wgap) / wgap.second : wgap.first;
155
156 const auto lagrange_multiplier =
158
159 // keep the negative normal pressure (compressive per convention here)
160 auto normal_pressure = min(0.0, penalty * gap + lagrange_multiplier);
161
162 // we switch conventins here and consider positive normal pressure as compressive
163 _dof_to_normal_pressure[dof_object] = -normal_pressure;
164 }
165}
166
167void
173
174Real
176{
177 const auto it = _dof_to_normal_pressure.find(_subproblem.mesh().nodePtr(node->id()));
178
179 if (it != _dof_to_normal_pressure.end())
180 return MetaPhysicL::raw_value(it->second);
181 else
182 return 0.0;
183}
184
185Real
187{
188 const auto it = _dof_to_lagrange_multiplier.find(_subproblem.mesh().nodePtr(node->id()));
189
190 if (it != _dof_to_lagrange_multiplier.end())
191 return -MetaPhysicL::raw_value(it->second);
192 else
193 return 0.0;
194}
195
196void
198{
200 for (const auto qp : make_range(_qrule_msm->n_points()))
201 _contact_pressure[qp] = 0.0;
202
203 for (const auto i : make_range(_test->size()))
204 {
205 const Node * const node = _lower_secondary_elem->node_ptr(i);
206
207 for (const auto qp : make_range(_qrule_msm->n_points()))
208 _contact_pressure[qp] += (*_test)[i][qp] * _dof_to_normal_pressure[node];
209 }
210}
211
212ADReal
214{
215 // A node only enters this map once it has received a weighted-gap contribution, so a node on a
216 // partially integrated element may be absent. Absent means no contact pressure, matching how the
217 // interpolated pressure in reinit() treats it.
218 return findValue(_dof_to_normal_pressure, cast_ptr<const DofObject *>(&node), ADReal(0));
219}
220
221void
223{
224 // Let's not clear the LMs for improved performance in
225 // nonlinear problems
226
227 // timestepSetup() runs once per time step attempt, so on a retried timestep (e.g. --test-restep
228 // or a rejected step) the LMs hold the discarded attempt's values; restore the accepted ones
229 const bool retried_timestep = _fe_problem.timeStep() == _t_step_old_normal;
231 if (retried_timestep)
233 else
235
236 // reset penalty
237 for (auto & dof_lp : _dof_to_local_penalty)
238 dof_lp.second = _penalty;
239
240 // clear previous gap
241 for (auto & dof_pg : _dof_to_previous_gap)
242 dof_pg.second = 0.0;
243
244 // save old timestep
245 _dt_old = _dt;
246}
247
248void
253
254bool
256{
257 using std::max, std::min, std::abs;
258
259 Real max_positive_gap = 0.0;
260 Real min_negative_gap = 0.0;
261
262 // Get maximum gap to ascertain whether we are converged.
263 for (auto & [dof_object, wgap] : _dof_to_weighted_gap)
264 {
265 const auto gap = physicalGap(wgap);
266 {
267 // Check condition for nodes that are active
268 if (gap < 0 || _dof_to_lagrange_multiplier[dof_object] < 0.0)
269 {
270 max_positive_gap = max(max_positive_gap, gap);
271 min_negative_gap = min(min_negative_gap, gap);
272 }
273 }
274 }
275
276 // Communicate the extreme gap values in parallel.
277 std::vector<Real> recv;
278 if (this->_communicator.rank() == 0)
279 recv.resize(this->_communicator.size());
280 this->_communicator.gather(
281 0, -min_negative_gap > max_positive_gap ? min_negative_gap : max_positive_gap, recv);
282
283 if (this->_communicator.rank() == 0)
284 {
285 min_negative_gap = *std::min_element(recv.begin(), recv.end());
286 max_positive_gap = *std::max_element(recv.begin(), recv.end());
287
288 // report the gap value with the largest magnitude
289 const Real reported_gap =
290 -min_negative_gap > max_positive_gap ? min_negative_gap : max_positive_gap;
291 if (abs(reported_gap) > _penetration_tolerance)
292 {
293 mooseInfoRepeated("Penetration tolerance fail max_gap = ",
294 reported_gap,
295 " (gap_tol=",
297 "). Iteration number is: ",
299 ".");
300 return false;
301 }
302 else
303 mooseInfoRepeated("Penetration tolerance success max_gap = ",
304 reported_gap,
305 " (gap_tol=",
307 "). Iteration number is: ",
309 ".");
310 }
311
312 return true;
313}
314
315void
317{
318 // Loop over all nodes for which a gap has been computed
319 for (auto & [dof_object, wgap] : _dof_to_weighted_gap)
320 {
321 const Real gap = physicalGap(wgap);
322 // Store previous augmented lagrange iteration gap
323 _dof_to_previous_gap[dof_object] = gap;
324 }
325}
326
327void
329{
330 using std::abs;
331
332 for (const auto & [dof_object, wgap] : _dof_to_weighted_gap)
333 {
334 auto & penalty = _dof_to_local_penalty[dof_object];
335 if (penalty == 0.0)
336 penalty = _penalty;
337
338 const auto gap = getNormalGap(static_cast<const Node *>(dof_object));
339 auto & lagrange_multiplier = _dof_to_lagrange_multiplier[dof_object];
340
341 const auto possible_normalization =
342 (_use_physical_gap ? libmesh_map_find(_dof_to_weighted_gap, dof_object).second : 1.0);
343
344 // Update penalty (the factor of 1/4 is suggested in the literature, the limit on AL iteration
345 // caps the penalty increase)
346 // Before we were updating the LM before adapting the penalty factor
347 if (lagrange_multiplier + gap * penalty / possible_normalization <= 0)
348 lagrange_multiplier += gap * penalty / possible_normalization;
349 else
350 lagrange_multiplier = 0.0;
351
352 const auto previous_gap = _dof_to_previous_gap[dof_object];
353 Real eval_tn = 0;
354
356 {
357 if (abs(gap) > 0.25 * abs(previous_gap) && abs(gap) > _penetration_tolerance)
358 penalty *= _penalty_multiplier;
359 }
361 bussettaAdaptivePenalty(previous_gap, gap, penalty, eval_tn);
362
363 // Heuristics to bound the penalty factor
364 if (penalty < _penalty)
365 penalty = _penalty;
366 else if (penalty > _max_penalty_multiplier * _penalty)
368 }
369}
370
371void
373 const Real gap,
374 Real & penalty,
375 Real & eval_tn)
376{
377 // Positive gaps means no contact
378 if (previous_gap > 0.0)
379 {
380 penalty = _penalty;
381 eval_tn = 0.0;
382 }
383 else
384 {
385 if (previous_gap * gap < 0)
386 eval_tn = 0.0;
387 else
388 eval_tn = penalty * previous_gap;
389
390 adaptiveNormalPenalty(previous_gap, gap, penalty);
391 }
392}
393
394void
396 const Real gap,
397 Real & penalty)
398{
399 using std::abs, std::max, std::sqrt;
400
401 const bool condition_one = abs(abs(previous_gap / gap) - 1.0) < 0.01;
402 const bool condition_two = abs(gap) > 1.01 * abs(previous_gap);
403
404 if (previous_gap * gap < 0)
405 {
406 if (previous_gap > _penetration_tolerance)
407 penalty = abs(penalty * previous_gap / gap * (abs(gap) + _penetration_tolerance) /
408 (gap - previous_gap));
409 else
410 penalty = abs(penalty * previous_gap / 10.0 / gap);
411 }
412 else if (abs(gap) > _penetration_tolerance)
413 {
414 if (abs(gap - previous_gap) >
415 max(gap / 10.0, max(previous_gap / 10.0, 5.0 * _penetration_tolerance)))
416 penalty *= 10.0;
417 else if (condition_one && gap < 10.0 * _penetration_tolerance)
418 penalty *= MathUtils::pow(sqrt(abs(gap) / _penetration_tolerance - 1.0) + 1.0, 2);
419 else if (condition_two)
420 penalty *= 10.0 * previous_gap / gap;
421 else
422 penalty *= sqrt(abs(gap) / _penetration_tolerance - 1.0) + 1.0;
423 }
424}
DualNumber< Real, DNDerivativeType, true > ADReal
void mooseInfoRepeated(Args &&... args)
registerMooseObject("ContactApp", PenaltyWeightedGapUserObject)
void ErrorVector unsigned int
Interface class for user objects that support the augmented Lagrange formalism as implemented in Augm...
Class to provide an interface for parameters and routines required to check convergence for the augme...
virtual int & timeStep() const
void suppressParameter(const std::string &name)
void addParamNamesToGroup(const std::string &space_delim_names, const std::string group_name)
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)
void addCoupledVar(const std::string &name, const std::string &doc_string)
void addRangeCheckedParam(const std::string &name, const T &value, const std::string &parsed_function, const std::string &doc_string)
void paramError(const std::string &param, Args... args) const
bool isParamValid(const std::string &name) const
virtual const Node * nodePtr(const dof_id_type i) const
virtual const FieldVariablePhiValue & phiLower() const override
Elem const *const & _lower_secondary_elem
const libMesh::QBase *const & _qrule_msm
User object for computing weighted gaps and contact pressure for penalty based mortar constraints.
const Real _penalty
The penalty factor.
std::unordered_map< const DofObject *, Real > _dof_to_previous_gap
Map from degree of freedom to previous AL iteration gap values.
std::unordered_map< const DofObject *, Real > _dof_to_lagrange_multiplier_start
Augmented lagrange multipliers at the start of the current timestep, restored when the timestep is re...
void adaptiveNormalPenalty(const Real previous_gap, const Real gap, Real &penalty)
See Algorithm 3 of 'The adapted augmented Lagrangian method: a new method for the resolution of the m...
virtual bool isAugmentedLagrangianConverged() override
PenaltyWeightedGapUserObject(const InputParameters &parameters)
virtual ADReal nodalContactPressure(const Node &node) const override
const Real _penalty_multiplier
penalty growth factor for augmented Lagrange
std::unordered_map< const DofObject *, Real > _dof_to_local_penalty
Map from degree of freedom to local penalty value.
enum PenaltyWeightedGapUserObject::AdaptivityNormalPenalty _adaptivity_normal
virtual const VariableTestValue & tractionBasis() const override
virtual Real getNormalContactPressure(const Node *const node) const override
std::unordered_map< const DofObject *, Real > _dof_to_lagrange_multiplier
Map from degree of freedom to augmented lagrange multiplier.
virtual void augmentedLagrangianSetup() override
const Real _penetration_tolerance
penetration tolerance for augmented Lagrange contact
AugmentedLagrangianContactProblemInterface *const _augmented_lagrange_problem
augmented Lagrange problem and iteration number
virtual const VariableTestValue & test() const override
ADVariableValue _contact_pressure
The contact pressure on the mortar segument quadrature points.
const MooseVariable *const _aux_lm_var
The auxiliary Lagrange multiplier variable (used together whith the Petrov-Galerkin approach)
const Real & _dt
Current delta t... or timestep size.
std::unordered_map< const DofObject *, ADReal > _dof_to_normal_pressure
Map from degree of freedom to normal pressure for reporting.
virtual void updateAugmentedLagrangianMultipliers() override
virtual const ADVariableValue & contactPressure() const override
int & _t_step_old_normal
The timestep index at which _dof_to_lagrange_multiplier_start was last saved.
AdaptivityNormalPenalty
The adaptivity method for the penalty factor at augmentations.
const bool _use_physical_gap
Use scaled or physical gap.
const Real _max_penalty_multiplier
Maximum multiplier applied to the initial penalty factor in AL.
virtual Real getNormalLagrangeMultiplier(const Node *const node) const
void bussettaAdaptivePenalty(const Real previous_gap, const Real gap, Real &penalty, Real &eval_tn)
Adaptive, local penalty for AL.
virtual MooseMesh & mesh()=0
processor_id_type size() const
processor_id_type rank() const
void gather(const unsigned int root_id, const T &send_data, std::vector< T, A > &recv) const
SubProblem & _subproblem
Creates dof object to weighted gap map.
static InputParameters validParams()
V findValue(const std::unordered_map< K, V > &map, const K &key, const V &default_value=0) const
Find a value in a map or return a default if the key doesn't exist.
const MooseVariable *const _disp_x_var
The x displacement variable.
const bool _has_disp_z
For 2D mortar contact no displacement will be specified, so const pointers used.
virtual void finalize() override
virtual Real getNormalGap(const Node *const) const
FEProblemBase & _fe_problem
The base finite element problem.
static void addNodalNormalCoupling(InputParameters &params)
Request the secondary face point-neighbor matrix coupling that nodal-normal derivatives need.
ADReal adPhysicalGap(const std::pair< ADReal, Real > &gap) const
const MooseVariable *const _disp_y_var
The y displacement variable.
std::unordered_map< const DofObject *, std::pair< ADReal, Real > > _dof_to_weighted_gap
A map from node to weighted gap and normalization (if requested)
const MooseVariable *const _disp_z_var
The z displacement variable.
virtual void initialize() override
Real physicalGap(const std::pair< ADReal, Real > &gap) const
Compute physical gap from integration gap quantity.
const VariableTestValue * _test
A pointer to the test function associated with the weighted gap.
const Parallel::Communicator & _communicator
unsigned int n_points() const
T pow(T x, int e)
auto raw_value(const Eigen::Map< T > &in)
VariableShapeValue< true > VariableTestValue
VariableValueTempl< true > ADVariableValue