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