LCOV - code coverage report
Current view: top level - src/constraints - ModularGapConductanceConstraint.C (source / functions) Hit Total Coverage
Test: idaholab/moose heat_transfer: #31405 (292dce) with base fef103 Lines: 177 207 85.5 %
Date: 2025-09-04 07:53:51 Functions: 10 10 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 "ModularGapConductanceConstraint.h"
      11             : #include "GapFluxModelBase.h"
      12             : #include "GapFluxModelPressureDependentConduction.h"
      13             : 
      14             : #include "MooseError.h"
      15             : 
      16             : #include "libmesh/parallel_algebra.h"
      17             : 
      18             : registerMooseObject("HeatTransferApp", ModularGapConductanceConstraint);
      19             : 
      20             : InputParameters
      21        1582 : ModularGapConductanceConstraint::validParams()
      22             : {
      23        1582 :   InputParameters params = ADMortarConstraint::validParams();
      24        1582 :   params.addClassDescription(
      25             :       "Computes the residual and Jacobian contributions for the 'Lagrange Multiplier' "
      26             :       "implementation of the thermal contact problem. For more information, see the "
      27             :       "detailed description here: http://tinyurl.com/gmmhbe9");
      28        3164 :   params.addParam<std::vector<UserObjectName>>("gap_flux_models",
      29             :                                                "List of GapFluxModel user objects");
      30        3164 :   params.addCoupledVar("displacements", "Displacement variables");
      31             : 
      32        3164 :   MooseEnum gap_geometry_type("AUTO PLATE CYLINDER SPHERE", "AUTO");
      33        3164 :   params.addParam<MooseEnum>(
      34             :       "gap_geometry_type",
      35             :       gap_geometry_type,
      36             :       "Gap calculation type. The geometry type is used to compute "
      37             :       "gap distances and scale fluxes to ensure energy balance. If AUTO is selected, the gap "
      38             :       "geometry is automatically set via the mesh coordinate system.");
      39        3164 :   params.addRangeCheckedParam<Real>("max_gap", 1.0e6, "max_gap>=0", "A maximum gap size");
      40        3164 :   params.addParam<RealVectorValue>("cylinder_axis_point_1",
      41             :                                    "Start point for line defining cylindrical axis");
      42        3164 :   params.addParam<RealVectorValue>("cylinder_axis_point_2",
      43             :                                    "End point for line defining cylindrical axis");
      44        3164 :   params.addParam<RealVectorValue>("sphere_origin", "Origin for sphere geometry");
      45             : 
      46             :   // We should default use_displaced_mesh to true. If no displaced mesh exists
      47             :   // FEProblemBase::addConstraint will automatically correct it to false. However,
      48             :   // this will still prompt a call from AugmentSparsityOnInterface to get a displaced
      49             :   // mortar interface since object._use_displaced_mesh = true.
      50             : 
      51             :   // These parameter groups have to match the MortarGapHeatTransferAction's
      52        3164 :   params.addParamNamesToGroup("gap_flux_models", "Gap flux models");
      53        3164 :   params.addParamNamesToGroup(
      54             :       "gap_geometry_type max_gap cylinder_axis_point_1 cylinder_axis_point_2 sphere_origin",
      55             :       "Gap geometry");
      56             : 
      57        1582 :   return params;
      58        1582 : }
      59             : 
      60         637 : ModularGapConductanceConstraint::ModularGapConductanceConstraint(const InputParameters & parameters)
      61             :   : ADMortarConstraint(parameters),
      62        1274 :     _gap_flux_model_names(getParam<std::vector<UserObjectName>>("gap_flux_models")),
      63        1274 :     _disp_name(parameters.getVecMooseType("displacements")),
      64         637 :     _n_disp(_disp_name.size()),
      65         637 :     _disp_secondary(_n_disp),
      66         637 :     _disp_primary(_n_disp),
      67        1274 :     _gap_geometry_type(getParam<MooseEnum>("gap_geometry_type").getEnum<GapGeometry>()),
      68         637 :     _gap_width(0.0),
      69         637 :     _surface_integration_factor(1.0),
      70        1274 :     _p1(declareRestartableData<Point>("cylinder_axis_point_1", Point(0, 1, 0))),
      71        1274 :     _p2(declareRestartableData<Point>("cylinder_axis_point_2", Point(0, 0, 0))),
      72         637 :     _r1(0),
      73         637 :     _r2(0),
      74        1274 :     _max_gap(getParam<Real>("max_gap")),
      75         637 :     _adjusted_length(0.0),
      76         637 :     _disp_x_var(getVar("displacements", 0)),
      77         637 :     _disp_y_var(getVar("displacements", 1)),
      78        1274 :     _disp_z_var(_n_disp == 3 ? getVar("displacements", 2) : nullptr)
      79             : {
      80        1321 :   if (_n_disp && !getParam<bool>("use_displaced_mesh"))
      81           0 :     paramWarning("displacements",
      82             :                  "You are coupling displacement variables but are evaluating the gap width on the "
      83             :                  "undisplaced mesh. This is probably a mistake.");
      84             : 
      85        1093 :   for (unsigned int i = 0; i < _n_disp; ++i)
      86             :   {
      87         456 :     auto & disp_var = _subproblem.getStandardVariable(_tid, _disp_name[i]);
      88         456 :     _disp_secondary[i] = &disp_var.adSln();
      89         456 :     _disp_primary[i] = &disp_var.adSlnNeighbor();
      90             :   }
      91             : 
      92        1274 :   const auto use_displaced_mesh = getParam<bool>("use_displaced_mesh");
      93             : 
      94        1588 :   for (const auto & name : _gap_flux_model_names)
      95             :   {
      96         951 :     const auto & gap_model = getUserObjectByName<GapFluxModelBase>(name);
      97             : 
      98             :     // This constraint explicitly calls the gap flux model user objects to
      99             :     // obtain contributions to its residuals. It therefore depends on all
     100             :     // variables and material properties, that these gap flux models use, to be
     101             :     // computed and up to date. To ensure that we collect all variable and
     102             :     // material property dependencies of these models and declare them as
     103             :     // dependencies of this constraint object. This turns an implicit, hidden
     104             :     // dependency into an explicit dependency that MOOSE will automatically fulfill.
     105             : 
     106             :     // pass variable dependencies through
     107             :     const auto & var_dependencies = gap_model.getMooseVariableDependencies();
     108        2035 :     for (const auto & var : var_dependencies)
     109        2168 :       addMooseVariableDependency(var);
     110             : 
     111             :     // pass material property dependencies through
     112         951 :     const auto & mat_dependencies = gap_model.getMatPropDependencies();
     113         951 :     _material_property_dependencies.insert(mat_dependencies.begin(), mat_dependencies.end());
     114             : 
     115             :     // ensure that the constraint and the flux models operate on the same mesh
     116         951 :     if (gap_model.parameters().get<bool>("use_displaced_mesh") != use_displaced_mesh)
     117           0 :       paramError(
     118             :           "use_displaced_mesh",
     119             :           "The gap flux model '",
     120             :           name,
     121             :           "' should operate on the same mesh (displaced/undisplaced) as the constraint object");
     122             : 
     123             :     // add gap model to list
     124         951 :     _gap_flux_models.push_back(&gap_model);
     125             :   }
     126         637 : }
     127             : 
     128             : void
     129         637 : ModularGapConductanceConstraint::initialSetup()
     130             : {
     131             :   ///set generated from the passed in vector of subdomain names
     132         637 :   const auto & subdomainIDs = _mesh.meshSubdomains();
     133             : 
     134             :   // make sure all subdomains are using the same coordinate system
     135         637 :   Moose::CoordinateSystemType coord_system = feProblem().getCoordSystem(*subdomainIDs.begin());
     136        3470 :   for (auto subdomain : subdomainIDs)
     137        2833 :     if (feProblem().getCoordSystem(subdomain) != coord_system)
     138           0 :       mooseError("ModularGapConductanceConstraint requires all subdomains to have the same "
     139             :                  "coordinate system.");
     140             : 
     141             :   // Select proper coordinate system and geometry (plate, cylinder, sphere)
     142         637 :   setGapGeometryParameters(
     143         637 :       _pars, coord_system, feProblem().getAxisymmetricRadialCoord(), _gap_geometry_type);
     144         635 : }
     145             : 
     146             : void
     147         637 : ModularGapConductanceConstraint::setGapGeometryParameters(
     148             :     const InputParameters & params,
     149             :     const Moose::CoordinateSystemType coord_sys,
     150             :     unsigned int axisymmetric_radial_coord,
     151             :     GapGeometry & gap_geometry_type)
     152             : {
     153             : 
     154             :   // Determine what type of gap geometry we are dealing with
     155             :   // Either user input or from system's coordinate systems
     156         637 :   if (gap_geometry_type == GapGeometry::AUTO)
     157             :   {
     158         494 :     if (coord_sys == Moose::COORD_XYZ)
     159         475 :       gap_geometry_type = GapGeometry::PLATE;
     160          19 :     else if (coord_sys == Moose::COORD_RZ)
     161          19 :       gap_geometry_type = GapGeometry::CYLINDER;
     162           0 :     else if (coord_sys == Moose::COORD_RSPHERICAL)
     163           0 :       gap_geometry_type = GapGeometry::SPHERE;
     164             :     else
     165           0 :       mooseError("Internal Error");
     166             :   }
     167             : 
     168        1274 :   if (params.isParamValid("cylinder_axis_point_1") != params.isParamValid("cylinder_axis_point_2"))
     169           0 :     paramError(
     170             :         "cylinder_axis_point_1",
     171             :         "Either specify both `cylinder_axis_point_1` and `cylinder_axis_point_2` or neither.");
     172             : 
     173             :   // Check consistency of geometry information
     174             :   // Inform the user of needed input according to gap geometry (if not PLATE)
     175         637 :   if (gap_geometry_type == GapGeometry::PLATE)
     176             :   {
     177         491 :     if (coord_sys == Moose::COORD_RSPHERICAL)
     178           0 :       paramError("gap_geometry_type",
     179             :                  "'gap_geometry_type = PLATE' cannot be used with models having a spherical "
     180             :                  "coordinate system.");
     181             :   }
     182         146 :   else if (gap_geometry_type == GapGeometry::CYLINDER)
     183             :   {
     184          76 :     if (coord_sys == Moose::COORD_XYZ)
     185             :     {
     186          76 :       if (params.isParamValid("cylinder_axis_point_1") &&
     187          57 :           params.isParamValid("cylinder_axis_point_2"))
     188             :       {
     189          19 :         _p1 = params.get<RealVectorValue>("cylinder_axis_point_1");
     190          19 :         _p2 = params.get<RealVectorValue>("cylinder_axis_point_2");
     191             :       }
     192          19 :       else if (_mesh.dimension() == 3)
     193           0 :         paramError("gap_geometry_type",
     194             :                    "For 'gap_geometry_type = CYLINDER' to be used with a Cartesian model in 3D, "
     195             :                    "'cylinder_axis_point_1' and 'cylinder_axis_point_2' must be specified.");
     196             :       else
     197             :       {
     198          19 :         deduceGeometryParameters();
     199          19 :         mooseInfoRepeated(
     200             :             "ModularGapConductanceConstraint '", name(), "' deduced cylinder axis as ", _p1, _p2);
     201             :       }
     202             :     }
     203          38 :     else if (coord_sys == Moose::COORD_RZ)
     204             :     {
     205          76 :       if (params.isParamValid("cylinder_axis_point_1") ||
     206          76 :           params.isParamValid("cylinder_axis_point_2"))
     207           0 :         paramError("cylinder_axis_point_1",
     208             :                    "The 'cylinder_axis_point_1' and 'cylinder_axis_point_2' cannot be specified "
     209             :                    "with axisymmetric models.  The y-axis is used as the cylindrical axis of "
     210             :                    "symmetry.");
     211             : 
     212          38 :       if (axisymmetric_radial_coord == 0) // R-Z problem
     213             :       {
     214          38 :         _p1 = Point(0, 0, 0);
     215          38 :         _p2 = Point(0, 1, 0);
     216             :       }
     217             :       else // Z-R problem
     218             :       {
     219           0 :         _p1 = Point(0, 0, 0);
     220           0 :         _p2 = Point(1, 0, 0);
     221             :       }
     222             :     }
     223           0 :     else if (coord_sys == Moose::COORD_RSPHERICAL)
     224           0 :       paramError("gap_geometry_type",
     225             :                  "'gap_geometry_type = CYLINDER' cannot be used with models having a spherical "
     226             :                  "coordinate system.");
     227             :   }
     228          70 :   else if (gap_geometry_type == GapGeometry::SPHERE)
     229             :   {
     230          70 :     if (coord_sys == Moose::COORD_XYZ || coord_sys == Moose::COORD_RZ)
     231             :     {
     232         140 :       if (params.isParamValid("sphere_origin"))
     233          49 :         _p1 = params.get<RealVectorValue>("sphere_origin");
     234          21 :       else if (coord_sys == Moose::COORD_XYZ)
     235             :       {
     236          19 :         deduceGeometryParameters();
     237          19 :         mooseInfoRepeated(
     238             :             "ModularGapConductanceConstraint '", name(), "' deduced sphere origin as ", _p1);
     239             :       }
     240             :       else
     241           2 :         paramError("gap_geometry_type",
     242             :                    "For 'gap_geometry_type = SPHERE' to be used with an axisymmetric "
     243             :                    "model, 'sphere_origin' must be specified.");
     244             :     }
     245           0 :     else if (coord_sys == Moose::COORD_RSPHERICAL)
     246             :     {
     247           0 :       if (params.isParamValid("sphere_origin"))
     248           0 :         paramError("sphere_origin",
     249             :                    "The 'sphere_origin' cannot be specified with spherical models.  x=0 is used "
     250             :                    "as the spherical origin.");
     251           0 :       _p1 = Point(0, 0, 0);
     252             :     }
     253             :   }
     254         635 : }
     255             : 
     256             : ADReal
     257    26614938 : ModularGapConductanceConstraint::computeSurfaceIntegrationFactor() const
     258             : {
     259             : 
     260    26614938 :   ADReal surface_integration_factor = 1.0;
     261             : 
     262    26614938 :   if (_gap_geometry_type == GapGeometry::CYLINDER)
     263      269433 :     surface_integration_factor = 0.5 * (_r1 + _r2) / _radius;
     264    26525127 :   else if (_gap_geometry_type == GapGeometry::SPHERE)
     265    10096560 :     surface_integration_factor = 0.25 * (_r1 + _r2) * (_r1 + _r2) / (_radius * _radius);
     266             : 
     267    26614938 :   return surface_integration_factor;
     268             : }
     269             : 
     270             : ADReal
     271    26614938 : ModularGapConductanceConstraint::computeGapLength() const
     272             : {
     273             : 
     274    26614938 :   if (_gap_geometry_type == GapGeometry::CYLINDER)
     275             :   {
     276      179622 :     const auto denominator = _radius * std::log(_r2 / _r1);
     277       89811 :     return std::min(denominator, _max_gap);
     278             :   }
     279    26525127 :   else if (_gap_geometry_type == GapGeometry::SPHERE)
     280             :   {
     281     7572420 :     const auto denominator = _radius * _radius * ((1.0 / _r1) - (1.0 / _r2));
     282     2524140 :     return std::min(denominator, _max_gap);
     283             :   }
     284             :   else
     285    48001974 :     return std::min(_r2 - _r1, _max_gap);
     286             : }
     287             : 
     288             : void
     289    26614938 : ModularGapConductanceConstraint::computeGapRadii(const ADReal & gap_length)
     290             : {
     291    26614938 :   const Point & current_point = _q_point[_qp];
     292             : 
     293    26614938 :   if (_gap_geometry_type == GapGeometry::CYLINDER)
     294             :   {
     295             :     // The vector _p1 + t*(_p2-_p1) defines the cylindrical axis.  The point along this
     296             :     // axis closest to current_point is found by the following for t:
     297       89811 :     const Point p2p1(_p2 - _p1);
     298             :     const Point p1pc(_p1 - current_point);
     299       89811 :     const Real t = -(p1pc * p2p1) / p2p1.norm_sq();
     300             : 
     301             :     // The nearest point on the cylindrical axis to current_point is p.
     302             :     const Point p(_p1 + t * p2p1);
     303             :     Point rad_vec(current_point - p);
     304       89811 :     Real rad = rad_vec.norm();
     305             :     rad_vec /= rad;
     306             :     Real rad_dot_norm = rad_vec * _normals[_qp];
     307             : 
     308       89811 :     if (rad_dot_norm > 0)
     309             :     {
     310           0 :       _r1 = rad;
     311           0 :       _r2 = rad + gap_length;
     312           0 :       _radius = _r1;
     313             :     }
     314       89811 :     else if (rad_dot_norm < 0)
     315             :     {
     316       89811 :       _r1 = rad - gap_length;
     317       89811 :       _r2 = rad;
     318       89811 :       _radius = _r2;
     319             :     }
     320             :     else
     321           0 :       mooseError("Issue with cylindrical flux calculation normals.\n");
     322             :   }
     323    26525127 :   else if (_gap_geometry_type == GapGeometry::SPHERE)
     324             :   {
     325     2524140 :     const Point origin_to_curr_point(current_point - _p1);
     326             :     const Real normal_dot = origin_to_curr_point * _normals[_qp];
     327     2524140 :     const Real curr_point_radius = origin_to_curr_point.norm();
     328     2524140 :     if (normal_dot > 0) // on inside surface
     329             :     {
     330     2440620 :       _r1 = curr_point_radius;
     331     2440620 :       _r2 = curr_point_radius + gap_length;
     332     2440620 :       _radius = _r1;
     333             :     }
     334       83520 :     else if (normal_dot < 0) // on outside surface
     335             :     {
     336       83520 :       _r1 = curr_point_radius - gap_length;
     337       83520 :       _r2 = curr_point_radius;
     338       83520 :       _radius = _r2;
     339             :     }
     340             :     else
     341           0 :       mooseError("Issue with spherical flux calculation normals. \n");
     342             :   }
     343             :   else
     344             :   {
     345    24000987 :     _r2 = gap_length;
     346    24000987 :     _r1 = 0;
     347    24000987 :     _radius = 0;
     348             :   }
     349    26614938 : }
     350             : 
     351             : ADReal
     352   182406006 : ModularGapConductanceConstraint::computeQpResidual(Moose::MortarType mortar_type)
     353             : {
     354   182406006 :   switch (mortar_type)
     355             :   {
     356    77895534 :     case Moose::MortarType::Primary:
     357    77895534 :       return _lambda[_qp] * _test_primary[_i][_qp];
     358             : 
     359    77895534 :     case Moose::MortarType::Secondary:
     360   155791068 :       return -_lambda[_qp] * _test_secondary[_i][_qp];
     361             : 
     362    26614938 :     case Moose::MortarType::Lower:
     363             :     {
     364             :       // we are creating an AD version of phys points primary and secondary here...
     365    26614938 :       ADRealVectorValue ad_phys_points_primary = _phys_points_primary[_qp];
     366    26614938 :       ADRealVectorValue ad_phys_points_secondary = _phys_points_secondary[_qp];
     367             : 
     368             :       // ...which uses the derivative vector of the primary and secondary displacements as
     369             :       // an approximation of the true phys points derivatives when the mesh is displacing
     370    26614938 :       if (_displaced)
     371             :       {
     372             :         // Trim interior node variable derivatives
     373             :         const auto & primary_ip_lowerd_map = amg().getPrimaryIpToLowerElementMap(
     374       41976 :             *_lower_primary_elem, *_lower_primary_elem->interior_parent(), *_lower_secondary_elem);
     375             :         const auto & secondary_ip_lowerd_map =
     376       41976 :             amg().getSecondaryIpToLowerElementMap(*_lower_secondary_elem);
     377             : 
     378       41976 :         std::array<const MooseVariable *, 3> var_array{{_disp_x_var, _disp_y_var, _disp_z_var}};
     379             :         std::array<ADReal, 3> primary_disp;
     380             :         std::array<ADReal, 3> secondary_disp;
     381             : 
     382      125928 :         for (unsigned int i = 0; i < _n_disp; ++i)
     383             :         {
     384       83952 :           primary_disp[i] = (*_disp_primary[i])[_qp];
     385       83952 :           secondary_disp[i] = (*_disp_secondary[i])[_qp];
     386             :         }
     387             : 
     388       41976 :         trimInteriorNodeDerivatives(primary_ip_lowerd_map, var_array, primary_disp, false);
     389       41976 :         trimInteriorNodeDerivatives(secondary_ip_lowerd_map, var_array, secondary_disp, true);
     390             : 
     391             :         // Populate quantities with trimmed derivatives
     392      125928 :         for (unsigned int i = 0; i < _n_disp; ++i)
     393             :         {
     394       83952 :           ad_phys_points_primary(i).derivatives() = primary_disp[i].derivatives();
     395             :           ad_phys_points_secondary(i).derivatives() = secondary_disp[i].derivatives();
     396             :         }
     397             :       }
     398             : 
     399             :       // compute an ADReal gap width to pass to each gap flux model
     400    26614938 :       _gap_width = (ad_phys_points_primary - ad_phys_points_secondary) * _normals[_qp];
     401             : 
     402             :       // First, compute radii with _gap_width
     403    26614938 :       computeGapRadii(_gap_width);
     404             : 
     405             :       // With radii, compute adjusted gap length for conduction
     406    26614938 :       _adjusted_length = computeGapLength();
     407             : 
     408             :       // Ensure energy balance for non-flat (non-PLATE) general geometries when using radiation
     409    26614938 :       _surface_integration_factor = computeSurfaceIntegrationFactor();
     410             : 
     411             :       // Sum up all flux contributions from all supplied gap flux models
     412    26614938 :       ADReal flux = 0.0;
     413    79742902 :       for (const auto & model : _gap_flux_models)
     414    53127964 :         flux += model->computeFluxInternal(*this);
     415             : 
     416             :       // The Lagrange multiplier _is_ the gap flux
     417    53229876 :       return (_lambda[_qp] - flux) * _test[_i][_qp];
     418             :     }
     419             : 
     420           0 :     default:
     421           0 :       return 0;
     422             :   }
     423             : }
     424             : 
     425             : void
     426          38 : ModularGapConductanceConstraint::deduceGeometryParameters()
     427             : {
     428             :   Point position;
     429          38 :   Real area = 0.0;
     430             :   const auto my_pid = processor_id();
     431             : 
     432             :   // build side element list as (element, side, id) tuples
     433          38 :   const auto bnd = _mesh.buildActiveSideList();
     434             : 
     435          38 :   std::unique_ptr<const Elem> side_ptr;
     436        4598 :   for (auto [elem_id, side, id] : bnd)
     437        4560 :     if (id == _primary_id)
     438             :     {
     439        1520 :       const auto * elem = _mesh.elemPtr(elem_id);
     440        1520 :       if (elem->processor_id() != my_pid)
     441         560 :         continue;
     442             : 
     443             :       // update side_ptr
     444         960 :       elem->side_ptr(side_ptr, side);
     445             : 
     446             :       // area of the (linearized) side
     447         960 :       const auto side_area = side_ptr->volume();
     448             : 
     449             :       // position of the side
     450         960 :       const auto side_position = side_ptr->true_centroid();
     451             : 
     452             :       // sum up
     453             :       position += side_position * side_area;
     454         960 :       area += side_area;
     455             :     }
     456             : 
     457             :   // parallel communication
     458          38 :   _communicator.sum(position);
     459          38 :   _communicator.sum(area);
     460             : 
     461             :   // set axis
     462          38 :   if (area == 0.0)
     463             :   {
     464           0 :     if (_gap_geometry_type == GapGeometry::CYLINDER)
     465           0 :       paramError("gap_geometry_type",
     466             :                  "Unable to decuce cylinder axis automatically, please specify "
     467             :                  "'cylinder_axis_point_1' and 'cylinder_axis_point_2'.");
     468           0 :     else if (_gap_geometry_type == GapGeometry::SPHERE)
     469           0 :       paramError("gap_geometry_type",
     470             :                  "Unable to decuce sphere origin automatically, please specify "
     471             :                  "'sphere_origin'.");
     472             :     else
     473           0 :       mooseError("Internal error.");
     474             :   }
     475             : 
     476          38 :   _p1 = position / area;
     477          38 :   _p2 = _p1 + Point(0, 0, 1);
     478          38 : }

Generated by: LCOV version 1.14