LCOV - code coverage report
Current view: top level - src/functions - MultiControlDrumFunction.C (source / functions) Hit Total Coverage
Test: idaholab/moose reactor: #31405 (292dce) with base fef103 Lines: 115 117 98.3 %
Date: 2025-09-04 07:56:24 Functions: 3 3 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 "MultiControlDrumFunction.h"
      11             : 
      12             : registerMooseObject("ReactorApp", MultiControlDrumFunction);
      13             : 
      14             : InputParameters
      15          54 : MultiControlDrumFunction::validParams()
      16             : {
      17          54 :   InputParameters params = Function::validParams();
      18          54 :   params.addClassDescription(
      19             :       "A function that returns an absorber fraction for multiple control drums application.");
      20         108 :   params.addRequiredParam<MeshGeneratorName>(
      21             :       "mesh_generator",
      22             :       "Name of the mesh generator to be used to retrieve control drums information.");
      23         108 :   params.addRequiredParam<std::vector<Real>>(
      24             :       "angular_speeds", "Vector of angular rotation speeds of the control drum.");
      25         108 :   params.addRequiredParam<std::vector<Real>>(
      26             :       "start_angles",
      27             :       "Vector of initial angular positions of the beginning of the absorber components.");
      28         108 :   params.addRequiredParam<std::vector<Real>>(
      29             :       "angle_ranges", "Vector of angular ranges of the beginning of the absorber components.");
      30         108 :   params.addParam<Real>(
      31         108 :       "rotation_start_time", 0.0, "The time point at which the control drums start rotating.");
      32         108 :   params.addParam<Real>("rotation_end_time",
      33         108 :                         std::numeric_limits<Real>::max(),
      34             :                         "The time point at which the control drums stop rotating.");
      35         108 :   params.addParam<bool>(
      36         108 :       "use_control_drum_id", true, "Whether extra element id user_control_drum_id is used.");
      37         108 :   params.addParamNamesToGroup("use_control_drum_id", "Advanced");
      38             : 
      39          54 :   return params;
      40           0 : }
      41             : 
      42          32 : MultiControlDrumFunction::MultiControlDrumFunction(const InputParameters & parameters)
      43             :   : Function(parameters),
      44             :     MeshMetaDataInterface(this),
      45             :     ElementIDInterface(this),
      46           0 :     _mesh_generator(getParam<MeshGeneratorName>("mesh_generator")),
      47          64 :     _angular_speeds(getParam<std::vector<Real>>("angular_speeds")),
      48          64 :     _start_angles(getParam<std::vector<Real>>("start_angles")),
      49          64 :     _angle_ranges(getParam<std::vector<Real>>("angle_ranges")),
      50          64 :     _rotation_start_time(getParam<Real>("rotation_start_time")),
      51          64 :     _rotation_end_time(getParam<Real>("rotation_end_time")),
      52          64 :     _use_control_drum_id(getParam<bool>("use_control_drum_id")),
      53          32 :     _control_drum_id(_use_control_drum_id ? getElementIDByName("control_drum_id")
      54             :                                           : DofObject::invalid_id),
      55          32 :     _control_drum_positions(
      56          32 :         getMeshProperty<std::vector<Point>>("control_drum_positions", _mesh_generator)),
      57          32 :     _control_drums_azimuthal_meta(getMeshProperty<std::vector<std::vector<Real>>>(
      58          32 :         "control_drums_azimuthal_meta", _mesh_generator))
      59             : {
      60          32 :   if (_angular_speeds.size() != _start_angles.size())
      61           2 :     paramError("start_angles",
      62             :                "the size of 'start_angles' must be identical to the size of 'angular_speeds'");
      63          30 :   if (_angular_speeds.size() != _angle_ranges.size())
      64           2 :     paramError("angle_ranges",
      65             :                "the size of 'angle_ranges' must be identical to the size of 'angular_speeds'");
      66          28 :   if (_angular_speeds.size() != _control_drum_positions.size())
      67           2 :     paramError("angular_speeds",
      68             :                "the size of this parameter must be identical to the control drum number recorded "
      69             :                "in the MeshMetaData.");
      70          26 :   if (_rotation_end_time <= _rotation_start_time)
      71           2 :     paramError("rotation_end_time", "this parameter must be larger than rotation_start_time.");
      72          24 : }
      73             : 
      74             : Real
      75        6912 : MultiControlDrumFunction::value(Real t, const Point & p) const
      76             : {
      77             :   // Calculate the effective rotation time
      78        6912 :   const Real t_eff = t < _rotation_start_time
      79        6912 :                          ? 0.0
      80        6912 :                          : (t > _rotation_end_time ? (_rotation_end_time - _rotation_start_time)
      81             :                                                    : t - _rotation_start_time);
      82             :   // Find the closest control drum for a given Point p; only needed if control drum id is not used.
      83             :   std::vector<std::pair<Real, unsigned int>> meshcontrol_drum_dist_vec;
      84        6912 :   if (!_use_control_drum_id)
      85             :   {
      86       24192 :     for (unsigned int i = 0; i < _control_drum_positions.size(); i++)
      87             :     {
      88       20736 :       Real cd_dist = std::sqrt(Utility::pow<2>(p(0) - _control_drum_positions[i](0)) +
      89       20736 :                                Utility::pow<2>(p(1) - _control_drum_positions[i](1)));
      90       20736 :       meshcontrol_drum_dist_vec.push_back(std::make_pair(cd_dist, i));
      91             :     }
      92        3456 :     std::sort(meshcontrol_drum_dist_vec.begin(), meshcontrol_drum_dist_vec.end());
      93             :   }
      94        6912 :   const unsigned int cd_id = _use_control_drum_id
      95        6912 :                                  ? (_control_drum_id > 0 ? _control_drum_id - 1 : 0)
      96        6912 :                                  : meshcontrol_drum_dist_vec.front().second;
      97        6912 :   const auto & cd_pos = _control_drum_positions[cd_id];
      98             : 
      99        6912 :   Real dynamic_start_angle = (_angular_speeds[cd_id] * t_eff + _start_angles[cd_id]) / 180.0 * M_PI;
     100        6912 :   Real dynamic_end_angle = dynamic_start_angle + _angle_ranges[cd_id] / 180.0 * M_PI;
     101             : 
     102        6912 :   dynamic_start_angle = atan2(std::sin(dynamic_start_angle),
     103        6912 :                               std::cos(dynamic_start_angle)) /
     104        6912 :                         M_PI * 180.0; // quick way to move to -M_PI to M_PI
     105        6912 :   dynamic_end_angle = atan2(std::sin(dynamic_end_angle),
     106        6912 :                             std::cos(dynamic_end_angle)) /
     107        6912 :                       M_PI * 180.0; // quick way to move to -M_PI to M_PI
     108             : 
     109             :   // Locate the first mesh azimuthal angle that is greater than dynamic_start_angle
     110        6912 :   auto start_bound = std::upper_bound(_control_drums_azimuthal_meta[cd_id].begin(),
     111        6912 :                                       _control_drums_azimuthal_meta[cd_id].end(),
     112             :                                       dynamic_start_angle);
     113             :   // Locate the first mesh azimuthal angle that is greater than dynamic_end_angle
     114        6912 :   auto end_bound = std::upper_bound(_control_drums_azimuthal_meta[cd_id].begin(),
     115        6912 :                                     _control_drums_azimuthal_meta[cd_id].end(),
     116             :                                     dynamic_end_angle);
     117             : 
     118             :   // Locate the elements that contain the start/end angles.
     119             :   // This part seems long; but it only solves one simple issue -> transition from M_PI to -M_PI
     120             : 
     121             :   // The two azimuthal ends of the element that the start angle intercepts;
     122             :   // and the two azimuthal ends of the element that the end angle intercepts;
     123             :   Real start_low, start_high, end_low, end_high;
     124             :   // This means that the dynamic_start_angle is greater than the lowest mesh azimuthal angle and
     125             :   // lower than the greatest mesh azimuthal angle. Namely, dynamic_start_angle does not cause the
     126             :   // "transition from M_PI to -M_PI" issue.
     127        6912 :   if (start_bound != _control_drums_azimuthal_meta[cd_id].begin() &&
     128             :       start_bound != _control_drums_azimuthal_meta[cd_id].end())
     129             :   {
     130        6816 :     start_low = *(start_bound - 1);
     131        6816 :     start_high = *start_bound;
     132             :   }
     133             :   // On the contrary, this means the dynamic_start_angle intercepts an element across the
     134             :   // M_PI(-M_PI) azimuthal angle. Namely, start_high is actually lower than start_low because of
     135             :   // this transition.
     136             :   else
     137             :   {
     138          96 :     start_high = _control_drums_azimuthal_meta[cd_id].front();
     139          96 :     start_low = _control_drums_azimuthal_meta[cd_id].back();
     140             :   }
     141             : 
     142             :   // This means that dynamic_end_angle is greater than the lowest mesh azimuthal angle and lower
     143             :   // than the greatest mesh azimuthal angle. Namely, dynamic_end_angle does not cause the
     144             :   // "transition from M_PI to -M_PI" issue.
     145        6912 :   if (end_bound != _control_drums_azimuthal_meta[cd_id].begin() &&
     146             :       end_bound != _control_drums_azimuthal_meta[cd_id].end())
     147             :   {
     148        6528 :     end_low = *(end_bound - 1);
     149        6528 :     end_high = *end_bound;
     150             :   }
     151             :   // On the contrary, this means the dynamic_end_angle intercepts an element across the
     152             :   // M_PI(-M_PI) azimuthal angle. Namely, end_high is actually lower than end_low because of
     153             :   // this transition.
     154             :   else
     155             :   {
     156         384 :     end_high = _control_drums_azimuthal_meta[cd_id].front();
     157         384 :     end_low = _control_drums_azimuthal_meta[cd_id].back();
     158             :   }
     159             : 
     160             :   // azimuthal_p is the azimuthal angle of the point whose functon value needs to be calculated.
     161        6912 :   Real azimuthal_p = atan2(p(1) - cd_pos(1), p(0) - cd_pos(0)) / M_PI * 180;
     162             : 
     163             :   // If there is not periodical transition from M_PI to -M_PI, we always have:
     164             :   // start_low < start_high < end_low < end_high
     165             :   // In the presence of "transition from M_PI to -M_PI", the relation above is compromised.
     166             :   // Based on how the relation is compromised, we can tell where the transition occurs.
     167             : 
     168             :   // Most trivial scenario -> no transition from M_PI to -M_PI is involved (it happens to a pure
     169             :   // reflector element)
     170        6912 :   if (end_high >= start_low)
     171             :   {
     172             :     // the point is located in an element that is purely absorber.
     173        5952 :     if (azimuthal_p >= start_high && azimuthal_p <= end_low)
     174             :       return 100.0;
     175             :     // the point is located in an element that is purely reflector.
     176        5034 :     else if (azimuthal_p <= start_low || azimuthal_p >= end_high)
     177             :       return 0.0;
     178             :     // the point is located in a mixed element intercepted by dynamic_start_angle so that volume
     179             :     // fraction is calculated.
     180         744 :     else if (azimuthal_p < start_high && azimuthal_p > start_low)
     181             :     {
     182         372 :       Real start_interval = start_high - start_low;
     183         372 :       Real stab_interval = start_high - dynamic_start_angle;
     184         372 :       return stab_interval / start_interval * 100.0;
     185             :     }
     186             :     // the point is located in a mixed element intercepted by dynamic_end_angle so that volume
     187             :     // fraction is calculated.
     188             :     else
     189             :     {
     190         372 :       Real end_interval = end_high - end_low;
     191         372 :       Real endab_interval = dynamic_end_angle - end_low;
     192         372 :       return endab_interval / end_interval * 100.0;
     193             :     }
     194             :   }
     195             :   // The element intercepted by dynamic_end_angle has the "transition from M_PI to -M_PI" issue.
     196             :   // This is still relatively simple because only one of the mixed element is affected.
     197         960 :   else if (end_low >= end_high)
     198             :   {
     199             :     // (Not affected) the point is located in an element that is purely absorber.
     200         384 :     if (azimuthal_p >= start_high && azimuthal_p <= end_low)
     201             :       return 100.0;
     202             :     // (Affected) the point is located in an element that is purely reflector.
     203         286 :     else if (azimuthal_p <= start_low && azimuthal_p >= end_high)
     204             :       return 0.0;
     205             :     // (Not affected) the point is located in a mixed element intercepted by dynamic_start_angle so
     206             :     // that volume fraction is calculated.
     207          48 :     else if (azimuthal_p < start_high && azimuthal_p > start_low)
     208             :     {
     209          24 :       Real start_interval = start_high - start_low;
     210          24 :       Real stab_interval = start_high - dynamic_start_angle;
     211          24 :       return stab_interval / start_interval * 100.0;
     212             :     }
     213             :     // (Affected) the point is located in a mixed element intercepted by dynamic_end_angle so that
     214             :     // volume fraction is calculated. As this element is also intercepted by azimuthal_angle = M_PI
     215             :     // (-M_PI), an extra 2 * M_PI (360 degrees) shift is used to correct this transition effect.
     216             :     else
     217             :     {
     218          24 :       Real end_interval = end_high - end_low + 360.0;
     219          24 :       Real endab_interval = (dynamic_end_angle - end_low >= 0.0)
     220          24 :                                 ? (dynamic_end_angle - end_low)
     221             :                                 : (dynamic_end_angle - end_low + 360.0);
     222          24 :       return endab_interval / end_interval * 100.0;
     223             :     }
     224             :   }
     225             :   // The "transition from M_PI to -M_PI" happens to an pure absorber element
     226         576 :   else if (start_high >= end_low)
     227             :   {
     228             :     // (Affected) the point is located in an element that is purely absorber.
     229         480 :     if (azimuthal_p >= start_high || azimuthal_p <= end_low)
     230             :       return 100.0;
     231             :     // (Affected) the point is located in an element that is purely reflector.
     232         372 :     else if (azimuthal_p >= end_high && azimuthal_p <= start_low)
     233             :       return 0.0;
     234             :     // (Not affected) the point is located in a mixed element intercepted by dynamic_start_angle so
     235             :     // that volume fraction is calculated.
     236          60 :     else if (azimuthal_p < start_high && azimuthal_p > start_low)
     237             :     {
     238          30 :       Real start_interval = start_high - start_low;
     239          30 :       Real stab_interval = start_high - dynamic_start_angle;
     240          30 :       return stab_interval / start_interval * 100.0;
     241             :     }
     242             :     // (Not affected) the point is located in a mixed element intercepted by dynamic_end_angle so
     243             :     // that volume fraction is calculated.
     244             :     else
     245             :     {
     246          30 :       Real end_interval = end_high - end_low;
     247          30 :       Real endab_interval = dynamic_end_angle - end_low;
     248          30 :       return endab_interval / end_interval * 100.0;
     249             :     }
     250             :   }
     251             :   // The element intercepted by dynamic_start_angle has the "transition from M_PI to -M_PI" issue.
     252             :   // This is still relatively simple because only one of the mixed element is affected.
     253             :   else
     254             :   {
     255             :     // (Not affected) the point is located in an element that is purely absorber.
     256          96 :     if (azimuthal_p >= start_high && azimuthal_p <= end_low)
     257             :       return 100.0;
     258             :     // (Affected) the point is located in an element that is purely reflector.
     259          84 :     else if (azimuthal_p >= end_high && azimuthal_p <= start_low)
     260             :       return 0.0;
     261             :     // (Affected) the point is located in a mixed element intercepted by dynamic_start_angle so that
     262             :     // volume fraction is calculated. As this element is also intercepted by azimuthal_angle = M_PI
     263             :     // (-M_PI), an extra 2 * M_PI (360 degrees) shift is used to correct this transition effect.
     264          12 :     else if (azimuthal_p > start_low || azimuthal_p < start_high)
     265             :     {
     266           6 :       Real start_interval = start_high - start_low + 360.0;
     267           6 :       Real stab_interval = (start_high - dynamic_start_angle >= 0.0)
     268           6 :                                ? (start_high - dynamic_start_angle)
     269             :                                : (start_high - dynamic_start_angle + 360.0);
     270           6 :       return stab_interval / start_interval * 100.0;
     271             :     }
     272             :     // (Not affected) the point is located in a mixed element intercepted by dynamic_end_angle so
     273             :     // that volume fraction is calculated.
     274             :     else
     275             :     {
     276           6 :       Real end_interval = end_high - end_low;
     277           6 :       Real endab_interval = dynamic_end_angle - end_low;
     278           6 :       return endab_interval / end_interval * 100.0;
     279             :     }
     280             :   }
     281        6912 : }

Generated by: LCOV version 1.14