LCOV - code coverage report
Current view: top level - src/interfacekernels - InterfaceKernel.C (source / functions) Hit Total Coverage
Test: idaholab/moose framework: #32971 (54bef8) with base c6cf66 Lines: 175 222 78.8 %
Date: 2026-05-29 20:35:17 Functions: 16 20 80.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 "InterfaceKernel.h"
      11             : 
      12             : // MOOSE includes
      13             : #include "Assembly.h"
      14             : #include "MooseVariableFE.h"
      15             : #include "SystemBase.h"
      16             : #include "AuxiliarySystem.h"
      17             : #include "FEProblemBase.h"
      18             : 
      19             : #include "libmesh/quadrature.h"
      20             : 
      21             : template <typename T>
      22             : InputParameters
      23       22856 : InterfaceKernelTempl<T>::validParams()
      24             : {
      25       22856 :   InputParameters params = InterfaceKernelBase::validParams();
      26             :   if (std::is_same<T, Real>::value)
      27       39506 :     params.registerBase("InterfaceKernel");
      28             :   else if (std::is_same<T, RealVectorValue>::value)
      29        6206 :     params.registerBase("VectorInterfaceKernel");
      30             :   else
      31             :     ::mooseError("unsupported InterfaceKernelTempl specialization");
      32       22856 :   return params;
      33           0 : }
      34             : 
      35             : template <typename T>
      36         752 : InterfaceKernelTempl<T>::InterfaceKernelTempl(const InputParameters & parameters)
      37             :   : InterfaceKernelBase(parameters),
      38             :     NeighborMooseVariableInterface<T>(this,
      39             :                                       false,
      40             :                                       Moose::VarKindType::VAR_SOLVER,
      41             :                                       std::is_same<T, Real>::value
      42             :                                           ? Moose::VarFieldType::VAR_FIELD_STANDARD
      43             :                                           : Moose::VarFieldType::VAR_FIELD_VECTOR),
      44        1504 :     _var(*this->mooseVariable()),
      45         752 :     _normals(_assembly.normals()),
      46         752 :     _u(_is_implicit ? _var.sln() : _var.slnOld()),
      47         752 :     _grad_u(_is_implicit ? _var.gradSln() : _var.gradSlnOld()),
      48         752 :     _phi(_assembly.phiFace(_var)),
      49         752 :     _grad_phi(_assembly.gradPhiFace(_var)),
      50         752 :     _test(_var.phiFace()),
      51         752 :     _grad_test(_var.gradPhiFace()),
      52        1504 :     _neighbor_var(*getVarHelper<MooseVariableFE<T>>("neighbor_var", 0)),
      53         752 :     _neighbor_value(_is_implicit ? _neighbor_var.slnNeighbor() : _neighbor_var.slnOldNeighbor()),
      54         752 :     _grad_neighbor_value(_neighbor_var.gradSlnNeighbor()),
      55         752 :     _phi_neighbor(_assembly.phiFaceNeighbor(_neighbor_var)),
      56         752 :     _grad_phi_neighbor(_assembly.gradPhiFaceNeighbor(_neighbor_var)),
      57         752 :     _test_neighbor(_neighbor_var.phiFaceNeighbor()),
      58         752 :     _grad_test_neighbor(_neighbor_var.gradPhiFaceNeighbor()),
      59        1504 :     _same_system(_var.sys().number() == _neighbor_var.sys().number())
      60             : {
      61             :   // Neighbor variable dependency is added by
      62             :   // NeighborCoupleableMooseVariableDependencyIntermediateInterface
      63         752 :   addMooseVariableDependency(this->mooseVariable());
      64             : 
      65        1504 :   if (!parameters.isParamValid("boundary"))
      66           0 :     mooseError(
      67             :         "In order to use an interface kernel, you must specify a boundary where it will live.");
      68             : 
      69        1504 :   if (parameters.isParamSetByUser("save_in"))
      70             :   {
      71          26 :     if (_save_in_strings.size() != _save_in_var_side.size())
      72           0 :       mooseError("save_in and save_in_var_side must be the same length");
      73             :     else
      74             :     {
      75          78 :       for (unsigned i = 0; i < _save_in_strings.size(); ++i)
      76             :       {
      77          52 :         MooseVariable * var = &_subproblem.getStandardVariable(_tid, _save_in_strings[i]);
      78             : 
      79          52 :         if (_sys.hasVariable(_save_in_strings[i]))
      80           0 :           mooseError("Trying to use solution variable " + _save_in_strings[i] +
      81           0 :                      " as a save_in variable in " + name());
      82             : 
      83          52 :         if (_save_in_var_side[i] == "m")
      84             :         {
      85          26 :           if (var->feType() != _var.feType())
      86           0 :             mooseError(
      87           0 :                 "Error in " + name() +
      88             :                 ". There is a mismatch between the fe_type of the save-in Auxiliary variable "
      89             :                 "and the fe_type of the the primary side nonlinear "
      90             :                 "variable this interface kernel object is acting on.");
      91          26 :           _primary_save_in_residual_variables.push_back(var);
      92             :         }
      93             :         else
      94             :         {
      95          26 :           if (var->feType() != _neighbor_var.feType())
      96           0 :             mooseError(
      97           0 :                 "Error in " + name() +
      98             :                 ". There is a mismatch between the fe_type of the save-in Auxiliary variable "
      99             :                 "and the fe_type of the the secondary side nonlinear "
     100             :                 "variable this interface kernel object is acting on.");
     101          26 :           _secondary_save_in_residual_variables.push_back(var);
     102             :         }
     103             : 
     104          52 :         var->sys().addVariableToZeroOnResidual(_save_in_strings[i]);
     105          52 :         addMooseVariableDependency(var);
     106             :       }
     107             :     }
     108             :   }
     109             : 
     110         752 :   _has_primary_residuals_saved_in = _primary_save_in_residual_variables.size() > 0;
     111         752 :   _has_secondary_residuals_saved_in = _secondary_save_in_residual_variables.size() > 0;
     112             : 
     113        1504 :   if (parameters.isParamSetByUser("diag_save_in"))
     114             :   {
     115          26 :     if (_diag_save_in_strings.size() != _diag_save_in_var_side.size())
     116           0 :       mooseError("diag_save_in and diag_save_in_var_side must be the same length");
     117             :     else
     118             :     {
     119          78 :       for (unsigned i = 0; i < _diag_save_in_strings.size(); ++i)
     120             :       {
     121          52 :         MooseVariable * var = &_subproblem.getStandardVariable(_tid, _diag_save_in_strings[i]);
     122             : 
     123          52 :         if (_sys.hasVariable(_diag_save_in_strings[i]))
     124           0 :           mooseError("Trying to use solution variable " + _diag_save_in_strings[i] +
     125           0 :                      " as a save_in variable in " + name());
     126             : 
     127          52 :         if (_diag_save_in_var_side[i] == "m")
     128             :         {
     129          26 :           if (var->feType() != _var.feType())
     130           0 :             mooseError(
     131           0 :                 "Error in " + name() +
     132             :                 ". There is a mismatch between the fe_type of the save-in Auxiliary variable "
     133             :                 "and the fe_type of the the primary side nonlinear "
     134             :                 "variable this interface kernel object is acting on.");
     135          26 :           _primary_save_in_jacobian_variables.push_back(var);
     136             :         }
     137             :         else
     138             :         {
     139          26 :           if (var->feType() != _neighbor_var.feType())
     140           0 :             mooseError(
     141           0 :                 "Error in " + name() +
     142             :                 ". There is a mismatch between the fe_type of the save-in Auxiliary variable "
     143             :                 "and the fe_type of the the secondary side nonlinear "
     144             :                 "variable this interface kernel object is acting on.");
     145          26 :           _secondary_save_in_jacobian_variables.push_back(var);
     146             :         }
     147             : 
     148          52 :         var->sys().addVariableToZeroOnJacobian(_diag_save_in_strings[i]);
     149          52 :         addMooseVariableDependency(var);
     150             :       }
     151             :     }
     152             :   }
     153             : 
     154         752 :   _has_primary_jacobians_saved_in = _primary_save_in_jacobian_variables.size() > 0;
     155         752 :   _has_secondary_jacobians_saved_in = _secondary_save_in_jacobian_variables.size() > 0;
     156         752 : }
     157             : 
     158             : template <typename T>
     159             : void
     160      117886 : InterfaceKernelTempl<T>::computeElemNeighResidual(Moose::DGResidualType type)
     161             : {
     162             :   bool is_elem;
     163      117886 :   if (type == Moose::Element)
     164       58999 :     is_elem = true;
     165             :   else
     166       58887 :     is_elem = false;
     167             : 
     168      117886 :   const TemplateVariableTestValue & test_space = is_elem ? _test : _test_neighbor;
     169             : 
     170      117886 :   if (is_elem)
     171       58999 :     prepareVectorTag(_assembly, _var.number());
     172             :   else
     173       58887 :     prepareVectorTagNeighbor(_assembly, _neighbor_var.number());
     174             : 
     175      538884 :   for (_qp = 0; _qp < _qrule->n_points(); _qp++)
     176             :   {
     177      420998 :     initQpResidual(type);
     178     5292754 :     for (_i = 0; _i < test_space.size(); _i++)
     179     4871756 :       _local_re(_i) += _JxW[_qp] * _coord[_qp] * computeQpResidual(type);
     180             :   }
     181             : 
     182      117886 :   accumulateTaggedLocalResidual();
     183             : 
     184             :   // To save the diagonal of the Jacobian
     185      117886 :   if (_has_primary_residuals_saved_in && is_elem)
     186          64 :     for (const auto & var : _primary_save_in_residual_variables)
     187          64 :       var->sys().solution().add_vector(_local_re, var->dofIndices());
     188      117854 :   else if (_has_secondary_residuals_saved_in && !is_elem)
     189          64 :     for (const auto & var : _secondary_save_in_residual_variables)
     190          32 :       var->sys().solution().add_vector(_local_re, var->dofIndicesNeighbor());
     191      117886 : }
     192             : 
     193             : template <typename T>
     194             : void
     195       59123 : InterfaceKernelTempl<T>::computeResidual()
     196             : {
     197             :   // in the gmsh mesh format (at least in the version 2 format) the "sideset" physical entities are
     198             :   // associated only with the lower-dimensional geometric entity that is the boundary between two
     199             :   // higher-dimensional element faces. It does not have a sidedness to it like the exodus format
     200             :   // does. Consequently we may naively try to execute an interface kernel twice, one time where _var
     201             :   // has dofs on _current_elem *AND* _neighbor_var has dofs on _neighbor_elem, and the other time
     202             :   // where _var has dofs on _neighbor_elem and _neighbor_var has dofs on _current_elem. We only want
     203             :   // to execute in the former case. In the future we should remove this and add some kind of "block"
     204             :   // awareness to interface kernels to avoid all the unnecessary reinit that happens before we hit
     205             :   // this return
     206      118198 :   if (!_var.activeOnSubdomain(_current_elem->subdomain_id()) ||
     207       59075 :       !_neighbor_var.activeOnSubdomain(_neighbor_elem->subdomain_id()))
     208          48 :     return;
     209             : 
     210       59075 :   precalculateResidual();
     211             : 
     212             :   // Compute the residual for this element
     213       59075 :   computeElemNeighResidual(Moose::Element);
     214             : 
     215             :   // Compute the residual for the neighbor
     216             :   // This also prevents computing a residual if the neighbor variable is auxiliary
     217       59075 :   if (_same_system)
     218       58963 :     computeElemNeighResidual(Moose::Neighbor);
     219             : }
     220             : 
     221             : template <typename T>
     222             : void
     223       31092 : InterfaceKernelTempl<T>::computeElemNeighJacobian(Moose::DGJacobianType type)
     224             : {
     225       31092 :   const TemplateVariableTestValue & test_space =
     226       31092 :       (type == Moose::ElementElement || type == Moose::ElementNeighbor) ? _test : _test_neighbor;
     227       31092 :   const TemplateVariableTestValue & loc_phi =
     228       31092 :       (type == Moose::ElementElement || type == Moose::NeighborElement) ? _phi : _phi_neighbor;
     229             : 
     230             :   unsigned int ivar, jvar;
     231             : 
     232       31092 :   switch (type)
     233             :   {
     234        8933 :     case Moose::ElementElement:
     235        8933 :       ivar = jvar = _var.number();
     236        8933 :       break;
     237        6633 :     case Moose::ElementNeighbor:
     238        6633 :       ivar = _var.number(), jvar = _neighbor_var.number();
     239        6633 :       break;
     240        6633 :     case Moose::NeighborElement:
     241        6633 :       ivar = _neighbor_var.number(), jvar = _var.number();
     242        6633 :       break;
     243        8893 :     case Moose::NeighborNeighbor:
     244        8893 :       ivar = _neighbor_var.number(), jvar = _neighbor_var.number();
     245        8893 :       break;
     246           0 :     default:
     247           0 :       mooseError("Unknown DGJacobianType ", type);
     248             :   }
     249             : 
     250       31092 :   if (type == Moose::ElementElement)
     251        8933 :     prepareMatrixTag(_assembly, ivar, jvar);
     252             :   else
     253       22159 :     prepareMatrixTagNeighbor(_assembly, ivar, jvar, type);
     254             : 
     255      101700 :   for (_qp = 0; _qp < _qrule->n_points(); _qp++)
     256             :   {
     257       70608 :     initQpJacobian(type);
     258      510320 :     for (_i = 0; _i < test_space.size(); _i++)
     259     4376832 :       for (_j = 0; _j < loc_phi.size(); _j++)
     260     3937120 :         _local_ke(_i, _j) += _JxW[_qp] * _coord[_qp] * computeQpJacobian(type);
     261             :   }
     262             : 
     263       31092 :   accumulateTaggedLocalMatrix();
     264             : 
     265             :   // To save the diagonal of the Jacobian
     266       31092 :   if (_has_primary_jacobians_saved_in && type == Moose::ElementElement)
     267             :   {
     268          16 :     auto rows = _local_ke.m();
     269          16 :     DenseVector<Number> diag(rows);
     270          48 :     for (decltype(rows) i = 0; i < rows; i++)
     271          32 :       diag(i) = _local_ke(i, i);
     272             : 
     273          32 :     for (const auto & var : _primary_save_in_jacobian_variables)
     274          16 :       var->sys().solution().add_vector(diag, var->dofIndices());
     275          16 :   }
     276       31076 :   else if (_has_secondary_jacobians_saved_in && type == Moose::NeighborNeighbor)
     277             :   {
     278          16 :     auto rows = _local_ke.m();
     279          16 :     DenseVector<Number> diag(rows);
     280          48 :     for (decltype(rows) i = 0; i < rows; i++)
     281          32 :       diag(i) = _local_ke(i, i);
     282             : 
     283          32 :     for (const auto & var : _secondary_save_in_jacobian_variables)
     284          16 :       var->sys().solution().add_vector(diag, var->dofIndicesNeighbor());
     285          16 :   }
     286       31092 : }
     287             : 
     288             : template <typename T>
     289             : void
     290        2276 : InterfaceKernelTempl<T>::computeJacobian()
     291             : {
     292             :   // in the gmsh mesh format (at least in the version 2 format) the "sideset" physical entities are
     293             :   // associated only with the lower-dimensional geometric entity that is the boundary between two
     294             :   // higher-dimensional element faces. It does not have a sidedness to it like the exodus format
     295             :   // does. Consequently we may naively try to execute an interface kernel twice, one time where _var
     296             :   // has dofs on _current_elem *AND* _neighbor_var has dofs on _neighbor_elem, and the other time
     297             :   // where _var has dofs on _neighbor_elem and _neighbor_var has dofs on _current_elem. We only want
     298             :   // to execute in the former case. In the future we should remove this and add some kind of "block"
     299             :   // awareness to interface kernels to avoid all the unnecessary reinit that happens before we hit
     300             :   // this return
     301        4552 :   if (!_var.activeOnSubdomain(_current_elem->subdomain_id()) ||
     302        2276 :       !_neighbor_var.activeOnSubdomain(_neighbor_elem->subdomain_id()))
     303           0 :     return;
     304             : 
     305        2276 :   precalculateJacobian();
     306             : 
     307        2276 :   computeElemNeighJacobian(Moose::ElementElement);
     308        2276 :   if (_same_system)
     309        2260 :     computeElemNeighJacobian(Moose::NeighborNeighbor);
     310             : }
     311             : 
     312             : template <typename T>
     313             : void
     314           0 : InterfaceKernelTempl<T>::computeOffDiagElemNeighJacobian(Moose::DGJacobianType type,
     315             :                                                          unsigned int jvar)
     316             : {
     317           0 :   const TemplateVariableTestValue & test_space =
     318           0 :       (type == Moose::ElementElement || type == Moose::ElementNeighbor) ? _test : _test_neighbor;
     319           0 :   const TemplateVariableTestValue & loc_phi =
     320           0 :       (type == Moose::ElementElement || type == Moose::NeighborElement) ? _phi : _phi_neighbor;
     321             : 
     322             :   unsigned int ivar;
     323             : 
     324           0 :   if (type == Moose::ElementElement || type == Moose::ElementNeighbor)
     325           0 :     ivar = _var.number();
     326             :   else
     327           0 :     ivar = _neighbor_var.number();
     328             : 
     329           0 :   if (type == Moose::ElementElement)
     330           0 :     prepareMatrixTag(_assembly, ivar, jvar);
     331             :   else
     332           0 :     prepareMatrixTagNeighbor(_assembly, ivar, jvar, type);
     333             : 
     334             :   // Prevent calling of Jacobian computation if jvar doesn't lie in the current block
     335           0 :   if ((_local_ke.m() == test_space.size()) && (_local_ke.n() == loc_phi.size()))
     336           0 :     for (_qp = 0; _qp < _qrule->n_points(); _qp++)
     337             :     {
     338           0 :       initQpOffDiagJacobian(type, jvar);
     339           0 :       for (_i = 0; _i < test_space.size(); _i++)
     340           0 :         for (_j = 0; _j < loc_phi.size(); _j++)
     341           0 :           _local_ke(_i, _j) += _JxW[_qp] * _coord[_qp] * computeQpOffDiagJacobian(type, jvar);
     342             :     }
     343             : 
     344           0 :   accumulateTaggedLocalMatrix();
     345           0 : }
     346             : 
     347             : template <typename T>
     348             : void
     349       13439 : InterfaceKernelTempl<T>::computeElementOffDiagJacobian(unsigned int jvar)
     350             : {
     351             :   // in the gmsh mesh format (at least in the version 2 format) the "sideset" physical entities are
     352             :   // associated only with the lower-dimensional geometric entity that is the boundary between two
     353             :   // higher-dimensional element faces. It does not have a sidedness to it like the exodus format
     354             :   // does. Consequently we may naively try to execute an interface kernel twice, one time where _var
     355             :   // has dofs on _current_elem *AND* _neighbor_var has dofs on _neighbor_elem, and the other time
     356             :   // where _var has dofs on _neighbor_elem and _neighbor_var has dofs on _current_elem. We only want
     357             :   // to execute in the former case. In the future we should remove this and add some kind of "block"
     358             :   // awareness to interface kernels to avoid all the unnecessary reinit that happens before we hit
     359             :   // this return
     360       26814 :   if (!_var.activeOnSubdomain(_current_elem->subdomain_id()) ||
     361       13375 :       !_neighbor_var.activeOnSubdomain(_neighbor_elem->subdomain_id()))
     362          64 :     return;
     363             : 
     364       13375 :   bool is_jvar_not_interface_var = true;
     365       13375 :   if (jvar == _var.number())
     366             :   {
     367        6701 :     precalculateJacobian();
     368        6701 :     computeElemNeighJacobian(Moose::ElementElement);
     369        6701 :     is_jvar_not_interface_var = false;
     370             :   }
     371       13375 :   if (jvar == _neighbor_var.number() && _same_system)
     372             :   {
     373        6677 :     precalculateJacobian();
     374        6677 :     computeElemNeighJacobian(Moose::ElementNeighbor);
     375        6677 :     is_jvar_not_interface_var = false;
     376             :   }
     377             : 
     378       13375 :   if (is_jvar_not_interface_var)
     379             :   {
     380           0 :     precalculateOffDiagJacobian(jvar);
     381           0 :     computeOffDiagElemNeighJacobian(Moose::ElementElement, jvar);
     382           0 :     computeOffDiagElemNeighJacobian(Moose::ElementNeighbor, jvar);
     383             :   }
     384             : }
     385             : 
     386             : template <typename T>
     387             : void
     388       13415 : InterfaceKernelTempl<T>::computeNeighborOffDiagJacobian(unsigned int jvar)
     389             : {
     390             :   // in the gmsh mesh format (at least in the version 2 format) the "sideset" physical entities are
     391             :   // associated only with the lower-dimensional geometric entity that is the boundary between two
     392             :   // higher-dimensional element faces. It does not have a sidedness to it like the exodus format
     393             :   // does. Consequently we may naively try to execute an interface kernel twice, one time where _var
     394             :   // has dofs on _current_elem *AND* _neighbor_var has dofs on _neighbor_elem, and the other time
     395             :   // where _var has dofs on _neighbor_elem and _neighbor_var has dofs on _current_elem. We only want
     396             :   // to execute in the former case. In the future we should remove this and add some kind of "block"
     397             :   // awareness to interface kernels to avoid all the unnecessary reinit that happens before we hit
     398             :   // this return
     399       26766 :   if (!_var.activeOnSubdomain(_current_elem->subdomain_id()) ||
     400       13351 :       !_neighbor_var.activeOnSubdomain(_neighbor_elem->subdomain_id()))
     401          64 :     return;
     402             : 
     403             :   // We don't care about any contribution to the neighbor Jacobian rows if it's not in the system
     404             :   // we are currently working with (the variable's system)
     405       13351 :   if (!_same_system)
     406           0 :     return;
     407             : 
     408       13351 :   bool is_jvar_not_interface_var = true;
     409       13351 :   if (jvar == _var.number())
     410             :   {
     411        6677 :     precalculateJacobian();
     412        6677 :     computeElemNeighJacobian(Moose::NeighborElement);
     413        6677 :     is_jvar_not_interface_var = false;
     414             :   }
     415       13351 :   if (jvar == _neighbor_var.number())
     416             :   {
     417        6677 :     precalculateJacobian();
     418        6677 :     computeElemNeighJacobian(Moose::NeighborNeighbor);
     419        6677 :     is_jvar_not_interface_var = false;
     420             :   }
     421             : 
     422       13351 :   if (is_jvar_not_interface_var)
     423             :   {
     424           0 :     precalculateOffDiagJacobian(jvar);
     425           0 :     computeOffDiagElemNeighJacobian(Moose::NeighborElement, jvar);
     426           0 :     computeOffDiagElemNeighJacobian(Moose::NeighborNeighbor, jvar);
     427             :   }
     428             : }
     429             : 
     430             : template <typename T>
     431             : void
     432          62 : InterfaceKernelTempl<T>::computeResidualAndJacobian()
     433             : {
     434          62 :   computeResidual();
     435             : 
     436          62 :   if (!isImplicit())
     437           0 :     return;
     438             : 
     439         238 :   for (const auto & [ivariable, jvariable] : _fe_problem.couplingEntries(_tid, _sys.number()))
     440             :   {
     441         176 :     if (ivariable->isFV())
     442           0 :       continue;
     443             : 
     444         176 :     const unsigned int ivar = ivariable->number();
     445         176 :     const unsigned int jvar = jvariable->number();
     446             : 
     447         176 :     prepareShapes(jvar);
     448         176 :     prepareNeighborShapes(jvar);
     449             : 
     450         176 :     if (_var.number() == ivar)
     451         100 :       computeElementOffDiagJacobian(jvar);
     452             : 
     453         176 :     if (_same_system)
     454         152 :       if (_neighbor_var.number() == ivar)
     455          76 :         computeNeighborOffDiagJacobian(jvar);
     456             :   }
     457             : }
     458             : 
     459             : // Explicitly instantiates the two versions of the InterfaceKernelTempl class
     460             : template class InterfaceKernelTempl<Real>;
     461             : template class InterfaceKernelTempl<RealVectorValue>;

Generated by: LCOV version 1.14