LCOV - code coverage report
Current view: top level - src/tallies - CellTally.C (source / functions) Hit Total Coverage
Test: neams-th-coe/cardinal: be601f Lines: 91 99 91.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 "CellTally.h"
      21             : 
      22             : registerMooseObject("CardinalApp", CellTally);
      23             : 
      24             : InputParameters
      25        3696 : CellTally::validParams()
      26             : {
      27        3696 :   auto params = TallyBase::validParams();
      28        3696 :   params.addClassDescription("A class which implements distributed cell tallies.");
      29        7392 :   params.addParam<std::vector<SubdomainName>>(
      30             :       "block",
      31             :       "Subdomains for which to add tallies in OpenMC. If not provided, cell "
      32             :       "tallies will be applied over the entire mesh.");
      33        7392 :   params.addParam<std::vector<SubdomainName>>("blocks",
      34             :                                               "This parameter is deprecated, use 'block' instead!");
      35             : 
      36        7392 :   params.addParam<bool>(
      37             :       "check_equal_mapped_tally_volumes",
      38        7392 :       false,
      39             :       "Whether to check if the tallied cells map to regions in the mesh of equal volume. "
      40             :       "This can be helpful to ensure that the volume normalization of OpenMC's tallies doesn't "
      41             :       "introduce any unintentional distortion just because the mapped volumes are different. "
      42             :       "You should only set this to true if your OpenMC tally cells are all the same volume!");
      43       11088 :   params.addRangeCheckedParam<Real>("equal_tally_volume_abs_tol",
      44        7392 :                                     1e-8,
      45             :                                     "equal_tally_volume_abs_tol > 0",
      46             :                                     "Absolute tolerance for comparing tally volumes");
      47             : 
      48        3696 :   return params;
      49           0 : }
      50             : 
      51        1814 : CellTally::CellTally(const InputParameters & parameters)
      52             :   : TallyBase(parameters),
      53        3572 :     _check_equal_mapped_tally_volumes(getParam<bool>("check_equal_mapped_tally_volumes")),
      54        5386 :     _equal_tally_volume_abs_tol(getParam<Real>("equal_tally_volume_abs_tol"))
      55             : {
      56        3572 :   if (isParamSetByUser("blocks"))
      57           0 :     mooseError("This parameter is deprecated, use 'block' instead!");
      58             : 
      59        3572 :   if (isParamValid("block"))
      60             :   {
      61        4758 :     auto block_names = getParam<std::vector<SubdomainName>>("block");
      62        1586 :     if (block_names.empty())
      63           0 :       paramError("block", "Subdomain names must be provided if using 'block'!");
      64             : 
      65        1586 :     auto block_ids = _openmc_problem.getMooseMesh().getSubdomainIDs(block_names);
      66        1586 :     std::copy(
      67             :         block_ids.begin(), block_ids.end(), std::inserter(_tally_blocks, _tally_blocks.end()));
      68             : 
      69             :     // Check to make sure all of the blocks are in the mesh.
      70        1586 :     const auto & subdomains = _openmc_problem.getMooseMesh().meshSubdomains();
      71        4327 :     for (std::size_t b = 0; b < block_names.size(); ++b)
      72        2743 :       if (subdomains.find(block_ids[b]) == subdomains.end())
      73           4 :         paramError("block",
      74           2 :                    "Block '" + block_names[b] + "' specified in 'block' not found in mesh!");
      75        1584 :   }
      76             :   else
      77             :   {
      78             :     // Tally over all mesh blocks if no blocks are provided.
      79         400 :     for (const auto & s : _openmc_problem.getMooseMesh().meshSubdomains())
      80             :       _tally_blocks.insert(s);
      81             :   }
      82        1784 : }
      83             : 
      84             : std::pair<unsigned int, openmc::Filter *>
      85        1764 : CellTally::spatialFilter()
      86             : {
      87             :   // Check to make sure we can map tallies to the mesh subdomains requested in tally_blocks.
      88        1764 :   checkCellMappedSubdomains();
      89             : 
      90        1762 :   if (_openmc_problem.cellToElem().size() == 0)
      91           0 :     mooseError("Did not find any overlap between MOOSE elements and OpenMC cells for "
      92             :                "the specified blocks!");
      93             : 
      94        1762 :   auto tally_cells = getTallyCells();
      95             :   std::vector<openmc::CellInstance> cells;
      96       64154 :   for (const auto & c : tally_cells)
      97       62394 :     cells.push_back({c.first, c.second});
      98             : 
      99        1760 :   _cell_filter = dynamic_cast<openmc::CellInstanceFilter *>(openmc::Filter::create("cellinstance"));
     100        1760 :   _cell_filter->set_cell_instances(cells);
     101             : 
     102        3520 :   return std::make_pair(openmc::model::tally_filters.size() - 1, _cell_filter);
     103             : }
     104             : 
     105             : Real
     106        2557 : CellTally::storeResultsInner(const std::vector<unsigned int> & var_numbers,
     107             :                              unsigned int local_score,
     108             :                              unsigned int global_score,
     109             :                              std::vector<xt::xtensor<double, 1>> tally_vals,
     110             :                              bool norm_by_src_rate)
     111             : {
     112             :   Real total = 0.0;
     113             : 
     114        6188 :   for (unsigned int ext_bin = 0; ext_bin < _num_ext_filter_bins; ++ext_bin)
     115             :   {
     116             :     int i = 0;
     117      239521 :     for (const auto & c : _openmc_problem.cellToElem())
     118             :     {
     119      235890 :       auto cell_info = c.first;
     120             : 
     121             :       // if this cell doesn't have any tallies, skip it
     122      235890 :       if (!_cell_has_tally[cell_info])
     123       32608 :         continue;
     124             : 
     125      203282 :       Real unnormalized_tally = tally_vals[local_score](ext_bin * _cell_filter->n_bins() + i++);
     126             : 
     127             :       // divide each tally value by the volume that it corresponds to in MOOSE
     128             :       // because we will apply it as a volumetric tally
     129      203282 :       Real volumetric_tally = unnormalized_tally;
     130      406532 :       volumetric_tally *= norm_by_src_rate ? _openmc_problem.tallyMultiplier(global_score) /
     131      203250 :                                                  _openmc_problem.cellMappedVolume(cell_info)
     132             :                                            : 1.0;
     133      203282 :       total += _ext_bins_to_skip[ext_bin] ? 0.0 : unnormalized_tally;
     134             : 
     135      203282 :       auto var = var_numbers[_num_ext_filter_bins * local_score + ext_bin];
     136      203282 :       fillElementalAuxVariable(var, c.second, volumetric_tally);
     137             :     }
     138             :   }
     139             : 
     140        2557 :   return total;
     141             : }
     142             : 
     143             : void
     144        1764 : CellTally::checkCellMappedSubdomains()
     145             : {
     146             :   _cell_has_tally.clear();
     147             : 
     148             :   // If the OpenMC cell maps to multiple subdomains that _also_ have different
     149             :   // tally settings, we need to error because we are unsure of whether to add tallies or not;
     150             :   // both of these need to be true to error
     151       76566 :   for (const auto & c : _openmc_problem.cellToElem())
     152             :   {
     153             :     bool at_least_one_in_tallies = false;
     154             :     bool at_least_one_not_in_tallies = false;
     155             :     int block_in_tallies, block_not_in_tallies;
     156             : 
     157       74804 :     auto cell_info = c.first;
     158       74804 :     auto cell_subdomains = _openmc_problem.getCellToElementSub(cell_info);
     159      179508 :     for (const auto & s : cell_subdomains)
     160             :     {
     161      104706 :       if (!at_least_one_in_tallies)
     162             :       {
     163       74830 :         at_least_one_in_tallies = _tally_blocks.count(s) != 0;
     164       74830 :         block_in_tallies = s;
     165             :       }
     166             : 
     167      104706 :       if (!at_least_one_not_in_tallies)
     168             :       {
     169      104680 :         at_least_one_not_in_tallies = _tally_blocks.count(s) == 0;
     170      104680 :         block_not_in_tallies = s;
     171             :       }
     172             : 
     173             :       // can cut the search early if we've already hit multiple tally settings
     174      104706 :       if (at_least_one_in_tallies && at_least_one_not_in_tallies)
     175             :         break;
     176             :     }
     177             : 
     178       74804 :     if (at_least_one_in_tallies && at_least_one_not_in_tallies)
     179           6 :       mooseError("cell " + _openmc_problem.printCell(cell_info) +
     180             :                  " maps to blocks with different tally settings!\n"
     181           2 :                  "Block " +
     182           0 :                  Moose::stringify(block_in_tallies) +
     183             :                  " is in 'block', but "
     184           2 :                  "block " +
     185           0 :                  Moose::stringify(block_not_in_tallies) + " is not.");
     186             : 
     187       74802 :     _cell_has_tally[cell_info] = at_least_one_in_tallies;
     188             :   }
     189        1762 : }
     190             : 
     191             : std::vector<OpenMCCellAverageProblem::cellInfo>
     192        1762 : CellTally::getTallyCells() const
     193             : {
     194             :   bool is_first_tally_cell = true;
     195        1762 :   OpenMCCellAverageProblem::cellInfo first_tally_cell;
     196             :   Real mapped_tally_volume;
     197             : 
     198             :   std::vector<OpenMCCellAverageProblem::cellInfo> tally_cells;
     199             : 
     200       76554 :   for (const auto & c : _openmc_problem.cellToElem())
     201             :   {
     202       74794 :     auto cell_info = c.first;
     203             : 
     204       74794 :     if (_cell_has_tally.at(cell_info))
     205             :     {
     206       62398 :       tally_cells.push_back(cell_info);
     207             : 
     208       62398 :       if (is_first_tally_cell)
     209             :       {
     210             :         is_first_tally_cell = false;
     211             :         first_tally_cell = cell_info;
     212        1762 :         mapped_tally_volume = _openmc_problem.cellMappedVolume(first_tally_cell);
     213             :       }
     214             : 
     215       62398 :       if (_check_equal_mapped_tally_volumes)
     216             :       {
     217        6270 :         Real diff = std::abs(mapped_tally_volume - _openmc_problem.cellMappedVolume(cell_info));
     218        6270 :         bool absolute_diff = diff > _equal_tally_volume_abs_tol;
     219        6270 :         bool relative_diff = diff / mapped_tally_volume > 1e-3;
     220        6270 :         if (absolute_diff && relative_diff)
     221             :         {
     222           2 :           std::stringstream msg;
     223             :           msg << "Detected un-equal mapped tally volumes!\n cell "
     224           2 :               << _openmc_problem.printCell(first_tally_cell) << " maps to a volume of "
     225           4 :               << Moose::stringify(_openmc_problem.cellMappedVolume(first_tally_cell))
     226           4 :               << " (cm3)\n cell " << _openmc_problem.printCell(cell_info) << " maps to a volume of "
     227           4 :               << Moose::stringify(_openmc_problem.cellMappedVolume(cell_info))
     228             :               << " (cm3).\n\n"
     229             :                  "If the tallied cells in your OpenMC model are of identical volumes, this means "
     230             :                  "that you can get\n"
     231             :                  "distortion of the volumetric tally output. For instance, suppose you have "
     232             :                  "two equal-size OpenMC\n"
     233             :                  "cells which have the same volume - but each OpenMC cell maps to a MOOSE region "
     234             :                  "of different volume\n"
     235             :                  "just due to the nature of the centroid mapping scheme. Even if those two tallies "
     236             :                  "do actually have the\n"
     237             :                  "same value, the volumetric tally will be different because you'll be "
     238             :                  "dividing each tally by a\n"
     239          10 :                  "different mapped MOOSE volume.\n\n";
     240             : 
     241           2 :           if (_openmc_problem.hasPointTransformations())
     242             :             msg << "NOTE: You have imposed symmetry, which means that you'll hit this error if any "
     243             :                    "of your tally\n"
     244             :                    "cells are cut by symmetry planes. If your tally cells would otherwise be the "
     245             :                    "same volume if NOT\n"
     246             :                    "imposing symmetry, or if your tally cells are not the same volume regardless, "
     247             :                    "you need to set\n"
     248           0 :                    "'check_equal_mapped_tally_volumes = false'.";
     249             :           else
     250             :             msg << "We recommend re-creating the mesh mirror to have an equal volume mapping of "
     251             :                    "MOOSE elements to each\n"
     252             :                    "OpenMC cell. Or, you can disable this check by setting "
     253           2 :                    "'check_equal_mapped_tally_volumes = false'.";
     254             : 
     255           2 :           mooseError(msg.str());
     256           0 :         }
     257             :       }
     258             :     }
     259             :   }
     260             : 
     261        1760 :   return tally_cells;
     262             : }
     263             : #endif

Generated by: LCOV version 1.14