LCOV - code coverage report
Current view: top level - src/tallies - MeshTally.C (source / functions) Hit Total Coverage
Test: neams-th-coe/cardinal: cf347a Lines: 113 117 96.6 %
Date: 2026-06-03 12:57:15 Functions: 7 7 100.0 %
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_OPENMC_COUPLING
      20             : #include "MeshTally.h"
      21             : #include "DisplacedProblem.h"
      22             : 
      23             : #include "libmesh/replicated_mesh.h"
      24             : 
      25             : registerMooseObject("CardinalApp", MeshTally);
      26             : 
      27             : InputParameters
      28        1161 : MeshTally::validParams()
      29             : {
      30        1161 :   auto params = TallyBase::validParams();
      31        1161 :   params.addClassDescription("A class which implements unstructured mesh tallies.");
      32        2322 :   params.addParam<std::string>("mesh_template",
      33             :                                "Mesh tally template for OpenMC when using mesh tallies; "
      34             :                                "at present, this mesh must exactly match the mesh used in the "
      35             :                                "[Mesh] block because a one-to-one copy "
      36             :                                "is used to get OpenMC's tally results on the [Mesh].");
      37        2322 :   params.addParam<Point>("mesh_translation",
      38             :                          "Coordinate to which this mesh should be "
      39             :                          "translated. Units must match those used to define the [Mesh].");
      40             : 
      41             :   // The index of this tally into an array of mesh translations. Defaults to zero.
      42        1161 :   params.addPrivateParam<unsigned int>("instance", 0);
      43             : 
      44        1161 :   return params;
      45           0 : }
      46             : 
      47         767 : MeshTally::MeshTally(const InputParameters & parameters)
      48             :   : TallyBase(parameters),
      49        2082 :     _mesh_translation(isParamValid("mesh_translation") ? getParam<Point>("mesh_translation")
      50             :                                                        : Point(0.0, 0.0, 0.0)),
      51        1530 :     _instance(getParam<unsigned int>("instance")),
      52        2974 :     _use_dof_map(_is_adaptive || isParamValid("block"))
      53             : {
      54             :   bool nu_scatter =
      55         765 :       std::find(_tally_score.begin(), _tally_score.end(), "nu-scatter") != _tally_score.end();
      56             : 
      57             :   // Error check the estimators.
      58        1530 :   if (isParamValid("estimator"))
      59             :   {
      60          14 :     if (_estimator == openmc::TallyEstimator::TRACKLENGTH)
      61           2 :       paramError("estimator",
      62             :                  "Tracklength estimators are currently incompatible with mesh tallies!");
      63             :   }
      64             :   else
      65        1502 :     _estimator = nu_scatter ? openmc::TallyEstimator::ANALOG : openmc::TallyEstimator::COLLISION;
      66             : 
      67             :   // Error check the mesh template.
      68         765 :   if (_openmc_problem.getMooseMesh().getMesh().allow_renumbering() &&
      69           2 :       !_openmc_problem.getMooseMesh().getMesh().is_replicated())
      70           2 :     mooseError(
      71             :         "Mesh tallies currently require 'allow_renumbering = false' to be set in the [Mesh]!");
      72             : 
      73        1522 :   if (isParamValid("mesh_template"))
      74             :   {
      75         652 :     if (_is_adaptive)
      76           2 :       paramError("mesh_template",
      77             :                  "Adaptivity is not supported when loading a mesh from 'mesh_template'!");
      78             : 
      79        1300 :     if (isParamValid("block"))
      80           2 :       paramError("block",
      81             :                  "Block restriction is currently not supported for mesh tallies which load a "
      82             :                  "mesh from a file!");
      83             : 
      84         648 :     if (_openmc_problem.useDisplaced())
      85           1 :       paramError("mesh_template",
      86             :                  "Tallying on a file-based mesh is not supported for moving-mesh cases as there is "
      87             :                  "not a mechanism to update the mesh geometry. You can use a mesh tally for moving "
      88             :                  "mesh cases by instead tallying directly on the [Mesh]. Simply do not provide the "
      89             :                  "'mesh_template' parameter.");
      90             : 
      91        1294 :     _mesh_template_filename = &getParam<std::string>("mesh_template");
      92             :   }
      93             :   else
      94             :   {
      95             :     // for distributed meshes, each rank only owns a portion of the mesh information, but
      96             :     // OpenMC wants the entire mesh to be available on every rank. We might be able to add
      97             :     // this feature in the future, but will need to investigate
      98         109 :     if (!_openmc_problem.getMooseMesh().getMesh().is_replicated())
      99           0 :       mooseError("Directly tallying on the [Mesh] block by OpenMC is not yet supported "
     100             :                  "for distributed meshes!");
     101             : 
     102         218 :     if (isParamValid("mesh_translation"))
     103           0 :       paramError("mesh_translation",
     104             :                  "The mesh filter cannot be translated if directly tallying on the mesh "
     105             :                  "provided in the [Mesh] block!");
     106             :   }
     107             : 
     108             :   /**
     109             :    * If the instance isn't zero this variable is a translated mesh tally. It will accumulate it's
     110             :    * scores in a different set of variables (the auxvars which are added by the first tally in a
     111             :    * sequence of mesh tallies), and so it doesn't need to create any auxvars.
     112             :    */
     113         756 :   if (_instance != 0)
     114         368 :     _tally_name = std::vector<std::string>();
     115         756 : }
     116             : 
     117             : std::pair<unsigned int, openmc::Filter *>
     118         769 : MeshTally::spatialFilter()
     119             : {
     120             :   // Create the OpenMC mesh which will be tallied on.
     121         769 :   if (!_mesh_template_filename)
     122             :   {
     123             :     auto msh =
     124         137 :         dynamic_cast<const libMesh::ReplicatedMesh *>(_openmc_problem.getMooseMesh().getMeshPtr());
     125         137 :     if (!msh)
     126           0 :       mooseError("Internal error: The mesh is not a replicated mesh.");
     127             : 
     128             :     // Adaptivity and block restriction both require a map from the element subsets we want to
     129             :     // tally on to the full mesh.
     130         137 :     if (_use_dof_map)
     131             :     {
     132          86 :       _bin_to_element_mapping.clear();
     133             : 
     134             :       auto begin = _tally_blocks.size() > 0
     135          86 :                        ? msh->active_subdomain_set_elements_begin(_tally_blocks)
     136         172 :                        : msh->active_elements_begin();
     137          86 :       auto end = _tally_blocks.size() > 0 ? msh->active_subdomain_set_elements_end(_tally_blocks)
     138          86 :                                           : msh->active_elements_end();
     139       25772 :       for (const auto & old_elem : libMesh::as_range(begin, end))
     140       25686 :         _bin_to_element_mapping.push_back(old_elem->id());
     141             : 
     142             :       _bin_to_element_mapping.shrink_to_fit();
     143             :     }
     144             : 
     145         137 :     openmc::model::meshes.emplace_back(std::make_unique<openmc::AdaptiveLibMesh>(
     146         137 :         _openmc_problem.getMooseMesh().getMesh(), _openmc_problem.scaling(), _tally_blocks));
     147             :   }
     148             :   else
     149         632 :     openmc::model::meshes.emplace_back(
     150        1264 :         std::make_unique<openmc::LibMesh>(*_mesh_template_filename, _openmc_problem.scaling()));
     151             : 
     152         769 :   _mesh_index = openmc::model::meshes.size() - 1;
     153         769 :   _mesh_template =
     154         769 :       dynamic_cast<openmc::UnstructuredMesh *>(openmc::model::meshes[_mesh_index].get());
     155             : 
     156             :   // by setting the ID to -1, OpenMC will automatically detect the next available ID
     157         769 :   _mesh_template->set_id(-1);
     158         769 :   _mesh_template->output_ = false;
     159             : 
     160         769 :   _mesh_filter = dynamic_cast<openmc::MeshFilter *>(openmc::Filter::create("mesh"));
     161         769 :   _mesh_filter->set_mesh(_mesh_index);
     162         769 :   _mesh_filter->set_translation({_mesh_translation(0), _mesh_translation(1), _mesh_translation(2)});
     163             : 
     164             :   // Validate the mesh filters to make sure we can run a copy transfer to the [Mesh].
     165         769 :   checkMeshTemplateAndTranslations();
     166             : 
     167         763 :   return std::make_pair(openmc::model::tally_filters.size() - 1, _mesh_filter);
     168             : }
     169             : 
     170             : void
     171          61 : MeshTally::resetTally()
     172             : {
     173          61 :   TallyBase::resetTally();
     174             : 
     175             :   // Erase the OpenMC mesh.
     176          61 :   openmc::model::meshes.erase(openmc::model::meshes.begin() + _mesh_index);
     177          61 : }
     178             : 
     179             : void
     180        1041 : MeshTally::gatherLinkedSum()
     181             : {
     182        1041 :   if (_linked_tallies.size() == 0)
     183             :     return;
     184             : 
     185        2448 :   for (const auto & other : _linked_tallies)
     186             :   {
     187        3360 :     for (unsigned int score = 0; score < _tally_score.size(); ++score)
     188             :     {
     189        1728 :       _linked_local_sum_tally[score] += other->getSum(score);
     190        1728 :       if (other->addingGlobalTally())
     191         272 :         _global_sum_tally[score] =
     192         272 :             _openmc_problem.tallySumAcrossBins({other->getWrappedGlobalTally()}, score);
     193             :     }
     194             :   }
     195             : }
     196             : 
     197             : Real
     198        1253 : MeshTally::storeResultsInner(const std::vector<unsigned int> & var_numbers,
     199             :                              unsigned int local_score,
     200             :                              const std::vector<OMCTensor> & tally_vals,
     201             :                              bool norm_by_src_rate)
     202             : {
     203             :   Real total = 0.0;
     204             : 
     205        1253 :   unsigned int mesh_offset = _instance * _mesh_filter->n_bins();
     206        2752 :   for (unsigned int ext_bin = 0; ext_bin < _num_ext_filter_bins; ++ext_bin)
     207             :   {
     208      469979 :     for (decltype(_mesh_filter->n_bins()) e = 0; e < _mesh_filter->n_bins(); ++e)
     209             :     {
     210      468480 :       Real unnormalized_tally = tally_vals[local_score](ext_bin * _mesh_filter->n_bins() + e);
     211             : 
     212             :       // divide each tally by the volume that it corresponds to in MOOSE
     213             :       // because we will apply it as a volumetric tally (per unit volume).
     214             :       // Because we require that the mesh template has units of cm based on the
     215             :       // mesh constructors in OpenMC, we need to adjust the division
     216      468480 :       Real volumetric_tally = unnormalized_tally;
     217      468480 :       volumetric_tally *= norm_by_src_rate
     218      923332 :                               ? _openmc_problem.tallyMultiplier(_tally_score[local_score],
     219      454852 :                                                                 _local_mean_tally[local_score]) /
     220      454852 :                                     _mesh_template->volume(e) * _openmc_problem.scaling() *
     221             :                                     _openmc_problem.scaling() * _openmc_problem.scaling()
     222             :                               : 1.0;
     223      468480 :       total += _ext_bins_to_skip[ext_bin] ? 0.0 : unnormalized_tally;
     224             : 
     225      468480 :       auto var = var_numbers[_num_ext_filter_bins * local_score + ext_bin];
     226      468480 :       auto elem_id = _use_dof_map ? _bin_to_element_mapping[e] : mesh_offset + e;
     227      468480 :       fillElementalAuxVariable(var, {elem_id}, volumetric_tally);
     228             :     }
     229             :   }
     230             : 
     231        1253 :   return total;
     232             : }
     233             : 
     234             : void
     235         769 : MeshTally::checkMeshTemplateAndTranslations()
     236             : {
     237             :   // we can do some rudimentary checking on the mesh template by comparing the centroid
     238             :   // coordinates compared to centroids in the [Mesh] (because right now, we just doing a simple
     239             :   // copy transfer that necessitates the meshes to have the same elements in the same order). In
     240             :   // other words, you might have two meshes that represent the same geometry, the element ordering
     241             :   // could be different.
     242         769 :   unsigned int mesh_offset = _instance * _mesh_filter->n_bins();
     243      242361 :   for (int e = 0; e < _mesh_filter->n_bins(); ++e)
     244             :   {
     245      241598 :     auto elem_id = _use_dof_map ? _bin_to_element_mapping[e] : mesh_offset + e;
     246      241598 :     auto elem_ptr = _openmc_problem.getMooseMesh().queryElemPtr(elem_id);
     247             : 
     248             :     // if element is not on this part of the distributed mesh, skip it
     249      241598 :     if (!elem_ptr)
     250       51402 :       continue;
     251             : 
     252      190196 :     const auto pt = _mesh_template->centroid(e);
     253      190196 :     Point centroid_template = {pt[0], pt[1], pt[2]};
     254             : 
     255             :     // The translation applied in OpenMC isn't actually registered in the mesh itself;
     256             :     // it is always added on to the point, so we need to do the same here
     257             :     centroid_template += _mesh_translation;
     258             : 
     259             :     // because the mesh template and [Mesh] may be in different units, we need
     260             :     // to adjust the [Mesh] by the scaling factor before doing a comparison.
     261      190196 :     Point centroid_mesh = elem_ptr->vertex_average() * _openmc_problem.scaling();
     262             : 
     263             :     // if the centroids are the same except for a factor of 'scaling', then we can
     264             :     // guess that the mesh_template is probably not in units of centimeters
     265      190196 :     if (_openmc_problem.hasScaling())
     266             :     {
     267             :       // if scaling was applied correctly, then each calculation of 'scaling' here should equal 1.
     268             :       // Otherwise, if they're all the same, then 'scaling_x' is probably the factor by which the
     269             :       // mesh_template needs to be multiplied, so we can print a helpful error message
     270             :       bool incorrect_scaling = true;
     271      122024 :       for (unsigned int j = 0; j < OpenMCCellAverageProblem::DIMENSION; ++j)
     272             :       {
     273       91518 :         Real scaling = centroid_mesh(j) / centroid_template(j);
     274       91518 :         incorrect_scaling = incorrect_scaling && !MooseUtils::absoluteFuzzyEqual(scaling, 1.0);
     275             :       }
     276             : 
     277       30506 :       if (incorrect_scaling)
     278           2 :         paramError("mesh_template",
     279             :                    "The centroids of the 'mesh_template' differ from the "
     280           2 :                    "centroids of the [Mesh] by a factor of " +
     281           2 :                        Moose::stringify(centroid_mesh(0) / centroid_template(0)) +
     282             :                        ".\nDid you forget that the 'mesh_template' must be in "
     283             :                        "the same units as the [Mesh]?");
     284             :     }
     285             : 
     286             :     // check if centroids are the same
     287             :     bool different_centroids = false;
     288      760776 :     for (unsigned int j = 0; j < OpenMCCellAverageProblem::DIMENSION; ++j)
     289      570582 :       different_centroids = different_centroids ||
     290             :                             !MooseUtils::absoluteFuzzyEqual(centroid_mesh(j), centroid_template(j));
     291             : 
     292      190194 :     if (different_centroids)
     293           4 :       paramError(
     294             :           "mesh_template",
     295           4 :           "Centroid for element " + Moose::stringify(elem_id) +
     296           8 :               " in the [Mesh] (cm): " + _openmc_problem.printPoint(centroid_mesh) +
     297           4 :               "\ndoes not match centroid for element " + Moose::stringify(e) +
     298           8 :               " in the 'mesh_template' with instance " + Moose::stringify(_instance) +
     299           8 :               " (cm): " + _openmc_problem.printPoint(centroid_template) +
     300             :               "!\n\nThe copy transfer requires that the [Mesh] and 'mesh_template' be identical.");
     301             :   }
     302         763 : }
     303             : #endif

Generated by: LCOV version 1.14