https://mooseframework.inl.gov
Loading...
Searching...
No Matches
LMWeightedGapUserObject.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 "MooseVariableFE.h"
12#include "SystemBase.h"
13#include "MortarContactUtils.h"
15
16#include "libmesh/fe.h"
17
19
22{
23 auto params = emptyInputParameters();
24 params.addRequiredCoupledVar(
25 "lm_variable", "The Lagrange multiplier variable representing the contact pressure.");
26 params.addParam<bool>(
27 "use_petrov_galerkin", false, "Whether to use the Petrov-Galerkin approach.");
28 params.addCoupledVar("aux_lm",
29 "Auxiliary Lagrange multiplier variable that is utilized together with the "
30 "Petrov-Galerkin approach.");
31 params.addParam<bool>(
32 "use_nodal_scaling",
33 false,
34 "Whether to apply the node-based Lagrange-multiplier scaling of Popp et al. (2013) to "
35 "improve the conditioning of the linear system when secondary elements are only partially "
36 "covered (edge dropping). See the documentation for the current limitations.");
37 return params;
38}
39
42{
45 "Provides the mortar normal Lagrange multiplier for constraint enforcement.");
47 params.suppressParameter<bool>("ghost_point_neighbors");
49 return params;
50}
51
53 : WeightedGapUserObject(parameters),
54 _lm_var(getVar("lm_variable", 0)),
55 _use_petrov_galerkin(getParam<bool>("use_petrov_galerkin")),
56 _aux_lm_var(isCoupled("aux_lm") ? getVar("aux_lm", 0) : nullptr),
57 _use_nodal_scaling(getParam<bool>("use_nodal_scaling"))
58{
59 checkInput(_lm_var, "lm_variable");
60 verifyLagrange(*_lm_var, "lm_variable");
61
62 if (_use_petrov_galerkin && ((!isParamValid("aux_lm")) || _aux_lm_var == nullptr))
63 paramError("use_petrov_galerkin",
64 "We need to specify an auxiliary variable `aux_lm` while using the Petrov-Galerkin "
65 "approach");
66
68 paramError("aux_lm",
69 "Auxiliary LM variable needs to use standard shape function, i.e., set `use_dual = "
70 "false`.");
71
73 {
74 // kappa_j's denominator int_e N_j is zero at TRI6 and TET10 vertices and negative at QUAD8 and
75 // HEX20 corners, and its numerator has to be integrated with the same basis, so every variable
76 // entering kappa_j has to be first order.
77 if (_lm_var->feType().order != FIRST || _disp_x_var->feType().order != FIRST ||
79 paramError("use_nodal_scaling",
80 "Node-based scaling is only implemented for first-order Lagrange multiplier and "
81 "displacement variables.");
82
83 // Scaling rescales the partially covered multiplier DOFs that the default treatment zeroes.
84 if (!getParam<bool>("correct_edge_dropping"))
85 paramError("use_nodal_scaling",
86 "Node-based scaling requires 'correct_edge_dropping = true'.");
87
88 // This FE object is shared by all fullNodalIntegrals() evaluations.
89 const FEType fe_type(FIRST, LAGRANGE);
90 const auto lower_dim = _subproblem.mesh().dimension() - 1;
91 _nodal_scaling_fe = FEBase::build(lower_dim, fe_type);
92 _nodal_scaling_qrule = std::make_unique<QGauss>(lower_dim, fe_type.default_quadrature_order());
93 _nodal_scaling_fe->attach_quadrature_rule(_nodal_scaling_qrule.get());
94 }
95}
96
97void
99 const std::string & var_param_name) const
100{
101 if (isCoupledConstant(var_param_name))
102 paramError(var_param_name,
103 "The Lagrange multiplier variable must be an actual variable and not a constant.");
104 else if (!var)
105 paramError(var_param_name,
106 "The Lagrange multiplier variables must be provided and be actual variables.");
107}
108
109void
111 const std::string & var_param_name) const
112{
113 if (var.feType().family != LAGRANGE)
114 paramError(var_param_name, "The Lagrange multiplier variables must be of Lagrange type");
115}
116
117void
119 const std::string & var_param_name,
120 const MooseVariable & reference_var,
121 const std::string & reference_var_param_name) const
122{
123 verifyLagrange(var, var_param_name);
124
125 if (var.feType() != reference_var.feType())
126 paramError(var_param_name,
127 "'",
128 var_param_name,
129 "' must use the same finite element type (order and family) as '",
130 reference_var_param_name,
131 "'.");
132
133 if (var.useDual() != reference_var.useDual())
134 paramError(var_param_name,
135 "'",
136 var_param_name,
137 "' must use the same dual/standard basis setting as '",
138 reference_var_param_name,
139 "'.");
140}
141
142const VariableTestValue &
147
148const ADVariableValue &
150{
151 // Under node-based scaling, the stored multiplier is zhat_j = kappa_j*lambda_j.
152 // nodalContactPressure() and getNormalContactPressure() return the physical pressure lambda_j.
153 return _lm_var->adSlnLower();
154}
155
156void
162
163void
169
170void
176
177void
189
190void
192{
194 return;
195
196 const auto * const dof = static_cast<const DofObject *>(_lower_secondary_elem->node_ptr(_i));
197
198 // Numerator of kappa_j (Popp 2013 eq. 36): covered fraction (int_{e_int} N_j)/(int_e N_j) summed
199 // over adjacent elements. Both use the standard N_j (fePhiLower), not the dual test, so kappa_j
200 // is 1 at full coverage in every coordinate system.
201 const auto & std_phi = _assembly.fePhiLower<Real>(_disp_x_var->feType());
202 const auto & full_integrals = fullNodalIntegrals(_lower_secondary_elem);
203 _dof_to_covered_fraction_sum[dof] += std_phi[_i][_qp] * _qp_factor / full_integrals[_i];
204
205 // Coverage-independent divisor for `normalize_c` (see normalizeCDivisor()): sum the full-element
206 // integral int_e N_j once per distinct adjacent element, not once per quadrature point, since it
207 // does not depend on qp and multiple mortar segments may cover the same element.
208 if (_full_normalization_elems[dof].insert(_lower_secondary_elem->id()).second)
209 _dof_to_full_normalization[dof] += full_integrals[_i];
210}
211
212void
214{
216 return;
217
218 // Reduce the processor-local numerators (the thread sums only rank-owned elements).
219 // send_data_back = true: non-owner ranks also need kappa_j for the primary-side coupling.
222 _nodal,
224 /*send_data_back=*/true);
225
226 // Same reduction for the normalize_c divisor sum; also needed on non-owner ranks.
229 _nodal,
231 /*send_data_back=*/true);
232
233 // Divide by n_j^e (adjacent-element count, including fully dropped neighbors) for the eq. 36
234 // mean; the fully ghosted mortar interface makes that count global on every process.
235 const auto & nodes_to_secondary_elem = amg().nodesToSecondaryElem();
236 for (const auto & [dof, fraction_sum] : _dof_to_covered_fraction_sum)
238 fraction_sum / libmesh_map_find(nodes_to_secondary_elem, dof->id()).size();
239}
240
241const std::vector<Real> &
243{
244 const auto it = _elem_to_full_nodal_integral.find(elem->id());
245 if (it != _elem_to_full_nodal_integral.end())
246 return it->second;
247
248 // A separate finite element avoids reinit of the shared mortar-segment state.
249 const std::vector<Real> & JxW = _nodal_scaling_fe->get_JxW();
250 const std::vector<Point> & q_points = _nodal_scaling_fe->get_xyz();
251 const std::vector<std::vector<Real>> & phi = _nodal_scaling_fe->get_phi();
252 _nodal_scaling_fe->reinit(elem);
253
254 std::vector<Real> integrals(phi.size(), 0);
255 for (const auto qp : make_range(_nodal_scaling_qrule->n_points()))
256 {
257 Real coord;
258 coordTransformFactor(_subproblem, elem->subdomain_id(), q_points[qp], coord);
259 for (const auto j : index_range(integrals))
260 integrals[j] += phi[j][qp] * JxW[qp] * coord;
261 }
262
263 return _elem_to_full_nodal_integral.emplace(elem->id(), std::move(integrals)).first->second;
264}
265
266const VariableTestValue &
271
272ADReal
274{
275 const auto sys_num = _lm_var->sys().number();
276 const auto var_num = _lm_var->number();
277 mooseAssert(node.n_dofs(sys_num, var_num),
278 "The Lagrange multiplier must have a degree of freedom at this secondary node.");
279
280 // Seed the lower-dimensional nodal Lagrange multiplier derivative explicitly.
281 const auto dof_index = node.dof_number(sys_num, var_num, 0);
282 ADReal nodal_pressure = (*_lm_var->sys().currentSolution())(dof_index);
284 Moose::derivInsert(nodal_pressure.derivatives(), dof_index, 1.);
285 // Recover the physical pressure lambda_j = zhat_j / kappa_j (Popp 2013 eq. 39). nodalScale() is
286 // keyed by displaced-mesh node pointers, so resolve the supplied node id through that mesh.
287 return nodal_pressure / nodalScale(_subproblem.mesh().nodePtr(node.id()));
288}
289
290Real
292{
293 const auto sys_num = _lm_var->sys().number();
294 const auto var_num = _lm_var->number();
295 if (!node->n_dofs(sys_num, var_num))
296 mooseError("No degrees of freedom for the Lagrange multiplier at the node. If this is being "
297 "called from an aux kernel make sure that your aux variable has the same order as "
298 "your Lagrange multiplier");
299
300 const auto dof_number = node->dof_number(sys_num, var_num, /*component=*/0);
301 // Recover the physical pressure lambda_j = zhat_j / kappa_j (Popp 2013 eq. 39). nodalScale() is
302 // keyed by displaced-mesh node pointers, so resolve the supplied node id through that mesh.
303 return (*_lm_var->sys().currentSolution())(dof_number) /
304 nodalScale(_subproblem.mesh().nodePtr(node->id()));
305}
DualNumber< Real, DNDerivativeType, true > ADReal
void coordTransformFactor(const SubProblem &s, SubdomainID sub_id, const P &point, C &factor, SubdomainID neighbor_sub_id=libMesh::Elem::invalid_subdomain_id)
InputParameters emptyInputParameters()
registerMooseObject("ContactApp", LMWeightedGapUserObject)
const OutputTools< OutputType >::VariablePhiValue & fePhiLower(FEType type) const
const std::unordered_map< dof_id_type, std::vector< const Elem * > > & nodesToSecondaryElem() const
virtual bool isCoupledConstant(const std::string &var_name) const
void suppressParameter(const std::string &name)
void addClassDescription(const std::string &doc_string)
User object for computing weighted gaps and contact pressure for Lagrange multipler based mortar cons...
const std::vector< Real > & fullNodalIntegrals(const Elem *elem)
Full coordinate-weighted integral int_e N_j per local node on the secondary lower-dimensional element...
void initializeNodalScaling()
The node-based scaling steps, kept out of initialize()/finalize()/computeQpIProperties() so that a cl...
std::unordered_map< const DofObject *, std::unordered_set< dof_id_type > > _full_normalization_elems
Elements already folded into _dof_to_full_normalization for a given node, to avoid double counting wh...
std::unordered_map< const DofObject *, Real > _dof_to_full_normalization
Per-node sum, over each distinct adjacent secondary element, of the full-element integral int_e N_j (...
const bool _use_nodal_scaling
Whether to apply the Popp et al.
virtual void initialize() override
virtual Real nodalScale(const DofObject *const dof) const override
Node-based Lagrange-multiplier scaling factor kappa_j of Popp et al.
virtual void finalize() override
LMWeightedGapUserObject(const InputParameters &parameters)
std::unordered_map< dof_id_type, std::vector< Real > > _elem_to_full_nodal_integral
Cache of the per-node full integrals int_e N_j (see fullNodalIntegrals()), keyed by element id; clear...
static InputParameters validParams()
const MooseVariable *const _aux_lm_var
The auxiliary Lagrange multiplier variable (used together whith the Petrov-Galerkin approach)
virtual const ADVariableValue & contactPressure() const override
virtual const VariableTestValue & tractionBasis() const override
The traction stays on the Lagrange multiplier's own basis, which is the basis adSlnLower() uses.
std::unique_ptr< libMesh::FEBase > _nodal_scaling_fe
Finite element and quadrature rule used to evaluate fullNodalIntegrals()
const bool _use_petrov_galerkin
Whether to use Petrov-Galerkin approach.
void verifyLagrange(const MooseVariable &var, const std::string &var_name) const
Verify that the provided variables have degrees of freedom at nodes.
std::unordered_map< const DofObject *, Real > _dof_to_covered_fraction_sum
Per-node numerator of kappa_j (Popp 2013 eq.
void checkInput(const MooseVariable *const var, const std::string &var_name) const
Check user input validity for provided variable.
virtual void computeQpIProperties() override
Computes properties that are functions both of _qp and _i, for example the weighted gap.
std::unique_ptr< libMesh::QGauss > _nodal_scaling_qrule
virtual ADReal nodalContactPressure(const Node &node) const override
virtual Real getNormalContactPressure(const Node *const) const override
virtual const VariableTestValue & test() const override
const MooseVariableFE< Real > *const _lm_var
The Lagrange multiplier variable representing the contact pressure.
std::unordered_map< const DofObject *, Real > _dof_to_nodal_scale
A map from node to its node-based scaling factor kappa_j (see nodalScale())
static InputParameters newParams()
New parameters that this sub-class introduces.
void paramError(const std::string &param, Args... args) const
void mooseError(Args &&... args) const
bool isParamValid(const std::string &name) const
virtual unsigned int dimension() const
virtual const Node * nodePtr(const dof_id_type i) const
const libMesh::FEType & feType() const
bool useDual() const
SystemBase & sys()
unsigned int number() const
const ADTemplateVariableValue< OutputType > & adSlnLower() const
virtual const FieldVariablePhiValue & phiLower() const override
Elem const *const & _lower_secondary_elem
const AutomaticMortarGeneration & amg() const
virtual MooseMesh & mesh()=0
virtual const NumericVector< Number > *const & currentSolution() const=0
unsigned int number() const
SubProblem & _subproblem
Assembly & _assembly
SystemBase & _sys
Creates dof object to weighted gap map.
static InputParameters validParams()
unsigned int _i
Test function index.
const MooseVariable *const _disp_x_var
The x displacement variable.
virtual void finalize() override
unsigned int _qp
Quadrature point index for the mortar segments.
Real _qp_factor
The value of the LM at the current quadrature point.
static void addNodalNormalCoupling(InputParameters &params)
Request the secondary face point-neighbor matrix coupling that nodal-normal derivatives need.
virtual void computeQpIProperties()
Computes properties that are functions both of _qp and _i, for example the weighted gap.
virtual void initialize() override
const bool _nodal
Whether the dof objects are nodal; if they're not, then they're elemental.
OrderWrapper order
const Parallel::Communicator & _communicator
VariableShapeValue< true > VariableTestValue
VariableValueTempl< true > ADVariableValue
void communicateRealObject(std::unordered_map< const DofObject *, T > &dof_to_adreal, const MooseMesh &mesh, const bool nodal, const Parallel::Communicator &communicator, const bool send_data_back)
bool doDerivatives(const SubProblem &subproblem, const SystemBase &sys)
void derivInsert(SemiDynamicSparseNumberArray< Real, libMesh::dof_id_type, NWrapper< N > > &derivs, libMesh::dof_id_type index, Real value)