LCOV - code coverage report
Current view: top level - src/userobjects - NekSpatialBinUserObject.C (source / functions) Hit Total Coverage
Test: neams-th-coe/cardinal: ddd5f2 Lines: 148 152 97.4 %
Date: 2026-06-07 19:35:24 Functions: 15 16 93.8 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /********************************************************************/
       2             : /*                  SOFTWARE COPYRIGHT NOTIFICATION                 */
       3             : /*                             Cardinal                             */
       4             : /*                                                                  */
       5             : /*                  (c) 2021 UChicago Argonne, LLC                  */
       6             : /*                        ALL RIGHTS RESERVED                       */
       7             : /*                                                                  */
       8             : /*                 Prepared by UChicago Argonne, LLC                */
       9             : /*               Under Contract No. DE-AC02-06CH11357               */
      10             : /*                With the U. S. Department of Energy               */
      11             : /*                                                                  */
      12             : /*             Prepared by Battelle Energy Alliance, LLC            */
      13             : /*               Under Contract No. DE-AC07-05ID14517               */
      14             : /*                With the U. S. Department of Energy               */
      15             : /*                                                                  */
      16             : /*                 See LICENSE for full restrictions                */
      17             : /********************************************************************/
      18             : 
      19             : #ifdef ENABLE_NEK_COUPLING
      20             : 
      21             : #include "NekSpatialBinUserObject.h"
      22             : #include "NekInterface.h"
      23             : #include "CardinalUtils.h"
      24             : 
      25             : InputParameters
      26         433 : NekSpatialBinUserObject::validParams()
      27             : {
      28         433 :   InputParameters params = GeneralUserObject::validParams();
      29         433 :   params += NekBase::validParams();
      30         433 :   params += NekFieldInterface::validParams();
      31         866 :   params.addRequiredParam<std::vector<UserObjectName>>(
      32             :       "bins", "Userobjects providing a spatial bin given a point");
      33         866 :   params.addParam<unsigned int>(
      34             :       "interval",
      35         866 :       1,
      36             :       "Frequency (in number of time steps) with which to execute this user object; user objects "
      37             :       "can be expensive and not necessary to evaluate on every single time step. NOTE: you "
      38             :       "probably want to match with 'time_step_interval' in the Output");
      39         866 :   params.addParam<bool>(
      40             :       "map_space_by_qp",
      41         866 :       false,
      42             :       "Whether to map the NekRS spatial domain to a bin according to the element centroids (true) "
      43             :       "or quadrature point locations (false).");
      44         866 :   params.addParam<bool>(
      45             :       "check_zero_contributions",
      46         866 :       true,
      47             :       "Whether to throw an error if no GLL points/element centroids in the NekRS mesh map to a "
      48             :       "spatial bin; this "
      49             :       "can be used to ensure that the bins are sufficiently big to get at least one contributing "
      50             :       "point from the NekRS mesh.");
      51         433 :   return params;
      52           0 : }
      53             : 
      54         220 : NekSpatialBinUserObject::NekSpatialBinUserObject(const InputParameters & parameters)
      55             :   : GeneralUserObject(parameters),
      56             :     NekBase(this, parameters),
      57             :     NekFieldInterface(this, parameters, true /* allow normal */),
      58         219 :     _interval(getParam<unsigned int>("interval")),
      59         438 :     _bin_names(getParam<std::vector<UserObjectName>>("bins")),
      60         438 :     _map_space_by_qp(getParam<bool>("map_space_by_qp")),
      61         438 :     _check_zero_contributions(getParam<bool>("check_zero_contributions")),
      62         219 :     _bin_values(nullptr),
      63         219 :     _bin_values_x(nullptr),
      64         219 :     _bin_values_y(nullptr),
      65         219 :     _bin_values_z(nullptr),
      66         219 :     _bin_volumes(nullptr),
      67         219 :     _bin_counts(nullptr),
      68         219 :     _bin_partial_values(nullptr),
      69         220 :     _bin_partial_counts(nullptr)
      70             : {
      71         219 :   _fixed_mesh = !nekrs::hasMovingMesh();
      72             : 
      73         219 :   if (_bin_names.size() == 0)
      74           0 :     paramError("bins", "Length of vector must be greater than zero!");
      75             : 
      76         570 :   for (auto & b : _bin_names)
      77             :   {
      78             :     // first check that the user object exists
      79         353 :     if (!hasUserObjectByName<UserObject>(b))
      80           2 :       mooseError("Bin user object with name '" + b +
      81             :                  "' not found in problem. The user objects "
      82           1 :                  "in 'bins' must be listed before the '" +
      83           0 :                  name() + "' user object.");
      84             : 
      85             :     // then check that it's the right type
      86         352 :     if (!hasUserObjectByName<SpatialBinUserObject>(b))
      87           2 :       mooseError("Bin user object with name '" + b +
      88             :                  "' must inherit from SpatialBinUserObject.\n\n"
      89             :                  "Volume options: HexagonalSubchannelBin, LayeredBin, RadialBin\n"
      90             :                  "Side options: HexagonalSubchannelGapBin, LayeredGapBin");
      91             : 
      92         351 :     _bins.push_back(&getUserObjectByName<SpatialBinUserObject>(b));
      93             :   }
      94             : 
      95         217 :   _n_bins = num_bins();
      96             : 
      97         217 :   _bin_values = (double *)calloc(_n_bins, sizeof(double));
      98         217 :   _bin_volumes = (double *)calloc(_n_bins, sizeof(double));
      99         217 :   _bin_partial_values = (double *)calloc(_n_bins, sizeof(double));
     100         217 :   _bin_counts = (int *)calloc(_n_bins, sizeof(int));
     101         217 :   _bin_partial_counts = (int *)calloc(_n_bins, sizeof(int));
     102             : 
     103         217 :   if (_field == field::velocity_component)
     104             :   {
     105          17 :     _bin_values_x = (double *)calloc(_n_bins, sizeof(double));
     106          17 :     _bin_values_y = (double *)calloc(_n_bins, sizeof(double));
     107          17 :     _bin_values_z = (double *)calloc(_n_bins, sizeof(double));
     108             :   }
     109             : 
     110         217 :   _has_direction = {false, false, false};
     111         217 :   _bin_providing_direction.resize(3);
     112         566 :   for (unsigned int b = 0; b < _bins.size(); ++b)
     113             :   {
     114             :     const auto & bin = _bins[b];
     115             : 
     116             :     // directions provided by this bin
     117         351 :     auto bin_directions = bin->directions();
     118             : 
     119         820 :     for (const auto & d : bin_directions)
     120             :     {
     121         471 :       if (_has_direction[d])
     122             :       {
     123           2 :         const auto & bin_providing_d = _bins[_bin_providing_direction[d]];
     124           2 :         mooseError("Cannot combine multiple distributions in the same coordinate direction!\n"
     125           2 :                    "Bin '" +
     126           4 :                    bin->name() + "' conflicts with bin '" + bin_providing_d->name() + "'.");
     127             :       }
     128             :       else
     129             :       {
     130             :         _has_direction[d] = true;
     131         469 :         _bin_providing_direction[d] = b;
     132             :       }
     133             :     }
     134         349 :   }
     135             : 
     136             :   // initialize all points to (0, 0, 0)
     137       44857 :   for (unsigned int i = 0; i < _n_bins; ++i)
     138       44642 :     _points.push_back(Point(0.0, 0.0, 0.0));
     139             : 
     140             :   // we will at most have 3 separate distributions
     141         215 :   if (_bins.size() == 1)
     142         102 :     computePoints1D();
     143         113 :   else if (_bins.size() == 2)
     144          96 :     computePoints2D();
     145             :   else
     146          17 :     computePoints3D();
     147             : 
     148             :   // with a user-specified direction, the direction for each bin is the same
     149         215 :   if (_field == field::velocity_component && _velocity_component == component::user)
     150        1016 :     for (unsigned int i = 0; i < _n_bins; ++i)
     151        1008 :       _velocity_bin_directions.push_back(_velocity_direction);
     152         215 : }
     153             : 
     154         208 : NekSpatialBinUserObject::~NekSpatialBinUserObject()
     155             : {
     156         208 :   freePointer(_bin_values);
     157         208 :   freePointer(_bin_volumes);
     158         208 :   freePointer(_bin_counts);
     159         208 :   freePointer(_bin_partial_values);
     160         208 :   freePointer(_bin_partial_counts);
     161             : 
     162         208 :   freePointer(_bin_values_x);
     163         208 :   freePointer(_bin_values_y);
     164         208 :   freePointer(_bin_values_z);
     165         416 : }
     166             : 
     167             : void
     168        1432 : NekSpatialBinUserObject::execute()
     169             : {
     170        1432 :   if (_fe_problem.timeStep() % _interval == 0)
     171         216 :     executeUserObject();
     172        1432 : }
     173             : 
     174             : Point
     175   126587040 : NekSpatialBinUserObject::nekPoint(const int & local_elem_id, const int & local_node_id) const
     176             : {
     177   126587040 :   if (_map_space_by_qp)
     178    99589536 :     return nekrs::gllPoint(local_elem_id, local_node_id);
     179             :   else
     180    26997504 :     return nekrs::centroid(local_elem_id);
     181             : }
     182             : 
     183             : void
     184         459 : NekSpatialBinUserObject::resetPartialStorage()
     185             : {
     186       59638 :   for (unsigned int i = 0; i < _n_bins; ++i)
     187             :   {
     188       59179 :     _bin_partial_values[i] = 0.0;
     189       59179 :     _bin_partial_counts[i] = 0;
     190             :   }
     191         459 : }
     192             : 
     193             : void
     194         211 : NekSpatialBinUserObject::computeBinVolumes()
     195             : {
     196         211 :   getBinVolumes();
     197             : 
     198         211 :   if (_check_zero_contributions)
     199             :   {
     200       11327 :     for (unsigned int i = 0; i < _n_bins; ++i)
     201             :     {
     202       11142 :       if (_bin_counts[i] == 0)
     203             :       {
     204           3 :         std::string map = _map_space_by_qp ? "GLL points" : "element centroids";
     205           4 :         mooseError(
     206           4 :             "Failed to map any " + map + " to bin " + Moose::stringify(i) +
     207             :             "!\n\n"
     208             :             "This can happen if the bins are much finer than the NekRS mesh or if the bins are "
     209             :             "defined in a way that results in bins entirely outside the NekRS domain. You can turn "
     210             :             "this error off by setting 'check_zero_contributions = false' (at your own risk!).");
     211             :       }
     212             :     }
     213             :   }
     214         209 : }
     215             : 
     216             : Real
     217     1671078 : NekSpatialBinUserObject::spatialValue(const Point & p) const
     218             : {
     219     1671078 :   return _bin_values[bin(p)];
     220             : }
     221             : 
     222             : const std::vector<unsigned int>
     223       97088 : NekSpatialBinUserObject::unrolledBin(const unsigned int & total_bin_index) const
     224             : {
     225             :   std::vector<unsigned int> local_bins;
     226       97088 :   local_bins.resize(_bins.size());
     227             : 
     228       97088 :   int running_index = total_bin_index;
     229      269800 :   for (int i = _bins.size() - 1; i >= 0; --i)
     230             :   {
     231      172712 :     local_bins[i] = running_index % _bins[i]->num_bins();
     232      172712 :     running_index -= local_bins[i];
     233      172712 :     running_index /= _bins[i]->num_bins();
     234             :   }
     235             : 
     236       97088 :   return local_bins;
     237           0 : }
     238             : 
     239             : const unsigned int
     240    59791362 : NekSpatialBinUserObject::bin(const Point & p) const
     241             : {
     242             :   // get the indices into each of the individual bin objects
     243             :   std::vector<unsigned int> indices;
     244   169139690 :   for (const auto & b : _bins)
     245   109348328 :     indices.push_back(b->bin(p));
     246             : 
     247             :   // convert to a total index into the multidimensional bin union
     248    59791362 :   unsigned int index = indices[0];
     249   109348328 :   for (unsigned int i = 1; i < _bins.size(); ++i)
     250    49556966 :     index = index * _bins[i]->num_bins() + indices[i];
     251             : 
     252    59791362 :   return index;
     253    59791362 : }
     254             : 
     255             : const unsigned int
     256        8361 : NekSpatialBinUserObject::num_bins() const
     257             : {
     258             :   unsigned int num_bins = 1;
     259       25020 :   for (const auto & b : _bins)
     260       16659 :     num_bins *= b->num_bins();
     261             : 
     262        8361 :   return num_bins;
     263             : }
     264             : 
     265             : void
     266         102 : NekSpatialBinUserObject::computePoints1D()
     267             : {
     268         574 :   for (unsigned int i = 0; i < _bins[0]->num_bins(); ++i)
     269             :   {
     270         472 :     std::vector<unsigned int> indices = {i};
     271         472 :     fillCoordinates(indices, _points[i]);
     272         472 :   }
     273         102 : }
     274             : 
     275             : void
     276          96 : NekSpatialBinUserObject::computePoints2D()
     277             : {
     278             :   int p = 0;
     279        1778 :   for (unsigned int i = 0; i < _bins[0]->num_bins(); ++i)
     280             :   {
     281       35796 :     for (unsigned int j = 0; j < _bins[1]->num_bins(); ++j, ++p)
     282             :     {
     283       34114 :       std::vector<unsigned int> indices = {i, j};
     284       34114 :       fillCoordinates(indices, _points[p]);
     285       34114 :     }
     286             :   }
     287          96 : }
     288             : 
     289             : void
     290          17 : NekSpatialBinUserObject::computePoints3D()
     291             : {
     292             :   int p = 0;
     293          64 :   for (unsigned int i = 0; i < _bins[0]->num_bins(); ++i)
     294             :   {
     295         180 :     for (unsigned int j = 0; j < _bins[1]->num_bins(); ++j)
     296             :     {
     297       10189 :       for (unsigned int k = 0; k < _bins[2]->num_bins(); ++k, ++p)
     298             :       {
     299       10056 :         std::vector<unsigned int> indices = {i, j, k};
     300       10056 :         fillCoordinates(indices, _points[p]);
     301       10056 :       }
     302             :     }
     303             :   }
     304          17 : }
     305             : 
     306             : void
     307       44642 : NekSpatialBinUserObject::fillCoordinates(const std::vector<unsigned int> & indices, Point & p) const
     308             : {
     309      143510 :   for (unsigned int b = 0; b < _bins.size(); ++b)
     310             :   {
     311             :     const auto & bin = _bins[b];
     312       98868 :     const auto & centers = bin->getBinCenters();
     313             : 
     314      232118 :     for (const auto & d : bin->directions())
     315      232118 :       p(d) = centers[indices[b]](d);
     316             :   }
     317       44642 : }
     318             : 
     319             : #endif

Generated by: LCOV version 1.14