LCOV - code coverage report
Current view: top level - src/constraints - MortarConstraintBase.C (source / functions) Hit Total Coverage
Test: idaholab/moose framework: #32971 (54bef8) with base c6cf66 Lines: 113 134 84.3 %
Date: 2026-05-29 20:35:17 Functions: 6 6 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       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             : 
      10             : #include "MortarConstraintBase.h"
      11             : #include "FEProblemBase.h"
      12             : #include "Assembly.h"
      13             : #include "MooseVariableFE.h"
      14             : 
      15             : #include "libmesh/string_to_enum.h"
      16             : 
      17             : InputParameters
      18       48342 : MortarConstraintBase::validParams()
      19             : {
      20       48342 :   InputParameters params = Constraint::validParams();
      21       48342 :   params += MortarConsumerInterface::validParams();
      22       48342 :   params += TwoMaterialPropertyInterface::validParams();
      23             : 
      24             :   // Whether on a displaced or undisplaced mesh, coupling ghosting will only happen for
      25             :   // cross-interface elements
      26      145026 :   params.addRelationshipManager("AugmentSparsityOnInterface",
      27             :                                 Moose::RelationshipManagerType::COUPLING,
      28           0 :                                 [](const InputParameters & obj_params, InputParameters & rm_params)
      29             :                                 {
      30        3666 :                                   rm_params.set<bool>("use_displaced_mesh") =
      31        3666 :                                       obj_params.get<bool>("use_displaced_mesh");
      32        7332 :                                   rm_params.set<BoundaryName>("secondary_boundary") =
      33        7332 :                                       obj_params.get<BoundaryName>("secondary_boundary");
      34        7332 :                                   rm_params.set<BoundaryName>("primary_boundary") =
      35        7332 :                                       obj_params.get<BoundaryName>("primary_boundary");
      36        7332 :                                   rm_params.set<SubdomainName>("secondary_subdomain") =
      37        7332 :                                       obj_params.get<SubdomainName>("secondary_subdomain");
      38        7332 :                                   rm_params.set<SubdomainName>("primary_subdomain") =
      39        7332 :                                       obj_params.get<SubdomainName>("primary_subdomain");
      40        3666 :                                   rm_params.set<bool>("ghost_higher_d_neighbors") =
      41        3666 :                                       obj_params.get<bool>("ghost_higher_d_neighbors");
      42        3666 :                                 });
      43             : 
      44             :   // If the LM is ever a Lagrange variable, we will attempt to obtain its dof indices from the
      45             :   // process that owns the node. However, in order for the dof indices to get set for the Lagrange
      46             :   // variable, the process that owns the node needs to have local copies of any lower-d elements
      47             :   // that have the connected node. Note that the geometric ghosting done here is different than that
      48             :   // done by the AugmentSparsityOnInterface RM, even when ghost_point_neighbors is true. The latter
      49             :   // ghosts equal-manifold lower-dimensional secondary element point neighbors and their interface
      50             :   // couplings. This ghosts lower-dimensional point neighbors of higher-dimensional elements.
      51             :   // Neither is guaranteed to be a superset of the other. For instance ghosting of lower-d point
      52             :   // neighbors (AugmentSparsityOnInterface with ghost_point_neighbors = true) is only guaranteed to
      53             :   // ghost those lower-d point neighbors on *processes that own lower-d elements*. And you may have
      54             :   // a process that only owns higher-dimensional elements
      55             :   //
      56             :   // Note that in my experience it is only important for the higher-d lower-d point neighbors to be
      57             :   // ghosted when forming sparsity patterns and so I'm putting this here instead of at the
      58             :   // MortarConsumerInterface level
      59      145026 :   params.addRelationshipManager("GhostHigherDLowerDPointNeighbors",
      60             :                                 Moose::RelationshipManagerType::GEOMETRIC);
      61             : 
      62      193368 :   params.addParam<VariableName>("secondary_variable", "Primal variable on secondary surface.");
      63      193368 :   params.addParam<VariableName>(
      64             :       "primary_variable",
      65             :       "Primal variable on primary surface. If this parameter is not provided then the primary "
      66             :       "variable will be initialized to the secondary variable");
      67       96684 :   params.makeParamNotRequired<NonlinearVariableName>("variable");
      68      193368 :   params.setDocString(
      69             :       "variable",
      70             :       "The name of the lagrange multiplier variable that this constraint is applied to. This "
      71             :       "parameter may not be supplied in the case of using penalty methods for example");
      72      145026 :   params.addParam<bool>(
      73       96684 :       "compute_primal_residuals", true, "Whether to compute residuals for the primal variable.");
      74      145026 :   params.addParam<bool>(
      75       96684 :       "compute_lm_residuals", true, "Whether to compute Lagrange Multiplier residuals");
      76      241710 :   params.addDeprecatedParam<MooseEnum>(
      77             :       "quadrature",
      78      241710 :       MooseEnum("DEFAULT FIRST SECOND THIRD FOURTH", "DEFAULT"),
      79             :       "Polynomial basis order (think Variable order) to assume when building quadrature rules to "
      80             :       "use on mortar segments. "
      81             :       "For 2D mortar DEFAULT is recommended. "
      82             :       "For 3D mortar, QUAD meshes are integrated using triangular mortar segments. "
      83             :       "While DEFAULT order is typically sufficiently accurate, exact integration of "
      84             :       "QUAD mortar faces with first order polynomial bases (bilinears) requires building a "
      85             :       "quadrature rule based off of a *quadratic* (SECOND) polynomial basis on triangles. "
      86             :       "Similarly, exact integration of QUAD mortar faces with second order polynomial bases "
      87             :       "(biquadratics) requires building a quadrature rule based off of a *quartic* (FOURTH) "
      88             :       "polynomial basis on triangles. Note that the actual quadrature order will be double this "
      89             :       "parameter plus one.",
      90             :       "This parameter is deprecated in favor of "
      91             :       "'segment_quadrature' which directly specifies the quadrature order.");
      92      145026 :   params.addParam<MooseEnum>(
      93             :       "segment_quadrature",
      94      241710 :       MooseEnum("DEFAULT FIRST SECOND THIRD FOURTH FIFTH SIXTH SEVENTH EIGHTH NINTH", "DEFAULT"),
      95             :       "Mortar segment quadrature order. "
      96             :       "For 2D mortar DEFAULT is recommended. "
      97             :       "For 3D mortar, quad faces are integrated using triangular mortar segments. "
      98             :       "A finite element family of order p based on a quadrilateral element actually has polynomial "
      99             :       "order of 2*p because of the tensor-product nature of the element. Consequently, for exact "
     100             :       "integraton of something like a mass matrix term, if one is using a first order Lagrange "
     101             :       "variable (as an example), the 'segment_quadrature' should be set to 'fourth' because we "
     102             :       "double the polynomial order on the triangle to match the tensor product order on the quad, "
     103             :       "and then double again since we are multiplying the test and trial (shape) function "
     104             :       "polynomials for the mass matrix term.");
     105      145026 :   params.addParam<bool>(
     106             :       "use_petrov_galerkin",
     107       96684 :       false,
     108             :       "Whether to use the Petrov-Galerkin approach for the mortar-based constraints. If set to "
     109             :       "true, we use the standard basis as the test function and dual basis as "
     110             :       "the shape function for the interpolation of the Lagrange multiplier variable.");
     111      145026 :   params.addCoupledVar("aux_lm",
     112             :                        "Auxiliary Lagrange multiplier variable that is utilized together with the "
     113             :                        "Petrov-Galerkin approach.");
     114       48342 :   return params;
     115           0 : }
     116             : 
     117        1224 : MortarConstraintBase::MortarConstraintBase(const InputParameters & parameters)
     118             :   : Constraint(parameters),
     119             :     NeighborCoupleableMooseVariableDependencyIntermediateInterface(this, false, false),
     120             :     MortarConsumerInterface(this),
     121             :     TwoMaterialPropertyInterface(this, Moose::EMPTY_BLOCK_IDS, getBoundaryIDs()),
     122             :     MooseVariableInterface<Real>(this,
     123             :                                  true,
     124        4896 :                                  isParamValid("variable") ? "variable" : "secondary_variable",
     125             :                                  Moose::VarKindType::VAR_SOLVER,
     126             :                                  Moose::VarFieldType::VAR_FIELD_STANDARD),
     127        3672 :     _fe_problem(*getCheckedPointerParam<FEProblemBase *>("_fe_problem_base")),
     128        2448 :     _var(isParamValid("variable")
     129        2550 :              ? &_subproblem.getStandardVariable(_tid, parameters.getMooseType("variable"))
     130             :              : nullptr),
     131        1224 :     _secondary_var(
     132        2448 :         isParamValid("secondary_variable")
     133        4896 :             ? _sys.getActualFieldVariable<Real>(_tid, parameters.getMooseType("secondary_variable"))
     134        1224 :             : _sys.getActualFieldVariable<Real>(_tid, parameters.getMooseType("primary_variable"))),
     135        1224 :     _primary_var(
     136        2448 :         isParamValid("primary_variable")
     137        1224 :             ? _sys.getActualFieldVariable<Real>(_tid, parameters.getMooseType("primary_variable"))
     138             :             : _secondary_var),
     139             : 
     140        2448 :     _compute_primal_residuals(getParam<bool>("compute_primal_residuals")),
     141        1887 :     _compute_lm_residuals(!_var ? false : getParam<bool>("compute_lm_residuals")),
     142        1224 :     _test_dummy(),
     143        1224 :     _use_dual(_var ? _var->useDual() : false),
     144        1224 :     _tangents(_assembly.tangents()),
     145        1224 :     _coord(_assembly.mortarCoordTransformation()),
     146        1224 :     _q_point(_assembly.qPointsMortar()),
     147        2448 :     _use_petrov_galerkin(getParam<bool>("use_petrov_galerkin")),
     148        2472 :     _aux_lm_var(isCoupled("aux_lm") ? getVar("aux_lm", 0) : nullptr),
     149        2448 :     _test(_var
     150        1224 :               ? ((_use_petrov_galerkin && _aux_lm_var) ? _aux_lm_var->phiLower() : _var->phiLower())
     151             :               : _test_dummy),
     152        1224 :     _test_secondary(_secondary_var.phiFace()),
     153        1224 :     _test_primary(_primary_var.phiFaceNeighbor()),
     154        1224 :     _grad_test_secondary(_secondary_var.gradPhiFace()),
     155        1224 :     _grad_test_primary(_primary_var.gradPhiFaceNeighbor()),
     156        1224 :     _interior_secondary_elem(_assembly.elem()),
     157        1224 :     _interior_primary_elem(_assembly.neighbor()),
     158        6120 :     _displaced(getParam<bool>("use_displaced_mesh"))
     159             : {
     160        1224 :   if (_use_dual)
     161         124 :     _assembly.activateDual();
     162             : 
     163        1224 :   if (_use_petrov_galerkin && (!_use_dual))
     164           0 :     paramError("use_petrov_galerkin",
     165             :                "We need to set `use_dual = true` while using the Petrov-Galerkin approach");
     166             : 
     167        1248 :   if (_use_petrov_galerkin && ((!isParamValid("aux_lm")) || _aux_lm_var == nullptr))
     168           0 :     paramError("use_petrov_galerkin",
     169             :                "We need to specify an auxiliary variable `aux_lm` while using the Petrov-Galerkin "
     170             :                "approach");
     171             : 
     172        1224 :   if (_use_petrov_galerkin && _aux_lm_var->useDual())
     173           0 :     paramError("aux_lm",
     174             :                "Auxiliary LM variable needs to use standard shape function, i.e., set `use_dual = "
     175             :                "false`.");
     176        3672 :   if (isParamSetByUser("quadrature") && isParamSetByUser("segment_quadrature"))
     177           0 :     paramError("quadrature", "Only one of 'quadrature' and 'segment_quadrature' should be set.");
     178             : 
     179             :   // Note parameter is discretization order, we then convert to quadrature order
     180        2448 :   const auto & p_order = getParam<MooseEnum>("quadrature");
     181             :   // If quadrature not DEFAULT, set mortar qrule
     182        1224 :   if (p_order != "DEFAULT")
     183             :   {
     184           0 :     const Order q_order = static_cast<Order>(2 * Utility::string_to_enum<Order>(p_order) + 1);
     185           0 :     _assembly.setMortarQRule(q_order);
     186             :   }
     187        2448 :   const auto & q_order_enum = getParam<MooseEnum>("segment_quadrature");
     188        1224 :   if (q_order_enum != "DEFAULT")
     189             :   {
     190           0 :     const Order q_order = Utility::string_to_enum<Order>(q_order_enum);
     191           0 :     _assembly.setMortarQRule(q_order);
     192             :   }
     193             : 
     194        1224 :   if (_var)
     195         663 :     addMooseVariableDependency(_var);
     196        1224 :   addMooseVariableDependency(&_secondary_var);
     197        1224 :   addMooseVariableDependency(&_primary_var);
     198        1224 : }
     199             : 
     200             : void
     201      181890 : MortarConstraintBase::computeResidual()
     202             : {
     203      181890 :   precalculateResidual();
     204             : 
     205      181890 :   if (_compute_primal_residuals)
     206             :   {
     207             :     // Compute the residual for the secondary interior primal dofs
     208      181890 :     computeResidual(Moose::MortarType::Secondary);
     209             : 
     210             :     // Compute the residual for the primary interior primal dofs.
     211      181890 :     computeResidual(Moose::MortarType::Primary);
     212             :   }
     213             : 
     214      181890 :   if (_compute_lm_residuals)
     215             :     // Compute the residual for the lower dimensional LM dofs (if we even have an LM variable)
     216       86258 :     computeResidual(Moose::MortarType::Lower);
     217      181890 : }
     218             : 
     219             : void
     220       88234 : MortarConstraintBase::computeJacobian()
     221             : {
     222       88234 :   precalculateResidual();
     223             : 
     224       88234 :   if (_compute_primal_residuals)
     225             :   {
     226             :     // Compute the jacobian for the secondary interior primal dofs
     227       88234 :     computeJacobian(Moose::MortarType::Secondary);
     228             : 
     229             :     // Compute the jacobian for the primary interior primal dofs.
     230       88234 :     computeJacobian(Moose::MortarType::Primary);
     231             :   }
     232             : 
     233       88234 :   if (_compute_lm_residuals)
     234             :     // Compute the jacobian for the lower dimensional LM dofs (if we even have an LM variable)
     235       47610 :     computeJacobian(Moose::MortarType::Lower);
     236       88234 : }
     237             : 
     238             : void
     239       15070 : MortarConstraintBase::zeroInactiveLMDofs(const std::unordered_set<const Node *> & inactive_lm_nodes,
     240             :                                          const std::unordered_set<const Elem *> & inactive_lm_elems)
     241             : {
     242             :   // If no LM variable has been defined, skip
     243       15070 :   if (!_var)
     244        6274 :     return;
     245             : 
     246        8796 :   const auto sn = _sys.number();
     247        8796 :   const auto vn = _var->number();
     248             : 
     249             :   // If variable is nodal, zero DoFs based on inactive LM nodes
     250        8796 :   if (_var->isNodal())
     251             :   {
     252        6044 :     for (const auto node : inactive_lm_nodes)
     253             :     {
     254             :       // Allow mixed Lagrange orders between primal and LM
     255           0 :       if (!node->n_comp(sn, vn))
     256           0 :         continue;
     257             : 
     258           0 :       const auto dof_index = node->dof_number(sn, vn, 0);
     259             :       // No scaling; this is not physics
     260           0 :       if (_assembly.computingJacobian())
     261           0 :         addJacobianElement(
     262             :             _assembly, /*element_value=*/1, dof_index, dof_index, /*scaling_factor=*/1);
     263           0 :       if (_assembly.computingResidual())
     264             :       {
     265           0 :         const Real lm_value = _var->getNodalValue(*node);
     266           0 :         addResiduals(_assembly,
     267           0 :                      std::array<Real, 1>{{lm_value}},
     268           0 :                      std::array<dof_id_type, 1>{{dof_index}},
     269             :                      /*scaling_factor=*/1);
     270             :       }
     271             :     }
     272             :   }
     273             :   // If variable is elemental, zero based on inactive LM elems
     274             :   else
     275             :   {
     276        3121 :     for (const auto el : inactive_lm_elems)
     277             :     {
     278         369 :       const auto n_comp = el->n_comp(sn, vn);
     279             : 
     280         738 :       for (const auto comp : make_range(n_comp))
     281             :       {
     282         369 :         const auto dof_index = el->dof_number(sn, vn, comp);
     283             :         // No scaling; this is not physics
     284         369 :         if (_assembly.computingJacobian())
     285         123 :           addJacobianElement(
     286             :               _assembly, /*element_value=*/1, dof_index, dof_index, /*scaling_factor=*/1);
     287         369 :         if (_assembly.computingResidual())
     288             :         {
     289         246 :           const Real lm_value = _var->getElementalValue(el, comp);
     290         246 :           addResiduals(_assembly,
     291           0 :                        std::array<Real, 1>{{lm_value}},
     292         246 :                        std::array<dof_id_type, 1>{{dof_index}},
     293             :                        /*scaling_factor=*/1);
     294             :         }
     295             :       }
     296             :     }
     297             :   }
     298             : }

Generated by: LCOV version 1.14