LCOV - code coverage report
Current view: top level - src/tallies - MeshTally.C (source / functions) Hit Total Coverage
Test: neams-th-coe/cardinal: be601f Lines: 129 136 94.9 %
Date: 2025-07-15 20:50:38 Functions: 6 6 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        1089 : MeshTally::validParams()
      29             : {
      30        1089 :   auto params = TallyBase::validParams();
      31        1089 :   params.addClassDescription("A class which implements unstructured mesh tallies.");
      32        2178 :   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        2178 :   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        2178 :   params.addParam<std::vector<SubdomainName>>(
      41             :       "block",
      42             :       "Subdomains for which to add tallies in OpenMC. If not provided, this mesh "
      43             :       "tally will be applied over the entire mesh.");
      44        2178 :   params.addParam<std::vector<SubdomainName>>("blocks",
      45             :                                               "This parameter is deprecated, use 'block' instead!");
      46             : 
      47             :   // The index of this tally into an array of mesh translations. Defaults to zero.
      48        1089 :   params.addPrivateParam<unsigned int>("instance", 0);
      49             : 
      50        1089 :   return params;
      51           0 : }
      52             : 
      53         726 : MeshTally::MeshTally(const InputParameters & parameters)
      54             :   : TallyBase(parameters),
      55        1986 :     _mesh_translation(isParamValid("mesh_translation") ? getParam<Point>("mesh_translation")
      56             :                                                        : Point(0.0, 0.0, 0.0)),
      57        1452 :     _instance(getParam<unsigned int>("instance")),
      58        2836 :     _use_dof_map(_is_adaptive || isParamValid("block"))
      59             : {
      60        1452 :   if (isParamSetByUser("blocks"))
      61           0 :     mooseError("This parameter is deprecated, use 'block' instead!");
      62             : 
      63             :   bool nu_scatter =
      64         726 :       std::find(_tally_score.begin(), _tally_score.end(), "nu-scatter") != _tally_score.end();
      65             : 
      66             :   // Error check the estimators.
      67        1452 :   if (isParamValid("estimator"))
      68             :   {
      69          14 :     if (_estimator == openmc::TallyEstimator::TRACKLENGTH)
      70           2 :       paramError("estimator",
      71             :                  "Tracklength estimators are currently incompatible with mesh tallies!");
      72             :   }
      73             :   else
      74        1424 :     _estimator = nu_scatter ? openmc::TallyEstimator::ANALOG : openmc::TallyEstimator::COLLISION;
      75             : 
      76             :   // Error check the mesh template.
      77         726 :   if (_openmc_problem.getMooseMesh().getMesh().allow_renumbering() &&
      78           2 :       !_openmc_problem.getMooseMesh().getMesh().is_replicated())
      79           2 :     mooseError(
      80             :         "Mesh tallies currently require 'allow_renumbering = false' to be set in the [Mesh]!");
      81             : 
      82        1444 :   if (isParamValid("mesh_template"))
      83             :   {
      84         641 :     if (_is_adaptive)
      85           2 :       paramError("mesh_template",
      86             :                  "Adaptivity is not supported when loading a mesh from 'mesh_template'!");
      87             : 
      88        1278 :     if (isParamValid("block"))
      89           2 :       paramError("block",
      90             :                  "Block restriction is currently not supported for mesh tallies which load a "
      91             :                  "mesh from a file!");
      92             : 
      93         637 :     if (_openmc_problem.useDisplaced())
      94           1 :       paramError("mesh_template",
      95             :                  "Tallying on a file-based mesh is not supported for moving-mesh cases as there is "
      96             :                  "not a mechanism to update the mesh geometry. You can use a mesh tally for moving "
      97             :                  "mesh cases by instead tallying directly on the [Mesh]. Simply do not provide the "
      98             :                  "'mesh_template' parameter.");
      99             : 
     100        1272 :     _mesh_template_filename = &getParam<std::string>("mesh_template");
     101             :   }
     102             :   else
     103             :   {
     104          81 :     if (std::abs(_openmc_problem.scaling() - 1.0) > 1e-6)
     105           2 :       mooseError("Directly tallying on the [Mesh] is only supported for 'scaling' of unity. "
     106             :                  "Instead, please make a file containing your tally mesh and set it with "
     107             :                  "'mesh_template'. You can generate a mesh file corresponding to the [Mesh] "
     108           2 :                  "by running:\n\ncardinal-opt -i " +
     109           2 :                  _app.getFileName() + " --mesh-only");
     110             : 
     111             :     // for distributed meshes, each rank only owns a portion of the mesh information, but
     112             :     // OpenMC wants the entire mesh to be available on every rank. We might be able to add
     113             :     // this feature in the future, but will need to investigate
     114          79 :     if (!_openmc_problem.getMooseMesh().getMesh().is_replicated())
     115           0 :       mooseError("Directly tallying on the [Mesh] block by OpenMC is not yet supported "
     116             :                  "for distributed meshes!");
     117             : 
     118         158 :     if (isParamValid("mesh_translation"))
     119           0 :       paramError("mesh_translation",
     120             :                  "The mesh filter cannot be translated if directly tallying on the mesh "
     121             :                  "provided in the [Mesh] block!");
     122             : 
     123             :     // Fetch subdomain IDs for block restrictions.
     124         158 :     if (isParamValid("block"))
     125             :     {
     126          48 :       auto block_names = getParam<std::vector<SubdomainName>>("block");
     127          16 :       if (block_names.empty())
     128           2 :         paramError("block", "Subdomain names must be provided if using 'block'!");
     129             : 
     130          14 :       auto block_ids = _openmc_problem.getMooseMesh().getSubdomainIDs(block_names);
     131          14 :       std::copy(
     132             :           block_ids.begin(), block_ids.end(), std::inserter(_tally_blocks, _tally_blocks.end()));
     133             : 
     134             :       // Check to make sure all of the blocks are in the mesh.
     135          14 :       const auto & subdomains = _openmc_problem.getMooseMesh().meshSubdomains();
     136          34 :       for (std::size_t b = 0; b < block_names.size(); ++b)
     137          20 :         if (subdomains.find(block_ids[b]) == subdomains.end())
     138           0 :           paramError("block",
     139           0 :                      "Block '" + block_names[b] + "' specified in 'block' not found in mesh!");
     140          14 :     }
     141             :   }
     142             : 
     143             :   /**
     144             :    * If the instance isn't zero this variable is a translated mesh tally. It will accumulate it's
     145             :    * scores in a different set of variables (the auxvars which are added by the first tally in a
     146             :    * sequence of mesh tallies), and so it doesn't need to create any auxvars.
     147             :    */
     148         713 :   if (_instance != 0)
     149         356 :     _tally_name = std::vector<std::string>();
     150         713 : }
     151             : 
     152             : std::pair<unsigned int, openmc::Filter *>
     153         722 : MeshTally::spatialFilter()
     154             : {
     155             :   // Create the OpenMC mesh which will be tallied on.
     156         722 :   if (!_mesh_template_filename)
     157             :   {
     158             :     auto msh =
     159          85 :         dynamic_cast<const libMesh::ReplicatedMesh *>(_openmc_problem.getMooseMesh().getMeshPtr());
     160          85 :     if (!msh)
     161           0 :       mooseError("Internal error: The mesh is not a replicated mesh.");
     162             : 
     163             :     // Adaptivity and block restriction both require a map from the element subsets we want to
     164             :     // tally on to the full mesh.
     165          85 :     if (_use_dof_map)
     166             :     {
     167          50 :       _bin_to_element_mapping.clear();
     168             : 
     169             :       auto begin = _tally_blocks.size() > 0
     170          50 :                        ? msh->active_subdomain_set_elements_begin(_tally_blocks)
     171          70 :                        : msh->active_elements_begin();
     172          50 :       auto end = _tally_blocks.size() > 0 ? msh->active_subdomain_set_elements_end(_tally_blocks)
     173          50 :                                           : msh->active_elements_end();
     174      182692 :       for (const auto & old_elem : libMesh::as_range(begin, end))
     175      182642 :         _bin_to_element_mapping.push_back(old_elem->id());
     176             : 
     177             :       _bin_to_element_mapping.shrink_to_fit();
     178             :     }
     179             : 
     180             :     // When block restriction is active we need to create a copy of the mesh which only contains
     181             :     // elements in the desired blocks.
     182          85 :     if (_tally_blocks.size() > 0)
     183             :     {
     184          40 :       _libmesh_mesh_copy = std::make_unique<libMesh::ReplicatedMesh>(
     185          20 :           _openmc_problem.comm(), _openmc_problem.getMooseMesh().dimension());
     186             : 
     187          40 :       msh->create_submesh(*_libmesh_mesh_copy.get(),
     188          40 :                           msh->active_subdomain_set_elements_begin(_tally_blocks),
     189          40 :                           msh->active_subdomain_set_elements_end(_tally_blocks));
     190             :       _libmesh_mesh_copy->allow_find_neighbors(true);
     191             :       _libmesh_mesh_copy->allow_renumbering(false);
     192          20 :       _libmesh_mesh_copy->prepare_for_use();
     193             : 
     194          20 :       openmc::model::meshes.emplace_back(
     195          40 :           std::make_unique<openmc::LibMesh>(*_libmesh_mesh_copy.get(), _openmc_problem.scaling()));
     196             :     }
     197             :     else
     198          65 :       openmc::model::meshes.emplace_back(std::make_unique<openmc::LibMesh>(
     199          65 :           _openmc_problem.getMooseMesh().getMesh(), _openmc_problem.scaling()));
     200             :   }
     201             :   else
     202         637 :     openmc::model::meshes.emplace_back(
     203        1274 :         std::make_unique<openmc::LibMesh>(*_mesh_template_filename, _openmc_problem.scaling()));
     204             : 
     205         722 :   _mesh_index = openmc::model::meshes.size() - 1;
     206         722 :   _mesh_template = dynamic_cast<openmc::LibMesh *>(openmc::model::meshes[_mesh_index].get());
     207             : 
     208             :   // by setting the ID to -1, OpenMC will automatically detect the next available ID
     209         722 :   _mesh_template->set_id(-1);
     210         722 :   _mesh_template->output_ = false;
     211             : 
     212         722 :   _mesh_filter = dynamic_cast<openmc::MeshFilter *>(openmc::Filter::create("mesh"));
     213         722 :   _mesh_filter->set_mesh(_mesh_index);
     214         722 :   _mesh_filter->set_translation({_mesh_translation(0), _mesh_translation(1), _mesh_translation(2)});
     215             : 
     216             :   // Validate the mesh filters to make sure we can run a copy transfer to the [Mesh].
     217         722 :   checkMeshTemplateAndTranslations();
     218             : 
     219         716 :   return std::make_pair(openmc::model::tally_filters.size() - 1, _mesh_filter);
     220             : }
     221             : 
     222             : void
     223          33 : MeshTally::resetTally()
     224             : {
     225          33 :   TallyBase::resetTally();
     226             : 
     227             :   // Erase the OpenMC mesh.
     228          33 :   openmc::model::meshes.erase(openmc::model::meshes.begin() + _mesh_index);
     229          33 : }
     230             : 
     231             : Real
     232        1263 : MeshTally::storeResultsInner(const std::vector<unsigned int> & var_numbers,
     233             :                              unsigned int local_score,
     234             :                              unsigned int global_score,
     235             :                              std::vector<xt::xtensor<double, 1>> tally_vals,
     236             :                              bool norm_by_src_rate)
     237             : {
     238             :   Real total = 0.0;
     239             : 
     240        1263 :   unsigned int mesh_offset = _instance * _mesh_filter->n_bins();
     241        2772 :   for (unsigned int ext_bin = 0; ext_bin < _num_ext_filter_bins; ++ext_bin)
     242             :   {
     243     1214821 :     for (decltype(_mesh_filter->n_bins()) e = 0; e < _mesh_filter->n_bins(); ++e)
     244             :     {
     245     1213312 :       Real unnormalized_tally = tally_vals[local_score](ext_bin * _mesh_filter->n_bins() + e);
     246             : 
     247             :       // divide each tally by the volume that it corresponds to in MOOSE
     248             :       // because we will apply it as a volumetric tally (per unit volume).
     249             :       // Because we require that the mesh template has units of cm based on the
     250             :       // mesh constructors in OpenMC, we need to adjust the division
     251     1213312 :       Real volumetric_tally = unnormalized_tally;
     252     1213312 :       volumetric_tally *= norm_by_src_rate
     253     1213312 :                               ? _openmc_problem.tallyMultiplier(global_score) /
     254      949860 :                                     _mesh_template->volume(e) * _openmc_problem.scaling() *
     255             :                                     _openmc_problem.scaling() * _openmc_problem.scaling()
     256             :                               : 1.0;
     257     1213312 :       total += _ext_bins_to_skip[ext_bin] ? 0.0 : unnormalized_tally;
     258             : 
     259     1213312 :       auto var = var_numbers[_num_ext_filter_bins * local_score + ext_bin];
     260     1213312 :       auto elem_id = _use_dof_map ? _bin_to_element_mapping[e] : mesh_offset + e;
     261     2426624 :       fillElementalAuxVariable(var, {elem_id}, volumetric_tally);
     262             :     }
     263             :   }
     264             : 
     265        1263 :   return total;
     266             : }
     267             : 
     268             : void
     269         722 : MeshTally::checkMeshTemplateAndTranslations()
     270             : {
     271             :   // we can do some rudimentary checking on the mesh template by comparing the centroid
     272             :   // coordinates compared to centroids in the [Mesh] (because right now, we just doing a simple
     273             :   // copy transfer that necessitates the meshes to have the same elements in the same order). In
     274             :   // other words, you might have two meshes that represent the same geometry, the element ordering
     275             :   // could be different.
     276         722 :   unsigned int mesh_offset = _instance * _mesh_filter->n_bins();
     277      401370 :   for (int e = 0; e < _mesh_filter->n_bins(); ++e)
     278             :   {
     279      400654 :     auto elem_id = _use_dof_map ? _bin_to_element_mapping[e] : mesh_offset + e;
     280      400654 :     auto elem_ptr = _openmc_problem.getMooseMesh().queryElemPtr(elem_id);
     281             : 
     282             :     // if element is not on this part of the distributed mesh, skip it
     283      400654 :     if (!elem_ptr)
     284       60654 :       continue;
     285             : 
     286      340000 :     const auto pt = _mesh_template->centroid(e);
     287      340000 :     Point centroid_template = {pt[0], pt[1], pt[2]};
     288             : 
     289             :     // The translation applied in OpenMC isn't actually registered in the mesh itself;
     290             :     // it is always added on to the point, so we need to do the same here
     291             :     centroid_template += _mesh_translation;
     292             : 
     293             :     // because the mesh template and [Mesh] may be in different units, we need
     294             :     // to adjust the [Mesh] by the scaling factor before doing a comparison.
     295      340000 :     Point centroid_mesh = elem_ptr->vertex_average() * _openmc_problem.scaling();
     296             : 
     297             :     // if the centroids are the same except for a factor of 'scaling', then we can
     298             :     // guess that the mesh_template is probably not in units of centimeters
     299      340000 :     if (_openmc_problem.hasScaling())
     300             :     {
     301             :       // if scaling was applied correctly, then each calculation of 'scaling' here should equal 1.
     302             :       // Otherwise, if they're all the same, then 'scaling_x' is probably the factor by which the
     303             :       // mesh_template needs to be multiplied, so we can print a helpful error message
     304             :       bool incorrect_scaling = true;
     305      147240 :       for (unsigned int j = 0; j < OpenMCCellAverageProblem::DIMENSION; ++j)
     306             :       {
     307      110430 :         Real scaling = centroid_mesh(j) / centroid_template(j);
     308      110430 :         incorrect_scaling = incorrect_scaling && !MooseUtils::absoluteFuzzyEqual(scaling, 1.0);
     309             :       }
     310             : 
     311       36810 :       if (incorrect_scaling)
     312           2 :         paramError("mesh_template",
     313             :                    "The centroids of the 'mesh_template' differ from the "
     314           2 :                    "centroids of the [Mesh] by a factor of " +
     315           2 :                        Moose::stringify(centroid_mesh(0) / centroid_template(0)) +
     316             :                        ".\nDid you forget that the 'mesh_template' must be in "
     317             :                        "the same units as the [Mesh]?");
     318             :     }
     319             : 
     320             :     // check if centroids are the same
     321             :     bool different_centroids = false;
     322     1359992 :     for (unsigned int j = 0; j < OpenMCCellAverageProblem::DIMENSION; ++j)
     323     1019994 :       different_centroids = different_centroids ||
     324             :                             !MooseUtils::absoluteFuzzyEqual(centroid_mesh(j), centroid_template(j));
     325             : 
     326      339998 :     if (different_centroids)
     327           4 :       paramError(
     328             :           "mesh_template",
     329           4 :           "Centroid for element " + Moose::stringify(elem_id) +
     330           8 :               " in the [Mesh] (cm): " + _openmc_problem.printPoint(centroid_mesh) +
     331           4 :               "\ndoes not match centroid for element " + Moose::stringify(e) +
     332           8 :               " in the 'mesh_template' with instance " + Moose::stringify(_instance) +
     333           8 :               " (cm): " + _openmc_problem.printPoint(centroid_template) +
     334             :               "!\n\nThe copy transfer requires that the [Mesh] and 'mesh_template' be identical.");
     335             :   }
     336         716 : }
     337             : #endif

Generated by: LCOV version 1.14