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

Generated by: LCOV version 1.14