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 return params;
48}
49
51 : WeightedGapUserObject(parameters),
52 _lm_var(getVar("lm_variable", 0)),
53 _use_petrov_galerkin(getParam<bool>("use_petrov_galerkin")),
54 _aux_lm_var(isCoupled("aux_lm") ? getVar("aux_lm", 0) : nullptr),
55 _use_nodal_scaling(getParam<bool>("use_nodal_scaling"))
56{
57 checkInput(_lm_var, "lm_variable");
58 verifyLagrange(*_lm_var, "lm_variable");
59
60 if (_use_petrov_galerkin && ((!isParamValid("aux_lm")) || _aux_lm_var == nullptr))
61 paramError("use_petrov_galerkin",
62 "We need to specify an auxiliary variable `aux_lm` while using the Petrov-Galerkin "
63 "approach");
64
66 paramError("aux_lm",
67 "Auxiliary LM variable needs to use standard shape function, i.e., set `use_dual = "
68 "false`.");
69
71 {
72 // kappa_j's denominator int_e N_j is zero at TRI6 and TET10 vertices and negative at QUAD8 and
73 // HEX20 corners, and its numerator has to be integrated with the same basis, so every variable
74 // entering kappa_j has to be first order.
75 if (_lm_var->feType().order != FIRST || _disp_x_var->feType().order != FIRST ||
77 paramError("use_nodal_scaling",
78 "Node-based scaling is only implemented for first-order Lagrange multiplier and "
79 "displacement variables.");
80
81 // Scaling rescales the partially covered multiplier DOFs that the default treatment zeroes.
82 if (!getParam<bool>("correct_edge_dropping"))
83 paramError("use_nodal_scaling",
84 "Node-based scaling requires 'correct_edge_dropping = true'.");
85
86 // Built once here rather than per element in fullNodalIntegrals().
87 const FEType fe_type(FIRST, LAGRANGE);
88 const auto lower_dim = _subproblem.mesh().dimension() - 1;
89 _nodal_scaling_fe = FEBase::build(lower_dim, fe_type);
90 _nodal_scaling_qrule = std::make_unique<QGauss>(lower_dim, fe_type.default_quadrature_order());
91 _nodal_scaling_fe->attach_quadrature_rule(_nodal_scaling_qrule.get());
92 }
93}
94
95void
97 const std::string & var_param_name) const
98{
99 if (isCoupledConstant(var_param_name))
100 paramError(var_param_name,
101 "The Lagrange multiplier variable must be an actual variable and not a constant.");
102 else if (!var)
103 paramError(var_param_name,
104 "The Lagrange multiplier variables must be provided and be actual variables.");
105}
106
107void
109 const std::string & var_param_name) const
110{
111 if (var.feType().family != LAGRANGE)
112 paramError(var_param_name, "The Lagrange multiplier variables must be of Lagrange type");
113}
114
115const VariableTestValue &
120
121const ADVariableValue &
126
127void
133
134void
140
141void
147
148void
160
161void
163{
165 return;
166
167 const auto * const dof = static_cast<const DofObject *>(_lower_secondary_elem->node_ptr(_i));
168
169 // Numerator of kappa_j (Popp 2013 eq. 36): covered fraction (int_{e_int} N_j)/(int_e N_j) summed
170 // over adjacent elements. Both use the standard N_j (fePhiLower), not the dual test, so kappa_j
171 // is 1 at full coverage in every coordinate system.
172 const auto & std_phi = _assembly.fePhiLower<Real>(_disp_x_var->feType());
173 const auto & full_integrals = fullNodalIntegrals(_lower_secondary_elem);
174 _dof_to_covered_fraction_sum[dof] += std_phi[_i][_qp] * _qp_factor / full_integrals[_i];
175
176 // Coverage-independent divisor for `normalize_c` (see normalizeCDivisor()): sum the full-element
177 // integral int_e N_j once per distinct adjacent element, not once per quadrature point, since it
178 // does not depend on qp and multiple mortar segments may cover the same element.
179 if (_full_normalization_elems[dof].insert(_lower_secondary_elem->id()).second)
180 _dof_to_full_normalization[dof] += full_integrals[_i];
181}
182
183void
185{
187 return;
188
189 // Reduce the processor-local numerators (the thread sums only rank-owned elements).
190 // send_data_back = true: non-owner ranks also need kappa_j for the primary-side coupling.
193 _nodal,
195 /*send_data_back=*/true);
196
197 // Same reduction for the normalize_c divisor sum; also needed on non-owner ranks.
200 _nodal,
202 /*send_data_back=*/true);
203
204 // Divide by n_j^e (adjacent-element count, including fully dropped neighbors) for the eq. 36
205 // mean; the fully ghosted mortar interface makes that count global on every process.
206 const auto & nodes_to_secondary_elem = amg().nodesToSecondaryElem();
207 for (const auto & [dof, fraction_sum] : _dof_to_covered_fraction_sum)
209 fraction_sum / libmesh_map_find(nodes_to_secondary_elem, dof->id()).size();
210}
211
212const std::vector<Real> &
214{
215 const auto it = _elem_to_full_nodal_integral.find(elem->id());
216 if (it != _elem_to_full_nodal_integral.end())
217 return it->second;
218
219 // A separate finite element avoids reinit of the shared mortar-segment state.
220 const std::vector<Real> & JxW = _nodal_scaling_fe->get_JxW();
221 const std::vector<Point> & q_points = _nodal_scaling_fe->get_xyz();
222 const std::vector<std::vector<Real>> & phi = _nodal_scaling_fe->get_phi();
223 _nodal_scaling_fe->reinit(elem);
224
225 std::vector<Real> integrals(phi.size(), 0);
226 for (const auto qp : make_range(_nodal_scaling_qrule->n_points()))
227 {
228 Real coord;
229 coordTransformFactor(_subproblem, elem->subdomain_id(), q_points[qp], coord);
230 for (const auto j : index_range(integrals))
231 integrals[j] += phi[j][qp] * JxW[qp] * coord;
232 }
233
234 return _elem_to_full_nodal_integral.emplace(elem->id(), std::move(integrals)).first->second;
235}
236
237void
239{
241 return;
242
243 // The stored multiplier is scaled (zhat_j = kappa_j lambda_j); interpolate the physical pressure
244 // sum_j Phi_j (zhat_j/kappa_j) for the coupling (Popp 2013 eq. 39), cached once per segment.
245 // Phi_j is Real, so the multiplier derivatives are seeded exactly.
246 const auto & phi = _lm_var->phiLower();
247 const Elem * const lower_elem = _assembly.lowerDElem();
248 const auto sys_num = _lm_var->sys().number();
249 const auto var_num = _lm_var->number();
250 const auto & current_solution = *_lm_var->sys().currentSolution();
251
252 mooseAssert(phi.size(), "The Lagrange multiplier should have lower-dimensional shape functions");
253 const std::size_t n_qp = phi[0].size();
254 _scaled_contact_pressure.resize(n_qp);
255 for (const auto qp : make_range(n_qp))
257
258 // Loop over the multiplier's shape functions, which on a second-order mesh are fewer than the
259 // element's nodes
260 for (const auto j : index_range(phi))
261 {
262 const Node * const node = lower_elem->node_ptr(j);
263 const auto dof_index = node->dof_number(sys_num, var_num, /*component=*/0);
264 ADReal lm_value = current_solution(dof_index);
265 Moose::derivInsert(lm_value.derivatives(), dof_index, 1.);
266 const ADReal physical_pressure = lm_value / nodalScale(node);
267 for (const auto qp : make_range(n_qp))
268 _scaled_contact_pressure[qp] += phi[j][qp] * physical_pressure;
269 }
270}
271
272Real
274{
275 const auto sys_num = _lm_var->sys().number();
276 const auto var_num = _lm_var->number();
277 if (!node->n_dofs(sys_num, var_num))
278 mooseError("No degrees of freedom for the Lagrange multiplier at the node. If this is being "
279 "called from an aux kernel make sure that your aux variable has the same order as "
280 "your Lagrange multiplier");
281
282 const auto dof_number = node->dof_number(sys_num, var_num, /*component=*/0);
283 // Recover the physical pressure lambda_j = zhat_j / kappa_j (Popp 2013 eq. 39). nodalScale() is
284 // keyed by the displaced-mesh node pointer, and callers may pass a different pointer of the same
285 // id, so map through the mesh to match -- exactly as getNormalGap() does above.
286 return (*_lm_var->sys().currentSolution())(dof_number) /
287 nodalScale(_subproblem.mesh().nodePtr(node->id()));
288}
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 Elem *const & lowerDElem() const
const std::unordered_map< dof_id_type, std::vector< const Elem * > > & nodesToSecondaryElem() const
virtual bool isCoupledConstant(const std::string &var_name) const
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 reinit() override
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
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.
ADVariableValue _scaled_contact_pressure
Physical contact pressure sum_j Phi_j (zhat_j / kappa_j) at the segment quadrature points when node-b...
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 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
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.
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)
void derivInsert(SemiDynamicSparseNumberArray< Real, libMesh::dof_id_type, NWrapper< N > > &derivs, libMesh::dof_id_type index, Real value)