https://mooseframework.inl.gov
Loading...
Searching...
No Matches
ComputeWeightedGapCartesianLMMechanicalContact.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 "DisplacedProblem.h"
12#include "MortarContactUtils.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
28namespace
29{
30const InputParameters &
31assignVarsInParamsCartesianWeightedGap(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 // Mechanical contact residuals use only primal values and normals
51 params.set<bool>("_compute_primal_gradients") = false;
52 params.addClassDescription("Computes the weighted gap that will later be used to enforce the "
53 "zero-penetration mechanical contact conditions");
54 params.suppressParameter<VariableName>("secondary_variable");
55 params.suppressParameter<VariableName>("primary_variable");
56 params.addRequiredCoupledVar("disp_x", "The x displacement variable");
57 params.addRequiredCoupledVar("disp_y", "The y displacement variable");
58 params.addCoupledVar("disp_z", "The z displacement variable");
59 params.addParam<Real>(
60 "c", 1e6, "Parameter for balancing the size of the gap and contact pressure");
61 params.addRequiredCoupledVar("lm_x",
62 "Mechanical contact Lagrange multiplier along the x Cartesian axis");
64 "lm_y", "Mechanical contact Lagrange multiplier along the y Cartesian axis.");
65 params.addCoupledVar("lm_z",
66 "Mechanical contact Lagrange multiplier along the z Cartesian axis.");
67 params.set<bool>("interpolate_normals") = false;
68 params.addParam<bool>(
69 "normalize_c",
70 false,
71 "Whether to normalize c by weighting function norm. When unnormalized "
72 "the value of c effectively depends on element size since in the constraint we compare nodal "
73 "Lagrange Multiplier values to integrated gap values (LM nodal value is independent of "
74 "element size, where integrated values are dependent on element size).");
75 params.set<bool>("use_displaced_mesh") = true;
76 return params;
77}
78
80 const InputParameters & parameters)
81 : ADMortarConstraint(assignVarsInParamsCartesianWeightedGap(parameters)),
82 _secondary_disp_x(adCoupledValue("disp_x")),
83 _primary_disp_x(adCoupledNeighborValue("disp_x")),
84 _secondary_disp_y(adCoupledValue("disp_y")),
85 _primary_disp_y(adCoupledNeighborValue("disp_y")),
86 _has_disp_z(isCoupled("disp_z")),
87 _secondary_disp_z(_has_disp_z ? &adCoupledValue("disp_z") : nullptr),
88 _primary_disp_z(_has_disp_z ? &adCoupledNeighborValue("disp_z") : nullptr),
89 _c(getParam<Real>("c")),
90 _normalize_c(getParam<bool>("normalize_c")),
91 _nodal(getVar("disp_x", 0)->feType().family == LAGRANGE),
92 _disp_x_var(getVar("disp_x", 0)),
93 _disp_y_var(getVar("disp_y", 0)),
94 _disp_z_var(_has_disp_z ? getVar("disp_z", 0) : nullptr)
95{
96 _lm_vars.push_back(getVar("lm_x", 0));
97 _lm_vars.push_back(getVar("lm_y", 0));
98
99 if (isParamValid("lm_z") ^ _has_disp_z)
100 paramError("lm_z",
101 "In three-dimensions, both the Z Lagrange multiplier and the Z displacement need to "
102 "be provided");
103
104 if (_has_disp_z)
105 _lm_vars.push_back(getVar("lm_z", 0));
106
107 if (!getParam<bool>("use_displaced_mesh"))
109 "use_displaced_mesh",
110 "'use_displaced_mesh' must be true for the ComputeWeightedGapLMMechanicalContact object");
111
112 for (const auto i : index_range(_lm_vars))
113 if (!_lm_vars[i]->isNodal())
114 if (_lm_vars[i]->feType().order != static_cast<Order>(0))
115 mooseError("Normal contact constraints only support elemental variables of CONSTANT order");
116}
117
118ADReal
120{
121 mooseError("We should never call computeQpResidual for ComputeWeightedGapLMMechanicalContact");
122}
123
124void
126{
127 // Trim interior node variable derivatives
128 const auto & primary_ip_lowerd_map = amg().getPrimaryIpToLowerElementMap(
130 const auto & secondary_ip_lowerd_map =
132
133 std::array<const MooseVariable *, 3> var_array{{_disp_x_var, _disp_y_var, _disp_z_var}};
134 std::array<ADReal, 3> primary_disp{
135 {_primary_disp_x[_qp], _primary_disp_y[_qp], _has_disp_z ? (*_primary_disp_z)[_qp] : 0}};
136 std::array<ADReal, 3> secondary_disp{{_secondary_disp_x[_qp],
138 _has_disp_z ? (*_secondary_disp_z)[_qp] : 0}};
139
140 trimInteriorNodeDerivatives(primary_ip_lowerd_map, var_array, primary_disp, false);
141 trimInteriorNodeDerivatives(secondary_ip_lowerd_map, var_array, secondary_disp, true);
142
143 const ADReal & prim_x = primary_disp[0];
144 const ADReal & prim_y = primary_disp[1];
145 const ADReal * prim_z = nullptr;
146 if (_has_disp_z)
147 prim_z = &primary_disp[2];
148
149 const ADReal & sec_x = secondary_disp[0];
150 const ADReal & sec_y = secondary_disp[1];
151 const ADReal * sec_z = nullptr;
152 if (_has_disp_z)
153 sec_z = &secondary_disp[2];
154
155 // Compute gap vector
157
158 gap_vec(0).derivatives() = prim_x.derivatives() - sec_x.derivatives();
159 gap_vec(1).derivatives() = prim_y.derivatives() - sec_y.derivatives();
160 if (_has_disp_z)
161 gap_vec(2).derivatives() = prim_z->derivatives() - sec_z->derivatives();
162
163 // Compute integration point quantities
164 _qp_gap_nodal = gap_vec * (_JxW_msm[_qp] * _coord[_qp]);
165
166 // To do normalization of constraint coefficient (c_n)
168}
169
170void
172{
173 mooseAssert(_normals.size() == _lower_secondary_elem->n_nodes(),
174 "Making sure that _normals is the expected size");
175
176 // Get the _dof_to_weighted_gap map
177 const DofObject * dof = _lm_vars[0]->isNodal()
178 ? cast_ptr<const DofObject *>(_lower_secondary_elem->node_ptr(_i))
179 : cast_ptr<const DofObject *>(_lower_secondary_elem);
180
182
183 if (_normalize_c)
184 _dof_to_weighted_gap[dof].second += _test[_i][_qp] * _qp_factor;
185}
186
187void
195
196void
203
204void
209
210void
212{
213 if (mortar_type != Moose::MortarType::Lower)
214 return;
215
216 for (const auto i : index_range(_lm_vars))
217 {
218 mooseAssert(_lm_vars[i], "LM variable is null");
219 libmesh_ignore(i);
220 }
221
222 for (_qp = 0; _qp < _qrule_msm->n_points(); _qp++)
223 {
225 for (_i = 0; _i < _test.size(); ++_i)
226 {
228
229 // Get the _dof_to_weighted_gap map
230 const DofObject * dof = _lm_vars[0]->isNodal()
231 ? cast_ptr<const DofObject *>(_lower_secondary_elem->node_ptr(_i))
232 : cast_ptr<const DofObject *>(_lower_secondary_elem);
233 // We do not interpolate geometry, so just match the local node _i with the corresponding _i
235 const auto & nodal_tangents = amg().getNodalTangents(*_lower_secondary_elem);
236 _dof_to_tangent_vectors[dof][0] = nodal_tangents[0][_i];
237 _dof_to_tangent_vectors[dof][1] = nodal_tangents[1][_i];
238 }
239 }
240}
241
242void
244{
245 // During "computeResidual" and "computeJacobian" we are actually just computing properties on the
246 // mortar segment element mesh. We are *not* actually assembling into the residual/Jacobian. For
247 // the zero-penetration constraint, the property of interest is the map from node to weighted gap.
248 // Computation of the properties proceeds identically for residual and Jacobian evaluation hence
249 // why we simply call computeResidual here. We will assemble into the residual/Jacobian later from
250 // the post() method
251 computeResidual(mortar_type);
252}
253
254void
256{
259
260 for (const auto & pr : _dof_to_weighted_gap)
261 {
262 if (pr.first->processor_id() != this->processor_id())
263 continue;
264
265 _weighted_gap_ptr = &pr.second.first;
266 _normalization_ptr = &pr.second.second;
267
268 enforceConstraintOnDof(pr.first);
269 }
270}
271
272void
274 const std::unordered_set<const Node *> & inactive_lm_nodes)
275{
278
279 for (const auto & pr : _dof_to_weighted_gap)
280 {
281 if ((inactive_lm_nodes.find(static_cast<const Node *>(pr.first)) != inactive_lm_nodes.end()) ||
282 (pr.first->processor_id() != this->processor_id()))
283 continue;
284
285 _weighted_gap_ptr = &pr.second.first;
286 _normalization_ptr = &pr.second.second;
287
288 enforceConstraintOnDof(pr.first);
289 }
290}
291
292void
294{
295 const auto & weighted_gap = *_weighted_gap_ptr;
296 const Real c = _normalize_c ? _c / *_normalization_ptr : _c;
297
298 const auto dof_index_x = dof->dof_number(_sys.number(), _lm_vars[0]->number(), 0);
299 const auto dof_index_y = dof->dof_number(_sys.number(), _lm_vars[1]->number(), 0);
300 const Real scaling_factor_x = _lm_vars[0]->scalingFactor();
301 const Real scaling_factor_y = _lm_vars[1]->scalingFactor();
302 Real scaling_factor_z = 1;
303
304 ADReal lm_x = (*_sys.currentSolution())(dof_index_x);
305 ADReal lm_y = (*_sys.currentSolution())(dof_index_y);
306
307 Moose::derivInsert(lm_x.derivatives(), dof_index_x, 1.);
308 Moose::derivInsert(lm_y.derivatives(), dof_index_y, 1.);
309
310 dof_id_type dof_index_z(-1);
311 ADReal lm_z;
312 if (_has_disp_z)
313 {
314 dof_index_z = dof->dof_number(_sys.number(), _lm_vars[2]->number(), 0);
315 lm_z = (*_sys.currentSolution())(dof_index_z);
316 Moose::derivInsert(lm_z.derivatives(), dof_index_z, 1.);
317 scaling_factor_z = _lm_vars[2]->scalingFactor();
318 }
319
320 ADReal normal_pressure_value =
321 lm_x * _dof_to_normal_vector[dof](0) + lm_y * _dof_to_normal_vector[dof](1);
322 ADReal tangential_pressure_value =
323 lm_x * _dof_to_tangent_vectors[dof][0](0) + lm_y * _dof_to_tangent_vectors[dof][0](1);
324
325 ADReal tangential_pressure_value_dir;
326
327 if (_has_disp_z)
328 {
329 normal_pressure_value += lm_z * _dof_to_normal_vector[dof](2);
330 tangential_pressure_value += lm_z * _dof_to_tangent_vectors[dof][0](2);
331 tangential_pressure_value_dir = lm_x * _dof_to_tangent_vectors[dof][1](0) +
332 lm_y * _dof_to_tangent_vectors[dof][1](1) +
333 lm_z * _dof_to_tangent_vectors[dof][1](2);
334 }
335
336 ADReal normal_dof_residual = std::min(normal_pressure_value, weighted_gap * c);
337 ADReal tangential_dof_residual = tangential_pressure_value;
338
339 // Get index for normal constraint.
340 // We do this to get a decent Jacobian structure, which is key for the use of iterative solvers.
341 // Using old normal vector to avoid changes in the Jacobian structure within one time step
342
343 Real ny, nz;
344 // Intially, use the current normal vector
345 if (_dof_to_old_normal_vector[dof].norm() < TOLERANCE)
346 {
347 ny = _dof_to_normal_vector[dof](1);
348 nz = _dof_to_normal_vector[dof](2);
349 }
350 else
351 {
352 ny = _dof_to_old_normal_vector[dof](1);
353 nz = _dof_to_old_normal_vector[dof](2);
354 }
355 unsigned int component_normal = 0;
356 if (std::abs(ny) > 0.57735)
357 component_normal = 1;
358 else if (std::abs(nz) > 0.57735)
359 component_normal = 2;
360
361 libmesh_ignore(component_normal);
362
364 _assembly,
365 std::array<ADReal, 1>{{normal_dof_residual}},
366 std::array<dof_id_type, 1>{{component_normal == 0
367 ? dof_index_x
368 : (component_normal == 1 ? dof_index_y : dof_index_z)}},
369 component_normal == 0 ? scaling_factor_x
370 : (component_normal == 1 ? scaling_factor_y : scaling_factor_z));
371
373 _assembly,
374 std::array<ADReal, 1>{{tangential_dof_residual}},
375 std::array<dof_id_type, 1>{
376 {(component_normal == 0 || component_normal == 2) ? dof_index_y : dof_index_x}},
377 (component_normal == 0 || component_normal == 2) ? scaling_factor_y : scaling_factor_x);
378
379 if (_has_disp_z)
381 _assembly,
382 std::array<ADReal, 1>{{tangential_pressure_value_dir}},
383 std::array<dof_id_type, 1>{
384 {(component_normal == 0 || component_normal == 1) ? dof_index_z : dof_index_x}},
385 (component_normal == 0 || component_normal == 1) ? scaling_factor_z : scaling_factor_x);
386}
DualNumber< Real, DNDerivativeType, true > ADReal
registerMooseObject("ContactApp", ComputeWeightedGapCartesianLMMechanicalContact)
void mooseError(Args &&... args)
static InputParameters validParams()
std::map< unsigned int, unsigned int > getSecondaryIpToLowerElementMap(const Elem &lower_secondary_elem) const
std::map< unsigned int, unsigned int > getPrimaryIpToLowerElementMap(const Elem &primary_elem, const Elem &primary_elem_ip, const Elem &lower_secondary_elem) const
std::array< MooseUtils::SemidynamicVector< Point, 9 >, 2 > getNodalTangents(const Elem &secondary_elem) const
Computes the weighted gap that will later be used to enforce the zero-penetration mechanical contact ...
virtual void enforceConstraintOnDof(const DofObject *const dof)
Method called from post().
virtual void computeQpIProperties()
Computes properties that are functions both of _qp and _i, for example the weighted gap.
const ADVariableValue & _secondary_disp_x
x-displacement on the secondary face
Real _qp_factor
The value of the LM at the current quadrature point.
const MooseVariable *const _disp_y_var
The y displacement variable.
std::unordered_map< const DofObject *, RealVectorValue > _dof_to_normal_vector
A map from node to normal vector (2D)
std::vector< MooseVariable * > _lm_vars
Cartesian Lagrange multipliers for mechanical contact.
const ADVariableValue & _primary_disp_x
x-displacement on the primary face
const ADReal * _weighted_gap_ptr
A pointer members that can be used to help avoid copying ADReals.
std::unordered_map< const DofObject *, RealVectorValue > _dof_to_old_normal_vector
A map from node to normal vector (2D) - old.
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 ADVariableValue & _primary_disp_y
y-displacement on the primary face
ADRealVectorValue _qp_gap_nodal
Vector for computation of weighted gap with nodal normals.
bool _normalize_c
Whether to normalize weighted gap by weighting function norm.
const MooseVariable *const _disp_x_var
The x displacement variable.
void incorrectEdgeDroppingPost(const std::unordered_set< const Node * > &inactive_lm_nodes) override
Copy of the post routine but that skips assembling inactive nodes.
std::unordered_map< const DofObject *, std::array< RealVectorValue, 2 > > _dof_to_tangent_vectors
A map from node to tangent vector (2D for now)
virtual void computeQpProperties()
Computes properties that are functions only of the current quadrature point (_qp),...
const ADVariableValue & _secondary_disp_y
y-displacement on the secondary face
const bool _nodal
Whether the dof objects are nodal; if they're not, then they're elemental.
const MooseVariable *const _disp_z_var
The z displacement variable.
const bool _has_disp_z
For 2D mortar contact no displacement will be specified, so const pointers used.
unsigned int _qp
unsigned int _i
MooseVariable * getVar(const std::string &var_name, unsigned int comp)
void suppressParameter(const std::string &name)
void addRequiredCoupledVar(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)
void paramError(const std::string &param, Args... args) const
void mooseError(Args &&... args) const
bool isParamValid(const std::string &name) const
const MooseArray< Real > & _coord
const VariableTestValue & _test
Elem const *const & _lower_secondary_elem
const libMesh::QBase *const & _qrule_msm
const AutomaticMortarGeneration & amg() const
Elem const *const & _lower_primary_elem
const MooseArray< Point > & _phys_points_secondary
const MooseArray< Point > & _phys_points_primary
std::vector< Point > _normals
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 std::vector< Real > & _JxW_msm
bool isNodal() const
MooseMesh & _mesh
Assembly & _assembly
SystemBase & _sys
virtual const NumericVector< Number > *const & currentSolution() const=0
unsigned int number() const
void addResidualsAndJacobian(Assembly &assembly, const Residuals &residuals, const Indices &dof_indices, Real scaling_factor)
const Parallel::Communicator & _communicator
processor_id_type processor_id() const
unsigned int n_points() const
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.
void derivInsert(SemiDynamicSparseNumberArray< Real, libMesh::dof_id_type, NWrapper< N > > &derivs, libMesh::dof_id_type index, Real value)