LCOV - code coverage report
Current view: top level - src/meshgenerators - CircularBoundaryCorrectionGenerator.C (source / functions) Hit Total Coverage
Test: idaholab/moose framework: 2bf808 Lines: 241 250 96.4 %
Date: 2025-07-17 01:28:37 Functions: 7 7 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 "CircularBoundaryCorrectionGenerator.h"
      11             : 
      12             : #include "MooseUtils.h"
      13             : #include "LinearInterpolation.h"
      14             : #include "FillBetweenPointVectorsTools.h"
      15             : #include "CastUniquePointer.h"
      16             : 
      17             : // C++ includes
      18             : #include <cmath> // for atan2
      19             : 
      20             : registerMooseObject("MooseApp", CircularBoundaryCorrectionGenerator);
      21             : 
      22             : InputParameters
      23       14381 : CircularBoundaryCorrectionGenerator::validParams()
      24             : {
      25       14381 :   InputParameters params = MeshGenerator::validParams();
      26             : 
      27       14381 :   params.addRequiredParam<MeshGeneratorName>("input", "The input mesh to be modified.");
      28             : 
      29       43143 :   params.addParam<bool>("move_end_nodes_in_span_direction",
      30       28762 :                         false,
      31             :                         "Whether to move the end nodes in the span direction of the arc or not.");
      32             : 
      33       14381 :   params.addRequiredParam<std::vector<BoundaryName>>("input_mesh_circular_boundaries",
      34             :                                                      "Names of the circular boundaries.");
      35             : 
      36       14381 :   params.addRangeCheckedParam<std::vector<Real>>(
      37             :       "transition_layer_ratios",
      38             :       "transition_layer_ratios>0&transition_layer_ratios<=1.0",
      39             :       "Ratios to radii as the transition layers on both sides of the original radii (if this "
      40             :       "parameter is not provided, 0.01 will be used).");
      41             : 
      42       14381 :   params.addRangeCheckedParam<std::vector<Real>>(
      43             :       "custom_circular_tolerance",
      44             :       "custom_circular_tolerance>0",
      45             :       "Custom tolerances (L2 norm) used to verify whether the provided boundaries are circular "
      46             :       "(default is 1.0e-6).");
      47             : 
      48       14381 :   params.addClassDescription(
      49             :       "This CircularBoundaryCorrectionGenerator object is designed to correct full "
      50             :       "or partial circular boundaries in a 2D mesh to preserve areas.");
      51             : 
      52       14381 :   return params;
      53           0 : }
      54             : 
      55          62 : CircularBoundaryCorrectionGenerator::CircularBoundaryCorrectionGenerator(
      56          62 :     const InputParameters & parameters)
      57             :   : MeshGenerator(parameters),
      58          62 :     _input_name(getParam<MeshGeneratorName>("input")),
      59          62 :     _input_mesh_circular_boundaries(
      60             :         getParam<std::vector<BoundaryName>>("input_mesh_circular_boundaries")),
      61         240 :     _transition_layer_ratios(isParamValid("transition_layer_ratios")
      62          62 :                                  ? getParam<std::vector<Real>>("transition_layer_ratios")
      63         116 :                                  : std::vector<Real>(_input_mesh_circular_boundaries.size(), 0.01)),
      64         222 :     _custom_circular_tolerance(
      65         124 :         isParamValid("custom_circular_tolerance")
      66         124 :             ? (getParam<std::vector<Real>>("custom_circular_tolerance").size() == 1
      67          44 :                    ? std::vector<Real>(
      68             :                          _input_mesh_circular_boundaries.size(),
      69         102 :                          getParam<std::vector<Real>>("custom_circular_tolerance").front())
      70             :                    : getParam<std::vector<Real>>("custom_circular_tolerance"))
      71          80 :             : std::vector<Real>(_input_mesh_circular_boundaries.size(), 1.0e-6)),
      72          62 :     _move_end_nodes_in_span_direction(getParam<bool>("move_end_nodes_in_span_direction")),
      73         186 :     _input(getMeshByName(_input_name))
      74             : {
      75          62 :   if (_transition_layer_ratios.size() != _input_mesh_circular_boundaries.size())
      76           4 :     paramError("transition_layer_ratios",
      77             :                "this parameter must have the same length as 'input_mesh_circular_boundaries'.");
      78          58 :   if (_custom_circular_tolerance.size() != _input_mesh_circular_boundaries.size())
      79           4 :     paramError("custom_circular_tolerance",
      80             :                "if provided, this parameter must have either a unity length or the same length as "
      81             :                "'input_mesh_circular_boundaries'.");
      82          54 : }
      83             : 
      84             : std::unique_ptr<MeshBase>
      85          54 : CircularBoundaryCorrectionGenerator::generate()
      86             : {
      87          54 :   auto input_mesh = dynamic_cast<ReplicatedMesh *>(_input.get());
      88          54 :   if (!input_mesh)
      89           0 :     paramError("input", "Input is not a replicated mesh, which is required");
      90             : 
      91             :   // Check if the input mesh has the given boundaries
      92         130 :   for (const auto & bd : _input_mesh_circular_boundaries)
      93             :   {
      94          80 :     if (!MooseMeshUtils::hasBoundaryName(*input_mesh, bd))
      95           4 :       paramError("input_mesh_circular_boundaries",
      96           4 :                  "the boundary '" + bd + "' does not exist in the input mesh.");
      97             :   }
      98             :   _input_mesh_circular_bids =
      99          50 :       MooseMeshUtils::getBoundaryIDs(*input_mesh, _input_mesh_circular_boundaries, false);
     100             : 
     101          50 :   auto side_list = input_mesh->get_boundary_info().build_side_list();
     102          50 :   input_mesh->get_boundary_info().build_node_list_from_side_list();
     103          50 :   auto node_list = input_mesh->get_boundary_info().build_node_list();
     104             : 
     105          50 :   std::vector<std::vector<Point>> input_circ_bds_pts(_input_mesh_circular_bids.size());
     106             :   // Loop over all the boundary nodes and store the nodes (points) on each of the circular
     107             :   // boundaries to be corrected
     108        1832 :   for (unsigned int i = 0; i < node_list.size(); ++i)
     109             :   {
     110        1786 :     auto node_list_it = std::find(_input_mesh_circular_bids.begin(),
     111             :                                   _input_mesh_circular_bids.end(),
     112        1786 :                                   std::get<1>(node_list[i]));
     113        1786 :     if (node_list_it != _input_mesh_circular_bids.end())
     114             :     {
     115         842 :       input_circ_bds_pts[std::distance(_input_mesh_circular_bids.begin(), node_list_it)].push_back(
     116         842 :           input_mesh->point(std::get<0>(node_list[i])));
     117         842 :       if (!MooseUtils::absoluteFuzzyEqual(
     118         842 :               input_circ_bds_pts[std::distance(_input_mesh_circular_bids.begin(), node_list_it)]
     119         842 :                   .back()(2),
     120        1684 :               0.0))
     121           4 :         paramError("input_mesh_circular_boundaries", "Only boundaries in XY plane are supported.");
     122             :     }
     123             :   }
     124             :   std::vector<std::vector<std::pair<Point, Point>>> input_circ_bds_sds(
     125          46 :       _input_mesh_circular_bids.size());
     126             :   std::vector<std::vector<std::pair<dof_id_type, dof_id_type>>> input_circ_bds_sds_ids(
     127          46 :       _input_mesh_circular_bids.size());
     128             :   // Loop over all the boundary sides and store the sides (node pairs) on each of the circular
     129             :   // boundaries to be corrected
     130        1800 :   for (unsigned int i = 0; i < side_list.size(); ++i)
     131             :   {
     132        1754 :     auto side_list_it = std::find(_input_mesh_circular_bids.begin(),
     133             :                                   _input_mesh_circular_bids.end(),
     134        1754 :                                   std::get<2>(side_list[i]));
     135        1754 :     if (side_list_it != _input_mesh_circular_bids.end())
     136             :     {
     137             :       const auto side =
     138         814 :           input_mesh->elem_ptr(std::get<0>(side_list[i]))->side_ptr(std::get<1>(side_list[i]));
     139             :       // In case the sideset contains a pair of duplicated sides with different orientations, we
     140             :       // only store the first side found.
     141        1628 :       if (std::find(
     142         814 :               input_circ_bds_sds_ids[std::distance(_input_mesh_circular_bids.begin(), side_list_it)]
     143             :                   .begin(),
     144         814 :               input_circ_bds_sds_ids[std::distance(_input_mesh_circular_bids.begin(), side_list_it)]
     145             :                   .end(),
     146        1628 :               std::make_pair(side->node_id(0), side->node_id(1))) ==
     147         814 :               input_circ_bds_sds_ids[std::distance(_input_mesh_circular_bids.begin(), side_list_it)]
     148        2442 :                   .end() &&
     149        1628 :           std::find(
     150         814 :               input_circ_bds_sds_ids[std::distance(_input_mesh_circular_bids.begin(), side_list_it)]
     151             :                   .begin(),
     152         814 :               input_circ_bds_sds_ids[std::distance(_input_mesh_circular_bids.begin(), side_list_it)]
     153             :                   .end(),
     154        1628 :               std::make_pair(side->node_id(1), side->node_id(0))) ==
     155         814 :               input_circ_bds_sds_ids[std::distance(_input_mesh_circular_bids.begin(), side_list_it)]
     156        1628 :                   .end())
     157             :       {
     158         814 :         input_circ_bds_sds[std::distance(_input_mesh_circular_bids.begin(), side_list_it)]
     159         814 :             .push_back(std::make_pair(side->point(0), side->point(1)));
     160         814 :         input_circ_bds_sds_ids[std::distance(_input_mesh_circular_bids.begin(), side_list_it)]
     161         814 :             .push_back(std::make_pair(side->node_id(0), side->node_id(1)));
     162             :       }
     163         814 :     }
     164             :   }
     165             : 
     166             :   // Computes the correction coefficient and apply it;
     167             :   // Also records the nodes that have been modified to avert double-correction
     168          46 :   std::set<dof_id_type> mod_node_list;
     169             :   // And check if any of the boundaries are partial circles
     170          46 :   bool has_partial_circle = false;
     171          98 :   for (unsigned int i = 0; i < input_circ_bds_pts.size(); i++)
     172             :   {
     173          64 :     auto & input_circ_bd_pts = input_circ_bds_pts[i];
     174          64 :     if (input_circ_bd_pts.size() < 3)
     175           4 :       paramError("input_mesh_circular_boundaries",
     176             :                  "too few nodes in one of the provided boundaries.");
     177             :     Real bdry_rad;
     178             :     const Point boundary_origin =
     179          60 :         circularCenterCalculator(input_circ_bd_pts, bdry_rad, _custom_circular_tolerance[i]);
     180             : 
     181             :     // Check if the boundary is a full circle or partial
     182             :     // Also make an ordered array of nodes
     183             :     Real dummy_max_rad;
     184          56 :     std::vector<dof_id_type> ordered_node_list;
     185             :     bool is_bdry_closed;
     186         112 :     FillBetweenPointVectorsTools::isClosedLoop(*input_mesh,
     187             :                                                dummy_max_rad,
     188             :                                                ordered_node_list,
     189          56 :                                                input_circ_bds_sds_ids[i],
     190             :                                                boundary_origin,
     191             :                                                "boundary",
     192             :                                                is_bdry_closed,
     193             :                                                true);
     194             : 
     195          56 :     has_partial_circle = has_partial_circle || !is_bdry_closed;
     196             :     // If the user selects to move the end nodes of a partial circular boundary, we need to
     197             :     // calculate the displacement of the end nodes, which differs from the displacement of the
     198             :     // other nodes
     199             :     Real end_node_disp;
     200             :     // c_coeff is the coefficient used to scale the azimuthal angles
     201             :     // corr_factor > 1 means the partial boundary is less than a half circle
     202             :     // corr_factor < 1 means the partial boundary is more than a half circle
     203             :     // corr_factor = 1 means the boundary is a full circle or a half circle
     204             :     Real c_coeff;
     205          56 :     const Real corr_factor = generateRadialCorrectionFactor(input_circ_bds_sds[i],
     206             :                                                             boundary_origin,
     207             :                                                             is_bdry_closed,
     208          56 :                                                             _move_end_nodes_in_span_direction,
     209             :                                                             c_coeff,
     210             :                                                             end_node_disp);
     211             :     // Radial range that within which the nodes will be moved
     212          56 :     const Real transition_layer_thickness = _transition_layer_ratios[i] * bdry_rad;
     213             :     // For a partial boundary, we take out the start and end nodes from the ordered node list
     214          56 :     const Point pt_start = input_mesh->point(ordered_node_list.front());
     215          56 :     const Point pt_end = input_mesh->point(ordered_node_list.back());
     216             :     // The direction of the span of the partial boundary (which is an arc)
     217          56 :     const Point span_direction = is_bdry_closed ? Point(0.0, 0.0, 0.0) : (pt_start - pt_end).unit();
     218             :     // Although these are also calculated for a full circular boundary, they are not used
     219             : 
     220             :     // Find the center of the circular boundary's azimuthal angle and the half of the azimuthal
     221             :     // range
     222             :     Real center_bdry_azi;
     223             :     Real half_azi_range;
     224          56 :     if (MooseUtils::absoluteFuzzyEqual(c_coeff, 1.0))
     225             :     {
     226             :       // For a half circle, the center boundary azimuthal angle is the starting node's azimuthal
     227             :       // plus or minus pi/2
     228          46 :       const Point pt_second = input_mesh->point(ordered_node_list[1]);
     229             :       const Real pt_start_azi =
     230          46 :           std::atan2(pt_start(1) - boundary_origin(1), pt_start(0) - boundary_origin(0));
     231             :       const Real pt_second_azi =
     232          46 :           std::atan2(pt_second(1) - boundary_origin(1), pt_second(0) - boundary_origin(0));
     233             :       // Figure out whether it is plus or minus pi/2
     234          46 :       const Real azi_direction =
     235          46 :           (pt_second_azi - pt_start_azi > 0 or pt_second_azi - pt_start_azi < -M_PI) ? 1.0 : -1.0;
     236          46 :       center_bdry_azi = pt_start_azi + azi_direction * M_PI / 2.0;
     237          46 :       half_azi_range = M_PI / 2.0;
     238             :     }
     239             :     else
     240             :     {
     241             :       const Point center_bdry =
     242          10 :           (c_coeff > 1.0 ? 1.0 : -1.0) * (pt_start + pt_end) / 2.0 - boundary_origin;
     243             :       const Real pt_start_azi =
     244          10 :           std::atan2(pt_start(1) - boundary_origin(1), pt_start(0) - boundary_origin(0));
     245          10 :       center_bdry_azi = std::atan2(center_bdry(1), center_bdry(0));
     246          20 :       half_azi_range = std::abs(pt_start_azi - center_bdry_azi > M_PI
     247          10 :                                     ? pt_start_azi - center_bdry_azi - 2 * M_PI
     248           0 :                                     : (pt_start_azi - center_bdry_azi < -M_PI
     249           0 :                                            ? pt_start_azi - center_bdry_azi + 2 * M_PI
     250             :                                            : pt_start_azi - center_bdry_azi));
     251             :     }
     252             :     // Loop over all the nodes in the mesh to move the nodes in the transition layer so that the
     253             :     // circular boundary is corrected
     254        1936 :     for (auto & node : input_mesh->node_ptr_range())
     255             :     {
     256             :       // If the end nodes are set to move in the span direction, we should do so
     257        1902 :       if (node->id() == ordered_node_list.front() && _move_end_nodes_in_span_direction &&
     258          18 :           end_node_disp >= 0.0)
     259             :       {
     260          10 :         if (!mod_node_list.emplace((*node).id()).second)
     261           0 :           paramError("transition_layer_ratios", "the transition layers are overlapped.");
     262          10 :         (*node) = (*node) + end_node_disp * bdry_rad * span_direction;
     263        1218 :         continue;
     264             :       }
     265        1892 :       if (node->id() == ordered_node_list.back() && _move_end_nodes_in_span_direction &&
     266          18 :           end_node_disp >= 0.0)
     267             :       {
     268          10 :         if (!mod_node_list.emplace((*node).id()).second)
     269           0 :           paramError("transition_layer_ratios", "the transition layers are overlapped.");
     270          10 :         (*node) = (*node) - end_node_disp * bdry_rad * span_direction;
     271          10 :         continue;
     272             :       }
     273        1864 :       const Point tmp_pt = (*node) - boundary_origin;
     274        1864 :       const Real tmp_pt_azi = std::atan2(tmp_pt(1), tmp_pt(0));
     275        1864 :       const Real angle_diff =
     276        1864 :           tmp_pt_azi - center_bdry_azi > M_PI
     277        3496 :               ? tmp_pt_azi - center_bdry_azi - 2.0 * M_PI
     278        1632 :               : (tmp_pt_azi - center_bdry_azi < -M_PI ? tmp_pt_azi - center_bdry_azi + 2.0 * M_PI
     279             :                                                       : tmp_pt_azi - center_bdry_azi);
     280        1864 :       const Real pt_rad = tmp_pt.norm();
     281             :       // Skip the node if it is not within the radial and azimuthal range
     282         900 :       if ((!is_bdry_closed && MooseUtils::absoluteFuzzyGreaterThan(
     283         900 :                                   std::abs(angle_diff), half_azi_range, libMesh::TOLERANCE)) ||
     284        4390 :           pt_rad < bdry_rad - transition_layer_thickness ||
     285        1626 :           pt_rad > bdry_rad + transition_layer_thickness)
     286        1198 :         continue;
     287         666 :       const Real tmp_pt_azi_new =
     288         666 :           (_move_end_nodes_in_span_direction ? c_coeff : 1.0) * angle_diff + center_bdry_azi;
     289         666 :       const Real mod_corr_factor =
     290         666 :           pt_rad <= bdry_rad
     291         666 :               ? (1.0 + (corr_factor - 1.0) * (pt_rad - bdry_rad + transition_layer_thickness) /
     292             :                            transition_layer_thickness)
     293         134 :               : (1.0 + (corr_factor - 1.0) * (bdry_rad + transition_layer_thickness - pt_rad) /
     294             :                            transition_layer_thickness);
     295         666 :       if (!MooseUtils::absoluteFuzzyEqual(mod_corr_factor, 1.0))
     296             :       {
     297         666 :         if (!mod_node_list.emplace((*node).id()).second)
     298           4 :           paramError("transition_layer_ratios", "the transition layers are overlapped.");
     299             :       }
     300             :       const Point tmp_pt_alt =
     301         662 :           Point(pt_rad * std::cos(tmp_pt_azi_new), pt_rad * std::sin(tmp_pt_azi_new), tmp_pt(2)) *
     302         662 :           mod_corr_factor;
     303         662 :       (*node) = tmp_pt_alt + boundary_origin;
     304          52 :     }
     305          52 :   }
     306          34 :   if (!has_partial_circle && _move_end_nodes_in_span_direction)
     307           4 :     paramError("move_end_nodes_in_span_direction",
     308             :                "all the boundaries are closed, this parameter should be be set as 'true'.");
     309             :   // Loop over all the elements to check if any of them are inverted due to the correction
     310        1400 :   for (auto & elem : input_mesh->element_ptr_range())
     311        1370 :     if (elem->volume() < 0.0)
     312           0 :       paramError("transition_layer_ratios",
     313             :                  "some elements are inverted during circular boundary correction, consider tuning "
     314          30 :                  "this parameter.");
     315             : 
     316          60 :   return dynamic_pointer_cast<MeshBase>(_input);
     317          30 : }
     318             : 
     319             : Point
     320          60 : CircularBoundaryCorrectionGenerator::circularCenterCalculator(const std::vector<Point> & pts_list,
     321             :                                                               Real & radius,
     322             :                                                               const Real tol) const
     323             : {
     324             :   // Usually, just using the first three points would work
     325             :   // Here a more complex selection is made in case the first three points are too close to each
     326             :   // other.
     327             :   // Step 1: find the farthest point from the first point
     328          60 :   Real tmp_dis(0.0);
     329          60 :   unsigned int farthest_pt_id(0);
     330         830 :   for (unsigned int i = 1; i < pts_list.size(); i++)
     331         770 :     if ((pts_list[i] - pts_list[0]).norm() > tmp_dis)
     332             :     {
     333         458 :       tmp_dis = (pts_list[i] - pts_list[0]).norm();
     334         458 :       farthest_pt_id = i;
     335             :     }
     336             :   // Step 2: find the third point that is nearly in the middle of the first two
     337          60 :   unsigned int mid_pt_id(0);
     338         830 :   for (unsigned int i = 1; i < pts_list.size(); i++)
     339        1480 :     if (i != farthest_pt_id && std::abs((pts_list[farthest_pt_id] - pts_list[i]).norm() -
     340        1480 :                                         (pts_list[0] - pts_list[i]).norm()) < tmp_dis)
     341             :     {
     342         244 :       tmp_dis = std::abs((pts_list[farthest_pt_id] - pts_list[i]).norm() -
     343         244 :                          (pts_list[0] - pts_list[i]).norm());
     344         244 :       mid_pt_id = i;
     345             :     }
     346             : 
     347          60 :   const Real x12 = pts_list[0](0) - pts_list[farthest_pt_id](0);
     348          60 :   const Real x13 = pts_list[0](0) - pts_list[mid_pt_id](0);
     349             : 
     350          60 :   const Real y12 = pts_list[0](1) - pts_list[farthest_pt_id](1);
     351          60 :   const Real y13 = pts_list[0](1) - pts_list[mid_pt_id](1);
     352             : 
     353             :   const Real sx13 =
     354          60 :       pts_list[0](0) * pts_list[0](0) - pts_list[mid_pt_id](0) * pts_list[mid_pt_id](0);
     355             :   const Real sy13 =
     356          60 :       pts_list[0](1) * pts_list[0](1) - pts_list[mid_pt_id](1) * pts_list[mid_pt_id](1);
     357             : 
     358             :   const Real sx21 =
     359          60 :       pts_list[farthest_pt_id](0) * pts_list[farthest_pt_id](0) - pts_list[0](0) * pts_list[0](0);
     360             :   const Real sy21 =
     361          60 :       pts_list[farthest_pt_id](1) * pts_list[farthest_pt_id](1) - pts_list[0](1) * pts_list[0](1);
     362             : 
     363          60 :   const Real f =
     364          60 :       (sx13 * x12 + sy13 * x12 + sx21 * x13 + sy21 * x13) / (2.0 * (-y13 * x12 + y12 * x13));
     365          60 :   const Real g =
     366          60 :       (sx13 * y12 + sy13 * y12 + sx21 * y13 + sy21 * y13) / (2.0 * (-x13 * y12 + x12 * y13));
     367             : 
     368          60 :   const Real c = -pts_list[0](0) * pts_list[0](0) - pts_list[0](1) * pts_list[0](1) -
     369          60 :                  2.0 * g * pts_list[0](0) - 2.0 * f * pts_list[0](1);
     370          60 :   const Real r2 = f * f + g * g - c;
     371             : 
     372          60 :   radius = std::sqrt(r2);
     373          60 :   const Point circ_origin = Point(-g, -f, 0.0);
     374             : 
     375         734 :   for (const auto & pt : pts_list)
     376         678 :     if (!MooseUtils::absoluteFuzzyEqual((pt - circ_origin).norm(), radius, tol))
     377           4 :       paramError("input_mesh_circular_boundaries",
     378           4 :                  "the boundary provided is not circular. The generator found point " +
     379           8 :                      Moose::stringify(circ_origin) +
     380           4 :                      " to be the likely circle origin to the boundary, with a radius of " +
     381           8 :                      std::to_string(radius) + ". However, boundary node " + Moose::stringify(pt) +
     382           8 :                      " is at a radius of " + std::to_string((pt - circ_origin).norm()));
     383          56 :   return circ_origin;
     384             : }
     385             : 
     386             : Real
     387          56 : CircularBoundaryCorrectionGenerator::generateRadialCorrectionFactor(
     388             :     const std::vector<std::pair<Point, Point>> & bd_side_list,
     389             :     const Point & circle_center,
     390             :     const bool is_closed_loop,
     391             :     const bool move_end_nodes_in_span_direction,
     392             :     Real & c_coeff,
     393             :     Real & end_node_disp) const
     394             : {
     395          56 :   if (bd_side_list.empty())
     396           0 :     mooseError("The 'bd_side_list' argument of "
     397             :                "CircularBoundaryCorrectionGenerator::generateRadialCorrectionFactor is empty.");
     398          56 :   Real acc_area(0.0);
     399          56 :   Real acc_azi(0.0);
     400          56 :   std::vector<Real> d_azi_list;
     401         706 :   for (const auto & bd_side : bd_side_list)
     402             :   {
     403         650 :     const Point v1 = bd_side.first - circle_center;
     404         650 :     const Point v2 = bd_side.second - circle_center;
     405             :     // Use cross to calculate r * r * sin(d_azi_i) and then normalized by r * r to get sin(d_azi_i)
     406         650 :     acc_area += v1.cross(v2).norm() / v1.norm() / v2.norm();
     407         650 :     const Real azi1 = std::atan2(v1(1), v1(0));
     408         650 :     const Real azi2 = std::atan2(v2(1), v2(0));
     409             :     const Real d_azi =
     410         650 :         std::abs(azi1 - azi2) > M_PI ? 2 * M_PI - std::abs(azi1 - azi2) : std::abs(azi1 - azi2);
     411         650 :     acc_azi += d_azi;
     412         650 :     d_azi_list.push_back(d_azi);
     413             :   }
     414             : 
     415          56 :   if (!MooseUtils::absoluteFuzzyEqual(acc_azi, M_PI) && !is_closed_loop &&
     416             :       move_end_nodes_in_span_direction)
     417             :   {
     418          10 :     const Real k_1 = 1.0 + std::cos(acc_azi);
     419          10 :     const Real k_2 = acc_azi - std::sin(acc_azi);
     420             : 
     421          10 :     unsigned int ct = 0;
     422          10 :     Real diff = 1.0;
     423          10 :     Real c_old = 1.0;
     424          10 :     Real c_new = 1.0;
     425          30 :     while (diff >= libMesh::TOLERANCE && ct < 100)
     426             :     {
     427          20 :       c_old = c_new;
     428          20 :       const Real gc = k_2 + k_2 * std::cos(acc_azi * c_old) + k_1 * std::sin(acc_azi * c_old) -
     429          20 :                       k_1 * sineSummation(d_azi_list, c_old);
     430          20 :       const Real gcp = -k_2 * acc_azi * std::sin(acc_azi * c_old) +
     431          20 :                        k_1 * acc_azi * std::cos(acc_azi * c_old) -
     432          20 :                        k_1 * sinePrimeSummation(d_azi_list, c_old);
     433          20 :       c_new = c_old - gc / gcp;
     434          20 :       diff = std::abs(c_new - c_old);
     435          20 :       ct++;
     436             :     }
     437             : 
     438          10 :     if (ct >= 100)
     439           0 :       mooseError("Newton-Raphson method did not converge for generating the radial correction "
     440             :                  "factor for circular area preservation.");
     441             : 
     442          10 :     const Real norm_corr_factor = std::cos(0.5 * acc_azi) / std::cos(0.5 * acc_azi * c_new);
     443          10 :     end_node_disp = norm_corr_factor * std::sin(0.5 * acc_azi * c_new) - std::sin(0.5 * acc_azi);
     444          10 :     c_coeff = c_new;
     445             : 
     446          10 :     return norm_corr_factor;
     447             :   }
     448          46 :   end_node_disp = -1.0;
     449          46 :   c_coeff = 1.0;
     450          46 :   return std::sqrt(acc_azi / acc_area);
     451          56 : }
     452             : 
     453             : Real
     454          20 : CircularBoundaryCorrectionGenerator::sineSummation(const std::vector<Real> & th_list,
     455             :                                                    const Real coeff) const
     456             : {
     457          20 :   Real acc(0.0);
     458         220 :   for (const auto & th : th_list)
     459         200 :     acc += std::sin(th * coeff);
     460          20 :   return acc;
     461             : }
     462             : 
     463             : Real
     464          20 : CircularBoundaryCorrectionGenerator::sinePrimeSummation(const std::vector<Real> & th_list,
     465             :                                                         const Real coeff) const
     466             : {
     467             :   // Note that this calculates the derivative with regards to coeff
     468          20 :   Real acc(0.0);
     469         220 :   for (const auto & th : th_list)
     470         200 :     acc += th * std::cos(th * coeff);
     471          20 :   return acc;
     472             : }

Generated by: LCOV version 1.14