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
72 // The Contact action couples aux_lm only to this object, not to the constraint
75
77 {
78 // kappa_j's denominator int_e N_j is zero at TRI6 and TET10 vertices and negative at QUAD8 and
79 // HEX20 corners, and its numerator has to be integrated with the same basis, so every variable
80 // entering kappa_j has to be first order.
81 if (_lm_var->feType().order != FIRST || _disp_x_var->feType().order != FIRST ||
83 paramError("use_nodal_scaling",
84 "Node-based scaling is only implemented for first-order Lagrange multiplier and "
85 "displacement variables.");
86
87 // Scaling rescales the partially covered multiplier DOFs that the default treatment zeroes.
88 if (!getParam<bool>("correct_edge_dropping"))
89 paramError("use_nodal_scaling",
90 "Node-based scaling requires 'correct_edge_dropping = true'.");
91
92 // This FE object is shared by all fullNodalIntegrals() evaluations.
93 const FEType fe_type(FIRST, LAGRANGE);
94 const auto lower_dim = _subproblem.mesh().dimension() - 1;
95 _nodal_scaling_fe = FEBase::build(lower_dim, fe_type);
96 _nodal_scaling_qrule = std::make_unique<QGauss>(lower_dim, fe_type.default_quadrature_order());
97 _nodal_scaling_fe->attach_quadrature_rule(_nodal_scaling_qrule.get());
98 }
99}
100
101void
103 const std::string & var_param_name) const
104{
105 if (isCoupledConstant(var_param_name))
106 paramError(var_param_name,
107 "The Lagrange multiplier variable must be an actual variable and not a constant.");
108 else if (!var)
109 paramError(var_param_name,
110 "The Lagrange multiplier variables must be provided and be actual variables.");
111}
112
113void
115 const std::string & var_param_name) const
116{
117 if (var.feType().family != LAGRANGE)
118 paramError(var_param_name, "The Lagrange multiplier variables must be of Lagrange type");
119}
120
121void
123 const std::string & var_param_name,
124 const MooseVariable & reference_var,
125 const std::string & reference_var_param_name) const
126{
127 verifyLagrange(var, var_param_name);
128
129 if (var.feType() != reference_var.feType())
130 paramError(var_param_name,
131 "'",
132 var_param_name,
133 "' must use the same finite element type (order and family) as '",
134 reference_var_param_name,
135 "'.");
136
137 if (var.useDual() != reference_var.useDual())
138 paramError(var_param_name,
139 "'",
140 var_param_name,
141 "' must use the same dual/standard basis setting as '",
142 reference_var_param_name,
143 "'.");
144}
145
146const VariableTestValue &
151
152const ADVariableValue &
154{
155 // Under node-based scaling, the stored multiplier is zhat_j = kappa_j*lambda_j.
156 // nodalContactPressure() and getNormalContactPressure() return the physical pressure lambda_j.
157 return _lm_var->adSlnLower();
158}
159
160void
166
167void
173
174void
180
181void
193
194void
196{
198 return;
199
200 const auto * const dof = static_cast<const DofObject *>(_lower_secondary_elem->node_ptr(_i));
201
202 // Numerator of kappa_j (Popp 2013 eq. 36): covered fraction (int_{e_int} N_j)/(int_e N_j) summed
203 // over adjacent elements. Both use the standard N_j (fePhiLower), not the dual test, so kappa_j
204 // is 1 at full coverage in every coordinate system.
205 const auto & std_phi = _assembly.fePhiLower<Real>(_disp_x_var->feType());
206 const auto & full_integrals = fullNodalIntegrals(_lower_secondary_elem);
207 _dof_to_covered_fraction_sum[dof] += std_phi[_i][_qp] * _qp_factor / full_integrals[_i];
208
209 // Coverage-independent divisor for `normalize_c` (see normalizeCDivisor()): sum the full-element
210 // integral int_e N_j once per distinct adjacent element, not once per quadrature point, since it
211 // does not depend on qp and multiple mortar segments may cover the same element.
212 if (_full_normalization_elems[dof].insert(_lower_secondary_elem->id()).second)
213 _dof_to_full_normalization[dof] += full_integrals[_i];
214}
215
216void
218{
220 return;
221
222 // Reduce the processor-local numerators (the thread sums only rank-owned elements).
223 // send_data_back = true: non-owner ranks also need kappa_j for the primary-side coupling.
226 _nodal,
228 /*send_data_back=*/true);
229
230 // Same reduction for the normalize_c divisor sum; also needed on non-owner ranks.
233 _nodal,
235 /*send_data_back=*/true);
236
237 // Divide by n_j^e (adjacent-element count, including fully dropped neighbors) for the eq. 36
238 // mean; the fully ghosted mortar interface makes that count global on every process.
239 const auto & nodes_to_secondary_elem = amg().nodesToSecondaryElem();
240 for (const auto & [dof, fraction_sum] : _dof_to_covered_fraction_sum)
242 fraction_sum / libmesh_map_find(nodes_to_secondary_elem, dof->id()).size();
243}
244
245const std::vector<Real> &
247{
248 const auto it = _elem_to_full_nodal_integral.find(elem->id());
249 if (it != _elem_to_full_nodal_integral.end())
250 return it->second;
251
252 // A separate finite element avoids reinit of the shared mortar-segment state.
253 const std::vector<Real> & JxW = _nodal_scaling_fe->get_JxW();
254 const std::vector<Point> & q_points = _nodal_scaling_fe->get_xyz();
255 const std::vector<std::vector<Real>> & phi = _nodal_scaling_fe->get_phi();
256 _nodal_scaling_fe->reinit(elem);
257
258 std::vector<Real> integrals(phi.size(), 0);
259 for (const auto qp : make_range(_nodal_scaling_qrule->n_points()))
260 {
261 Real coord;
262 coordTransformFactor(_subproblem, elem->subdomain_id(), q_points[qp], coord);
263 for (const auto j : index_range(integrals))
264 integrals[j] += phi[j][qp] * JxW[qp] * coord;
265 }
266
267 return _elem_to_full_nodal_integral.emplace(elem->id(), std::move(integrals)).first->second;
268}
269
270const VariableTestValue &
275
276ADReal
278{
279 const auto sys_num = _lm_var->sys().number();
280 const auto var_num = _lm_var->number();
281 mooseAssert(node.n_dofs(sys_num, var_num),
282 "The Lagrange multiplier must have a degree of freedom at this secondary node.");
283
284 // Seed the lower-dimensional nodal Lagrange multiplier derivative explicitly.
285 const auto dof_index = node.dof_number(sys_num, var_num, 0);
286 ADReal nodal_pressure = (*_lm_var->sys().currentSolution())(dof_index);
288 Moose::derivInsert(nodal_pressure.derivatives(), dof_index, 1.);
289 // Recover the physical pressure lambda_j = zhat_j / kappa_j (Popp 2013 eq. 39). nodalScale() is
290 // keyed by displaced-mesh node pointers, so resolve the supplied node id through that mesh.
291 return nodal_pressure / nodalScale(_subproblem.mesh().nodePtr(node.id()));
292}
293
294Real
296{
297 const auto sys_num = _lm_var->sys().number();
298 const auto var_num = _lm_var->number();
299 if (!node->n_dofs(sys_num, var_num))
300 mooseError("No degrees of freedom for the Lagrange multiplier at the node. If this is being "
301 "called from an aux kernel make sure that your aux variable has the same order as "
302 "your Lagrange multiplier");
303
304 const auto dof_number = node->dof_number(sys_num, var_num, /*component=*/0);
305 // Recover the physical pressure lambda_j = zhat_j / kappa_j (Popp 2013 eq. 39). nodalScale() is
306 // keyed by displaced-mesh node pointers, so resolve the supplied node id through that mesh.
307 return (*_lm_var->sys().currentSolution())(dof_number) /
308 nodalScale(_subproblem.mesh().nodePtr(node->id()));
309}
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
void checkPetrovGalerkinQuadraticFaces(const MooseObject &moose_object, const MooseVariableFieldBase &lm_var) 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)