LCOV - code coverage report
Current view: top level - src/vectorpostprocessors - AverageSectionValueSampler.C (source / functions) Hit Total Coverage
Test: idaholab/moose solid_mechanics: f45d79 Lines: 154 166 92.8 %
Date: 2025-07-25 05:00:39 Functions: 9 9 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 "AverageSectionValueSampler.h"
      11             : #include "MooseMesh.h"
      12             : #include "SystemBase.h"
      13             : #include "Conversion.h"
      14             : #include <limits>
      15             : 
      16             : registerMooseObject("SolidMechanicsApp", AverageSectionValueSampler);
      17             : 
      18             : InputParameters
      19          92 : AverageSectionValueSampler::validParams()
      20             : {
      21          92 :   InputParameters params = GeneralVectorPostprocessor::validParams();
      22          92 :   params.addClassDescription("Compute the section's variable average in three-dimensions "
      23             :                              "given a user-defined definition of the cross section.");
      24         184 :   params.addParam<std::vector<SubdomainName>>(
      25             :       "block",
      26             :       "The list of blocks in which to search for cross sectional nodes to compute the variable "
      27             :       "average.");
      28         184 :   params.addRequiredParam<Point>("axis_direction", "Direction of the structural component's axis");
      29         184 :   params.addRequiredParam<Point>("reference_point",
      30             :                                  "Structural component reference starting point from which the "
      31             :                                  "input parameter 'lengths' applies.");
      32         184 :   params.addParam<Real>("cross_section_maximum_radius",
      33         184 :                         std::numeric_limits<Real>::max(),
      34             :                         "Radial distance with respect to the body axis within which nodes are "
      35             :                         "considered to belong to this "
      36             :                         "structural component. Used to disambiguate multiple components that share "
      37             :                         "the same mesh block.");
      38         184 :   params.addRequiredParam<std::vector<VariableName>>(
      39             :       "variables", "Variables for the cross section output. These variables must be nodal.");
      40         184 :   params.addDeprecatedParam<std::vector<Real>>(
      41             :       "lengths",
      42             :       {},
      43             :       "Positions along axis from reference_point at which to compute average values.",
      44             :       "Use the 'positions' parameter instead");
      45         184 :   params.addParam<std::vector<Real>>(
      46             :       "positions",
      47             :       {},
      48             :       "Positions along axis from reference_point at which to compute average values.");
      49          92 :   params.addParam<RealVectorValue>(
      50             :       "symmetry_plane",
      51          92 :       RealVectorValue(0, 0, 0),
      52             :       "Vector normal to a symmetry plane, used if the section has a symmetry plane through it. "
      53             :       "Causes the variables to be treated as components of a vector and zeros out the compomnent "
      54             :       "in the direction normal to symmetry_plane. Three variables must be defined in 'variables' "
      55             :       "to use this option.");
      56         184 :   params.addParam<Real>("tolerance",
      57         184 :                         1.0e-6,
      58             :                         "Maximum axial distance of nodes from the specified axial lengths to "
      59             :                         "consider them in the cross-section average");
      60         184 :   params.addParam<bool>(
      61             :       "require_equal_node_counts",
      62         184 :       true,
      63             :       "Whether to require the number of nodes at each axial location to be equal");
      64          92 :   return params;
      65           0 : }
      66             : 
      67          48 : AverageSectionValueSampler::AverageSectionValueSampler(const InputParameters & parameters)
      68             :   : GeneralVectorPostprocessor(parameters),
      69          48 :     _mesh(_app.actionWarehouse().mesh()),
      70          96 :     _variables(getParam<std::vector<VariableName>>("variables")),
      71          96 :     _direction(getParam<Point>("axis_direction")),
      72          96 :     _reference_point(getParam<Point>("reference_point")),
      73         192 :     _positions(isParamSetByUser("lengths") ? getParam<std::vector<Real>>("lengths")
      74         144 :                                            : getParam<std::vector<Real>>("positions")),
      75          96 :     _have_symmetry_plane(isParamSetByUser("symmetry_plane")),
      76          96 :     _symmetry_plane(getParam<RealVectorValue>("symmetry_plane")),
      77          48 :     _automatically_locate_positions(_positions.size() == 0),
      78          96 :     _tolerance(getParam<Real>("tolerance")),
      79          48 :     _number_of_nodes(_positions.size()),
      80          96 :     _cross_section_maximum_radius(getParam<Real>("cross_section_maximum_radius")),
      81          96 :     _require_equal_node_counts(getParam<bool>("require_equal_node_counts")),
      82          96 :     _need_mesh_initializations(true)
      83             : {
      84          48 :   if (parameters.isParamSetByUser("lengths") && parameters.isParamSetByUser("positions"))
      85           0 :     paramError("lengths", "The 'lengths' and 'positions' parameters cannot both be set.");
      86             : 
      87          96 :   if (parameters.isParamSetByUser("lengths") && _positions.size() == 0)
      88           0 :     paramError("lengths", "If 'lengths' is specified, at least one value must be provided");
      89             : 
      90          96 :   if (parameters.isParamSetByUser("positions") && _positions.size() == 0)
      91           2 :     paramError("positions", "If 'positions' is specified, at least one value must be provided");
      92             : 
      93          46 :   if (_mesh->dimension() != 3)
      94           0 :     mooseError("The AverageSectionValueSampler postprocessor can only be used with three "
      95             :                "dimensional meshes.");
      96             : 
      97          46 :   if (!MooseUtils::absoluteFuzzyEqual(_direction.norm_sq(), 1.0))
      98           0 :     paramError("axis_direction", "Axis direction must be a unit vector.");
      99             : 
     100          46 :   _output_vector.resize(_variables.size() + 2);
     101         180 :   for (const auto j : make_range(_variables.size()))
     102         134 :     _output_vector[j] = &declareVector(_variables[j]);
     103             :   const auto pos_idx = _variables.size();
     104          46 :   _output_vector[pos_idx] = &declareVector("axial_position");
     105          46 :   _output_vector[pos_idx + 1] = &declareVector("node_count");
     106             : 
     107         178 :   for (const auto j : make_range(_variables.size()))
     108             :   {
     109         134 :     if (!_sys.hasVariable(_variables[j]))
     110           2 :       paramError("variables",
     111             :                  "One or more of the specified variables do not exist in this system.");
     112         132 :     const MooseVariable & variable = _sys.getFieldVariable<Real>(_tid, _variables[j]);
     113         132 :     if (!variable.isNodal())
     114           0 :       paramError("variables",
     115             :                  "One or more of the specified variables are not defined at the nodes.");
     116         132 :     _var_numbers.push_back(variable.number());
     117             :   }
     118             : 
     119          44 :   if (_have_symmetry_plane)
     120             :   {
     121           6 :     const auto symm_plane_norm = _symmetry_plane.norm();
     122           6 :     if (MooseUtils::absoluteFuzzyEqual(symm_plane_norm, 0.0))
     123           0 :       mooseError("Vector defined in 'symmetry_plane' cannot have a norm of zero");
     124           6 :     _symmetry_plane /= _symmetry_plane.norm();
     125           6 :     if (_variables.size() != 3)
     126           0 :       paramError("variables",
     127             :                  "If 'symmetry_plane' is prescribed, three variables must be provided (for the x, "
     128             :                  "y, z vector components, in that order)");
     129           6 :     mooseInfo("In AverageSectionValueSampler " + name() +
     130          12 :               ": \nTreating the variables as vector components (" + _variables[0] + ", " +
     131          18 :               _variables[1] + ", " + _variables[2] +
     132             :               ") and zeroing out the component normal to the prescribed 'symmetry_plane'\n");
     133             :   }
     134          44 : }
     135             : 
     136             : void
     137          44 : AverageSectionValueSampler::initialSetup()
     138             : {
     139          44 : }
     140             : 
     141             : void
     142           8 : AverageSectionValueSampler::meshChanged()
     143             : {
     144           8 :   _need_mesh_initializations = true;
     145           8 : }
     146             : 
     147             : void
     148          52 : AverageSectionValueSampler::initialize()
     149             : {
     150          52 :   if (_need_mesh_initializations)
     151             :   {
     152             :     // Option 1: locate positions first, then check counts
     153          52 :     if (_automatically_locate_positions)
     154          34 :       automaticallyLocatePositions();
     155          52 :     _need_mesh_initializations = false;
     156             :   }
     157             : 
     158         312 :   for (const auto j : make_range(_variables.size() + 2))
     159             :   {
     160         260 :     _output_vector[j]->clear();
     161         260 :     _output_vector[j]->resize(_positions.size());
     162             :   }
     163          52 :   _number_of_nodes.clear();
     164          52 :   _number_of_nodes.resize(_positions.size());
     165          52 : }
     166             : 
     167             : void
     168          52 : AverageSectionValueSampler::finalize()
     169             : {
     170         208 :   for (const auto j : make_range(_variables.size()))
     171         156 :     _communicator.sum(*(_output_vector[j]));
     172             : 
     173          52 :   _communicator.sum(_number_of_nodes);
     174             : 
     175         470 :   for (const auto li : index_range(_positions))
     176         418 :     if (_number_of_nodes[li] < 1)
     177           0 :       mooseError("No nodes were found in AverageSectionValueSampler postprocessor.");
     178             : 
     179         470 :   for (const auto li : index_range(_positions))
     180        1672 :     for (const auto j : make_range(_variables.size()))
     181        1254 :       (*_output_vector[j])[li] /= _number_of_nodes[li];
     182             : 
     183             :   const auto pos_idx = _variables.size();
     184          52 :   (*_output_vector[pos_idx]) = _positions;
     185          52 :   std::vector<Real> num_nodes_real(_number_of_nodes.begin(), _number_of_nodes.end());
     186          52 :   (*_output_vector[pos_idx + 1]) = num_nodes_real;
     187             : 
     188          52 :   if (_require_equal_node_counts)
     189             :   {
     190         314 :     for (const auto li : index_range(_number_of_nodes))
     191             :     {
     192         276 :       if (_number_of_nodes[li] != _number_of_nodes[0])
     193             :       {
     194           2 :         std::ostringstream pos_out;
     195          26 :         for (const auto li2 : index_range(_number_of_nodes))
     196          24 :           pos_out << std::setw(10) << _positions[li2] << std::setw(10) << _number_of_nodes[li2]
     197          24 :                   << "\n";
     198           4 :         mooseError(
     199             :             "Node counts do not match for all axial positions. If this behavior "
     200             :             "is desired to accommodate nonuniform meshes, set "
     201           2 :             "'require_equal_node_counts=false'\n     Axial      Node\n  Position     Count\n" +
     202           0 :             pos_out.str());
     203           0 :       }
     204             :     }
     205             :   }
     206          50 : }
     207             : 
     208             : void
     209          52 : AverageSectionValueSampler::execute()
     210             : {
     211         104 :   _block_ids = _mesh->getSubdomainIDs(getParam<std::vector<SubdomainName>>("block"));
     212             : 
     213          52 :   auto * active_nodes = _mesh->getActiveSemiLocalNodeRange();
     214             : 
     215             :   std::vector<std::vector<Real>> output_vector_partial(_variables.size(),
     216          52 :                                                        std::vector<Real>(_positions.size()));
     217             : 
     218          52 :   const NumericVector<Number> & _sol = *_sys.currentSolution();
     219             : 
     220       24852 :   for (const auto & node : *active_nodes)
     221             :   {
     222       24800 :     const std::set<SubdomainID> & node_blk_ids = _mesh->getNodeBlockIds(*node);
     223             : 
     224      207076 :     for (const auto li : index_range(_positions))
     225             :     {
     226      349680 :       for (const auto i : _block_ids)
     227             :       {
     228      182276 :         if (node_blk_ids.find(i) != node_blk_ids.end())
     229             :         {
     230             :           // Check if node is within tolerance of user-prescribed plane
     231      182276 :           if (std::abs(axialPosition(*node) - _positions[li]) > _tolerance)
     232      167404 :             continue;
     233       15320 :           if ((*node).processor_id() != processor_id())
     234         448 :             continue;
     235             : 
     236             :           // Retrieve nodal variables
     237       14872 :           std::vector<Real> this_node_vars(_var_numbers.size());
     238       59488 :           for (const auto j : make_range(_var_numbers.size()))
     239       44616 :             this_node_vars[j] = _sol(node->dof_number(_sys.number(), _var_numbers[j], 0));
     240             : 
     241       14872 :           if (_have_symmetry_plane)
     242             :           {
     243             :             RealVectorValue this_node_vec_var(
     244             :                 this_node_vars[0], this_node_vars[1], this_node_vars[2]);
     245        1144 :             this_node_vec_var -= _symmetry_plane * this_node_vec_var * _symmetry_plane;
     246        1144 :             this_node_vars[0] = this_node_vec_var(0);
     247        1144 :             this_node_vars[1] = this_node_vec_var(1);
     248        1144 :             this_node_vars[2] = this_node_vec_var(2);
     249             :           }
     250             : 
     251       59488 :           for (const auto j : make_range(_var_numbers.size()))
     252       44616 :             output_vector_partial[j][li] += this_node_vars[j];
     253             : 
     254       14872 :           _number_of_nodes[li]++;
     255             :           break;
     256             :         }
     257             :       }
     258             :     }
     259             :   }
     260             : 
     261         208 :   for (const auto j : make_range(_variables.size()))
     262         156 :     *_output_vector[j] = output_vector_partial[j];
     263          52 : }
     264             : 
     265             : Real
     266      195996 : AverageSectionValueSampler::axialPosition(const Node & node) const
     267             : {
     268             :   // Compute node location w.r.t. structural component length
     269             :   const Point relative_distance{
     270      195996 :       node(0) - _reference_point(0), node(1) - _reference_point(1), node(2) - _reference_point(2)};
     271             : 
     272             :   const Real axial_position = _direction * relative_distance;
     273             :   const Real in_plane_distance =
     274      195996 :       (relative_distance - relative_distance * _direction * _direction).norm();
     275             : 
     276             :   // If the in-plane distance is greater than the specified cross-section radius, the point is not
     277             :   // in this component
     278      195996 :   if (in_plane_distance > _cross_section_maximum_radius)
     279             :     return std::numeric_limits<Real>::max();
     280             : 
     281      187548 :   return axial_position;
     282             : }
     283             : 
     284             : void
     285          34 : AverageSectionValueSampler::automaticallyLocatePositions()
     286             : {
     287          34 :   _positions.clear();
     288             : 
     289             :   // Data structure used to collect the locations with nodes on each processor,
     290             :   // and gather in parallel.
     291             :   std::vector<Real> pos_vec;
     292             : 
     293             :   // Data structure used to store parallel-gathered locations
     294             :   std::set<Real> pos_set;
     295             : 
     296          68 :   _block_ids = _mesh->getSubdomainIDs(getParam<std::vector<SubdomainName>>("block"));
     297          34 :   auto * nodes = _mesh->getLocalNodeRange();
     298             : 
     299       13754 :   for (const auto node : *nodes)
     300             :   {
     301       13720 :     const std::set<SubdomainID> & node_blk_ids = _mesh->getNodeBlockIds(*node);
     302             : 
     303       27440 :     for (const auto i : _block_ids)
     304             :     {
     305       13720 :       if (node_blk_ids.find(i) != node_blk_ids.end())
     306             :       {
     307             :         bool found_match = false;
     308       13720 :         Real axial_position = axialPosition(*node);
     309       13720 :         if (axial_position == std::numeric_limits<Real>::max()) // Node not in this component
     310           0 :           continue;
     311       82320 :         for (auto & pos : pos_vec)
     312             :         {
     313       81938 :           const Real dist_to_plane = std::abs(axial_position - pos);
     314       81938 :           if (dist_to_plane <= _tolerance)
     315             :           {
     316             :             found_match = true;
     317             :             break;
     318             :           }
     319             :         }
     320       13720 :         if (!found_match)
     321         382 :           pos_vec.emplace_back(axial_position);
     322             :       }
     323             :     }
     324             :   }
     325             : 
     326          34 :   _communicator.allgather(pos_vec);
     327             : 
     328         640 :   for (const auto & posv : pos_vec)
     329             :   {
     330             :     bool found_match = false;
     331        3708 :     for (auto & poss : pos_set)
     332             :     {
     333        3326 :       if (std::abs(posv - poss) < _tolerance)
     334             :       {
     335             :         found_match = true;
     336             :         break;
     337             :       }
     338             :     }
     339         606 :     if (!found_match)
     340         382 :       pos_set.insert(posv);
     341             :   }
     342         416 :   for (const auto & poss : pos_set)
     343         382 :     _positions.emplace_back(poss);
     344          34 : }

Generated by: LCOV version 1.14