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 params.addClassDescription("Computes the weighted gap that will later be used to enforce the "
51 "zero-penetration mechanical contact conditions");
52 params.suppressParameter<VariableName>("secondary_variable");
53 params.suppressParameter<VariableName>("primary_variable");
54 params.addRequiredCoupledVar("disp_x", "The x displacement variable");
55 params.addRequiredCoupledVar("disp_y", "The y displacement variable");
56 params.addCoupledVar("disp_z", "The z displacement variable");
57 params.addParam<Real>(
58 "c", 1e6, "Parameter for balancing the size of the gap and contact pressure");
59 params.addRequiredCoupledVar("lm_x",
60 "Mechanical contact Lagrange multiplier along the x Cartesian axis");
62 "lm_y", "Mechanical contact Lagrange multiplier along the y Cartesian axis.");
63 params.addCoupledVar("lm_z",
64 "Mechanical contact Lagrange multiplier along the z Cartesian axis.");
65 params.set<bool>("interpolate_normals") = false;
66 params.addParam<bool>(
67 "normalize_c",
68 false,
69 "Whether to normalize c by weighting function norm. When unnormalized "
70 "the value of c effectively depends on element size since in the constraint we compare nodal "
71 "Lagrange Multiplier values to integrated gap values (LM nodal value is independent of "
72 "element size, where integrated values are dependent on element size).");
73 params.set<bool>("use_displaced_mesh") = true;
74 return params;
75}
76
78 const InputParameters & parameters)
79 : ADMortarConstraint(assignVarsInParamsCartesianWeightedGap(parameters)),
80 _secondary_disp_x(adCoupledValue("disp_x")),
81 _primary_disp_x(adCoupledNeighborValue("disp_x")),
82 _secondary_disp_y(adCoupledValue("disp_y")),
83 _primary_disp_y(adCoupledNeighborValue("disp_y")),
84 _has_disp_z(isCoupled("disp_z")),
85 _secondary_disp_z(_has_disp_z ? &adCoupledValue("disp_z") : nullptr),
86 _primary_disp_z(_has_disp_z ? &adCoupledNeighborValue("disp_z") : nullptr),
87 _c(getParam<Real>("c")),
88 _normalize_c(getParam<bool>("normalize_c")),
89 _nodal(getVar("disp_x", 0)->feType().family == LAGRANGE),
90 _disp_x_var(getVar("disp_x", 0)),
91 _disp_y_var(getVar("disp_y", 0)),
92 _disp_z_var(_has_disp_z ? getVar("disp_z", 0) : nullptr)
93{
94 _lm_vars.push_back(getVar("lm_x", 0));
95 _lm_vars.push_back(getVar("lm_y", 0));
96
97 if (isParamValid("lm_z") ^ _has_disp_z)
98 paramError("lm_z",
99 "In three-dimensions, both the Z Lagrange multiplier and the Z displacement need to "
100 "be provided");
101
102 if (_has_disp_z)
103 _lm_vars.push_back(getVar("lm_z", 0));
104
105 if (!getParam<bool>("use_displaced_mesh"))
107 "use_displaced_mesh",
108 "'use_displaced_mesh' must be true for the ComputeWeightedGapLMMechanicalContact object");
109
110 for (const auto i : index_range(_lm_vars))
111 if (!_lm_vars[i]->isNodal())
112 if (_lm_vars[i]->feType().order != static_cast<Order>(0))
113 mooseError("Normal contact constraints only support elemental variables of CONSTANT order");
114}
115
116ADReal
118{
119 mooseError("We should never call computeQpResidual for ComputeWeightedGapLMMechanicalContact");
120}
121
122void
124{
125 // Trim interior node variable derivatives
126 const auto & primary_ip_lowerd_map = amg().getPrimaryIpToLowerElementMap(
128 const auto & secondary_ip_lowerd_map =
130
131 std::array<const MooseVariable *, 3> var_array{{_disp_x_var, _disp_y_var, _disp_z_var}};
132 std::array<ADReal, 3> primary_disp{
133 {_primary_disp_x[_qp], _primary_disp_y[_qp], _has_disp_z ? (*_primary_disp_z)[_qp] : 0}};
134 std::array<ADReal, 3> secondary_disp{{_secondary_disp_x[_qp],
136 _has_disp_z ? (*_secondary_disp_z)[_qp] : 0}};
137
138 trimInteriorNodeDerivatives(primary_ip_lowerd_map, var_array, primary_disp, false);
139 trimInteriorNodeDerivatives(secondary_ip_lowerd_map, var_array, secondary_disp, true);
140
141 const ADReal & prim_x = primary_disp[0];
142 const ADReal & prim_y = primary_disp[1];
143 const ADReal * prim_z = nullptr;
144 if (_has_disp_z)
145 prim_z = &primary_disp[2];
146
147 const ADReal & sec_x = secondary_disp[0];
148 const ADReal & sec_y = secondary_disp[1];
149 const ADReal * sec_z = nullptr;
150 if (_has_disp_z)
151 sec_z = &secondary_disp[2];
152
153 // Compute gap vector
155
156 gap_vec(0).derivatives() = prim_x.derivatives() - sec_x.derivatives();
157 gap_vec(1).derivatives() = prim_y.derivatives() - sec_y.derivatives();
158 if (_has_disp_z)
159 gap_vec(2).derivatives() = prim_z->derivatives() - sec_z->derivatives();
160
161 // Compute integration point quantities
162 _qp_gap_nodal = gap_vec * (_JxW_msm[_qp] * _coord[_qp]);
163
164 // To do normalization of constraint coefficient (c_n)
166}
167
168void
170{
171 mooseAssert(_normals.size() == _lower_secondary_elem->n_nodes(),
172 "Making sure that _normals is the expected size");
173
174 // Get the _dof_to_weighted_gap map
175 const DofObject * dof = _lm_vars[0]->isNodal()
176 ? static_cast<const DofObject *>(_lower_secondary_elem->node_ptr(_i))
177 : static_cast<const DofObject *>(_lower_secondary_elem);
178
180
181 if (_normalize_c)
182 _dof_to_weighted_gap[dof].second += _test[_i][_qp] * _qp_factor;
183}
184
185void
193
194void
201
202void
207
208void
210{
211 if (mortar_type != Moose::MortarType::Lower)
212 return;
213
214 for (const auto i : index_range(_lm_vars))
215 {
216 mooseAssert(_lm_vars[i], "LM variable is null");
217 libmesh_ignore(i);
218 }
219
220 for (_qp = 0; _qp < _qrule_msm->n_points(); _qp++)
221 {
223 for (_i = 0; _i < _test.size(); ++_i)
224 {
226
227 // Get the _dof_to_weighted_gap map
228 const DofObject * dof =
229 _lm_vars[0]->isNodal()
230 ? static_cast<const DofObject *>(_lower_secondary_elem->node_ptr(_i))
231 : static_cast<const DofObject *>(_lower_secondary_elem);
232 // We do not interpolate geometry, so just match the local node _i with the corresponding _i
234 const auto & nodal_tangents = amg().getNodalTangents(*_lower_secondary_elem);
235 _dof_to_tangent_vectors[dof][0] = nodal_tangents[0][_i];
236 _dof_to_tangent_vectors[dof][1] = nodal_tangents[1][_i];
237 }
238 }
239}
240
241void
243{
244 // During "computeResidual" and "computeJacobian" we are actually just computing properties on the
245 // mortar segment element mesh. We are *not* actually assembling into the residual/Jacobian. For
246 // the zero-penetration constraint, the property of interest is the map from node to weighted gap.
247 // Computation of the properties proceeds identically for residual and Jacobian evaluation hence
248 // why we simply call computeResidual here. We will assemble into the residual/Jacobian later from
249 // the post() method
250 computeResidual(mortar_type);
251}
252
253void
255{
258
259 for (const auto & pr : _dof_to_weighted_gap)
260 {
261 if (pr.first->processor_id() != this->processor_id())
262 continue;
263
264 _weighted_gap_ptr = &pr.second.first;
265 _normalization_ptr = &pr.second.second;
266
267 enforceConstraintOnDof(pr.first);
268 }
269}
270
271void
273 const std::unordered_set<const Node *> & inactive_lm_nodes)
274{
277
278 for (const auto & pr : _dof_to_weighted_gap)
279 {
280 if ((inactive_lm_nodes.find(static_cast<const Node *>(pr.first)) != inactive_lm_nodes.end()) ||
281 (pr.first->processor_id() != this->processor_id()))
282 continue;
283
284 _weighted_gap_ptr = &pr.second.first;
285 _normalization_ptr = &pr.second.second;
286
287 enforceConstraintOnDof(pr.first);
288 }
289}
290
291void
293{
294 const auto & weighted_gap = *_weighted_gap_ptr;
295 const Real c = _normalize_c ? _c / *_normalization_ptr : _c;
296
297 const auto dof_index_x = dof->dof_number(_sys.number(), _lm_vars[0]->number(), 0);
298 const auto dof_index_y = dof->dof_number(_sys.number(), _lm_vars[1]->number(), 0);
299 const Real scaling_factor_x = _lm_vars[0]->scalingFactor();
300 const Real scaling_factor_y = _lm_vars[1]->scalingFactor();
301 Real scaling_factor_z = 1;
302
303 ADReal lm_x = (*_sys.currentSolution())(dof_index_x);
304 ADReal lm_y = (*_sys.currentSolution())(dof_index_y);
305
306 Moose::derivInsert(lm_x.derivatives(), dof_index_x, 1.);
307 Moose::derivInsert(lm_y.derivatives(), dof_index_y, 1.);
308
309 dof_id_type dof_index_z(-1);
310 ADReal lm_z;
311 if (_has_disp_z)
312 {
313 dof_index_z = dof->dof_number(_sys.number(), _lm_vars[2]->number(), 0);
314 lm_z = (*_sys.currentSolution())(dof_index_z);
315 Moose::derivInsert(lm_z.derivatives(), dof_index_z, 1.);
316 scaling_factor_z = _lm_vars[2]->scalingFactor();
317 }
318
319 ADReal normal_pressure_value =
320 lm_x * _dof_to_normal_vector[dof](0) + lm_y * _dof_to_normal_vector[dof](1);
321 ADReal tangential_pressure_value =
322 lm_x * _dof_to_tangent_vectors[dof][0](0) + lm_y * _dof_to_tangent_vectors[dof][0](1);
323
324 ADReal tangential_pressure_value_dir;
325
326 if (_has_disp_z)
327 {
328 normal_pressure_value += lm_z * _dof_to_normal_vector[dof](2);
329 tangential_pressure_value += lm_z * _dof_to_tangent_vectors[dof][0](2);
330 tangential_pressure_value_dir = lm_x * _dof_to_tangent_vectors[dof][1](0) +
331 lm_y * _dof_to_tangent_vectors[dof][1](1) +
332 lm_z * _dof_to_tangent_vectors[dof][1](2);
333 }
334
335 ADReal normal_dof_residual = std::min(normal_pressure_value, weighted_gap * c);
336 ADReal tangential_dof_residual = tangential_pressure_value;
337
338 // Get index for normal constraint.
339 // We do this to get a decent Jacobian structure, which is key for the use of iterative solvers.
340 // Using old normal vector to avoid changes in the Jacobian structure within one time step
341
342 Real ny, nz;
343 // Intially, use the current normal vector
344 if (_dof_to_old_normal_vector[dof].norm() < TOLERANCE)
345 {
346 ny = _dof_to_normal_vector[dof](1);
347 nz = _dof_to_normal_vector[dof](2);
348 }
349 else
350 {
351 ny = _dof_to_old_normal_vector[dof](1);
352 nz = _dof_to_old_normal_vector[dof](2);
353 }
354 unsigned int component_normal = 0;
355 if (std::abs(ny) > 0.57735)
356 component_normal = 1;
357 else if (std::abs(nz) > 0.57735)
358 component_normal = 2;
359
360 libmesh_ignore(component_normal);
361
363 _assembly,
364 std::array<ADReal, 1>{{normal_dof_residual}},
365 std::array<dof_id_type, 1>{{component_normal == 0
366 ? dof_index_x
367 : (component_normal == 1 ? dof_index_y : dof_index_z)}},
368 component_normal == 0 ? scaling_factor_x
369 : (component_normal == 1 ? scaling_factor_y : scaling_factor_z));
370
372 _assembly,
373 std::array<ADReal, 1>{{tangential_dof_residual}},
374 std::array<dof_id_type, 1>{
375 {(component_normal == 0 || component_normal == 2) ? dof_index_y : dof_index_x}},
376 (component_normal == 0 || component_normal == 2) ? scaling_factor_y : scaling_factor_x);
377
378 if (_has_disp_z)
380 _assembly,
381 std::array<ADReal, 1>{{tangential_pressure_value_dir}},
382 std::array<dof_id_type, 1>{
383 {(component_normal == 0 || component_normal == 1) ? dof_index_z : dof_index_x}},
384 (component_normal == 0 || component_normal == 1) ? scaling_factor_z : scaling_factor_x);
385}
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)