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

Generated by: LCOV version 1.14