https://mooseframework.inl.gov
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 
28 namespace
29 {
30 const InputParameters &
31 assignVarsInParamsCartesianWeightedGap(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");
61  params.addRequiredCoupledVar(
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"))
106  paramError(
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 
116 ADReal
118 {
119  mooseError("We should never call computeQpResidual for ComputeWeightedGapLMMechanicalContact");
120 }
121 
122 void
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 
168 void
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 
185 void
187 {
189 
190  for (auto & map_pr : _dof_to_normal_vector)
191  _dof_to_old_normal_vector.emplace(map_pr);
192 }
193 
194 void
196 {
197  _dof_to_weighted_gap.clear();
198  _dof_to_normal_vector.clear();
199  _dof_to_tangent_vectors.clear();
200 }
201 
202 void
204 {
205  residualSetup();
206 }
207 
208 void
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 
241 void
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 
253 void
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 
271 void
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 
291 void
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 }
MooseMesh & _mesh
LAGRANGE
virtual const NumericVector< Number > *const & currentSolution() const=0
virtual void computeQpProperties()
Computes properties that are functions only of the current quadrature point (_qp), e.g.
const MooseVariable *const _disp_z_var
The z displacement variable.
Real _qp_factor
The value of the LM at the current quadrature point.
void addResidualsAndJacobian(Assembly &assembly, const Residuals &residuals, const Indices &dof_indices, Real scaling_factor)
void paramError(const std::string &param, Args... args) const
void addParam(const std::string &name, const std::initializer_list< typename T::value_type > &value, const std::string &doc_string)
const bool _has_disp_z
For 2D mortar contact no displacement will be specified, so const pointers used.
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::pair< ADReal, Real > > _dof_to_weighted_gap
A map from node to weighted gap and normalization (if requested)
void mooseError(Args &&... args)
std::map< unsigned int, unsigned int > getPrimaryIpToLowerElementMap(const Elem &primary_elem, const Elem &primary_elem_ip, const Elem &lower_secondary_elem) const
std::vector< std::pair< R1, R2 > > get(const std::string &param1, const std::string &param2) const
registerMooseObject("ContactApp", ComputeWeightedGapCartesianLMMechanicalContact)
const MooseVariable *const _disp_y_var
The y displacement variable.
T & set(const std::string &name, bool quiet_mode=false)
virtual void computeQpIProperties()
Computes properties that are functions both of _qp and _i, for example the weighted gap...
const MooseArray< Point > & _phys_points_primary
Elem const *const & _lower_primary_elem
MooseVariable * getVar(const std::string &var_name, unsigned int comp)
const std::vector< Real > & _JxW_msm
const Parallel::Communicator & _communicator
unsigned int _i
const bool _nodal
Whether the dof objects are nodal; if they&#39;re not, then they&#39;re elemental.
bool _normalize_c
Whether to normalize weighted gap by weighting function norm.
DualNumber< Real, DNDerivativeType, true > ADReal
virtual void computeResidual() override
const libMesh::QBase *const & _qrule_msm
const VariableTestValue & _test
void suppressParameter(const std::string &name)
SystemBase & _sys
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.
Elem const *const & _lower_secondary_elem
void libmesh_ignore(const Args &...)
const AutomaticMortarGeneration & amg() const
std::unordered_map< const DofObject *, std::array< RealVectorValue, 2 > > _dof_to_tangent_vectors
A map from node to tangent vector (2D for now)
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)
virtual void enforceConstraintOnDof(const DofObject *const dof)
Method called from post().
std::vector< Point > _normals
std::array< MooseUtils::SemidynamicVector< Point, 9 >, 2 > getNodalTangents(const Elem &secondary_elem) const
unsigned int n_points() const
std::map< unsigned int, unsigned int > getSecondaryIpToLowerElementMap(const Elem &lower_secondary_elem) const
std::unordered_map< const DofObject *, RealVectorValue > _dof_to_normal_vector
A map from node to normal vector (2D)
unsigned int number() const
const MooseArray< Point > & _phys_points_secondary
auto norm(const T &a) -> decltype(std::abs(a))
Computes the weighted gap that will later be used to enforce the zero-penetration mechanical contact ...
Assembly & _assembly
bool isNodal() const
void addCoupledVar(const std::string &name, const std::string &doc_string)
void addRequiredCoupledVar(const std::string &name, const std::string &doc_string)
std::vector< MooseVariable * > _lm_vars
Cartesian Lagrange multipliers for mechanical contact.
const ADVariableValue & _secondary_disp_y
y-displacement on the secondary face
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
const MooseVariable *const _disp_x_var
The x displacement variable.
ADRealVectorValue _qp_gap_nodal
Vector for computation of weighted gap with nodal normals.
virtual void computeJacobian() override
void mooseError(Args &&... args) const
void addClassDescription(const std::string &doc_string)
static InputParameters validParams()
void derivInsert(SemiDynamicSparseNumberArray< Real, libMesh::dof_id_type, NWrapper< N >> &derivs, libMesh::dof_id_type index, Real value)
const ADVariableValue & _secondary_disp_x
x-displacement on the secondary face
bool isParamValid(const std::string &name) const
const ADVariableValue & _primary_disp_x
x-displacement on the primary face
const MooseArray< Real > & _coord
std::unordered_map< const DofObject *, RealVectorValue > _dof_to_old_normal_vector
A map from node to normal vector (2D) - old.
processor_id_type processor_id() const
unsigned int _qp
const ADVariableValue & _primary_disp_y
y-displacement on the primary face
auto index_range(const T &sizable)
uint8_t dof_id_type
const ADReal * _weighted_gap_ptr
A pointer members that can be used to help avoid copying ADReals.