LCOV - code coverage report
Current view: top level - src/base - OpenMCCellAverageProblem.C (source / functions) Hit Total Coverage
Test: neams-th-coe/cardinal: 920dc5 Lines: 1364 1446 94.3 %
Date: 2025-08-10 20:41:39 Functions: 76 76 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             : 
      21             : #include "OpenMCCellAverageProblem.h"
      22             : 
      23             : #include "DelimitedFileReader.h"
      24             : #include "DisplacedProblem.h"
      25             : #include "TallyBase.h"
      26             : #include "CellTally.h"
      27             : #include "AddTallyAction.h"
      28             : #include "SetupMGXSAction.h"
      29             : #include "OpenMCVolumeCalculation.h"
      30             : #include "CreateDisplacedProblemAction.h"
      31             : 
      32             : #include "openmc/constants.h"
      33             : #include "openmc/cross_sections.h"
      34             : #include "openmc/dagmc.h"
      35             : #include "openmc/error.h"
      36             : #include "openmc/lattice.h"
      37             : #include "openmc/particle.h"
      38             : #include "openmc/photon.h"
      39             : #include "openmc/message_passing.h"
      40             : #include "openmc/nuclide.h"
      41             : #include "openmc/random_lcg.h"
      42             : #include "openmc/settings.h"
      43             : #include "openmc/summary.h"
      44             : #include "openmc/tallies/trigger.h"
      45             : #include "openmc/volume_calc.h"
      46             : #include "openmc/universe.h"
      47             : #include "xtensor/xarray.hpp"
      48             : 
      49             : registerMooseObject("CardinalApp", OpenMCCellAverageProblem);
      50             : 
      51             : bool OpenMCCellAverageProblem::_first_transfer = true;
      52             : bool OpenMCCellAverageProblem::_printed_initial = false;
      53             : bool OpenMCCellAverageProblem::_printed_triso_warning = false;
      54             : 
      55             : InputParameters
      56        3870 : OpenMCCellAverageProblem::validParams()
      57             : {
      58        3870 :   InputParameters params = OpenMCProblemBase::validParams();
      59        7740 :   params.addParam<bool>("output_cell_mapping",
      60        7740 :                         true,
      61             :                         "Whether to automatically output the mapping from OpenMC cells to the "
      62             :                         "[Mesh], usually for diagnostic purposes");
      63             : 
      64        7740 :   params.addParam<bool>("check_tally_sum",
      65             :                         "Whether to check consistency between the local tallies "
      66             :                         "with a global tally sum");
      67        7740 :   params.addParam<MooseEnum>(
      68             :       "initial_properties",
      69        7740 :       getInitialPropertiesEnum(),
      70             :       "Where to read the temperature and density initial conditions for OpenMC");
      71             : 
      72        7740 :   params.addParam<bool>("export_properties",
      73        7740 :                         false,
      74             :                         "Whether to export OpenMC's temperature and density properties to an HDF5 "
      75             :                         "file after updating them from MOOSE.");
      76        7740 :   params.addParam<bool>(
      77             :       "normalize_by_global_tally",
      78        7740 :       true,
      79             :       "Whether to normalize local tallies by a global tally (true) or else by the sum "
      80             :       "of the local tally (false)");
      81        7740 :   params.addParam<bool>("assume_separate_tallies",
      82        7740 :                         false,
      83             :                         "Whether to assume that all tallies added in the XML files or by Cardinal "
      84             :                         "are spatially separate. This is a performance optimization");
      85             : 
      86        7740 :   params.addParam<bool>("map_density_by_cell",
      87        7740 :       true,
      88             :       "Whether to apply a unique density to every OpenMC cell (the default), or "
      89             :       "instead apply a unique density to every OpenMC material (even if that material is "
      90             :       "filled into more than one cell). If your OpenMC model has a unique material "
      91             :       "in every cell you want to receive density feedback, these two options are IDENTICAL");
      92             : 
      93             :   MooseEnum scores_heat(
      94        7740 :     "heating heating_local kappa_fission fission_q_prompt fission_q_recoverable");
      95        7740 :   params.addParam<MooseEnum>(
      96             :       "source_rate_normalization",
      97             :       scores_heat,
      98             :       "Score to use for computing the "
      99             :       "particle source rate (source/sec) for a certain tallies in "
     100             :       "eigenvalue mode. In other words, the "
     101             :       "source/sec is computed as (power divided by the global value of this tally)");
     102             : 
     103        7740 :   params.addParam<MooseEnum>(
     104             :       "k_trigger",
     105        7740 :       getTallyTriggerEnum(),
     106             :       "Trigger criterion to determine when OpenMC simulation is complete based on k");
     107        7740 :   params.addRangeCheckedParam<Real>(
     108             :       "k_trigger_threshold", "k_trigger_threshold > 0", "Threshold for the k trigger");
     109        7740 :   params.addRangeCheckedParam<unsigned int>(
     110             :       "max_batches", "max_batches > 0", "Maximum number of batches, when using triggers");
     111       11610 :   params.addRangeCheckedParam<unsigned int>(
     112        7740 :       "batch_interval", 1, "batch_interval > 0", "Trigger batch interval");
     113             : 
     114        7740 :   params.addParam<std::vector<std::vector<std::string>>>(
     115             :       "temperature_variables",
     116             :       "Vector of variable names corresponding to the temperatures sent into OpenMC. Each entry maps to "
     117             :       "the corresponding entry in 'temperature_blocks.' If not specified, each entry defaults to 'temp'");
     118        7740 :   params.addParam<std::vector<std::vector<SubdomainName>>>(
     119             :       "temperature_blocks",
     120             :       "Blocks corresponding to each of the 'temperature_variables'. If not specified, "
     121             :       "there will be no temperature feedback to OpenMC.");
     122             : 
     123        7740 :   params.addParam<std::vector<std::vector<std::string>>>(
     124             :       "density_variables",
     125             :       "Vector of variable names corresponding to the densities sent into OpenMC. Each entry maps "
     126             :       "to the corresponding entry in 'density_blocks.' If not specified, each entry defaults to "
     127             :       "'density'");
     128        7740 :   params.addParam<std::vector<std::vector<SubdomainName>>>(
     129             :       "density_blocks",
     130             :       "Blocks corresponding to each of the 'density_variables'. If not specified, "
     131             :       "there will be no density feedback to OpenMC.");
     132             : 
     133        7740 :   params.addParam<unsigned int>("cell_level",
     134             :                                 "Coordinate level in OpenMC (across the entire geometry) to use "
     135             :                                 "for identifying cells");
     136        7740 :   params.addParam<unsigned int>(
     137             :       "lowest_cell_level",
     138             :       "Lowest coordinate level in OpenMC to use for identifying cells. The cell level for coupling "
     139             :       "will use the value set with this parameter unless the geometry does not have that many "
     140             :       "layers of geometry nesting, in which case the locally lowest depth is used");
     141             : 
     142        7740 :   params.addParam<std::vector<SubdomainName>>(
     143             :       "identical_cell_fills",
     144             :       "Blocks on which the OpenMC cells have identical fill universes; this is an optimization to "
     145             :       "speed up initialization for TRISO problems while also reducing memory usage. It is assumed "
     146             :       "that any cell which maps to one of these subdomains has exactly the same universe filling "
     147             :       "it as all other cells which map to these subdomains. We HIGHLY recommend that the first "
     148             :       "time you try using this, that you also set 'check_identical_cell_fills = true' to catch "
     149             :       "any possible user errors which would exclude you from using this option safely.");
     150        7740 :   params.addParam<bool>(
     151             :       "check_identical_cell_fills",
     152        7740 :       false,
     153             :       "Whether to check that your model does indeed have identical cell fills, allowing "
     154             :       "you to set 'identical_cell_fills' to speed up initialization");
     155             : 
     156        7740 :   params.addParam<MooseEnum>("relaxation",
     157        7740 :                              getRelaxationEnum(),
     158             :                              "Type of relaxation to apply to the OpenMC solution");
     159       11610 :   params.addRangeCheckedParam<Real>("relaxation_factor",
     160        7740 :                                     0.5,
     161             :                                     "relaxation_factor > 0.0 & relaxation_factor < 2.0",
     162             :                                     "Relaxation factor for use with constant relaxation");
     163        7740 :   params.addParam<int>("first_iteration_particles",
     164             :                        "Number of particles to use for first iteration "
     165             :                        "when using Dufek-Gudowski relaxation");
     166             : 
     167        7740 :   params.addParam<UserObjectName>(
     168             :       "symmetry_mapper",
     169             :       "User object (of type SymmetryPointGenerator) "
     170             :       "to map from a symmetric OpenMC model to a full-domain [Mesh]. For example, you can use this "
     171             :       "to map from a quarter-symmetric OpenMC model to a whole-domain [Mesh].");
     172             : 
     173        7740 :   params.addParam<UserObjectName>(
     174             :       "volume_calculation",
     175             :       "User object that will perform a stochastic volume calculation to get the OpenMC "
     176             :       "cell volumes. This can be used to check that the MOOSE regions to which the cells map are "
     177             :       "of approximately the same volume as the true cells.");
     178        7740 :   params.addParam<UserObjectName>("skinner", "When using DAGMC geometries, an optional skinner that will "
     179             :     "regenerate the OpenMC geometry on-the-fly according to iso-contours of temperature and density");
     180        3870 :   params.addClassDescription(
     181             :       "Couple OpenMC to MOOSE through cell-averaged temperature, density, and tallies.");
     182             : 
     183        3870 :   return params;
     184        3870 : }
     185             : 
     186        1952 : OpenMCCellAverageProblem::OpenMCCellAverageProblem(const InputParameters & params)
     187             :   : OpenMCProblemBase(params),
     188        1940 :     _serialized_solution(_aux->serializedSolution()),
     189        3880 :     _output_cell_mapping(getParam<bool>("output_cell_mapping")),
     190        1940 :     _initial_condition(
     191        1940 :         getParam<MooseEnum>("initial_properties").getEnum<coupling::OpenMCInitialCondition>()),
     192        3880 :     _relaxation(getParam<MooseEnum>("relaxation").getEnum<relaxation::RelaxationEnum>()),
     193        3880 :     _k_trigger(getParam<MooseEnum>("k_trigger").getEnum<trigger::TallyTriggerTypeEnum>()),
     194        3880 :     _export_properties(getParam<bool>("export_properties")),
     195        3880 :     _normalize_by_global(_run_mode == openmc::RunMode::FIXED_SOURCE
     196        1940 :                              ? false
     197        3790 :                              : getParam<bool>("normalize_by_global_tally")),
     198        3880 :     _using_skinner(isParamValid("skinner")),
     199        1940 :     _need_to_reinit_coupling(_has_adaptivity || _using_skinner),
     200        1940 :     _check_tally_sum(
     201        1940 :         isParamValid("check_tally_sum")
     202        4307 :             ? getParam<bool>("check_tally_sum")
     203        1513 :             : (_run_mode == openmc::RunMode::FIXED_SOURCE ? true : _normalize_by_global)),
     204        3880 :     _relaxation_factor(getParam<Real>("relaxation_factor")),
     205        1940 :     _has_identical_cell_fills(params.isParamSetByUser("identical_cell_fills")),
     206        3880 :     _check_identical_cell_fills(getParam<bool>("check_identical_cell_fills")),
     207        3880 :     _assume_separate_tallies(getParam<bool>("assume_separate_tallies")),
     208        3880 :     _map_density_by_cell(getParam<bool>("map_density_by_cell")),
     209        1940 :     _specified_density_feedback(params.isParamSetByUser("density_blocks")),
     210        1940 :     _specified_temperature_feedback(params.isParamSetByUser("temperature_blocks")),
     211        1441 :     _needs_to_map_cells(_specified_density_feedback || _specified_temperature_feedback),
     212        1940 :     _needs_global_tally(_check_tally_sum || _normalize_by_global),
     213        1940 :     _volume_calc(nullptr),
     214        1940 :     _symmetry(nullptr),
     215        5832 :     _initial_num_openmc_surfaces(openmc::model::surfaces.size())
     216             : {
     217        1940 :   if (_specified_temperature_feedback && openmc::settings::temperature_range[1] == 0.0)
     218           2 :     mooseWarning("For multiphysics simulations, we recommend setting the 'temperature_range' in "
     219             :                  "OpenMC's settings.xml file. This will pre-load nuclear data over a range of "
     220             :                  "temperatures, instead of only the temperatures defined in the XML file.\n\nFor "
     221             :                  "efficiency purposes, OpenMC only checks that cell temperatures are within the "
     222             :                  "global min/max of loaded data, which can be different from data loaded for each "
     223             :                  "nuclide. Run may abort suddenly if requested nuclear data is not available.");
     224             : 
     225             :   // Check to see if a displaced problem is being initialized
     226             :   const auto & dis_actions =
     227        1938 :       getMooseApp().actionWarehouse().getActions<CreateDisplacedProblemAction>();
     228        3876 :   for (const auto & act : dis_actions)
     229             :   {
     230        1938 :     auto displacements = act->isParamValid("displacements");
     231        3876 :     auto use = act->getParam<bool>("use_displaced_mesh");
     232        1938 :     _use_displaced = displacements && use;
     233             : 
     234             :     // print a warning if the user added displacements, but are not using them
     235        1938 :     if (!use && displacements)
     236           0 :       mooseWarning("When 'use_displaced_mesh' is false, the 'displacements' are unused!");
     237             : 
     238        5814 :     if (act->isParamSetByUser("use_displaced_mesh") && use && !displacements)
     239           0 :       mooseWarning("When 'use_displaced_mesh' is true, but no 'displacements' are provided, then "
     240             :                    "the displaced mesh will not be used.");
     241             : 
     242        1938 :     _need_to_reinit_coupling |= _use_displaced;
     243             :   }
     244             : 
     245        1938 :   if (_use_displaced && !_using_skinner)
     246          15 :     mooseWarning("Your problem has a moving mesh, but you have not provided a 'skinner'. The "
     247             :                  "[Mesh] will move, but the underlying OpenMC geometry will remain unchanged. "
     248             :                  "Unexpected behavior may occur.");
     249             : 
     250             :   // Look through the list of AddTallyActions to see if we have a CellTally. If so, we need to map
     251             :   // cells.
     252        1937 :   const auto & tally_actions = getMooseApp().actionWarehouse().getActions<AddTallyAction>();
     253        3965 :   for (const auto & act : tally_actions)
     254        2028 :     _has_cell_tallies |= act->getMooseObjectType() == "CellTally";
     255             : 
     256             :   // Repeat the same check for SetUpMGXSActions.
     257        1937 :   const auto & mgxs_actions = getMooseApp().actionWarehouse().getActions<SetupMGXSAction>();
     258        2005 :   for (const auto & act : mgxs_actions)
     259          68 :     _has_cell_tallies |= act->addingCellTallies();
     260        1937 :   _needs_to_map_cells |= _has_cell_tallies;
     261             : 
     262        1937 :   if (!_needs_to_map_cells)
     263         292 :     checkUnusedParam(params,
     264             :                      "output_cell_mapping",
     265             :                      "'temperature_blocks', 'density_blocks', and 'tally_blocks' are empty");
     266             : 
     267        1937 :   if (!_specified_temperature_feedback && !_specified_density_feedback)
     268         844 :     checkUnusedParam(
     269             :         params, "initial_properties", "'temperature_blocks' and 'density_blocks' are unused");
     270             : 
     271             :   // We need to clear and re-initialize OpenMC problem in the cases of:
     272             :   //   - the [Mesh] is being adaptively refined
     273             :   //   - the [Mesh] is deforming in space
     274             :   //
     275             :   // If the [Mesh] is changing, then we certainly know that the mesh tallies
     276             :   // need to be re-initialized because (a) for file-based mesh tallies, we need
     277             :   // to enforce that the mesh is identical to the [Mesh] and (b) for directly
     278             :   // tallying on the [Mesh], we need to pass that mesh info into OpenMC. For good
     279             :   // measure, we also need to re-initialize cell tallies because it's possible
     280             :   // that as the [Mesh] changes, the mapping from OpenMC cells to the [Mesh]
     281             :   // also changes, which could open the door to new cell IDs/instances being added
     282             :   // to the cell instance filter. If we need to re-init tallies, then we can't
     283             :   // guarantee that the tallies from iteration to iteration correspond to exactly
     284             :   // the same number of bins or to exactly the same regions of space, so we must
     285             :   // disable relaxation.
     286        1937 :   if (_need_to_reinit_coupling && _relaxation != relaxation::none)
     287           4 :     paramError(
     288             :         "relaxation",
     289             :         "When adaptivity is requested or a displaced problem is used, the mapping from the "
     290             :         "OpenMC model to the [Mesh] may vary in time. This means that we have no guarantee that "
     291             :         "the "
     292             :         "number of tally bins (or even the regions of space corresponding to each bin) are fixed. "
     293             :         "Therefore, it is not possible to apply relaxation to the OpenMC tallies because you might "
     294             :         "end up trying to add vectors of different length (and possibly spatial mapping).");
     295             : 
     296        1933 :   if (_run_mode == openmc::RunMode::FIXED_SOURCE)
     297         178 :     checkUnusedParam(params, "normalize_by_global_tally", "running OpenMC in fixed source mode");
     298             : 
     299        1931 :   if (_run_mode != openmc::RunMode::EIGENVALUE && _k_trigger != trigger::none)
     300           2 :     paramError("k_trigger",
     301             :                "Cannot specify a 'k_trigger' for OpenMC runs that are not eigenvalue mode!");
     302             : 
     303        1929 :   if (_assume_separate_tallies && _needs_global_tally)
     304           2 :     paramError("assume_separate_tallies",
     305             :                "Cannot assume separate tallies when either of 'check_tally_sum' or"
     306             :                "'normalize_by_global_tally' is true!");
     307             : 
     308             :   // determine the number of particles set either through XML or the wrapping
     309        1927 :   if (_relaxation == relaxation::dufek_gudowski)
     310             :   {
     311          32 :     checkUnusedParam(params, "particles", "using Dufek-Gudowski relaxation");
     312          32 :     checkRequiredParam(params, "first_iteration_particles", "using Dufek-Gudowski relaxation");
     313          32 :     openmc::settings::n_particles = getParam<int>("first_iteration_particles");
     314             :   }
     315             :   else
     316        3822 :     checkUnusedParam(params, "first_iteration_particles", "not using Dufek-Gudowski relaxation");
     317             : 
     318        1927 :   if (!_specified_density_feedback || _using_skinner)
     319        2862 :     checkUnusedParam(params,
     320             :                      "map_density_by_cell",
     321             :                      "either (i) applying geometry skinning or (ii) 'density_blocks' is empty");
     322             : 
     323             :     // OpenMC will throw an error if the geometry contains DAG universes but OpenMC wasn't compiled
     324             :     // with DAGMC. So we can assume that if we have a DAGMC geometry, that we will also by this
     325             :     // point have DAGMC enabled.
     326             : #ifdef ENABLE_DAGMC
     327             :   bool has_csg;
     328             :   bool has_dag;
     329         989 :   geometryType(has_csg, has_dag);
     330             : 
     331         989 :   if (!has_dag)
     332        1879 :     checkUnusedParam(
     333             :         params, "skinner", "the OpenMC model does not contain any DagMC universes", true);
     334          49 :   else if (_using_skinner)
     335             :   {
     336             :     // Loop over all universes to find the DAGMC universe and to check and make sure we only have
     337             :     // the one.
     338             :     unsigned int num_dag_universes = 0;
     339          64 :     for (const auto & universe : openmc::model::universes)
     340             :     {
     341          37 :       if (universe->geom_type() == openmc::GeometryType::DAG)
     342             :       {
     343          28 :         _dagmc_universe_id = universe->id_;
     344          28 :         num_dag_universes++;
     345             :       }
     346             :     }
     347             : 
     348          27 :     if (num_dag_universes != 1)
     349           2 :       mooseError("The 'skinner' can only be used when the OpenMC geometry contains a single DAGMC "
     350             :                  "universe.\n"
     351           1 :                  "Your geometry contains " +
     352           0 :                  Moose::stringify(num_dag_universes) + " DAGMC universes.");
     353             : 
     354             :     // Loop over each element of each lattice to make sure that it doesn't contain the DAGMC
     355             :     // universe.
     356          26 :     for (const auto & lattice : openmc::model::lattices)
     357             :     {
     358           3 :       for (openmc::LatticeIter it = lattice->begin(); it != lattice->end(); ++it)
     359           2 :         if (openmc::model::universes[*it]->id_ == _dagmc_universe_id)
     360           1 :           mooseError("The 'skinner' cannot be used when the DAGMC universe is contained in lattice "
     361             :                      "geometry.");
     362             : 
     363           1 :       if (lattice->outer_ != openmc::NO_OUTER_UNIVERSE &&
     364           1 :           openmc::model::universes[lattice->outer_]->id_ == _dagmc_universe_id)
     365           1 :         mooseError("The 'skinner' cannot be used when the DAGMC universe is used as the outer "
     366             :                    "universe of a lattice.");
     367             :     }
     368             : 
     369             :     // Need to make sure that there is only a single cell which uses the DAGMC universe as it's
     370             :     // fill. The root universe must contain that cell, otherwise the DAGMC universe may be
     371             :     // replicated across the problem.
     372             :     unsigned int num_dag_instances = 0;
     373         101 :     for (const auto & cell : openmc::model::cells)
     374             :     {
     375          77 :       if (cell->type_ == openmc::Fill::UNIVERSE &&
     376           6 :           cell->fill_ == openmc::model::universe_map.at(_dagmc_universe_id))
     377             :       {
     378           5 :         _dagmc_root_universe = false;
     379           5 :         num_dag_instances++;
     380           5 :         _cell_using_dagmc_universe_id = cell->id_;
     381             :       }
     382             :     }
     383             : 
     384          24 :     if (num_dag_instances > 1)
     385           2 :       mooseError("The 'skinner' can only be used when the DAGMC universe in the OpenMC geometry is "
     386             :                  "used as a cell "
     387           1 :                  "fill at most once.\n Your geometry contains " +
     388           0 :                  Moose::stringify(num_dag_instances) +
     389             :                  " cells which "
     390             :                  "use the DAGMC universe as their fill.");
     391             : 
     392          23 :     if (!_dagmc_root_universe &&
     393           3 :         openmc::model::cells[openmc::model::cell_map.at(_cell_using_dagmc_universe_id)]
     394           3 :                 ->universe_ != openmc::model::root_universe)
     395           1 :       mooseError("The 'skinner' can only be used when the cell using the DAGMC universe as a fill "
     396             :                  "is contained in the "
     397             :                  "root universe.");
     398             : 
     399             :     // The newly-generated DAGMC cells could be disjoint in space, in which case
     400             :     // it is impossible for us to know with 100% certainty a priori how many materials
     401             :     // we would need to create.
     402          22 :     _map_density_by_cell = false;
     403             :   }
     404             : #else
     405        1876 :   checkUnusedParam(params, "skinner", "DAGMC geometries in OpenMC are not enabled in this build of Cardinal");
     406             : #endif
     407             : 
     408        1921 :   _n_particles_1 = nParticles();
     409             : 
     410        1921 :   if (_relaxation != relaxation::constant)
     411        3578 :     checkUnusedParam(params, "relaxation_factor", "not using constant relaxation");
     412             : 
     413        1921 :   readBlockParameters("identical_cell_fills", _identical_cell_fill_blocks);
     414             : 
     415        1921 :   if (!_has_identical_cell_fills)
     416        3754 :     checkUnusedParam(
     417             :         params, "check_identical_cell_fills", "'identical_cell_fills' is not specified");
     418             : 
     419        3832 :   readBlockVariables("temperature", "temp", _temp_vars_to_blocks, _temp_blocks);
     420        3814 :   readBlockVariables("density", "density", _density_vars_to_blocks, _density_blocks);
     421             : 
     422        1973 :   for (const auto & i : _identical_cell_fill_blocks)
     423          72 :     if (std::find(_density_blocks.begin(), _density_blocks.end(), i) != _density_blocks.end())
     424           2 :       paramError(
     425             :           "identical_cell_fills",
     426             :           "Entries in 'identical_cell_fills' cannot be contained in 'density_blocks'; the\n"
     427             :           "identical fill universe optimization is not yet implemented for density feedback.");
     428             : 
     429        1901 :   if (_needs_to_map_cells)
     430             :   {
     431        5271 :     if (isParamValid("cell_level") == isParamValid("lowest_cell_level"))
     432           1 :       mooseError("Either 'cell_level' or 'lowest_cell_level' must be specified. You have given "
     433             :                  "either both or none.");
     434             : 
     435             :     std::string selected_param;
     436        3512 :     if (isParamValid("cell_level"))
     437             :     {
     438        3476 :       _cell_level = getParam<unsigned int>("cell_level");
     439             :       selected_param = "cell_level";
     440             : 
     441        1738 :       if (_cell_level >= openmc::model::n_coord_levels)
     442           4 :         paramError(selected_param,
     443             :                    "Coordinate level for finding cells cannot be greater than total number "
     444           2 :                    "of coordinate levels: " +
     445           0 :                        Moose::stringify(openmc::model::n_coord_levels) + "!");
     446             :     }
     447             :     else
     448             :     {
     449          36 :       _cell_level = getParam<unsigned int>("lowest_cell_level");
     450             :       selected_param = "lowest_cell_level";
     451             :     }
     452             :   }
     453             :   else
     454             :   {
     455         288 :     checkUnusedParam(params,
     456             :                      "cell_level",
     457             :                      "'temperature_blocks', 'density_blocks', and 'tally_blocks' are empty");
     458         288 :     checkUnusedParam(params,
     459             :                      "lowest_cell_level",
     460             :                      "'temperature_blocks', 'density_blocks', and 'tally_blocks' are empty");
     461             :   }
     462        1898 : }
     463             : 
     464             : const MooseMesh &
     465     1417380 : OpenMCCellAverageProblem::getMooseMesh() const
     466             : {
     467     1417380 :   return mesh(_use_displaced);
     468             : }
     469             : 
     470             : MooseMesh &
     471   172333685 : OpenMCCellAverageProblem::getMooseMesh()
     472             : {
     473             :   // TODO: this could go into MOOSE framework directly
     474   172333685 :   if (_use_displaced && !_displaced_problem)
     475           0 :     mooseWarning("Displaced mesh was requested but the displaced problem does not exist. "
     476             :                  "Regular mesh will be returned");
     477             : 
     478   172333685 :   MooseMesh & m = ((_use_displaced && _displaced_problem) ? _displaced_problem->mesh() : mesh());
     479   172333685 :   return m;
     480             : }
     481             : 
     482             : void
     483        3832 : OpenMCCellAverageProblem::readBlockVariables(
     484             :     const std::string & param,
     485             :     const std::string & default_name,
     486             :     std::map<std::string, std::vector<SubdomainName>> & vars_to_specified_blocks,
     487             :     std::vector<SubdomainID> & specified_blocks)
     488             : {
     489        3832 :   std::string b = param + "_blocks";
     490        3832 :   std::string v = param + "_variables";
     491             : 
     492        3832 :   if (!isParamValid(b))
     493             :   {
     494        5502 :     checkUnusedParam(parameters(), v, "not setting '" + b + "'");
     495             :     return;
     496             :   }
     497             : 
     498             :   std::vector<std::vector<SubdomainName>> blocks;
     499        3986 :   read2DBlockParameters(b, blocks, specified_blocks);
     500             : 
     501             :   // now, get the names of those temperature variables
     502             :   std::vector<std::vector<std::string>> vars;
     503        1988 :   if (isParamValid(v))
     504             :   {
     505          74 :     vars = getParam<std::vector<std::vector<std::string>>>(v);
     506             : 
     507         222 :     checkEmptyVector(vars, "'" + v + "");
     508         254 :     for (const auto & t : vars)
     509         540 :       checkEmptyVector(t, "Entries in '" + v + "'");
     510             : 
     511          74 :     if (vars.size() != blocks.size())
     512          20 :       mooseError("'" + v + "' and '" + b + "' must be the same length!\n'" + v + "' is of length " +
     513          12 :                  std::to_string(vars.size()) + " and '" + b + "' is of length " +
     514           4 :                  std::to_string(blocks.size()));
     515             : 
     516             :     // TODO: for now, we restrict each set of blocks to map to a single temperature variable
     517         234 :     for (std::size_t i = 0; i < vars.size(); ++i)
     518         168 :       if (vars[i].size() > 1)
     519          12 :         mooseError("Each entry in '" + v + "' must be of length 1. Entry " + std::to_string(i) +
     520           8 :                    " is of length " + std::to_string(vars[i].size()));
     521             :   }
     522             :   else
     523             :   {
     524             :     // set a reasonable default, if not specified
     525        1914 :     vars.resize(blocks.size(), std::vector<std::string>(1));
     526        3828 :     for (std::size_t i = 0; i < blocks.size(); ++i)
     527             :       vars[i][0] = default_name;
     528             :   }
     529             : 
     530        4058 :   for (std::size_t i = 0; i < vars.size(); ++i)
     531        5094 :     for (std::size_t j = 0; j < blocks[i].size(); ++j)
     532        3016 :       vars_to_specified_blocks[vars[i][0]].push_back(blocks[i][j]);
     533        1980 : }
     534             : 
     535             : void
     536        1769 : OpenMCCellAverageProblem::initialSetup()
     537             : {
     538        1769 :   OpenMCProblemBase::initialSetup();
     539             : 
     540        1769 :   getOpenMCUserObjects();
     541             : 
     542        1765 :   if (!_needs_to_map_cells)
     543         258 :     checkUnusedParam(parameters(),
     544             :                      "volume_calculation",
     545             :                      "'temperature_blocks', 'density_blocks', and 'tally_blocks' are empty");
     546        3272 :   else if (isParamValid("volume_calculation"))
     547             :   {
     548         127 :     const auto & name = getParam<UserObjectName>("volume_calculation");
     549         127 :     auto * base = &getUserObject<UserObject>(name);
     550             : 
     551         127 :     _volume_calc = dynamic_cast<OpenMCVolumeCalculation *>(base);
     552             : 
     553         127 :     if (!_volume_calc)
     554           0 :       paramError("volume_calculation", "The 'volume_calculation' user object must be of type "
     555             :         "OpenMCVolumeCalculation!");
     556             :   }
     557             : 
     558        3530 :   if (isParamValid("symmetry_mapper"))
     559             :   {
     560          43 :     const auto & name = getParam<UserObjectName>("symmetry_mapper");
     561          43 :     auto base = &getUserObject<UserObject>(name);
     562             : 
     563          43 :     _symmetry = dynamic_cast<SymmetryPointGenerator *>(base);
     564             : 
     565          43 :     if (!_symmetry)
     566           2 :       paramError("symmetry_mapper",
     567             :                  "The 'symmetry_mapper' user object has to be of type SymmetryPointGenerator!");
     568             :   }
     569             : 
     570             :   // Get triggers.
     571        1763 :   getTallyTriggerParameters(_pars);
     572             : 
     573        1761 :   setupProblem();
     574             : 
     575             : #ifdef ENABLE_DAGMC
     576         876 :   if (_using_skinner)
     577             :   {
     578          21 :     std::set<SubdomainID> t(_temp_blocks.begin(), _temp_blocks.end());
     579          21 :     std::set<SubdomainID> d(_density_blocks.begin(), _density_blocks.end());
     580             : 
     581          21 :     if (t != getMooseMesh().meshSubdomains())
     582           0 :       paramError("temperature_blocks",
     583             :                  "The 'skinner' requires temperature feedback to be applied over the entire mesh. "
     584             :                  "Please update `temperature_blocks` to include all blocks.");
     585             : 
     586          21 :     if (d != getMooseMesh().meshSubdomains() && _specified_density_feedback)
     587           0 :       paramError("density_blocks",
     588             :                  "The 'skinner' requires density feedback to be applied over the entire mesh. "
     589             :                  "Please update `density_blocks` to include all blocks.");
     590             : 
     591          21 :     if (t != d && _specified_density_feedback)
     592           0 :       mooseError("The 'skinner' will apply skinning over the entire domain, and requires that the "
     593             :                  "entire problem uses identical settings for feedback. Please update "
     594             :                  "'temperature_blocks' and 'density_blocks' to include all blocks.");
     595             : 
     596          21 :     if (_symmetry)
     597           1 :       mooseError("Cannot combine the 'skinner' with 'symmetry_mapper'!\n\nWhen using a skinner, "
     598             :         "the [Mesh] must exactly match the underlying OpenMC model, so there is\n"
     599             :         "no need to transform spatial coordinates to map between OpenMC and the [Mesh].");
     600             : 
     601             :     // Rudimentary error checking to make sure all non-void DAGMC cells are mapped. This helps catch
     602             :     // errors where the skinned MOOSE mesh deletes DAGMC geometry.
     603             :     std::set<int32_t> mapped_dag_cells;
     604          80 :     for (const auto & c : openmc::model::cells)
     605         171 :       for (const auto & [c_info, elem] : _cell_to_elem)
     606         111 :         if (c->geom_type() == openmc::GeometryType::DAG &&
     607         109 :             c_info.first == openmc::model::cell_map.at(c->id_))
     608          36 :           mapped_dag_cells.insert(c->id_);
     609             : 
     610             :     unsigned int num_unmapped = 0;
     611             :     unsigned int num_dag_cells = 0;
     612          80 :     for (const auto & c : openmc::model::cells)
     613             :     {
     614             :       auto no_void =
     615          60 :           std::find(c->material_.begin(), c->material_.end(), MATERIAL_VOID) == c->material_.end();
     616          60 :       if (mapped_dag_cells.count(c->id_) == 0 && c->geom_type() == openmc::GeometryType::DAG &&
     617             :           no_void)
     618           1 :         num_unmapped++;
     619          60 :       if (c->geom_type() == openmc::GeometryType::DAG)
     620          59 :         num_dag_cells++;
     621             :     }
     622             : 
     623          20 :     if (num_unmapped > 0)
     624           2 :       mooseWarning("Your DAGMC geometry contains unmapped cells! The skinner assumes that "
     625             :                    "the DAG geometry used in the OpenMC model maps one to one to the mesh "
     626             :                    "mirror; if that is not the case the skinner may delete some parts of "
     627           1 :                    "your OpenMC model when the underlying geometry is regenerated. You have " +
     628           1 :                    Moose::stringify(num_unmapped) + " unmapped DAGMC cells out of " +
     629           0 :                    Moose::stringify(num_dag_cells) + " DAGMC cells.");
     630             : 
     631          19 :     const auto & name = getParam<UserObjectName>("skinner");
     632          19 :     auto base = &getUserObject<UserObject>(name);
     633             : 
     634          19 :     _skinner = dynamic_cast<MoabSkinner *>(base);
     635             : 
     636          19 :     if (!_skinner)
     637           1 :       paramError("skinner", "The 'skinner' user object must be of type MoabSkinner!");
     638             : 
     639          18 :     if (_skinner->hasDensitySkinning() != _specified_density_feedback)
     640           1 :       mooseError(
     641             :           "Detected inconsistent settings for density skinning and 'density_blocks'. If applying "
     642             :           "density feedback with 'density_blocks', then you must apply density skinning in the '",
     643             :           name,
     644             :           "' user object (and vice versa)");
     645             : 
     646          17 :     if (_initial_condition == coupling::hdf5)
     647           1 :       paramError("initial_properties", "Cannot load initial temperature and density properties from "
     648             :         "HDF5 files because there is no guarantee that the geometry (which is adaptively changing) matches "
     649             :         "that used to write the HDF5 file.");
     650             : 
     651             :     // If the DAGMC universe is the root universe the geometry contains no CSG cells. We need
     652             :     // to force the skinner to add a graveyard as the problem will contain no boundary contitions
     653             :     // after skinning is performed. If there are CSG cells in the geometry, this is not the case
     654             :     // as the DAGMC universe is embedded in a cell (which applies boundary conditions).
     655          16 :     if (_dagmc_root_universe)
     656          15 :       _skinner->setGraveyard(true);
     657             : 
     658          15 :     _skinner->setScaling(_scaling);
     659          15 :     _skinner->setVerbosity(_verbose);
     660          15 :     _skinner->makeDependentOnExternalAction();
     661          15 :     _skinner->setUseDisplacedMesh(_use_displaced);
     662             : 
     663             :     // the skinner expects that there is one OpenMC material per subdomain (otherwise this
     664             :     // indicates that our [Mesh] doesn't match the .h5m model, because DAGMC itself imposes
     665             :     // the one-material-per-cell case. In the future, if we generate DAGMC models directly
     666             :     // from the [Mesh] (bypassing the .h5m), we would not need this error check.
     667          15 :     _skinner->setMaterialNames(getMaterialInEachSubdomain());
     668          14 :     _skinner->initialize();
     669             :   }
     670             : #endif
     671        1703 : }
     672             : 
     673             : std::vector<std::string>
     674          15 : OpenMCCellAverageProblem::getMaterialInEachSubdomain() const
     675             : {
     676             :   std::vector<std::string> mats;
     677          43 :   for (const auto & s : _subdomain_to_material)
     678             :   {
     679          29 :     if (s.second.size() > 1)
     680             :     {
     681           1 :       std::stringstream msg;
     682             :       msg << "The 'skinner' expects to find one OpenMC material mapped to each [Mesh] subdomain, but " <<
     683           3 :         Moose::stringify(s.second.size()) << " materials\nmapped to subdomain " << s.first <<
     684             :         ". This indicates your [Mesh] is not " <<
     685           1 :         "consistent with the .h5m model.\n\nThe materials which mapped to subdomain " << s.first << " are:\n";
     686             : 
     687           3 :       for (const auto & m : s.second)
     688           4 :         msg << "\n" << materialName(m);
     689             : 
     690           1 :       mooseError(msg.str());
     691           0 :     }
     692             : 
     693          56 :     mats.push_back(materialName(*(s.second.begin())));
     694             :   }
     695             : 
     696          14 :   return mats;
     697           0 : }
     698             : 
     699             : void
     700        1865 : OpenMCCellAverageProblem::setupProblem()
     701             : {
     702             :   // establish the local -> global element mapping for convenience
     703        1865 :   _local_to_global_elem.clear();
     704    11841725 :   for (unsigned int e = 0; e < getMooseMesh().nElem(); ++e)
     705             :   {
     706    11839860 :     const auto * elem = getMooseMesh().queryElemPtr(e);
     707    11839860 :     if (!isLocalElem(elem) || !elem->active())
     708     6521324 :       continue;
     709             : 
     710     5318536 :     _local_to_global_elem.push_back(e);
     711             :   }
     712             : 
     713        1865 :   _n_openmc_cells = numCells();
     714             : 
     715        1865 :   initializeElementToCellMapping();
     716             : 
     717        1838 :   getMaterialFills();
     718             : 
     719             :   // we do this last so that we can at least hit any other errors first before
     720             :   // spending time on the costly filled cell caching
     721        1832 :   cacheContainedCells();
     722             : 
     723             :   // save the number of contained cells for printing in every transfer if verbose
     724             :   _cell_to_n_contained.clear();
     725       40505 :   for (const auto & c : _cell_to_elem)
     726             :   {
     727       38679 :     const auto & cell_info = c.first;
     728             :     int32_t n_contained = 0;
     729             : 
     730     2815674 :     for (const auto & cc : containedMaterialCells(cell_info))
     731     2776995 :       n_contained += cc.second.size();
     732             : 
     733       38679 :     _cell_to_n_contained[cell_info] = n_contained;
     734             :   }
     735             : 
     736        1826 :   subdomainsToMaterials();
     737             : 
     738        1826 :   initializeTallies();
     739        1814 : }
     740             : 
     741             : void
     742        1763 : OpenMCCellAverageProblem::getTallyTriggerParameters(const InputParameters & parameters)
     743             : {
     744             :   // parameters needed for k triggers
     745             :   bool has_tally_trigger = false;
     746        1763 :   if (_k_trigger != trigger::none)
     747             :   {
     748          72 :     checkRequiredParam(parameters, "k_trigger_threshold", "using a k trigger");
     749          72 :     openmc::settings::keff_trigger.threshold = getParam<Real>("k_trigger_threshold");
     750             :     has_tally_trigger = true;
     751             :   }
     752             :   else
     753        3454 :     checkUnusedParam(parameters, "k_trigger_threshold", "not using a k trigger");
     754             : 
     755             :   // Check to see if any of the local tallies have triggers.
     756        4197 :   for (const auto & local_tally : _local_tallies)
     757        2434 :     has_tally_trigger = has_tally_trigger || local_tally->hasTrigger();
     758             : 
     759        1763 :   if (has_tally_trigger) // at least one trigger
     760             :   {
     761         116 :     openmc::settings::trigger_on = true;
     762         232 :     checkRequiredParam(parameters, "max_batches", "using triggers");
     763             : 
     764         116 :     if (_skip_statepoint)
     765           0 :       checkUnusedParam(parameters, "skip_statepoint", "using a trigger");
     766             : 
     767         232 :     int err = openmc_set_n_batches(getParam<unsigned int>("max_batches"),
     768             :                                    true /* set the max batches */,
     769         116 :                                    true /* add the last batch for statepoint writing */);
     770         116 :     catchOpenMCError(err, "set the maximum number of batches");
     771             : 
     772         228 :     openmc::settings::trigger_batch_interval = getParam<unsigned int>("batch_interval");
     773             :   }
     774             :   else
     775             :   {
     776        3294 :     checkUnusedParam(parameters, "max_batches", "not using triggers");
     777        3294 :     checkUnusedParam(parameters, "batch_interval", "not using triggers");
     778             : 
     779        1647 :     if (_skip_statepoint)
     780             :       openmc::settings::statepoint_batch.clear();
     781             :   }
     782        1761 : }
     783             : 
     784             : std::vector<const TallyBase *>
     785         438 : OpenMCCellAverageProblem::getTalliesByScore(const std::string & score)
     786             : {
     787             :   // Loop over all of the tallies and check to see if they contain the requested score.
     788             :   std::vector<const TallyBase *> tallies;
     789         940 :   for (const auto & t : _local_tallies)
     790         502 :     if (t->hasScore(score))
     791         502 :       tallies.push_back(t.get());
     792             : 
     793         438 :   return tallies;
     794             : }
     795             : 
     796             : std::vector<const MooseVariableFE<Real> *>
     797          50 : OpenMCCellAverageProblem::getTallyScoreVariables(const std::string & score,
     798             :                                                  THREAD_ID tid,
     799             :                                                  const std::string & output,
     800             :                                                  bool skip_func_exp)
     801             : {
     802             :   std::vector<const MooseVariableFE<Real> *> score_vars;
     803         106 :   for (const auto & t : _local_tallies)
     804             :   {
     805          56 :     if (t->hasScore(score))
     806             :     {
     807          50 :       auto vars = t->getScoreVars(score);
     808         100 :       for (unsigned int ext_bin = 0; ext_bin < vars.size(); ++ext_bin)
     809             :       {
     810          50 :         if (skip_func_exp && t->extBinSkipped(ext_bin))
     811           0 :           continue;
     812          50 :         score_vars.emplace_back(
     813         100 :             dynamic_cast<const MooseVariableFE<Real> *>(&getVariable(tid, vars[ext_bin] + output)));
     814             :       }
     815          50 :     }
     816             :   }
     817             : 
     818          50 :   if (score_vars.size() == 0)
     819           0 :     mooseError("No tallies contain the requested score " + score + "!");
     820             : 
     821          50 :   return score_vars;
     822             : }
     823             : 
     824             : std::vector<const VariableValue *>
     825          50 : OpenMCCellAverageProblem::getTallyScoreVariableValues(const std::string & score,
     826             :                                                       THREAD_ID tid,
     827             :                                                       const std::string & output,
     828             :                                                       bool skip_func_exp)
     829             : {
     830             :   std::vector<const VariableValue *> score_vars;
     831         106 :   for (const auto & t : _local_tallies)
     832             :   {
     833          56 :     if (t->hasScore(score))
     834             :     {
     835          50 :       auto vars = t->getScoreVars(score);
     836         100 :       for (unsigned int ext_bin = 0; ext_bin < vars.size(); ++ext_bin)
     837             :       {
     838          50 :         if (skip_func_exp && t->extBinSkipped(ext_bin))
     839           0 :           continue;
     840          50 :         score_vars.emplace_back(
     841         150 :             &(dynamic_cast<MooseVariableFE<Real> *>(&getVariable(tid, vars[ext_bin] + output))
     842          50 :                   ->sln()));
     843             :       }
     844          50 :     }
     845             :   }
     846             : 
     847          50 :   if (score_vars.size() == 0)
     848           0 :     mooseError("No tallies contain the requested score " + score + "!");
     849             : 
     850          50 :   return score_vars;
     851             : }
     852             : 
     853             : std::vector<const VariableValue *>
     854           4 : OpenMCCellAverageProblem::getTallyScoreNeighborVariableValues(const std::string & score,
     855             :                                                               THREAD_ID tid,
     856             :                                                               const std::string & output,
     857             :                                                               bool skip_func_exp)
     858             : {
     859             :   std::vector<const VariableValue *> score_vars;
     860           8 :   for (const auto & t : _local_tallies)
     861             :   {
     862           4 :     if (t->hasScore(score))
     863             :     {
     864           4 :       auto vars = t->getScoreVars(score);
     865           8 :       for (unsigned int ext_bin = 0; ext_bin < vars.size(); ++ext_bin)
     866             :       {
     867           4 :         if (skip_func_exp && t->extBinSkipped(ext_bin))
     868           0 :           continue;
     869           4 :         score_vars.emplace_back(
     870          12 :             &(dynamic_cast<MooseVariableFE<Real> *>(&getVariable(tid, vars[ext_bin] + output))
     871           4 :                   ->slnNeighbor()));
     872             :       }
     873           4 :     }
     874             :   }
     875             : 
     876           4 :   if (score_vars.size() == 0)
     877           0 :     mooseError("No tallies contain the requested score " + score + "!");
     878             : 
     879           4 :   return score_vars;
     880             : }
     881             : 
     882             : bool
     883           8 : OpenMCCellAverageProblem::hasOutput(const std::string & score, const std::string & output) const
     884             : {
     885          10 :   for (const auto & t : _local_tallies)
     886          14 :     if (std::find(t->getOutputs().begin(), t->getOutputs().end(), output) !=
     887           8 :             t->getOutputs().end() &&
     888           6 :         t->hasScore(score))
     889             :       return true;
     890             :   return false;
     891             : }
     892             : 
     893             : void
     894        1921 : OpenMCCellAverageProblem::readBlockParameters(const std::string name,
     895             :                                               std::unordered_set<SubdomainID> & blocks)
     896             : {
     897        1921 :   if (isParamValid(name))
     898             :   {
     899          44 :     auto names = getParam<std::vector<SubdomainName>>(name);
     900          88 :     checkEmptyVector(names, "'" + name + "'");
     901             : 
     902             :     // here, we do not use the displaced mesh because we need to call this during initial
     903             :     // setup when the displaced problem does not yet exist. However, displacing the mesh
     904             :     // should not influence the subdomain IDs anyways
     905          44 :     auto b_ids = mesh().getSubdomainIDs(names);
     906          44 :     std::copy(b_ids.begin(), b_ids.end(), std::inserter(blocks, blocks.end()));
     907          88 :     checkBlocksInMesh(name, b_ids, names);
     908          44 :   }
     909        1921 : }
     910             : 
     911             : void
     912        2036 : OpenMCCellAverageProblem::checkBlocksInMesh(const std::string name,
     913             :                                             const std::vector<SubdomainID> & ids,
     914             :                                             const std::vector<SubdomainName> & names) const
     915             : {
     916             :   // here, we do not use the displaced mesh because we need to call this during initial
     917             :   // setup when the displaced problem does not yet exist. However, displacing the mesh
     918             :   // should not influence the subdomain IDs anyways
     919        2036 :   const auto & subdomains = mesh().meshSubdomains();
     920        5156 :   for (std::size_t b = 0; b < names.size(); ++b)
     921        3120 :     if (subdomains.find(ids[b]) == subdomains.end())
     922           0 :       mooseError("Block '" + names[b] + "' specified in '" + name + "' " + "not found in mesh!");
     923        2036 : }
     924             : 
     925             : void
     926        1998 : OpenMCCellAverageProblem::read2DBlockParameters(const std::string name,
     927             :                                                 std::vector<std::vector<SubdomainName>> & names,
     928             :                                                 std::vector<SubdomainID> & flattened_ids)
     929             : {
     930        1998 :   if (isParamValid(name))
     931             :   {
     932        1998 :     names = getParam<std::vector<std::vector<SubdomainName>>>(name);
     933             : 
     934             :     // check that entire vector is not empty
     935        5992 :     checkEmptyVector(names, "'" + name + "'");
     936             : 
     937             :     // check that each entry in vector is not empty
     938        4102 :     for (const auto & n : names)
     939        6326 :       checkEmptyVector(n, "Entries in '" + name + "'");
     940             : 
     941             :     // flatten the 2-d set of names into a 1-d vector
     942             :     std::vector<SubdomainName> flattened_names;
     943        4098 :     for (const auto & slice : names)
     944        5154 :       for (const auto & i : slice)
     945        3048 :         flattened_names.push_back(i);
     946             : 
     947             :     // here, we do not use the displaced mesh because we need to call this during initial
     948             :     // setup when the displaced problem does not yet exist. However, displacing the mesh
     949             :     // should not influence the subdomain IDs anyways
     950        1992 :     flattened_ids = mesh().getSubdomainIDs(flattened_names);
     951        3984 :     checkBlocksInMesh(name, flattened_ids, flattened_names);
     952             : 
     953             :     // should not be any duplicate blocks
     954             :     std::set<SubdomainName> n;
     955        5032 :     for (const auto & b : flattened_names)
     956             :     {
     957             :       if (n.count(b))
     958           4 :         mooseError(
     959           4 :             "Subdomains cannot be repeated in '" + name + "'! Subdomain '", b, "' is duplicated.");
     960        3040 :       n.insert(b);
     961             :     }
     962        1988 :   }
     963        1988 : }
     964             : 
     965             : coupling::CouplingFields
     966    13234873 : OpenMCCellAverageProblem::elemFeedback(const Elem * elem) const
     967             : {
     968    13234873 :   const auto & id = elem->subdomain_id();
     969             :   bool has_density =
     970    13234873 :       std::find(_density_blocks.begin(), _density_blocks.end(), id) != _density_blocks.end();
     971    13234873 :   bool has_temp = std::find(_temp_blocks.begin(), _temp_blocks.end(), id) != _temp_blocks.end();
     972             : 
     973    13234873 :   if (has_density && has_temp)
     974             :     return coupling::density_and_temperature;
     975    11935471 :   else if (!has_density && has_temp)
     976             :     return coupling::temperature;
     977     9332740 :   else if (has_density && !has_temp)
     978             :     return coupling::density;
     979             :   else
     980     9331276 :     return coupling::none;
     981             : }
     982             : 
     983             : void
     984        1865 : OpenMCCellAverageProblem::storeElementPhase()
     985             : {
     986             :   std::set<SubdomainID> excl_temp_blocks;
     987             :   std::set<SubdomainID> excl_density_blocks;
     988             :   std::set<SubdomainID> intersect;
     989             : 
     990        1865 :   std::set<SubdomainID> t(_temp_blocks.begin(), _temp_blocks.end());
     991        1865 :   std::set<SubdomainID> d(_density_blocks.begin(), _density_blocks.end());
     992             : 
     993        1865 :   std::set_difference(t.begin(),
     994             :                       t.end(),
     995             :                       d.begin(),
     996             :                       d.end(),
     997             :                       std::inserter(excl_temp_blocks, excl_temp_blocks.end()));
     998             : 
     999        1865 :   std::set_difference(d.begin(),
    1000             :                       d.end(),
    1001             :                       t.begin(),
    1002             :                       t.end(),
    1003             :                       std::inserter(excl_density_blocks, excl_density_blocks.end()));
    1004             : 
    1005        1865 :   std::set_intersection(
    1006             :       t.begin(), t.end(), d.begin(), d.end(), std::inserter(intersect, intersect.begin()));
    1007             : 
    1008        1865 :   _n_moose_temp_density_elems = 0;
    1009        2427 :   for (const auto & s : intersect)
    1010         562 :     _n_moose_temp_density_elems += numElemsInSubdomain(s);
    1011             : 
    1012        1865 :   _n_moose_temp_elems = 0;
    1013        3726 :   for (const auto & s : excl_temp_blocks)
    1014        1861 :     _n_moose_temp_elems += numElemsInSubdomain(s);
    1015             : 
    1016        1865 :   _n_moose_density_elems = 0;
    1017        1903 :   for (const auto & s : excl_density_blocks)
    1018          38 :     _n_moose_density_elems += numElemsInSubdomain(s);
    1019             : 
    1020        1865 :   _n_moose_none_elems = getMooseMesh().getMesh().n_active_elem() - _n_moose_temp_density_elems -
    1021        1865 :                         _n_moose_temp_elems - _n_moose_density_elems;
    1022        1865 : }
    1023             : 
    1024             : void
    1025        1860 : OpenMCCellAverageProblem::computeCellMappedVolumes()
    1026             : {
    1027             :   std::vector<Real> volumes;
    1028             : 
    1029       25829 :   for (const auto & c : _local_cell_to_elem)
    1030             :   {
    1031       23969 :     Real vol = 0.0;
    1032     4694305 :     for (const auto & e : c.second)
    1033             :     {
    1034             :       // we are looping over local elements, so no need to check for nullptr
    1035     4670336 :       const auto * elem = getMooseMesh().queryElemPtr(globalElemID(e));
    1036     4670336 :       vol += elem->volume();
    1037             :     }
    1038             : 
    1039       23969 :     volumes.push_back(vol);
    1040             :   }
    1041             : 
    1042        1860 :   gatherCellSum(volumes, _cell_to_elem_volume);
    1043        1860 : }
    1044             : 
    1045             : template <typename T>
    1046             : void
    1047       10636 : OpenMCCellAverageProblem::gatherCellSum(std::vector<T> & local,
    1048             :                                         std::map<cellInfo, T> & global) const
    1049             : {
    1050             :   global.clear();
    1051       10636 :   _communicator.allgather(local);
    1052             : 
    1053      348555 :   for (unsigned int i = 0; i < _flattened_ids.size(); ++i)
    1054             :   {
    1055             :     cellInfo cell_info = {_flattened_ids[i], _flattened_instances[i]};
    1056             : 
    1057             :     if (global.count(cell_info))
    1058      102710 :       global[cell_info] += local[i];
    1059             :     else
    1060      235209 :       global[cell_info] = local[i];
    1061             :   }
    1062       10636 : }
    1063             : 
    1064             : template <typename T>
    1065             : void
    1066        3820 : OpenMCCellAverageProblem::gatherCellVector(std::vector<T> & local, std::vector<unsigned int> & n_local,
    1067             :   std::map<cellInfo, std::vector<T>> & global)
    1068             : {
    1069             :   global.clear();
    1070        3820 :   _communicator.allgather(n_local);
    1071        3820 :   _communicator.allgather(local);
    1072             : 
    1073             :   int e = 0;
    1074      145082 :   for (unsigned int i = 0; i < _flattened_ids.size(); ++i)
    1075             :   {
    1076             :     cellInfo cell_info = {_flattened_ids[i], _flattened_instances[i]};
    1077             : 
    1078    27769774 :     for (unsigned int j = e; j < e + n_local[i]; ++j)
    1079    27628512 :       global[cell_info].push_back(local[j]);
    1080             : 
    1081      141262 :     e += n_local[i];
    1082             :   }
    1083        3820 : }
    1084             : 
    1085             : coupling::CouplingFields
    1086    15092797 : OpenMCCellAverageProblem::cellFeedback(const cellInfo & cell_info) const
    1087             : {
    1088             :   // _cell_to_elem only holds cells that are coupled by feedback to the [Mesh] (for sake of
    1089             :   // efficiency in cell-based loops for updating temperatures, densities and
    1090             :   // extracting the tally). But in some auxiliary kernels, we figure out
    1091             :   // an element's phase in terms of the cell that it maps to. For these cells that
    1092             :   // do *map* spatially, but just don't participate in coupling, _cell_to_elem doesn't
    1093             :   // have any notion of those elements
    1094             :   if (!_cell_phase.count(cell_info))
    1095           0 :     return coupling::none;
    1096             :   else
    1097    15092797 :     return _cell_phase.at(cell_info);
    1098             : }
    1099             : 
    1100             : void
    1101        1860 : OpenMCCellAverageProblem::getCellMappedPhase()
    1102             : {
    1103             :   std::vector<int> cells_n_temp;
    1104             :   std::vector<int> cells_n_temp_rho;
    1105             :   std::vector<int> cells_n_rho;
    1106             :   std::vector<int> cells_n_none;
    1107             : 
    1108             :   // whether each cell maps to a single phase
    1109       25829 :   for (const auto & c : _local_cell_to_elem)
    1110             :   {
    1111       23969 :     std::vector<int> f(4 /* number of coupling options */, 0);
    1112             : 
    1113             :     // we are looping over local elements, so no need to check for nullptr
    1114     4694305 :     for (const auto & e : c.second)
    1115     4670336 :       f[elemFeedback(getMooseMesh().queryElemPtr(globalElemID(e)))]++;
    1116             : 
    1117       23969 :     cells_n_temp.push_back(f[coupling::temperature]);
    1118       23969 :     cells_n_temp_rho.push_back(f[coupling::density_and_temperature]);
    1119       23969 :     cells_n_rho.push_back(f[coupling::density]);
    1120       23969 :     cells_n_none.push_back(f[coupling::none]);
    1121             :   }
    1122             : 
    1123        1860 :   gatherCellSum(cells_n_temp, _n_temp);
    1124        1860 :   gatherCellSum(cells_n_temp_rho, _n_temp_rho);
    1125        1860 :   gatherCellSum(cells_n_rho, _n_rho);
    1126        1860 :   gatherCellSum(cells_n_none, _n_none);
    1127        1860 : }
    1128             : 
    1129             : Real
    1130      609312 : OpenMCCellAverageProblem::cellVolume(const cellInfo & cell_info) const
    1131             : {
    1132             :   if (_cell_volume.count(cell_info))
    1133      609312 :     return _cell_volume.at(cell_info);
    1134             :   else
    1135           0 :     return 0.0;
    1136             : }
    1137             : 
    1138             : void
    1139        1846 : OpenMCCellAverageProblem::checkCellMappedPhase()
    1140             : {
    1141        1846 :   if (_volume_calc)
    1142             :   {
    1143         129 :     _volume_calc->initializeVolumeCalculation();
    1144         127 :     _volume_calc->computeVolumes();
    1145             :   }
    1146             : 
    1147             :   VariadicTable<std::string, int, int, int, int, std::string, std::string> vt(
    1148       14752 :       {"Cell", "  T  ", " rho ", "T+rho", "Other", "Mapped Vol", "Actual Vol"});
    1149             : 
    1150             :   bool has_mapping = false;
    1151             : 
    1152             :   std::vector<Real> cv;
    1153             :   _cell_phase.clear();
    1154       40803 :   for (const auto & c : _cell_to_elem)
    1155             :   {
    1156       38963 :     auto cell_info = c.first;
    1157       38963 :     int n_temp = _n_temp[cell_info];
    1158       38963 :     int n_rho = _n_rho[cell_info];
    1159       38963 :     int n_temp_rho = _n_temp_rho[cell_info];
    1160       38963 :     int n_none = _n_none[cell_info];
    1161             : 
    1162       38963 :     std::ostringstream vol;
    1163       38963 :     vol << std::setprecision(3) << std::scientific << "";
    1164       38963 :     if (_volume_calc)
    1165             :     {
    1166             :       Real v, std_dev;
    1167        2054 :       _volume_calc->cellVolume(c.first.first, v, std_dev);
    1168        2052 :       cv.push_back(v);
    1169        2052 :       vol << v << " +/- " << std_dev;
    1170             :     }
    1171             : 
    1172       38961 :     std::ostringstream map;
    1173       38961 :     map << std::setprecision(3) << std::scientific << _cell_to_elem_volume[cell_info];
    1174             : 
    1175             :     // okay to print vol.str() here because only rank 0 is printing (which is the only one
    1176             :     // with meaningful volume data from OpenMC)
    1177       77922 :     vt.addRow(printCell(cell_info, true), n_temp, n_rho, n_temp_rho, n_none, map.str(), vol.str());
    1178             : 
    1179             :     // cells can only map to a single type of feedback
    1180       38961 :     std::vector<bool> conditions = {n_temp_rho > 0, n_temp > 0, n_rho > 0, n_none > 0};
    1181       38961 :     if (std::count(conditions.begin(), conditions.end(), true) > 1)
    1182             :     {
    1183           2 :       std::stringstream msg;
    1184           2 :       std::vector<int> conds = {n_temp, n_rho, n_temp_rho, n_none};
    1185           2 :       int size = std::to_string(*std::max_element(conds.begin(), conds.end())).length();
    1186           4 :       msg << "Cell " << printCell(cell_info) << " mapped to:\n\n  " << std::setw(size) << n_temp
    1187           2 :           << "  elements with temperature feedback\n  " << std::setw(size) << n_rho
    1188           2 :           << "  elements with density feedback\n  " << std::setw(size) << n_temp_rho
    1189           2 :           << "  elements with both temperature and density feedback\n  " << std::setw(size)
    1190             :           << n_none
    1191             :           << "  uncoupled elements\n\n"
    1192             :              "Each OpenMC cell (ID, instance) pair must map to elements of the same coupling "
    1193           2 :              "settings.";
    1194           2 :       mooseError(msg.str());
    1195           0 :     }
    1196             : 
    1197       38959 :     if (n_temp)
    1198             :     {
    1199             :       has_mapping = true;
    1200       23769 :       _cell_phase[cell_info] = coupling::temperature;
    1201             :     }
    1202       15190 :     else if (n_rho)
    1203             :     {
    1204             :       has_mapping = true;
    1205          36 :       _cell_phase[cell_info] = coupling::density;
    1206             :     }
    1207       15154 :     else if (n_temp_rho)
    1208             :     {
    1209             :       has_mapping = true;
    1210        2458 :       _cell_phase[cell_info] = coupling::density_and_temperature;
    1211             :     }
    1212             :     else
    1213       12696 :       _cell_phase[cell_info] = coupling::none;
    1214       38959 :   }
    1215             : 
    1216             :   // collect values from rank 0 onto all other ranks, then populate cell_volume
    1217             :   // (this is necessary because in OpenMC, the stochastic volume calculation only
    1218             :   // gets meaningful results on rank 0
    1219        1840 :   if (_volume_calc)
    1220             :   {
    1221             :     _cell_volume.clear();
    1222         125 :     MPI_Bcast(cv.data(), cv.size(), MPI_DOUBLE, 0, _communicator.get());
    1223             :     int i = 0;
    1224        2177 :     for (const auto & c : _cell_to_elem)
    1225        2052 :       _cell_volume[c.first] = cv[i++];
    1226             :   }
    1227             : 
    1228        1840 :   if (_specified_density_feedback || _specified_temperature_feedback)
    1229        1447 :     if (!has_mapping)
    1230           2 :       mooseError("Feedback was specified using 'temperature_blocks' and/or 'density_blocks', but "
    1231             :                  "no MOOSE elements mapped to OpenMC cells!");
    1232             : 
    1233        1838 :   if (_verbose && _cell_to_elem.size())
    1234             :   {
    1235             :     _console
    1236        1406 :         << "\n ===================>     MAPPING FROM OPENMC TO MOOSE     <===================\n"
    1237        1406 :         << std::endl;
    1238        1406 :     _console << "          T:      # elems providing temperature-only feedback" << std::endl;
    1239        1406 :     _console << "          rho:    # elems providing density-only feedback" << std::endl;
    1240        1406 :     _console << "          T+rho:  # elems providing temperature and density feedback" << std::endl;
    1241        1406 :     _console << "          Other:  # elems which do not provide feedback to OpenMC" << std::endl;
    1242        1406 :     _console << "                    (but receives a cell tally from OpenMC)" << std::endl;
    1243        1406 :     _console << "     Mapped Vol:  volume of MOOSE elems each cell maps to" << std::endl;
    1244        1406 :     _console << "     Actual Vol:  OpenMC cell volume (computed with 'volume_calculation')\n"
    1245        1406 :              << std::endl;
    1246        1406 :     vt.print(_console);
    1247             :   }
    1248             : 
    1249        1838 :   printAuxVariableIO();
    1250        1838 :   _printed_initial = true;
    1251        5526 : }
    1252             : 
    1253             : void
    1254        1838 : OpenMCCellAverageProblem::printAuxVariableIO()
    1255             : {
    1256        1838 :   if (_printed_initial)
    1257             :     return;
    1258             : 
    1259        1734 :   if (!(_specified_density_feedback || _specified_temperature_feedback ||
    1260             :         _local_tallies.size() > 0))
    1261             :     return;
    1262             : 
    1263        1630 :   _console << "\n ===================>     AUXVARIABLES FOR OPENMC I/O     <===================\n"
    1264        1630 :            << std::endl;
    1265             : 
    1266        1630 :   if (_specified_density_feedback || _specified_temperature_feedback)
    1267             :   {
    1268        1361 :     _console << "      Subdomain:  subdomain name/ID" << std::endl;
    1269        1361 :     _console << "    Temperature:  variable OpenMC reads temperature from (empty if no feedback)"
    1270        1361 :              << std::endl;
    1271        1361 :     _console << "        Density:  variable OpenMC reads density from (empty if no feedback)\n"
    1272        1361 :              << std::endl;
    1273             : 
    1274             :     VariadicTable<std::string, std::string, std::string> aux(
    1275        5444 :         {"Subdomain", "Temperature", "Density"});
    1276             : 
    1277        3865 :     for (const auto & s : getMooseMesh().meshSubdomains())
    1278             :     {
    1279        2504 :       std::string temp = _subdomain_to_temp_vars.count(s) ? _subdomain_to_temp_vars[s].second : "";
    1280             :       std::string rho =
    1281        2504 :           _subdomain_to_density_vars.count(s) ? _subdomain_to_density_vars[s].second : "";
    1282             : 
    1283        2744 :       if (temp == "" && rho == "")
    1284             :         continue;
    1285             : 
    1286        4560 :       aux.addRow(subdomainName(s), temp, rho);
    1287             :     }
    1288             : 
    1289        1361 :     aux.print(_console);
    1290        1361 :     _console << std::endl;
    1291        1361 :   }
    1292             : 
    1293        1630 :   if (_local_tallies.size() > 0)
    1294             :   {
    1295        1499 :     _console << "    Tally Name:   Cardinal tally object name" << std::endl;
    1296        1499 :     _console << "    Tally Score:  OpenMC tally score" << std::endl;
    1297        1499 :     _console << "    AuxVariable:  variable where this score is written\n" << std::endl;
    1298             : 
    1299             :     VariadicTable<std::string, std::string, std::string> tallies(
    1300        5996 :         {"Tally Name", "Tally Score", "AuxVariable(s)"});
    1301        3916 :     for (unsigned int i = 0; i < _local_tallies.size(); ++i)
    1302             :     {
    1303             :       const auto & scores = _local_tallies[i]->getScores();
    1304             :       const auto & names = _local_tallies[i]->getAuxVarNames();
    1305             :       const auto bins = _local_tallies[i]->numExtFilterBins();
    1306        5188 :       for (unsigned int j = 0; j < scores.size(); ++j)
    1307             :       {
    1308        2771 :         if (names.size() == 0)
    1309         388 :           continue;
    1310             : 
    1311        5942 :         for (unsigned int k = bins * j; k < (j + 1) * bins; ++k)
    1312             :         {
    1313        3559 :           const auto l = j == 0 && k == bins * j ? _local_tallies[i]->name() : "";
    1314        3559 :           const auto c = k == bins * j ? scores[j] : "";
    1315        3559 :           const auto r = names[k];
    1316        7118 :           tallies.addRow(l, c, r);
    1317             :         }
    1318             :       }
    1319             :     }
    1320             : 
    1321        1499 :     tallies.print(_console);
    1322        1499 :   }
    1323        5720 : }
    1324             : 
    1325             : void
    1326        1860 : OpenMCCellAverageProblem::getCellMappedSubdomains()
    1327             : {
    1328             :   std::vector<unsigned int> n_elems;
    1329             :   std::vector<unsigned int> elem_ids;
    1330             : 
    1331       25829 :   for (const auto & c : _local_cell_to_elem)
    1332             :   {
    1333       23969 :     n_elems.push_back(c.second.size());
    1334     4694305 :     for (const auto & e : c.second)
    1335             :     {
    1336             :       // we are looping over local elements, so no need to check for nullptr
    1337     4670336 :       const auto * elem = getMooseMesh().queryElemPtr(globalElemID(e));
    1338     4670336 :       elem_ids.push_back(elem->subdomain_id());
    1339             :     }
    1340             :   }
    1341             : 
    1342             :   std::map<cellInfo, std::vector<unsigned int>> cell_to_subdomain_vec;
    1343        1860 :   gatherCellVector(elem_ids, n_elems, cell_to_subdomain_vec);
    1344             : 
    1345             :   // convert to a set
    1346             :   _cell_to_elem_subdomain.clear();
    1347       40917 :   for (const auto & c : cell_to_subdomain_vec)
    1348    10461053 :     for (const auto & s : c.second)
    1349    10421996 :       _cell_to_elem_subdomain[c.first].insert(s);
    1350             : 
    1351             :   // each cell must map to a consistent setting for identical_cell_fills
    1352             :   // (all of the blocks it maps to must either _all_ be in the identical blocks,
    1353             :   // or all excluded)
    1354        1860 :   if (_has_identical_cell_fills)
    1355             :   {
    1356       10658 :     for (const auto & c : _cell_to_elem)
    1357             :     {
    1358       10618 :       auto cell_info = c.first;
    1359             :       bool at_least_one_in = false;
    1360             :       bool at_least_one_out = false;
    1361             :       SubdomainID in;
    1362             :       SubdomainID out;
    1363       10618 :       auto subdomains = _cell_to_elem_subdomain[cell_info];
    1364       25438 :       for (const auto & s : subdomains)
    1365             :       {
    1366       14820 :         if (_identical_cell_fill_blocks.find(s) == _identical_cell_fill_blocks.end())
    1367             :         {
    1368             :           at_least_one_out = true;
    1369        5906 :           out = s;
    1370             :         }
    1371             :         else
    1372             :         {
    1373             :           at_least_one_in = true;
    1374        8914 :           in = s;
    1375             :         }
    1376             :       }
    1377             : 
    1378       10618 :       if (at_least_one_in && at_least_one_out)
    1379             :       {
    1380           2 :         std::stringstream msg;
    1381           2 :         msg << "Cell " << printCell(cell_info)
    1382             :             << " mapped to inconsistent 'identical_cell_fills' settings.\n"
    1383           6 :             << "Subdomain " << in << " is in 'identical_cell_fills', but " << out << " is not.\n\n"
    1384             :             << "All subdomains to which this cell maps must either ALL be in "
    1385           2 :                "'identical_cell_fills' or ALL excluded.";
    1386           2 :         mooseError(msg.str());
    1387           0 :       }
    1388             :     }
    1389             :   }
    1390        1858 : }
    1391             : 
    1392             : std::set<SubdomainID>
    1393        1826 : OpenMCCellAverageProblem::coupledSubdomains() const
    1394             : {
    1395             :   std::set<SubdomainID> subdomains;
    1396       40505 :   for (const auto & c : _cell_to_elem)
    1397             :   {
    1398       38679 :     const auto & subdomains_spanning_cell = _cell_to_elem_subdomain.at(c.first);
    1399       90056 :     for (const auto & s : subdomains_spanning_cell)
    1400       51377 :       subdomains.insert(s);
    1401             :   }
    1402             : 
    1403        1826 :   return subdomains;
    1404             : }
    1405             : 
    1406             : void
    1407        1826 : OpenMCCellAverageProblem::subdomainsToMaterials()
    1408             : {
    1409        1826 :   const auto time_start = std::chrono::high_resolution_clock::now();
    1410             : 
    1411        3652 :   TIME_SECTION("subdomainsToMaterials", 3, "Mapping OpenMC Materials to Mesh", true);
    1412             : 
    1413             :   _subdomain_to_material.clear();
    1414             : 
    1415       40505 :   for (const auto & c : _cell_to_elem)
    1416             :   {
    1417       38679 :     printTrisoHelp(time_start);
    1418             : 
    1419       38679 :     const auto mats = cellHasIdenticalFill(c.first)
    1420       38679 :                           ? _first_identical_cell_materials
    1421       38679 :                           : materialsInCells(containedMaterialCells(c.first));
    1422             : 
    1423       90056 :     for (const auto & s : _cell_to_elem_subdomain.at(c.first))
    1424   439531306 :       for (const auto & m : mats)
    1425   439479929 :         _subdomain_to_material[s].insert(m);
    1426             :   }
    1427             : 
    1428        5478 :   VariadicTable<std::string, std::string> vt({"Subdomain", "Material"});
    1429        1826 :   auto subdomains = coupledSubdomains();
    1430        4764 :   for (const auto & i : subdomains)
    1431             :   {
    1432             :     std::map<std::string, int> mat_to_num;
    1433             : 
    1434       10111 :     for (const auto & m : _subdomain_to_material[i])
    1435             :     {
    1436        7173 :       auto name = materialName(m);
    1437             :       if (mat_to_num.count(name))
    1438         688 :         mat_to_num[name] += 1;
    1439             :       else
    1440        6485 :         mat_to_num[name] = 1;
    1441             :     }
    1442             : 
    1443        2938 :     std::string mats = "";
    1444        9423 :     for (const auto & m : mat_to_num)
    1445             :     {
    1446        6517 :       std::string extra = m.second > 1 ? " (" + std::to_string(m.second) + ")" : "";
    1447       12970 :       mats += " " + m.first + extra + ",";
    1448             :     }
    1449             : 
    1450        2938 :     mats.pop_back();
    1451        5876 :     vt.addRow(subdomainName(i), mats);
    1452             :   }
    1453             : 
    1454        1826 :   if (_cell_to_elem.size())
    1455             :   {
    1456             :     _console
    1457        1687 :         << "\n ===================>  OPENMC SUBDOMAIN MATERIAL MAPPING  <====================\n"
    1458        1687 :         << std::endl;
    1459        1687 :     _console << "      Subdomain:  Subdomain name; if unnamed, we show the ID" << std::endl;
    1460        1687 :     _console << "       Material:  OpenMC material name(s) in this subdomain; if unnamed, we\n"
    1461        1687 :              << "                  show the ID. If N duplicate material names, we show the\n"
    1462        1687 :              << "                  number in ( ).\n"
    1463        1687 :              << std::endl;
    1464        1687 :     vt.print(_console);
    1465        1687 :     _console << std::endl;
    1466             :   }
    1467        7304 : }
    1468             : 
    1469             : void
    1470        1838 : OpenMCCellAverageProblem::getMaterialFills()
    1471             : {
    1472        5514 :   VariadicTable<std::string, int> vt({"Cell", "Material"});
    1473             : 
    1474             :   std::set<int32_t> materials_in_fluid;
    1475             :   std::set<int32_t> other_materials;
    1476             :   _cell_to_material.clear();
    1477             : 
    1478       40771 :   for (const auto & c : _cell_to_elem)
    1479             :   {
    1480       38937 :     auto cell_info = c.first;
    1481             : 
    1482             :     int32_t material_index;
    1483       38937 :     auto is_material_cell = materialFill(cell_info, material_index);
    1484             : 
    1485       38937 :     if (!hasDensityFeedback(cell_info))
    1486             :     {
    1487             :       // TODO: this check should be extended for non-fluid cells which may contain
    1488             :       // lattices or  universes
    1489       36459 :       if (is_material_cell)
    1490       30075 :         other_materials.insert(material_index);
    1491       36459 :       continue;
    1492             :     }
    1493             : 
    1494             :     // check for each material that we haven't already discovered it; if we have, this means we
    1495             :     // didnt set up the materials correctly (if mapping by cell)
    1496        2478 :     if (materials_in_fluid.find(material_index) == materials_in_fluid.end())
    1497        2388 :       materials_in_fluid.insert(material_index);
    1498          90 :     else if (_map_density_by_cell)
    1499           4 :       mooseError(printMaterial(material_index) +
    1500             :                  " is present in more than one density feedback cell.\n\nThis means that your "
    1501             :                  "model cannot independently change the density in cells filled with this "
    1502             :                  "material. You need to edit your OpenMC model to create additional materials "
    1503             :                  "unique to each density feedback cell.\n\n"
    1504             :                  "Or, if you want to apply feedback to a material spanning multiple "
    1505             :                  "cells, set 'map_density_by_cell' to false.");
    1506             : 
    1507        2476 :     if (!is_material_cell)
    1508           2 :       mooseError("Density transfer does not currently support cells filled with universes or lattices!");
    1509             : 
    1510        2474 :     _cell_to_material[cell_info] = material_index;
    1511        4948 :     vt.addRow(printCell(cell_info), materialID(material_index));
    1512             :   }
    1513             : 
    1514        1834 :   if (_verbose && _specified_density_feedback)
    1515             :   {
    1516         429 :     _console << "\n ===================>       OPENMC MATERIAL MAPPING       <====================\n" << std::endl;
    1517         429 :     _console <<   "           Cell:  OpenMC cell receiving density feedback" << std::endl;
    1518         429 :     _console << "       Material:  OpenMC material ID in this cell (-1 for void)\n" << std::endl;
    1519         429 :     vt.print(_console);
    1520             :   }
    1521             : 
    1522             :   // check that the same material is not present in both the density feedback regions and the
    1523             :   // no-density-feedback regions, because this would give unintended consequences where
    1524             :   // density is indeed actually changing in parts of the OpenMC model where the user doesn't
    1525             :   // want that to happen; TODO: we technically should also check that the materials receiving
    1526             :   // density feedback are not present in parts of the OpenMC which totally do not overlap with
    1527             :   // the [Mesh] (but we are not tracking their behavior anywhere, we could do this but we'd
    1528             :   // need to loop over ALL OpenMC cells, get their fills, and check)
    1529        4216 :   for (const auto & f : materials_in_fluid)
    1530             :     if (other_materials.count(f))
    1531           2 :       mooseError(
    1532           2 :           printMaterial(f) +
    1533             :           " is present in more than one OpenMC cell with different "
    1534             :           "density feedback settings!\nIn other words, this material will have its density changed "
    1535             :           "by Cardinal (because it is\ncontained in cells which map to the 'density_blocks'), but "
    1536             :           "this material is also present in\nOTHER OpenMC cells, which will give unintended "
    1537             :           "behavior "
    1538             :           "by changing density in ALL parts of the\ndomain containing this material (some of which "
    1539             :           "have not been coupled via Cardinal).\n\n"
    1540             :           "Please change your OpenMC model so that unique materials are used in regions which "
    1541             :           "receive "
    1542             :           "density feedback.");
    1543        5508 : }
    1544             : 
    1545             : void
    1546        1865 : OpenMCCellAverageProblem::initializeElementToCellMapping()
    1547             : {
    1548             :   /* We consider five different cases here based on how the MOOSE and OpenMC
    1549             :    * domains might overlap in space:
    1550             :    *
    1551             :    * 1: Perfect overlap, every MOOSE element maps to an OpenMC cell and every
    1552             :    *    OpenMC cell maps to MOOSE element(s)
    1553             :    *
    1554             :    * 2: MOOSE domain fully encloses the OpenMC domain, so that not every MOOSE
    1555             :    *    element maps to an OpenMC cell, but every OpenMC cell maps to a MOOSE element
    1556             :    *
    1557             :    * 3: OpenMC domain fully encloses the MOOSE domain, so that not every OpenMC
    1558             :    *    cell maps to MOOSE element(s), but every MOOSE element maps to an OpenMC cell
    1559             :    *
    1560             :    * 4: MOOSE and OpenMC domains only partially overlap, so that not every MOOSE
    1561             :    *    element maps to an OpenMC and not every OpenMC cell maps to MOOSE element(s)
    1562             :    *
    1563             :    * 5: The MOOSE and OpenMC domains do not overlap at all, so no MOOSE elements
    1564             :    *    map to OpenMC cells and no OpenMC cells map to MOOSE elements.
    1565             :    *
    1566             :    * We consider situation #5 to be an error, while the others are technically allowed.
    1567             :    * We need to error here before getting to OpenMC where we don't map to any cells but
    1568             :    * would still try to set a cell filter based on no cells.
    1569             :    */
    1570             : 
    1571             :   // First, figure out the phase of each element according to the blocks defined by the user
    1572        1865 :   storeElementPhase();
    1573             : 
    1574             :   // perform element to cell mapping
    1575        1865 :   mapElemsToCells();
    1576             : 
    1577        1860 :   if (!_material_cells_only)
    1578             :   {
    1579             :     // gather all cell indices from the initial mapping
    1580             :     std::vector<int32_t> mapped_cells;
    1581     8009652 :     for (const auto & item : _elem_to_cell)
    1582     8009552 :       mapped_cells.push_back(item.first);
    1583             : 
    1584         100 :     std::sort(mapped_cells.begin(), mapped_cells.end());
    1585         100 :     auto new_end = std::unique(mapped_cells.begin(), mapped_cells.end());
    1586             :     mapped_cells.erase(new_end, mapped_cells.end());
    1587         100 :     openmc::prepare_distribcell(&mapped_cells);
    1588             : 
    1589             :     // perform element to cell mapping again to get correct instances
    1590         100 :     mapElemsToCells();
    1591             :   }
    1592             : 
    1593             :   // For each cell, get one point inside it to speed up the particle search
    1594        1860 :   getPointInCell();
    1595             : 
    1596             :   // Compute the volume that each OpenMC cell maps to in the MOOSE mesh
    1597        1860 :   computeCellMappedVolumes();
    1598             : 
    1599             :   // Get the number of elements of each phase within the cells
    1600        1860 :   getCellMappedPhase();
    1601             : 
    1602             :   // Get the element subdomains within each cell
    1603        1860 :   getCellMappedSubdomains();
    1604             : 
    1605        1858 :   if (_cell_to_elem.size() == 0 && _has_cell_tallies)
    1606           2 :     mooseError("Did not find any overlap between MOOSE elements and OpenMC cells for "
    1607             :                "the specified blocks!");
    1608             : 
    1609        5568 :   _console << "\nMapping between " + Moose::stringify(getMooseMesh().getMesh().n_active_elem()) +
    1610        5568 :                   " MOOSE elements and " + Moose::stringify(_n_openmc_cells) +
    1611        1856 :                   " OpenMC cells (on " + Moose::stringify(openmc::model::n_coord_levels) +
    1612        3712 :                   " coordinate levels):"
    1613        1856 :            << std::endl;
    1614             : 
    1615             :   VariadicTable<std::string, int, int, int, int> vt(
    1616       11136 :       {"", "# T Elems", "# rho Elems", "# T+rho Elems", "# Uncoupled Elems"});
    1617        1856 :   vt.addRow("MOOSE mesh",
    1618             :             _n_moose_temp_elems,
    1619             :             _n_moose_density_elems,
    1620             :             _n_moose_temp_density_elems,
    1621             :             _n_moose_none_elems);
    1622        1856 :   vt.addRow("OpenMC cells",
    1623             :             _n_mapped_temp_elems,
    1624             :             _n_mapped_density_elems,
    1625             :             _n_mapped_temp_density_elems,
    1626             :             _n_mapped_none_elems);
    1627        1856 :   vt.print(_console);
    1628        1856 :   _console << std::endl;
    1629             : 
    1630        1856 :   if (_needs_to_map_cells)
    1631             :   {
    1632        1717 :     if (_n_moose_temp_elems && (_n_mapped_temp_elems != _n_moose_temp_elems))
    1633          90 :       mooseWarning("The [Mesh] has " + Moose::stringify(_n_moose_temp_elems) +
    1634             :                    " elements providing temperature feedback (the elements in "
    1635          32 :                    "'temperature_blocks'), but only " +
    1636          26 :                    Moose::stringify(_n_mapped_temp_elems) + " got mapped to OpenMC cells.");
    1637             : 
    1638        1711 :     if (_n_moose_temp_elems && (_n_mapped_density_elems != _n_moose_density_elems))
    1639           4 :       mooseWarning("The [Mesh] has " + Moose::stringify(_n_moose_density_elems) +
    1640             :                    " elements providing density feedback (the elements in "
    1641           2 :                    "'density_blocks'), but only " +
    1642           0 :                    Moose::stringify(_n_mapped_density_elems) + " got mapped to OpenMC cells.");
    1643             : 
    1644        1709 :     if (_n_moose_temp_density_elems &&
    1645         487 :         (_n_mapped_temp_density_elems != _n_moose_temp_density_elems))
    1646          28 :       mooseWarning("The [Mesh] has " + Moose::stringify(_n_moose_temp_density_elems) +
    1647             :                    " elements providing temperature and density feedback (the elements in the "
    1648          10 :                    "intersection of 'temperature_blocks' and 'density_blocks'), but only " +
    1649           8 :                    Moose::stringify(_n_mapped_temp_density_elems) + " got mapped to OpenMC cells.");
    1650             : 
    1651        1707 :     if (_n_mapped_none_elems && (_specified_temperature_feedback || _specified_density_feedback))
    1652         496 :       mooseWarning("Skipping OpenMC multiphysics feedback from " +
    1653         496 :                    Moose::stringify(_n_mapped_none_elems) +
    1654         248 :                    " [Mesh] elements, which occupy a volume of: " +
    1655         496 :                    Moose::stringify(_uncoupled_volume * _scaling * _scaling * _scaling) + " cm3");
    1656             : 
    1657        1707 :     if (_n_openmc_cells < _cell_to_elem.size())
    1658           0 :       mooseError("Internal error: _cell_to_elem has length ",
    1659           0 :                  _cell_to_elem.size(),
    1660             :                  " which should\n"
    1661             :                  "not exceed the number of OpenMC cells, ",
    1662           0 :                  _n_openmc_cells);
    1663             :   }
    1664             : 
    1665             :   // Check that each cell maps to a single phase
    1666        1846 :   checkCellMappedPhase();
    1667        5550 : }
    1668             : 
    1669             : void
    1670       35507 : OpenMCCellAverageProblem::setContainedCells(const cellInfo & cell_info,
    1671             :                                             const Point & hint,
    1672             :                                             std::map<cellInfo, containedCells> & map)
    1673             : {
    1674             :   containedCells contained_cells;
    1675             : 
    1676       35507 :   openmc::Position p{hint(0), hint(1), hint(2)};
    1677             : 
    1678       35507 :   const auto & cell = openmc::model::cells[cell_info.first];
    1679       35507 :   if (cell->type_ == openmc::Fill::MATERIAL)
    1680             :   {
    1681       33251 :     std::vector<int32_t> instances = {cell_info.second};
    1682       33251 :     contained_cells[cell_info.first] = instances;
    1683             :   }
    1684             :   else
    1685        4512 :     contained_cells = cell->get_contained_cells(cell_info.second, &p);
    1686             : 
    1687       35507 :   map[cell_info] = contained_cells;
    1688       35507 : }
    1689             : 
    1690             : void
    1691       77562 : OpenMCCellAverageProblem::printTrisoHelp(
    1692             :     const std::chrono::time_point<std::chrono::high_resolution_clock> & start) const
    1693             : {
    1694       77562 :   if (!_printed_triso_warning)
    1695             :   {
    1696       77562 :     auto stop = std::chrono::high_resolution_clock::now();
    1697       77562 :     auto elapsed = std::chrono::duration<double, std::milli>(stop - start).count() / 1e3;
    1698       77562 :     if (elapsed > 120.0)
    1699             :     {
    1700           0 :       _printed_triso_warning = true;
    1701           0 :       _console << "\nThis is taking a long time. Does your problem have TRISOs/other "
    1702             :                << "highly heterogeneous geometry?\nIf you are repeating the same TRISO/etc. "
    1703           0 :                   "universe many times "
    1704             :                << "through your OpenMC model, setting\n'identical_cell_fills' will give you a big "
    1705           0 :                   "speedup.\n\n"
    1706             :                << "For more information, consult the Cardinal documentation: "
    1707           0 :                   "https://tinyurl.com/54kz9aw8"
    1708           0 :                << std::endl;
    1709             :     }
    1710             :   }
    1711       77562 : }
    1712             : 
    1713             : void
    1714        1832 : OpenMCCellAverageProblem::cacheContainedCells()
    1715             : {
    1716        3664 :   TIME_SECTION("cacheContainedCells", 3, "Caching Contained Cells", true);
    1717             : 
    1718             :   bool first_cell = true;
    1719             :   bool second_cell = false;
    1720             :   containedCells first_cell_cc;
    1721             :   containedCells second_cell_cc;
    1722             :   bool used_cache_shortcut = false;
    1723             : 
    1724             :   _cell_to_contained_material_cells.clear();
    1725        1832 :   _first_identical_cell_materials.clear();
    1726             :   _instance_offsets.clear();
    1727             :   _n_offset.clear();
    1728             : 
    1729             :   int n = -1;
    1730        1832 :   const auto time_start = std::chrono::high_resolution_clock::now();
    1731       40715 :   for (const auto & c : _cell_to_elem)
    1732             :   {
    1733       38883 :     auto cell_info = c.first;
    1734       38883 :     Point hint = transformPointToOpenMC(_cell_to_point[cell_info]);
    1735             : 
    1736       38883 :     printTrisoHelp(time_start);
    1737             : 
    1738             :     // default to the normal behavior
    1739       38883 :     if (!cellHasIdenticalFill(cell_info))
    1740       34371 :       setContainedCells(cell_info, hint, _cell_to_contained_material_cells);
    1741             :     else
    1742             :     {
    1743             :       used_cache_shortcut = true;
    1744        4512 :       _n_offset[cell_info] = ++n;
    1745             : 
    1746        4512 :       if (first_cell)
    1747             :       {
    1748          30 :         setContainedCells(cell_info, hint, _cell_to_contained_material_cells);
    1749          30 :         first_cell_cc = _cell_to_contained_material_cells[cell_info];
    1750             :         _first_identical_cell = cell_info;
    1751          60 :         _first_identical_cell_materials = materialsInCells(first_cell_cc);
    1752             :         first_cell = false;
    1753             :         second_cell = true;
    1754             :       }
    1755        4482 :       else if (second_cell)
    1756             :       {
    1757          30 :         setContainedCells(cell_info, hint, _cell_to_contained_material_cells);
    1758          30 :         second_cell_cc = _cell_to_contained_material_cells[cell_info];
    1759             :         second_cell = false;
    1760             : 
    1761             :         // we will check for equivalence in the end mapping later; but here we still need
    1762             :         // some checks to make sure the structure is compatible
    1763          30 :         checkContainedCellsStructure(cell_info, first_cell_cc, second_cell_cc);
    1764             : 
    1765             :         // get the offset for each instance for each contained cell
    1766       14112 :         for (const auto & f : first_cell_cc)
    1767             :         {
    1768       14082 :           const auto id = f.first;
    1769             :           const auto & instances = f.second;
    1770             :           const auto & new_instances = second_cell_cc[id];
    1771             : 
    1772             :           std::vector<int32_t> offsets;
    1773     1057560 :           for (unsigned int i = 0; i < instances.size(); ++i)
    1774     1043478 :             offsets.push_back(new_instances[i] - instances[i]);
    1775             : 
    1776       14082 :           _instance_offsets[id] = offsets;
    1777             :         }
    1778             :       }
    1779             :     }
    1780             :   }
    1781             : 
    1782             :   // only need to check if we were attempting the shortcut
    1783        1832 :   if (_check_identical_cell_fills)
    1784             :   {
    1785          40 :     TIME_SECTION("verifyCacheContainedCells", 4, "Verifying Cached Contained Cells", true);
    1786             : 
    1787             :     std::map<cellInfo, containedCells> checking_cell_fills;
    1788        1096 :     for (const auto & c : _cell_to_elem)
    1789        1076 :       setContainedCells(c.first, transformPointToOpenMC(_cell_to_point[c.first]), checking_cell_fills);
    1790             : 
    1791             :     std::map<cellInfo, containedCells> current_cell_fills;
    1792        1096 :     for (const auto & c : _cell_to_elem)
    1793        2152 :       current_cell_fills[c.first] = containedMaterialCells(c.first);
    1794             : 
    1795             :     std::map<cellInfo, containedCells> ordered_reference(checking_cell_fills.begin(),
    1796          20 :                                                          checking_cell_fills.end());
    1797             :     std::map<cellInfo, containedCells> ordered(current_cell_fills.begin(),
    1798          20 :                                                current_cell_fills.end());
    1799          20 :     compareContainedCells(ordered_reference, ordered);
    1800             :   }
    1801             : 
    1802        1828 :   if (_has_identical_cell_fills && !used_cache_shortcut)
    1803          10 :     mooseWarning("You specified 'identical_cell_fills', but all cells which mapped to these "
    1804             :                  "subdomains were filled \n"
    1805             :                  "by a material (as opposed to a universe/lattice), so the 'identical_cell_fills' "
    1806             :                  "parameter is unused.");
    1807        1826 : }
    1808             : 
    1809             : void
    1810        1020 : OpenMCCellAverageProblem::checkContainedCellsStructure(const cellInfo & cell_info,
    1811             :                                                        containedCells & reference,
    1812             :                                                        containedCells & compare) const
    1813             : {
    1814             :   // make sure the number of keys is the same
    1815        1020 :   if (reference.size() != compare.size())
    1816           0 :     mooseError("The cell caching failed to identify identical number of cell IDs filling cell " +
    1817           0 :                printCell(cell_info) + "\nYou must unset 'identical_cell_fills'");
    1818             : 
    1819      200420 :   for (const auto & entry : reference)
    1820             :   {
    1821      199402 :     const auto & key = entry.first;
    1822             : 
    1823             :     // check that each key exists
    1824             :     if (!compare.count(key))
    1825           6 :       mooseError("Not all cells contain cell ID " + Moose::stringify(cellID(key)) +
    1826           4 :                  ". The offender is: cell " + printCell(cell_info) +
    1827             :                  ".\nYou must unset 'identical_cell_fills'!");
    1828             : 
    1829             :     // for each int32_t key, compare the std::vector<int32_t> map
    1830             :     const auto & reference_instances = entry.second;
    1831             :     const auto & compare_instances = compare[key];
    1832             : 
    1833             :     // they should have the same number of instances
    1834      199400 :     if (reference_instances.size() != compare_instances.size())
    1835           0 :       mooseError("The cell caching should have identified " +
    1836           0 :                  Moose::stringify(reference_instances.size()) + "cell instances in cell ID " +
    1837           0 :                  Moose::stringify(cellID(key)) + ", but instead found " +
    1838             :                  Moose::stringify(compare_instances.size()) +
    1839             :                  "\nYou must unset 'identical_cell_fills'");
    1840             :   }
    1841        1018 : }
    1842             : 
    1843             : void
    1844          20 : OpenMCCellAverageProblem::compareContainedCells(std::map<cellInfo, containedCells> & reference,
    1845             :                                                 std::map<cellInfo, containedCells> & compare) const
    1846             : {
    1847             :   // check that the number of keys matches
    1848          20 :   if (reference.size() != compare.size())
    1849           0 :     mooseError("The cell caching should have identified " + Moose::stringify(reference.size()) +
    1850             :                " cells, but instead "
    1851           0 :                "found " +
    1852             :                Moose::stringify(compare.size()));
    1853             : 
    1854             :   // loop over each cellInfo
    1855        1006 :   for (const auto & entry : reference)
    1856             :   {
    1857         990 :     auto cell_info = entry.first;
    1858             : 
    1859             :     // make sure the key exists
    1860             :     if (!compare.count(cell_info))
    1861           0 :       mooseError("The cell caching failed to map cell " + printCell(cell_info));
    1862             : 
    1863             :     // for each cellInfo key, compare the contained cells map
    1864         990 :     auto reference_map = reference[cell_info];
    1865         990 :     auto compare_map = compare[cell_info];
    1866             : 
    1867         990 :     checkContainedCellsStructure(cell_info, reference_map, compare_map);
    1868             : 
    1869             :     // loop over each contained cell
    1870      185886 :     for (const auto & nested_entry : reference_map)
    1871             :     {
    1872             :       // for each int32_t key, compare the std::vector<int32_t> map
    1873      184900 :       auto reference_instances = nested_entry.second;
    1874      184900 :       auto compare_instances = compare_map[nested_entry.first];
    1875             : 
    1876      184900 :       std::sort(reference_instances.begin(), reference_instances.end());
    1877      184900 :       std::sort(compare_instances.begin(), compare_instances.end());
    1878             : 
    1879             :       // and the instances should exactly match
    1880      184900 :       if (reference_instances != compare_instances)
    1881           2 :         mooseError(
    1882           2 :             "The cell caching failed to get correct instances for material cell ID " +
    1883           4 :             Moose::stringify(cellID(nested_entry.first)) + " within cell " + printCell(cell_info) +
    1884           0 :             ". You must unset 'identical_cell_fills'!" + "\n\nThis error might appear if:\n" +
    1885             :             " - There is a mismatch between your OpenMC model and the [Mesh]\n"
    1886             :             " - There are additional OpenMC cells filled with this repeatable universe/lattice, "
    1887             :             "but which are not mapping to the blocks in 'identical_cell_fills'");
    1888             :     }
    1889             :   }
    1890          16 : }
    1891             : 
    1892             : std::vector<int32_t>
    1893        1816 : OpenMCCellAverageProblem::getMappedTallyIDs() const
    1894             : {
    1895             :   std::vector<int32_t> tally_ids;
    1896             : 
    1897             :   // local mapped tallies
    1898        4293 :   for (const auto & t : _local_tallies)
    1899        2477 :     tally_ids.push_back(t->getTallyID());
    1900             :   // global normalization tallies
    1901        3125 :   for (const auto & t : _global_tallies)
    1902        1309 :     tally_ids.push_back(t->id());
    1903             :   // ensure the first global tally is added as well
    1904             :   openmc::model::tallies[_global_tally_index]->id();
    1905             : 
    1906        1816 :   return tally_ids;
    1907             : }
    1908             : 
    1909             : unsigned int
    1910     7435357 : OpenMCCellAverageProblem::getCellLevel(const Point & c) const
    1911             : {
    1912     7435357 :   unsigned int level = _cell_level;
    1913     7435357 :   if (_cell_level > _particle.n_coord() - 1)
    1914             :   {
    1915        8388 :     if (isParamValid("lowest_cell_level"))
    1916        4192 :       level = _particle.n_coord() - 1;
    1917             :     else
    1918             :     {
    1919           2 :       std::string l = Moose::stringify(_cell_level);
    1920           4 :       mooseError("Requested coordinate level of " + l +
    1921           6 :                  " exceeds number of nested coordinate levels at " + printPoint(c) + ": " +
    1922           2 :                  Moose::stringify(_particle.n_coord()) +
    1923             :                  ".\n\nYou can either change how the OpenMC model is built by nesting universes "
    1924           2 :                  "into deeper levels, or you can try setting 'lowest_cell_level = " +
    1925           0 :                  l +
    1926             :                  "', which will couple on the lowest level found in the geometry at any given x, "
    1927           2 :                  "y, z point, up to and including level " +
    1928           0 :                  l + ".");
    1929             :     }
    1930             :   }
    1931             : 
    1932     7435355 :   return level;
    1933             : }
    1934             : 
    1935             : void
    1936        1965 : OpenMCCellAverageProblem::mapElemsToCells()
    1937             : {
    1938             :   // reset counters, flags
    1939        1965 :   _n_mapped_temp_elems = 0;
    1940        1965 :   _n_mapped_density_elems = 0;
    1941        1965 :   _n_mapped_temp_density_elems = 0;
    1942        1965 :   _n_mapped_none_elems = 0;
    1943        1965 :   _uncoupled_volume = 0.0;
    1944        1965 :   _material_cells_only = true;
    1945             : 
    1946             :   // reset data structures
    1947        1965 :   _elem_to_cell.clear();
    1948             :   _cell_to_elem.clear();
    1949        1965 :   _flattened_ids.clear();
    1950        1965 :   _flattened_instances.clear();
    1951             : 
    1952             :   int local_elem = -1;
    1953    19827213 :   for (unsigned int e = 0; e < getMooseMesh().nElem(); ++e)
    1954             :   {
    1955    19825253 :     const auto * elem = getMooseMesh().queryElemPtr(e);
    1956             : 
    1957    19825253 :     if (!isLocalElem(elem) || !elem->active())
    1958    11260716 :       continue;
    1959             : 
    1960     8591049 :     local_elem++;
    1961             : 
    1962     8591049 :     auto id = elem->subdomain_id();
    1963     8591049 :     const Point & c = elem->vertex_average();
    1964     8591049 :     Real element_volume = elem->volume();
    1965             : 
    1966             :     // find the OpenMC cell at the location 'c' (if any)
    1967     8591049 :     bool error = findCell(c);
    1968             : 
    1969             :     // if we didn't find an OpenMC cell here, then we certainly have an uncoupled region
    1970     8591049 :     if (error)
    1971             :     {
    1972       26512 :       _uncoupled_volume += element_volume;
    1973       26512 :       _n_mapped_none_elems++;
    1974       26512 :       continue;
    1975             :     }
    1976             : 
    1977             :     // next, see what type of data is to be sent into OpenMC (to further classify
    1978             :     // the type of couling)
    1979     8564537 :     auto phase = elemFeedback(elem);
    1980             : 
    1981             :     // Loop over the tallies to check if any CellTally objects map to this element.
    1982             :     bool elem_mapped_to_cell_tally = false;
    1983    34447468 :     for (const auto & tally : _local_tallies)
    1984             :     {
    1985    25882931 :       auto cell_tally = dynamic_cast<const CellTally *>(tally.get());
    1986    25882931 :       if (cell_tally)
    1987    25202579 :         elem_mapped_to_cell_tally |=
    1988             :             cell_tally->getBlocks().find(id) != cell_tally->getBlocks().end();
    1989             :     }
    1990             : 
    1991     8564537 :     bool requires_mapping = phase != coupling::none || elem_mapped_to_cell_tally;
    1992             : 
    1993             :     // get the level in the OpenMC model to fetch mapped cell information. For
    1994             :     // uncoupled regions, we know we will be successful in finding a cell (because
    1995             :     // we already screened out uncoupled cells), and the id and instance are unused
    1996             :     // (so we can just set zero).
    1997     8564537 :     auto level = requires_mapping ? getCellLevel(c) : 0;
    1998             : 
    1999             :     // ensure the mapped cell isn't in a unvierse being used as the "outer"
    2000             :     // universe of a lattice in the OpenMC model
    2001             :     if (requires_mapping)
    2002     7435355 :       latticeOuterCheck(c, level);
    2003             : 
    2004     7435353 :     switch (phase)
    2005             :     {
    2006      665996 :       case coupling::density_and_temperature:
    2007             :       {
    2008      665996 :         _n_mapped_temp_density_elems++;
    2009      665996 :         break;
    2010             :       }
    2011     1429945 :       case coupling::temperature:
    2012             :       {
    2013     1429945 :         _n_mapped_temp_elems++;
    2014     1429945 :         break;
    2015             :       }
    2016         732 :       case coupling::density:
    2017             :       {
    2018         732 :         _n_mapped_density_elems++;
    2019         732 :         break;
    2020             :       }
    2021     6467860 :       case coupling::none:
    2022             :       {
    2023     6467860 :         _uncoupled_volume += element_volume;
    2024     6467860 :         _n_mapped_none_elems++;
    2025     6467860 :         break;
    2026             :       }
    2027           0 :       default:
    2028           0 :         mooseError("Unhandled CouplingFields enum!");
    2029             :     }
    2030             : 
    2031     8564533 :     auto cell_index = _particle.coord(level).cell();
    2032             : 
    2033             :     // Error if the user is attempting to use a skinner when mapping both CSG cells and DAGMC
    2034             :     // geometry to the MOOSE mesh. The skinner is currently not set up to ignore elements that
    2035             :     // map to cells and will generate DAGMC geometry that overlaps with pre-existing CSG cells.
    2036             :     // TODO: This would be nice to fix, but would require a rework of the skinner.
    2037     8564533 :     if (openmc::model::cells[cell_index]->geom_type() == openmc::GeometryType::CSG &&
    2038     8529237 :         _using_skinner)
    2039           1 :       mooseError("At present, the 'skinner' can only be used when the only OpenMC geometry "
    2040             :                  "which maps to the MOOSE mesh is DAGMC geometry. Your geometry contains CSG "
    2041             :                  "cells which map to the MOOSE mesh.");
    2042             : 
    2043     8564532 :     auto cell_instance = cell_instance_at_level(_particle, level);
    2044             : 
    2045             :     cellInfo cell_info = {cell_index, cell_instance};
    2046             : 
    2047     8564532 :     if (openmc::model::cells[cell_index]->type_ != openmc::Fill::MATERIAL)
    2048     1345336 :       _material_cells_only = false;
    2049             : 
    2050             :     // store the map of cells to elements that will be coupled via feedback or a tally
    2051     8564532 :     if (requires_mapping)
    2052     7435352 :       _cell_to_elem[cell_info].push_back(local_elem);
    2053             :   }
    2054             : 
    2055        1960 :   _communicator.sum(_n_mapped_temp_elems);
    2056        1960 :   _communicator.sum(_n_mapped_temp_density_elems);
    2057        1960 :   _communicator.sum(_n_mapped_density_elems);
    2058        1960 :   _communicator.sum(_n_mapped_none_elems);
    2059        1960 :   _communicator.sum(_uncoupled_volume);
    2060             : 
    2061             :   // if ANY rank finds a non-material cell, they will hold 0 (false)
    2062        1960 :   _communicator.min(_material_cells_only);
    2063             : 
    2064             :   // store the local mapping of cells to elements for convenience
    2065             :   _local_cell_to_elem = _cell_to_elem;
    2066             : 
    2067             :   // flatten the cell IDs and instances
    2068       37209 :   for (const auto & c : _cell_to_elem)
    2069             :   {
    2070       35249 :     auto cell_info = c.first;
    2071       35249 :     _flattened_ids.push_back(cell_info.first);
    2072       35249 :     _flattened_instances.push_back(cell_info.second);
    2073             :   }
    2074             : 
    2075        1960 :   _communicator.allgather(_flattened_ids);
    2076        1960 :   _communicator.allgather(_flattened_instances);
    2077             : 
    2078             :   // collect the _cell_to_elem onto all ranks
    2079             :   std::vector<unsigned int> n_elems;
    2080             :   std::vector<unsigned int> elems;
    2081       37209 :   for (const auto & c : _cell_to_elem)
    2082             :   {
    2083       35249 :     n_elems.push_back(c.second.size());
    2084     7469065 :     for (const auto & e : c.second)
    2085     7433816 :       elems.push_back(_local_to_global_elem[e]);
    2086             :   }
    2087             : 
    2088        1960 :   gatherCellVector(elems, n_elems, _cell_to_elem);
    2089             : 
    2090             :   // fill out the elem_to_cell structure
    2091             :   // TODO: figure out how to shrink this so we only store the mapping for active
    2092             :   // elements as opposed to the entire element hierarchy.
    2093        1960 :   _elem_to_cell.resize(getMooseMesh().nElem(), {UNMAPPED, UNMAPPED});
    2094       62173 :   for (const auto & c : _cell_to_elem)
    2095    17266729 :     for (const auto & e : c.second)
    2096    17206516 :       _elem_to_cell[e] = c.first;
    2097        1960 : }
    2098             : 
    2099             : void
    2100        1860 : OpenMCCellAverageProblem::getPointInCell()
    2101             : {
    2102             :   std::vector<Real> x;
    2103             :   std::vector<Real> y;
    2104             :   std::vector<Real> z;
    2105       25829 :   for (const auto & c : _local_cell_to_elem)
    2106             :   {
    2107             :     // we are only dealing with local elements here, no need to check for nullptr
    2108       23969 :     const Elem * elem = getMooseMesh().queryElemPtr(globalElemID(c.second[0]));
    2109       23969 :     const Point & p = elem->vertex_average();
    2110             : 
    2111       23969 :     x.push_back(p(0));
    2112       23969 :     y.push_back(p(1));
    2113       23969 :     z.push_back(p(2));
    2114             :   }
    2115             : 
    2116        1860 :   _communicator.allgather(x);
    2117        1860 :   _communicator.allgather(y);
    2118        1860 :   _communicator.allgather(z);
    2119             : 
    2120             :   // this will get a point from the lowest rank in each cell
    2121             :   _cell_to_point.clear();
    2122       58551 :   for (unsigned int i = 0; i < _flattened_ids.size(); ++i)
    2123             :   {
    2124             :     cellInfo cell_info = {_flattened_ids[i], _flattened_instances[i]};
    2125             :     if (!_cell_to_point.count(cell_info))
    2126       39057 :       _cell_to_point[cell_info] = Point(x[i], y[i], z[i]);
    2127             :   }
    2128        1860 : }
    2129             : 
    2130             : void
    2131         104 : OpenMCCellAverageProblem::resetTallies()
    2132             : {
    2133         104 :   if (_local_tallies.size() == 0 && !_needs_global_tally)
    2134             :     return;
    2135             : 
    2136             :   // We initialize [Problem/Tallies] by forward iterating this vector. We need to delete them in
    2137             :   // reverse.
    2138         190 :   for (int i = _local_tallies.size() - 1; i >= 0; --i)
    2139          86 :     _local_tallies[i]->resetTally();
    2140             : 
    2141             :   // erase global tallies
    2142         104 :   if (_needs_global_tally)
    2143             :   {
    2144         116 :     for (int i = _global_tally_index + _global_tally_scores.size() - 1; i >= 0; --i)
    2145             :     {
    2146          48 :       auto idx = openmc::model::tallies.begin() + _global_tally_index + i;
    2147             :       openmc::model::tallies.erase(idx);
    2148             :     }
    2149             :   }
    2150             : }
    2151             : 
    2152             : void
    2153        1826 : OpenMCCellAverageProblem::initializeTallies()
    2154             : {
    2155             :   // add trigger information for k, if present
    2156        1826 :   openmc::settings::keff_trigger.metric = triggerMetric(_k_trigger);
    2157             : 
    2158        1826 :   if (_local_tallies.size() == 0 && !_needs_global_tally)
    2159             :     return;
    2160             : 
    2161             :   // create the global tally for normalization; we make sure to use the
    2162             :   // same estimator as the local tally
    2163        1826 :   if (_needs_global_tally)
    2164             :   {
    2165        1358 :     _global_tally_index = openmc::model::tallies.size();
    2166             : 
    2167        1358 :     _global_tallies.clear();
    2168        2675 :     for (unsigned int i = 0; i < _global_tally_scores.size(); ++i)
    2169             :     {
    2170        1317 :       _global_tallies.push_back(openmc::Tally::create());
    2171        1317 :       _global_tallies[i]->set_scores(_global_tally_scores[i]);
    2172        1317 :       _global_tallies[i]->estimator_ = _global_tally_estimators[i];
    2173             :     }
    2174             : 
    2175        1358 :     _global_sum_tally.clear();
    2176        1358 :     _global_sum_tally.resize(_all_tally_scores.size(), 0.0);
    2177             :   }
    2178             : 
    2179             :   // Initialize all of the [Problem/Tallies].
    2180        4305 :   for (auto & local_tally : _local_tallies)
    2181        2489 :     local_tally->initializeTally();
    2182             : 
    2183             :   // Ensure that any tally editors don't apply to mapped tallies
    2184        1816 :   checkTallyEditorIDs();
    2185             : }
    2186             : 
    2187             : void
    2188           2 : OpenMCCellAverageProblem::latticeOuterError(const Point & c, int level) const
    2189             : {
    2190           2 :   const auto & cell = openmc::model::cells[_particle.coord(level).cell()];
    2191           2 :   std::stringstream msg;
    2192           2 :   msg << "The point " << c << " mapped to cell " << cell->id_
    2193             :       << " in the OpenMC model is inside a universe "
    2194             :          "used as the 'outer' universe of a lattice. "
    2195             :          "All cells used for mapping in lattices must be explicitly set "
    2196             :          "on the 'universes' attribute of lattice objects. "
    2197             :       << "If you want to obtain feedback or cell tallies here, you "
    2198             :          "will need to widen your lattice to have universes covering all of the space you "
    2199             :          "want feedback or cell tallies.\n\nIn other words, re-build your OpenMC model but replace "
    2200             :          "lattice.outer by simply creating extra rings/rows in your lattice to cover all the space "
    2201             :          "needed. For more information, see: "
    2202           2 :          "https://github.com/openmc-dev/openmc/issues/551.";
    2203           2 :   mooseError(msg.str());
    2204           0 : }
    2205             : 
    2206             : void
    2207     7435355 : OpenMCCellAverageProblem::latticeOuterCheck(const Point & c, int level) const
    2208             : {
    2209    20669174 :   for (int i = 0; i <= level; ++i)
    2210             :   {
    2211             :     const auto & coord = _particle.coord(i);
    2212             : 
    2213             :     // if there is no lattice at this level, move on
    2214    13233821 :     if (coord.lattice() == openmc::C_NONE)
    2215     7439771 :       continue;
    2216             : 
    2217     5794050 :     const auto & lat = openmc::model::lattices[coord.lattice()];
    2218             : 
    2219             :     // if the lattice's outer universe isn't set, move on
    2220     5794050 :     if (lat->outer_ == openmc::NO_OUTER_UNIVERSE)
    2221     4950528 :       continue;
    2222             : 
    2223      843522 :     if (coord.universe() != lat->outer_)
    2224      843520 :       continue;
    2225             : 
    2226             :     // move on if the lattice indices are valid (position is in the set of explicitly defined
    2227             :     // universes)
    2228           2 :     if (lat->are_valid_indices(coord.lattice_index()))
    2229           0 :       continue;
    2230             : 
    2231             :     // if we get here, the mapping is occurring in a universe that is not explicitly defined in the
    2232             :     // lattice
    2233           2 :     latticeOuterError(c, level);
    2234             :   }
    2235     7435353 : }
    2236             : 
    2237             : bool
    2238     8591049 : OpenMCCellAverageProblem::findCell(const Point & point)
    2239             : {
    2240     8591049 :   _particle.clear();
    2241             :   // Use a random direction to minimize "lost" virtual particles.
    2242     8591049 :   _particle.u() = {0.6339976, -0.538536, 0.555026};
    2243     8591049 :   _particle.u() /= _particle.u().norm();
    2244             : 
    2245     8591049 :   Point pt = transformPointToOpenMC(point);
    2246             : 
    2247     8591049 :   _particle.r() = {pt(0), pt(1), pt(2)};
    2248     8591049 :   return !openmc::exhaustive_find_cell(_particle);
    2249             : }
    2250             : 
    2251             : void
    2252        1825 : OpenMCCellAverageProblem::addExternalVariables()
    2253             : {
    2254             :   // We need to validate tallies here to we can add scores that may be missing.
    2255        1825 :   validateLocalTallies();
    2256             : 
    2257             :   // Add all of the auxvariables in which the [Tallies] block will store results.
    2258             :   unsigned int previous_valid_name_index = 0;
    2259        4295 :   for (unsigned int i = 0; i < _local_tallies.size(); ++i)
    2260             :   {
    2261        2480 :     _tally_var_ids.emplace_back();
    2262             : 
    2263             :     // We use this to check if a sequence of added tallies corresponds to a single translated mesh.
    2264             :     // If the number of names reported in getAuxVarNames is zero, the tally must store it's results
    2265             :     // in the variables added by the first mesh tally in the sequence.
    2266             :     bool is_instanced = _local_tallies[i]->getAuxVarNames().size() == 0;
    2267        2480 :     previous_valid_name_index = !is_instanced ? i : previous_valid_name_index;
    2268             : 
    2269        2480 :     const auto & names = _local_tallies[previous_valid_name_index]->getAuxVarNames();
    2270             : 
    2271        2480 :     _tally_ext_var_ids.emplace_back();
    2272        2480 :     if (_local_tallies[i]->hasOutputs())
    2273         136 :       _tally_ext_var_ids[i].resize(_local_tallies[i]->getOutputs().size());
    2274             : 
    2275        6642 :     for (unsigned int j = 0; j < names.size(); ++j)
    2276             :     {
    2277        4164 :       if (is_instanced)
    2278         532 :         _tally_var_ids[i].push_back(
    2279             :             _tally_var_ids[previous_valid_name_index][j]); // Use variables from first in sequence.
    2280             :       else
    2281        3632 :         _tally_var_ids[i].push_back(addExternalVariable(names[j]));
    2282             : 
    2283        4162 :       if (_local_tallies[i]->hasOutputs())
    2284             :       {
    2285             :         const auto & outs = _local_tallies[i]->getOutputs();
    2286         354 :         for (std::size_t k = 0; k < outs.size(); ++k)
    2287             :         {
    2288         190 :           std::string n = names[j] + "_" + outs[k];
    2289         190 :           if (is_instanced)
    2290          16 :             _tally_ext_var_ids[i][k].push_back(
    2291             :                 _tally_ext_var_ids[previous_valid_name_index][k]
    2292             :                                   [j]); // Use variables from first in sequence.
    2293             :           else
    2294         174 :             _tally_ext_var_ids[i][k].push_back(addExternalVariable(n));
    2295             :         }
    2296             :       }
    2297             :     }
    2298             :   }
    2299             : 
    2300             :   // create the variable(s) that will be used to receive density
    2301             :   _subdomain_to_density_vars.clear();
    2302        2326 :   for (const auto & v : _density_vars_to_blocks)
    2303             :   {
    2304         511 :     auto number = addExternalVariable(v.first, &v.second);
    2305             : 
    2306         511 :     auto ids = getMooseMesh().getSubdomainIDs(v.second);
    2307        1067 :     for (const auto & s : ids)
    2308        1112 :       _subdomain_to_density_vars[s] = {number, v.first};
    2309             :   }
    2310             : 
    2311             :   // create the variable(s) that will be used to receive temperature
    2312             :   _subdomain_to_temp_vars.clear();
    2313        3285 :   for (const auto & v : _temp_vars_to_blocks)
    2314             :   {
    2315        1470 :     auto number = addExternalVariable(v.first, &v.second);
    2316             : 
    2317        1470 :     auto ids = getMooseMesh().getSubdomainIDs(v.second);
    2318        3815 :     for (const auto & s : ids)
    2319        4690 :       _subdomain_to_temp_vars[s] = {number, v.first};
    2320             :   }
    2321             : 
    2322        1815 :   if (_output_cell_mapping && _needs_to_map_cells)
    2323             :   {
    2324        1551 :     std::string auxk_type = "CellIDAux";
    2325        1551 :     InputParameters params = _factory.getValidParams(auxk_type);
    2326        3102 :     addExternalVariable("cell_id");
    2327        3102 :     params.set<AuxVariableName>("variable") = "cell_id";
    2328        3102 :     addAuxKernel(auxk_type, "cell_id", params);
    2329             : 
    2330             :     auxk_type = "CellInstanceAux";
    2331        1551 :     params = _factory.getValidParams(auxk_type);
    2332        3102 :     addExternalVariable("cell_instance");
    2333        3102 :     params.set<AuxVariableName>("variable") = "cell_instance";
    2334        1551 :     addAuxKernel(auxk_type, "cell_instance", params);
    2335        3102 :   }
    2336             :   else
    2337             :     _console << "Skipping output of 'cell_id' and 'cell_instance' because 'temperature_blocks', "
    2338         264 :                 "'density_blocks', and 'tally_blocks' are all empty"
    2339         264 :              << std::endl;
    2340        1815 : }
    2341             : 
    2342             : void
    2343        2300 : OpenMCCellAverageProblem::externalSolve()
    2344             : {
    2345             :   // if using Dufek-Gudowski acceleration and this is not the first iteration, update
    2346             :   // the number of particles; we put this here so that changing the number of particles
    2347             :   // doesn't intrude with any other postprocessing routines that happen outside this class's purview
    2348        2300 :   if (_relaxation == relaxation::dufek_gudowski && !firstSolve())
    2349          32 :     dufekGudowskiParticleUpdate();
    2350             : 
    2351        2300 :   OpenMCProblemBase::externalSolve();
    2352        2294 : }
    2353             : 
    2354             : std::map<OpenMCCellAverageProblem::cellInfo, Real>
    2355        1336 : OpenMCCellAverageProblem::computeVolumeWeightedCellInput(
    2356             :     const std::map<SubdomainID, std::pair<unsigned int, std::string>> & var_num,
    2357             :     const std::vector<coupling::CouplingFields> * phase = nullptr) const
    2358             : {
    2359        1336 :   const auto & sys_number = _aux->number();
    2360             : 
    2361             :   // collect the volume-weighted product across local ranks
    2362             :   std::vector<Real> volume_product;
    2363       24988 :   for (const auto & c : _local_cell_to_elem)
    2364             :   {
    2365             :     // if a specific phase is passed in, only evaluate for those elements in the phase;
    2366             :     // in order to have the correct array sizes for gatherCellSum, we set zero values
    2367             :     // for any cells that aren't in the correct phase, and leave it up to the send...ToOpenMC()
    2368             :     // routines to properly shield against incorrect phases
    2369       23652 :     if (phase)
    2370             :     {
    2371       23652 :       if (std::find(phase->begin(), phase->end(), cellFeedback(c.first)) == phase->end())
    2372             :       {
    2373        4392 :         volume_product.push_back(0.0 /* dummy value */);
    2374        4392 :         continue;
    2375             :       }
    2376             :     }
    2377             : 
    2378       19260 :     Real product = 0.0;
    2379     1436640 :     for (const auto & e : c.second)
    2380             :     {
    2381             :       // we are only accessing local elements here, so no need to check for nullptr
    2382     1417380 :       const auto * elem = getMooseMesh().queryElemPtr(globalElemID(e));
    2383     1417380 :       auto v = var_num.at(elem->subdomain_id()).first;
    2384     1417380 :       auto dof_idx = elem->dof_number(sys_number, v, 0);
    2385     1417380 :       product += _serialized_solution(dof_idx) * elem->volume();
    2386             :     }
    2387             : 
    2388       19260 :     volume_product.push_back(product);
    2389             :   }
    2390             : 
    2391             :   std::map<cellInfo, Real> global_volume_product;
    2392        1336 :   gatherCellSum(volume_product, global_volume_product);
    2393             : 
    2394        1336 :   return global_volume_product;
    2395             : }
    2396             : 
    2397             : void
    2398        1500 : OpenMCCellAverageProblem::sendTemperatureToOpenMC() const
    2399             : {
    2400        1500 :   if (!_specified_temperature_feedback)
    2401         400 :     return;
    2402             : 
    2403        1100 :   _console << "Sending temperature to OpenMC cells... " << std::endl;
    2404             : 
    2405        1100 :   double maximum = std::numeric_limits<double>::min();
    2406        1100 :   double minimum = std::numeric_limits<double>::max();
    2407             : 
    2408             :   // collect the volume-temperature product across local ranks
    2409             :   std::vector<coupling::CouplingFields> phase = {coupling::temperature,
    2410        1100 :                                                  coupling::density_and_temperature};
    2411             :   std::map<cellInfo, Real> cell_vol_temp =
    2412        1100 :       computeVolumeWeightedCellInput(_subdomain_to_temp_vars, &phase);
    2413             : 
    2414             :   std::unordered_set<cellInfo> cells_already_set;
    2415             : 
    2416       31660 :   for (const auto & c : _cell_to_elem)
    2417             :   {
    2418       30566 :     auto cell_info = c.first;
    2419       30566 :     if (!hasTemperatureFeedback(cell_info))
    2420          24 :       continue;
    2421             : 
    2422       30542 :     Real average_temp = cell_vol_temp.at(cell_info) / _cell_to_elem_volume.at(cell_info);
    2423             : 
    2424       30542 :     minimum = std::min(minimum, average_temp);
    2425       30542 :     maximum = std::max(maximum, average_temp);
    2426             : 
    2427       30542 :     if (_verbose)
    2428       14256 :       _console << "Setting cell " << printCell(cell_info) << " ["
    2429        7128 :                << _cell_to_n_contained.at(cell_info)
    2430        7128 :                << " contained cells] to temperature (K): " << std::setw(4) << average_temp
    2431        7128 :                << std::endl;
    2432             : 
    2433       30542 :     containedCells contained_cells = containedMaterialCells(cell_info);
    2434             : 
    2435     5236592 :     for (const auto & contained : contained_cells)
    2436   401840544 :       for (const auto & instance : contained.second)
    2437             :       {
    2438             :         cellInfo ci = {contained.first, instance};
    2439             :         if (cells_already_set.count(ci))
    2440             :         {
    2441             :           double T;
    2442           2 :           openmc_cell_get_temperature(ci.first, &ci.second, &T);
    2443             : 
    2444           6 :           mooseError("Cell " + std::to_string(cellID(contained.first)) + ", instance " +
    2445           2 :                      std::to_string(instance) +
    2446           4 :                      " has already had its temperature set by Cardinal to " + std::to_string(T) +
    2447             :                      "! This indicates a problem with how you have built your geometry, because "
    2448             :                      "this cell is trying to receive a distribution of temperatures in space, but "
    2449             :                      "each successive set-temperature operation is only overwriting the previous "
    2450             :                      "value.\n\nThis error most often appears when you are filling a LATTICE into "
    2451             :                      "multiple cells. One fix is to first place that lattice into a universe, and "
    2452             :                      "then fill that UNIVERSE into multiple cells.");
    2453             :         }
    2454             : 
    2455             :         cells_already_set.insert(ci);
    2456   396634492 :         setCellTemperature(contained.first, instance, average_temp, cell_info);
    2457             :       }
    2458             :   }
    2459             : 
    2460        1094 :   if (!_verbose)
    2461         132 :     _console << " Sent cell-averaged min/max (K): " << minimum << ", " << maximum << std::endl;
    2462             : }
    2463             : 
    2464             : OpenMCCellAverageProblem::cellInfo
    2465     8076288 : OpenMCCellAverageProblem::firstContainedMaterialCell(const cellInfo & cell_info) const
    2466             : {
    2467     8076288 :   const auto & contained_cells = containedMaterialCells(cell_info);
    2468             :   const auto & instances = contained_cells.begin()->second;
    2469             :   cellInfo first_cell = {contained_cells.begin()->first, instances[0]};
    2470     8076288 :   return first_cell;
    2471             : }
    2472             : 
    2473             : void
    2474        1494 : OpenMCCellAverageProblem::sendDensityToOpenMC() const
    2475             : {
    2476        1494 :   if (!_specified_density_feedback)
    2477        1258 :     return;
    2478             : 
    2479         236 :   _console << "Sending density to OpenMC cells... " << std::endl;
    2480             : 
    2481         236 :   double maximum = std::numeric_limits<double>::min();
    2482         236 :   double minimum = std::numeric_limits<double>::max();
    2483             : 
    2484             :   // collect the volume-density product across local ranks
    2485             :   std::vector<coupling::CouplingFields> phase = {coupling::density,
    2486         236 :                                                  coupling::density_and_temperature};
    2487         236 :   std::map<cellInfo, Real> cell_vol_density = computeVolumeWeightedCellInput(_subdomain_to_density_vars, &phase);
    2488             : 
    2489             :   // in case multiple cells are filled by this material, assemble the sum of
    2490             :   // the rho-V product and V for each of those cells. If _map_density_by_cell
    2491             :   // is true, then the numerator and denominator are populated from just a single
    2492             :   // value (no sum)
    2493             :   std::map<int32_t, Real> numerator;
    2494             :   std::map<int32_t, Real> denominator;
    2495        9582 :   for (const auto & c : _cell_to_elem)
    2496             :   {
    2497        9346 :     auto cell_info = c.first;
    2498             : 
    2499        9346 :     if (!hasDensityFeedback(cell_info))
    2500        7388 :       continue;
    2501             : 
    2502        1958 :     auto mat_idx = _cell_to_material.at(cell_info);
    2503             : 
    2504             :     if (numerator.count(mat_idx))
    2505             :     {
    2506          88 :       numerator[mat_idx] += cell_vol_density.at(cell_info);
    2507          88 :       denominator[mat_idx] += _cell_to_elem_volume.at(cell_info);
    2508             :     }
    2509             :     else
    2510             :     {
    2511        1870 :       numerator[mat_idx] = cell_vol_density.at(cell_info);
    2512        1870 :       denominator[mat_idx] = _cell_to_elem_volume.at(cell_info);
    2513             :     }
    2514             :   }
    2515             : 
    2516        9568 :   for (const auto & c : _cell_to_elem)
    2517             :   {
    2518        9336 :     auto cell_info = c.first;
    2519             : 
    2520        9336 :     if (!hasDensityFeedback(cell_info))
    2521        7388 :       continue;
    2522             : 
    2523        1948 :     auto mat_idx = _cell_to_material.at(cell_info);
    2524        1948 :     Real average_density = numerator[mat_idx] / denominator[mat_idx];
    2525             : 
    2526        1948 :     minimum = std::min(minimum, average_density);
    2527        1948 :     maximum = std::max(maximum, average_density);
    2528             : 
    2529        1948 :     if (_verbose)
    2530        2996 :       _console << "Setting cell " << printCell(cell_info) << " to density (kg/m3): " << std::setw(4)
    2531        1498 :                << average_density << std::endl;
    2532             : 
    2533        1948 :     setCellDensity(average_density, cell_info);
    2534             :   }
    2535             : 
    2536         232 :   if (!_verbose)
    2537          10 :     _console << " Sent cell-averaged min/max (kg/m3): " << minimum << ", " << maximum << std::endl;
    2538             : }
    2539             : 
    2540             : Real
    2541     1153114 : OpenMCCellAverageProblem::tallyMultiplier(unsigned int global_score) const
    2542             : {
    2543     1153114 :   if (!isHeatingScore(_all_tally_scores[global_score]))
    2544             :   {
    2545             :     // we need to get an effective source rate (particles / second) in order to
    2546             :     // normalize the tally
    2547      627644 :     Real source = _local_mean_tally[global_score];
    2548      627644 :     if (_run_mode == openmc::RunMode::EIGENVALUE)
    2549      618784 :       source *= *_power / EV_TO_JOULE / _local_mean_tally[_source_rate_index];
    2550             :     else
    2551        8860 :       source *= *_source_strength;
    2552             : 
    2553             :     // - Reaction rate scores & 'inverse-velocity' have units of reactions/src (OpenMC) or
    2554             :     //   reactions/s (Cardinal).
    2555             :     // - 'damage-energy' has units of eV/src (OpenMC) or eV/s (Cardinal). While the units of
    2556             :     //   damage-energy are the same as a heating tally, we don't normalize it like one as it's
    2557             :     //   used as an intermediate to compute DPA.
    2558      954440 :     if (isReactionRateScore(_all_tally_scores[global_score]) ||
    2559      953328 :         _all_tally_scores[global_score] == "inverse-velocity" ||
    2560             :         _all_tally_scores[global_score] == "damage-energy")
    2561      302272 :       return source;
    2562             : 
    2563      325372 :     if (_all_tally_scores[global_score] == "flux")
    2564      325372 :       return source / _scaling;
    2565             :     else
    2566           0 :       mooseError("Unhandled tally score enum!");
    2567             :   }
    2568             :   else
    2569             :   {
    2570             :     // Heating tallies have units of eV / source particle
    2571      525470 :     if (_run_mode == openmc::RunMode::EIGENVALUE)
    2572      525390 :       return *_power;
    2573             :     else
    2574          80 :       return *_source_strength * EV_TO_JOULE * _local_mean_tally[global_score];
    2575             :   }
    2576             : }
    2577             : 
    2578             : Real
    2579        6492 : OpenMCCellAverageProblem::tallyNormalization(unsigned int global_score) const
    2580             : {
    2581        6492 :   return _normalize_by_global ? _global_sum_tally[global_score] : _local_sum_tally[global_score];
    2582             : }
    2583             : 
    2584             : void
    2585        3550 : OpenMCCellAverageProblem::relaxAndNormalizeTally(unsigned int global_score,
    2586             :                                                  unsigned int local_score,
    2587             :                                                  std::shared_ptr<TallyBase> local_tally)
    2588             : {
    2589        3550 :   Real comparison = tallyNormalization(global_score);
    2590             : 
    2591             :   Real alpha;
    2592        3550 :   switch (_relaxation)
    2593             :   {
    2594        2780 :     case relaxation::none:
    2595             :     {
    2596        2780 :       alpha = 1.0;
    2597        2780 :       break;
    2598             :     }
    2599         684 :     case relaxation::constant:
    2600             :     {
    2601         684 :       alpha = _relaxation_factor;
    2602         684 :       break;
    2603             :     }
    2604          38 :     case relaxation::robbins_monro:
    2605             :     {
    2606          38 :       alpha = 1.0 / (_fixed_point_iteration + 1);
    2607          38 :       break;
    2608             :     }
    2609          48 :     case relaxation::dufek_gudowski:
    2610             :     {
    2611          48 :       alpha = float(nParticles()) / float(_total_n_particles);
    2612          48 :       break;
    2613             :     }
    2614           0 :     default:
    2615           0 :       mooseError("Unhandled RelaxationEnum in OpenMCCellAverageProblem!");
    2616             :   }
    2617             : 
    2618        3550 :   local_tally->relaxAndNormalizeTally(local_score, alpha, comparison);
    2619        3550 : }
    2620             : 
    2621             : void
    2622          32 : OpenMCCellAverageProblem::dufekGudowskiParticleUpdate()
    2623             : {
    2624          32 :   int64_t n = (_n_particles_1 + std::sqrt(_n_particles_1 * _n_particles_1 +
    2625          32 :                                           4.0 * _n_particles_1 * _total_n_particles)) /
    2626          32 :               2.0;
    2627          32 :   openmc::settings::n_particles = n;
    2628          32 : }
    2629             : 
    2630             : void
    2631        2942 : OpenMCCellAverageProblem::checkNormalization(const Real & sum, unsigned int global_score) const
    2632             : {
    2633        2942 :   if (tallyNormalization(global_score) > ZERO_TALLY_THRESHOLD)
    2634        2934 :     if (_check_tally_sum && std::abs(sum - 1.0) > 1e-6)
    2635           0 :       mooseError("Tally normalization process failed for " + _all_tally_scores[global_score] +
    2636           0 :                  " score! Total fraction of " + Moose::stringify(sum) + " does not match 1.0!");
    2637        2942 : }
    2638             : 
    2639             : void
    2640        4610 : OpenMCCellAverageProblem::syncSolutions(ExternalProblem::Direction direction)
    2641             : {
    2642        4610 :   OpenMCProblemBase::syncSolutions(direction);
    2643             : 
    2644             :   // We can skip syncronizing the solution when running with adaptivity
    2645             :   // and the mesh hasn't changed. This only applies to steady-state calculations
    2646             :   // as the mesh is adapted once per timestep in a transient calculation.
    2647        4610 :   if (_has_adaptivity && !_run_on_adaptivity_cycle)
    2648             :     return;
    2649             : 
    2650        4550 :   _aux->serializeSolution();
    2651             : 
    2652        4550 :   switch (direction)
    2653             :   {
    2654        2286 :     case ExternalProblem::Direction::TO_EXTERNAL_APP:
    2655             :     {
    2656             :       // update the [Mesh] internally, so that if we have the skinner we then propagate those
    2657             :       // changes to the OpenMC geometry
    2658        2286 :       if (_use_displaced)
    2659             :       {
    2660          30 :         _console << "Updating the displaced mesh..." << std::endl;
    2661          30 :         _displaced_problem->updateMesh();
    2662             :       }
    2663             : 
    2664             : #ifdef ENABLE_DAGMC
    2665        1177 :       if (_skinner)
    2666             :       {
    2667             :         // Update the OpenMC geometry to take into account skinning. This also calls
    2668             :         // _skinner->update().
    2669          26 :         updateOpenMCGeometry();
    2670             : 
    2671             :         // Update the OpenMC materials (creating new ones as-needed to support the density binning)
    2672          26 :         updateMaterials();
    2673             : 
    2674             :         // regenerate the DAGMC geometry
    2675          26 :         reloadDAGMC();
    2676             :       }
    2677             : #endif
    2678        2286 :       if (_need_to_reinit_coupling)
    2679             :       {
    2680         104 :         if (_volume_calc)
    2681           2 :           _volume_calc->resetVolumeCalculation();
    2682             : 
    2683         104 :         resetTallies();
    2684         104 :         setupProblem();
    2685             :       }
    2686             : 
    2687             :       // Change nuclide composition of material; we put this here so that we can still then change
    2688             :       // the _overall_ density (like due to thermal expansion, which does not change the relative
    2689             :       // amounts of the different nuclides)
    2690        2286 :       sendNuclideDensitiesToOpenMC();
    2691             : 
    2692        2282 :       if (_first_transfer && (_specified_temperature_feedback || _specified_density_feedback))
    2693             :       {
    2694             :         std::string incoming_transfer =
    2695        2201 :             _specified_density_feedback ? "temperature and density" : "temperature";
    2696             : 
    2697        1328 :         switch (_initial_condition)
    2698             :         {
    2699           2 :           case coupling::hdf5:
    2700             :           {
    2701             :             // if we're reading temperature and density from an existing HDF5 file,
    2702             :             // we don't need to send anything in to OpenMC, so we can leave.
    2703           2 :             importProperties();
    2704           0 :             _console << "Skipping " << incoming_transfer
    2705           0 :                      << " transfer into OpenMC because 'initial_properties = hdf5'" << std::endl;
    2706           0 :             return;
    2707             :           }
    2708             :           case coupling::moose:
    2709             :           {
    2710             :             // transfer will happen from MOOSE - proceed normally
    2711             :             break;
    2712             :           }
    2713         780 :           case coupling::xml:
    2714             :           {
    2715             :             // if we're just using whatever temperature and density are already in the XML
    2716             :             // files, we don't need to send anything in to OpenMC, so we can leave.
    2717         780 :             _console << "Skipping " << incoming_transfer
    2718         780 :                      << " transfer into OpenMC because 'initial_properties = xml'" << std::endl;
    2719         780 :             return;
    2720             :           }
    2721           0 :           default:
    2722           0 :             mooseError("Unhandled OpenMCInitialConditionEnum!");
    2723             :         }
    2724             :       }
    2725             : 
    2726             :       // Because we require at least one of fluid_blocks and solid_blocks, we are guaranteed
    2727             :       // to be setting the temperature of all of the cells in cell_to_elem - only for the density
    2728             :       // transfer do we need to filter for the fluid cells
    2729        1500 :       sendTemperatureToOpenMC();
    2730             : 
    2731        1494 :       sendDensityToOpenMC();
    2732             : 
    2733        1490 :       if (_export_properties)
    2734           0 :         openmc_properties_export("properties.h5");
    2735             : 
    2736             :       break;
    2737             :     }
    2738        2264 :     case ExternalProblem::Direction::FROM_EXTERNAL_APP:
    2739             :     {
    2740        2264 :       _console << "Extracting OpenMC tallies..." << std::endl;
    2741             : 
    2742        2264 :       if (_local_tallies.size() == 0 && _global_tallies.size() == 0)
    2743             :         break;
    2744             : 
    2745             :       // Get the total tallies for normalization
    2746        2012 :       if (_global_tallies.size() > 0)
    2747             :       {
    2748        3248 :         for (unsigned int global_score = 0; global_score < _all_tally_scores.size(); ++global_score)
    2749             :         {
    2750        4190 :           for (unsigned int i = 0; i < _global_tallies.size(); ++i)
    2751             :           {
    2752        2370 :             auto loc = std::find(_global_tally_scores[i].begin(),
    2753             :                                  _global_tally_scores[i].end(),
    2754             :                                  _all_tally_scores[global_score]);
    2755        2370 :             if (loc == _global_tally_scores[i].end())
    2756             :               continue;
    2757             : 
    2758             :             auto index = loc - _global_tally_scores[i].begin();
    2759        1820 :             _global_sum_tally[global_score] = tallySumAcrossBins({_global_tallies[i]}, index);
    2760             :           }
    2761             :         }
    2762             :       }
    2763             : 
    2764             :       // Loop over all of the tallies and calculate their sums and averages.
    2765        5194 :       for (auto & local_tally : _local_tallies)
    2766        3182 :         local_tally->computeSumAndMean();
    2767             : 
    2768             :       // Accumulate the sums and means for every score.
    2769        2012 :       _local_sum_tally.clear();
    2770        2012 :       _local_sum_tally.resize(_all_tally_scores.size(), 0.0);
    2771        2012 :       _local_mean_tally.clear();
    2772        2012 :       _local_mean_tally.resize(_all_tally_scores.size(), 0.0);
    2773        5194 :       for (unsigned int i = 0; i < _local_tallies.size(); ++i)
    2774             :       {
    2775        8968 :         for (unsigned int global_score = 0; global_score < _all_tally_scores.size(); ++global_score)
    2776             :         {
    2777             :           const auto & tally_name = _all_tally_scores[global_score];
    2778        2234 :           if (_local_tally_score_map[i].count(tally_name) ==
    2779             :               0) // If the local tally doesn't have this score, skip it.
    2780        2234 :             continue;
    2781             : 
    2782        3552 :           auto local_score = _local_tally_score_map[i].at(_all_tally_scores[global_score]);
    2783        3552 :           _local_sum_tally[global_score] += _local_tallies[i]->getSum(local_score);
    2784        3552 :           _local_mean_tally[global_score] += _local_tallies[i]->getMean(local_score);
    2785             :         }
    2786             :       }
    2787             : 
    2788        2012 :       if (_check_tally_sum)
    2789        2350 :         for (unsigned int global_score = 0; global_score < _all_tally_scores.size(); ++global_score)
    2790        1332 :           checkTallySum(global_score);
    2791             : 
    2792             :       // Loop over the tallies to relax and normalize their results score by score. Then, store the
    2793             :       // results.
    2794             :       std::vector<Real> sums;
    2795        2010 :       sums.resize(_all_tally_scores.size(), 0.0);
    2796        5190 :       for (unsigned int i = 0; i < _local_tallies.size(); ++i)
    2797             :       {
    2798        8964 :         for (unsigned int global_score = 0; global_score < _all_tally_scores.size(); ++global_score)
    2799             :         {
    2800             :           const auto & tally_name = _all_tally_scores[global_score];
    2801        2234 :           if (_local_tally_score_map[i].count(tally_name) ==
    2802             :               0) // If the local tally doesn't have this score, skip it.
    2803        2234 :             continue;
    2804             : 
    2805        3550 :           auto local_score = _local_tally_score_map[i].at(tally_name);
    2806        7100 :           relaxAndNormalizeTally(global_score, local_score, _local_tallies[i]);
    2807             : 
    2808             :           // Store the tally results.
    2809        3550 :           sums[global_score] += _local_tallies[i]->storeResults(
    2810             :               _tally_var_ids[i], local_score, global_score, "relaxed");
    2811             : 
    2812             :           // Store additional tally outputs.
    2813        3550 :           if (_local_tallies[i]->hasOutputs())
    2814             :           {
    2815             :             const auto & outs = _local_tallies[i]->getOutputs();
    2816         518 :             for (unsigned int j = 0; j < outs.size(); ++j)
    2817         272 :               _local_tallies[i]->storeResults(
    2818             :                   _tally_ext_var_ids[i][j], local_score, global_score, outs[j]);
    2819             :           }
    2820             :         }
    2821             :       }
    2822             : 
    2823             :       // Check the normalization.
    2824        4952 :       for (unsigned int global_score = 0; global_score < _all_tally_scores.size(); ++global_score)
    2825        2942 :         checkNormalization(sums[global_score], global_score);
    2826             : 
    2827             :       break;
    2828             :     }
    2829           0 :     default:
    2830           0 :       mooseError("Unhandled Direction enum in OpenMCCellAverageProblem!");
    2831             :   }
    2832             : 
    2833        3752 :   _first_transfer = false;
    2834        3752 :   _aux->solution().close();
    2835        3752 :   _aux->system().update();
    2836             : }
    2837             : 
    2838             : void
    2839        1332 : OpenMCCellAverageProblem::checkTallySum(const unsigned int & score) const
    2840             : {
    2841        1332 :   if (std::abs(_global_sum_tally[score] - _local_sum_tally[score]) / _global_sum_tally[score] > 1e-6)
    2842             :   {
    2843           2 :     std::stringstream msg;
    2844           2 :     msg << _all_tally_scores[score] << " tallies do not match the global "
    2845           2 :         << _all_tally_scores[score] << " tally:\n"
    2846           2 :         << " Global value: " << Moose::stringify(_global_sum_tally[score])
    2847           4 :         << "\n Tally sum:    " << Moose::stringify(_local_sum_tally[score])
    2848           8 :         << "\n Difference:   " << _global_sum_tally[score] - _local_sum_tally[score]
    2849             :         << "\n\nThis means that the tallies created by Cardinal are missing some hits over the "
    2850             :            "domain.\n"
    2851           2 :         << "You can turn off this check by setting 'check_tally_sum' to false.";
    2852             : 
    2853           2 :     mooseError(msg.str());
    2854           0 :   }
    2855        1330 : }
    2856             : 
    2857             : void
    2858        1815 : OpenMCCellAverageProblem::createQRules(QuadratureType type,
    2859             :                                        Order order,
    2860             :                                        Order volume_order,
    2861             :                                        Order face_order,
    2862             :                                        SubdomainID block,
    2863             :                                        const bool allow_negative_qweights)
    2864             : {
    2865             :   // start copy: Copied from base class's createQRules in order to retain the same default behavior
    2866        1815 :   if (order == INVALID_ORDER)
    2867             :   {
    2868        1815 :     order = getNonlinearSystemBase(0).getMinQuadratureOrder();
    2869        1815 :     if (order < getAuxiliarySystem().getMinQuadratureOrder())
    2870        1725 :       order = getAuxiliarySystem().getMinQuadratureOrder();
    2871             :   }
    2872             : 
    2873        1815 :   if (volume_order == INVALID_ORDER)
    2874        1815 :     volume_order = order;
    2875             : 
    2876        1815 :   if (face_order == INVALID_ORDER)
    2877             :     face_order = order;
    2878             :   // end copy
    2879             : 
    2880             :   // The approximations made in elem->volume() are only valid for Gauss and Monomial quadratures
    2881             :   // if they are second order or above
    2882        3630 :   if (type == Moose::stringToEnum<QuadratureType>("GAUSS"))
    2883        3630 :     setMinimumVolumeQRules(volume_order, "GAUSS");
    2884        3630 :   if (type == Moose::stringToEnum<QuadratureType>("MONOMIAL"))
    2885           0 :     setMinimumVolumeQRules(volume_order, "MONOMIAL");
    2886        3630 :   if (type == Moose::stringToEnum<QuadratureType>("GAUSS_LOBATTO"))
    2887           0 :     setMinimumVolumeQRules(volume_order, "GAUSS_LOBATTO");
    2888             : 
    2889             :   // Some quadrature rules don't ever seem to give a matching elem->volume() with the MOOSE
    2890             :   // volume integrations
    2891        5445 :   if (type == Moose::stringToEnum<QuadratureType>("GRID") ||
    2892        5445 :       type == Moose::stringToEnum<QuadratureType>("TRAP"))
    2893           0 :     mooseError(
    2894           0 :         "The ", std::to_string(type), " quadrature set will never match the '_current_elem_volume' used to compute\n"
    2895             :         "integrals in MOOSE. This means that the tally computed by OpenMC is normalized by\n"
    2896             :         "a different volume than used for MOOSE volume integrations, such that the specified "
    2897             :         "'power' or 'source_strength'\n"
    2898             :         "would not be respected. Please switch to a different quadrature set.");
    2899             : 
    2900        1815 :   FEProblemBase::createQRules(
    2901             :       type, order, volume_order, face_order, block, allow_negative_qweights);
    2902        1815 : }
    2903             : 
    2904             : void
    2905        1815 : OpenMCCellAverageProblem::setMinimumVolumeQRules(Order & volume_order, const std::string & /* type */)
    2906             : {
    2907        3630 :   if (volume_order < Moose::stringToEnum<Order>("SECOND"))
    2908        1813 :     volume_order = SECOND;
    2909        1815 : }
    2910             : 
    2911             : double
    2912      375837 : OpenMCCellAverageProblem::cellMappedVolume(const cellInfo & cell_info) const
    2913             : {
    2914      375837 :   return _cell_to_elem_volume.at(cell_info);
    2915             : }
    2916             : 
    2917             : double
    2918     8076288 : OpenMCCellAverageProblem::cellTemperature(const cellInfo & cell_info) const
    2919             : {
    2920     8076288 :   auto material_cell = firstContainedMaterialCell(cell_info);
    2921             : 
    2922             :   double T;
    2923     8076288 :   int err = openmc_cell_get_temperature(material_cell.first, &material_cell.second, &T);
    2924     8076288 :   catchOpenMCError(err, "get temperature of cell " + printCell(cell_info));
    2925     8076288 :   return T;
    2926             : }
    2927             : 
    2928             : void
    2929          26 : OpenMCCellAverageProblem::reloadDAGMC()
    2930             : {
    2931             : #ifdef ENABLE_DAGMC
    2932          52 :   _dagmc.reset(new moab::DagMC(_skinner->moabPtr(),
    2933             :                                0.0 /* overlap tolerance, default */,
    2934             :                                0.001 /* numerical precision, default */,
    2935          78 :                                0 /* verbosity */));
    2936             : 
    2937             :   // Set up geometry in DagMC from already-loaded mesh
    2938          26 :   _dagmc->load_existing_contents();
    2939             : 
    2940             :   // Initialize acceleration data structures
    2941          26 :   _dagmc->init_OBBTree();
    2942             : 
    2943             :   // Get an iterator to the DAGMC universe unique ptr
    2944             :   auto univ_it =
    2945          26 :       openmc::model::universes.begin() + openmc::model::universe_map.at(_dagmc_universe_id);
    2946             : 
    2947             :   // Remove the old universe
    2948             :   openmc::model::universes.erase(univ_it);
    2949             : 
    2950             :   // Create new DAGMC universe
    2951          26 :   openmc::model::universes.emplace_back(std::make_unique<openmc::DAGUniverse>(_dagmc, "", true));
    2952          26 :   _dagmc_universe_id = openmc::model::universes.back()->id_;
    2953             : 
    2954             :   openmc::model::universe_map.clear();
    2955          54 :   for (int32_t i = 0; i < openmc::model::universes.size(); ++i)
    2956          28 :     openmc::model::universe_map[openmc::model::universes[i]->id_] = i;
    2957             : 
    2958          26 :   if (!_dagmc_root_universe)
    2959           2 :     openmc::model::cells[openmc::model::cell_map.at(_cell_using_dagmc_universe_id)]->fill_ =
    2960           2 :         _dagmc_universe_id;
    2961             : 
    2962          26 :   _console << "Re-generating OpenMC model with " << openmc::model::cells.size() << " cells... "
    2963          26 :            << std::endl;
    2964             : 
    2965             :   // Add cells to universes
    2966          26 :   openmc::populate_universes();
    2967             : 
    2968             :   // Set the root universe
    2969          26 :   openmc::model::root_universe = openmc::find_root_universe();
    2970          26 :   openmc::check_dagmc_root_univ();
    2971             : 
    2972             :   // Final geometry setup
    2973          26 :   openmc::finalize_geometry();
    2974             : 
    2975             :   // Finalize cross sections; we manually change the verbosity here because if skinning is
    2976             :   // enabled, we don't want to overwhelm the user with excess console output showing info
    2977             :   // which ultimately is no different from that shown on initialization
    2978          26 :   auto initial_verbosity = openmc::settings::verbosity;
    2979          26 :   openmc::settings::verbosity = 1;
    2980          26 :   openmc::finalize_cross_sections();
    2981             : 
    2982             :   // Needed to obtain correct cell instances
    2983          26 :   openmc::prepare_distribcell();
    2984          26 :   openmc::settings::verbosity = initial_verbosity;
    2985             : #endif
    2986          26 : }
    2987             : 
    2988             : void
    2989         612 : OpenMCCellAverageProblem::addFilter(const std::string & type,
    2990             :                                     const std::string & name,
    2991             :                                     InputParameters & moose_object_pars)
    2992             : {
    2993        1194 :   auto filter = addObject<FilterBase>(type, name, moose_object_pars, false)[0];
    2994         582 :   _filters[name] = filter;
    2995         582 : }
    2996             : 
    2997             : void
    2998        2541 : OpenMCCellAverageProblem::addTally(const std::string & type,
    2999             :                                    const std::string & name,
    3000             :                                    InputParameters & moose_object_pars)
    3001             : {
    3002        5039 :   auto tally = addObject<TallyBase>(type, name, moose_object_pars, false)[0];
    3003        2498 :   _local_tallies.push_back(tally);
    3004        2498 :   _local_tally_score_map.emplace_back();
    3005             : 
    3006             :   const auto & tally_scores = tally->getScores();
    3007        5350 :   for (unsigned int i = 0; i < tally_scores.size(); ++i)
    3008             :   {
    3009        2852 :     _local_tally_score_map.back()[tally_scores[i]] = i;
    3010             : 
    3011             :     // Add the local tally's score to the list of scores if we don't have it yet.
    3012        2852 :     if (std::find(_all_tally_scores.begin(), _all_tally_scores.end(), tally_scores[i]) ==
    3013             :         _all_tally_scores.end())
    3014        2462 :       _all_tally_scores.push_back(tally_scores[i]);
    3015             :   }
    3016             : 
    3017             :   // Add the associated global tally if required.
    3018        2498 :   if (_needs_global_tally && tally->getAuxVarNames().size() > 0)
    3019             :   {
    3020        1344 :     _global_tally_scores.push_back(tally_scores);
    3021        1344 :     _global_tally_estimators.push_back(tally->getTallyEstimator());
    3022             :   }
    3023        2498 : }
    3024             : 
    3025             : void
    3026        1825 : OpenMCCellAverageProblem::validateLocalTallies()
    3027             : {
    3028             :   // We can skip this check if we don't have tallies.
    3029        1825 :   if (_local_tallies.size() == 0)
    3030         255 :     return;
    3031             : 
    3032             :   /**
    3033             :    * Check to make sure local tallies don't share scores (unless they're distributed mesh tallies).
    3034             :    * This prevents normalization issues as we sum the values of all of the scores over all of the
    3035             :    * tally bins.
    3036             :    * TODO: we might be able to loosen this restriction later if there's a good way to
    3037             :    * account for bin overlap.
    3038             :    */
    3039             :   std::vector<unsigned int> tallies_per_score;
    3040        1570 :   tallies_per_score.resize(_all_tally_scores.size(), 0);
    3041        4062 :   for (unsigned int i = 0; i < _local_tallies.size(); ++i)
    3042             :   {
    3043        7514 :     for (unsigned int global_score = 0; global_score < _all_tally_scores.size(); ++global_score)
    3044             :     {
    3045             :       bool has_score = _local_tally_score_map[i].count(_all_tally_scores[global_score]) == 1;
    3046             :       // The second check is required to avoid multi counting translated mesh tallies.
    3047        2846 :       if (has_score && _local_tallies[i]->getAuxVarNames().size() > 0)
    3048        2458 :         tallies_per_score[global_score]++;
    3049             :     }
    3050             :   }
    3051             : 
    3052        4022 :   for (unsigned int global_score = 0; global_score < _all_tally_scores.size(); ++global_score)
    3053             :   {
    3054        2454 :     if (tallies_per_score[global_score] > 1)
    3055             :     {
    3056           4 :       mooseError("You have added " + Moose::stringify(tallies_per_score[global_score]) +
    3057           2 :                  " tallies which score " + _all_tally_scores[global_score] +
    3058             :                  "!\nCardinal does not support multiple tallies with the same"
    3059             :                  " scores as these tallies may have overlapping bins, preventing normalization.");
    3060             :     }
    3061             :   }
    3062             : 
    3063             :   // need some special treatment for non-heating scores, in eigenvalue mode
    3064             :   bool has_non_heating_score = false;
    3065        4020 :   for (const auto & t : _all_tally_scores)
    3066        2452 :     if (!isHeatingScore(t))
    3067             :       has_non_heating_score = true;
    3068             : 
    3069        1568 :   if (has_non_heating_score && _run_mode == openmc::RunMode::EIGENVALUE)
    3070             :   {
    3071             :     std::string non_heating_scores;
    3072        1740 :     for (const auto & e : _all_tally_scores)
    3073             :     {
    3074        1256 :       if (!isHeatingScore(e))
    3075             :       {
    3076         790 :         std::string l = e;
    3077             :         std::replace(l.begin(), l.end(), '-', '_');
    3078        1580 :         non_heating_scores += "" + l + ", ";
    3079             :       }
    3080             :     }
    3081             : 
    3082         484 :     if (non_heating_scores.length() > 0)
    3083         484 :       non_heating_scores.erase(non_heating_scores.length() - 2);
    3084             : 
    3085         966 :     checkRequiredParam(_pars,
    3086             :                        "source_rate_normalization",
    3087         484 :                        "using a non-heating tally (" + non_heating_scores + ") in eigenvalue mode");
    3088         482 :     const auto & norm = getParam<MooseEnum>("source_rate_normalization");
    3089             : 
    3090             :     // If the score is already in tally_score, no need to do anything special.
    3091         964 :     std::string n = enumToTallyScore(norm);
    3092         482 :     auto it = std::find(_all_tally_scores.begin(), _all_tally_scores.end(), n);
    3093         482 :     if (it != _all_tally_scores.end())
    3094         462 :       _source_rate_index = it - _all_tally_scores.begin();
    3095          20 :     else if (it == _all_tally_scores.end() && _local_tallies.size() == 1)
    3096             :     {
    3097          18 :       if (_local_tallies[0]->renamesTallyVars())
    3098           2 :         mooseError("When specifying 'name', the score indicated in "
    3099             :                    "'source_rate_normalization' must be\n"
    3100             :                    "listed in 'score' so that we know what you want to name that score (",
    3101             :                    norm,
    3102             :                    ")");
    3103             : 
    3104             :       // We can add the requested normalization score if and only if a single tally was added by
    3105             :       // [Tallies].
    3106          16 :       _all_tally_scores.push_back(n);
    3107          16 :       _local_tallies[0]->addScore(n);
    3108          16 :       _local_tally_score_map[0][n] = _local_tallies[0]->getScores().size() - 1;
    3109          16 :       _global_tally_scores[0].push_back(n);
    3110          16 :       _source_rate_index = _all_tally_scores.size() - 1;
    3111             :     }
    3112             :     else
    3113             :     {
    3114             :       // Otherwise, we error and let the user know that they need to add the score.
    3115           2 :       mooseError("The local tallies added in the [Tallies] block do not contain the requested "
    3116           2 :                  "heating score " +
    3117           0 :                  n +
    3118             :                  ". You must either add this score in one of the tallies or choose a different "
    3119             :                  "heating score.");
    3120             :     }
    3121         478 :   }
    3122        2168 :   else if (isParamValid("source_rate_normalization"))
    3123          24 :     mooseWarning(
    3124             :         "When either running in fixed-source mode, or all tallies have units of eV/src, the "
    3125             :         "'source_rate_normalization' parameter is unused!");
    3126             : }
    3127             : 
    3128             : void
    3129          26 : OpenMCCellAverageProblem::updateOpenMCGeometry()
    3130             : {
    3131             : #ifdef ENABLE_DAGMC
    3132             :   // Need to swap array indices back to ids as OpenMC swapped these when preparing geometry.
    3133         161 :   for (const auto & cell : openmc::model::cells)
    3134             :   {
    3135         135 :     if (cell->type_ == openmc::Fill::MATERIAL)
    3136             :     {
    3137             :       std::vector<int32_t> mat_ids;
    3138         266 :       for (const auto & mat_index : cell->material_)
    3139         133 :         mat_ids.push_back(mat_index == openmc::MATERIAL_VOID
    3140             :                               ? openmc::MATERIAL_VOID
    3141          94 :                               : openmc::model::materials[mat_index]->id_);
    3142         133 :       cell->material_ = mat_ids;
    3143             :     }
    3144         135 :     if (cell->type_ == openmc::Fill::UNIVERSE && cell->fill_ != openmc::C_NONE)
    3145           2 :       cell->fill_ = openmc::model::universes[cell->fill_]->id_;
    3146         135 :     if (cell->type_ == openmc::Fill::LATTICE && cell->fill_ != openmc::C_NONE)
    3147           0 :       cell->fill_ = openmc::model::lattices[cell->fill_]->id_;
    3148             : 
    3149         135 :     cell->universe_ = openmc::model::universes[cell->universe_]->id_;
    3150             :   }
    3151             : 
    3152          26 :   for (const auto & lattice : openmc::model::lattices)
    3153             :   {
    3154           0 :     for (openmc::LatticeIter it = lattice->begin(); it != lattice->end(); ++it)
    3155             :     {
    3156           0 :       int u_index = *it;
    3157           0 :       *it = openmc::model::universes[u_index]->id_;
    3158             :     }
    3159             : 
    3160           0 :     if (lattice->outer_ != openmc::NO_OUTER_UNIVERSE)
    3161           0 :       lattice->outer_ = openmc::model::universes[lattice->outer_]->id_;
    3162             :   }
    3163             : 
    3164             :   // skin the mesh geometry according to contours in temperature, density, and subdomain
    3165          26 :   _skinner->update();
    3166             : 
    3167             :   openmc::model::universe_level_counts.clear();
    3168             : 
    3169             :   // Clear nuclides and elements, these will get reset in read_ce_cross_sections
    3170             :   // Horrible circular logic means that clearing nuclides clears nuclide_map, but
    3171             :   // which is needed before nuclides gets reset (similar for elements)
    3172             :   std::unordered_map<std::string, int> nuclide_map_copy = openmc::data::nuclide_map;
    3173             :   openmc::data::nuclides.clear();
    3174             :   openmc::data::nuclide_map = nuclide_map_copy;
    3175             : 
    3176             :   std::unordered_map<std::string, int> element_map_copy = openmc::data::element_map;
    3177             :   openmc::data::elements.clear();
    3178             :   openmc::data::element_map = element_map_copy;
    3179             : 
    3180             :   // Clear existing DAGMC cell data. Cells cannot be deleted in-place as that invalidates
    3181             :   // all pointers and iterators, so we loop over the cell map to store a list of DAGMC cells.
    3182             :   // Afterwards, the cells contained in the list can be deleted.
    3183             :   std::vector<int32_t> cells_to_delete;
    3184         161 :   for (auto [id, index] : openmc::model::cell_map)
    3185         135 :     if (openmc::model::cells[index]->geom_type() == openmc::GeometryType::DAG)
    3186         133 :       cells_to_delete.push_back(openmc::model::cells[index]->id_);
    3187             : 
    3188         159 :   for (auto cell : cells_to_delete)
    3189             :   {
    3190         410 :     for (int32_t i = 0; i < openmc::model::cells.size(); ++i)
    3191             :     {
    3192         410 :       if (openmc::model::cells[i]->id_ == cell)
    3193             :       {
    3194             :         openmc::model::cells.erase(openmc::model::cells.begin() + i);
    3195         133 :         break;
    3196             :       }
    3197             :     }
    3198             :   }
    3199             :   cells_to_delete.clear();
    3200             : 
    3201             :   // Clear existing surface data. Similar to cells, deletion of the DAGMC surfaces must be
    3202             :   // deferred.
    3203             :   std::vector<int> surfaces_to_delete;
    3204         379 :   for (auto [id, index] : openmc::model::surface_map)
    3205         353 :     if (openmc::model::surfaces[index]->geom_type() == openmc::GeometryType::DAG)
    3206         341 :       surfaces_to_delete.push_back(openmc::model::surfaces[index]->id_);
    3207             : 
    3208         367 :   for (auto surface : surfaces_to_delete)
    3209             :   {
    3210        1772 :     for (int i = 0; i < openmc::model::surfaces.size(); ++i)
    3211             :     {
    3212        1772 :       if (openmc::model::surfaces[i]->id_ == surface)
    3213             :       {
    3214             :         openmc::model::surface_map.erase(surface);
    3215             :         openmc::model::surfaces.erase(openmc::model::surfaces.begin() + i);
    3216         341 :         break;
    3217             :       }
    3218             :     }
    3219             :   }
    3220             :   surfaces_to_delete.clear();
    3221             : 
    3222             :   // Need to rebuild the cell_map and surface_map since the indices have changed.
    3223             :   openmc::model::cell_map.clear();
    3224          28 :   for (int32_t i = 0; i < openmc::model::cells.size(); ++i)
    3225           2 :     openmc::model::cell_map[openmc::model::cells[i]->id_] = i;
    3226             : 
    3227             :   // Horrible hack since we can't undo the surface id -> index swap that happens in
    3228             :   // CSGCell.region_.expression_, and so the 'surface_map' cannot be rebuilt. Intead, 'surfaces' is
    3229             :   // resized to the original length and the positions of each surface are shuffled such that they
    3230             :   // correspond to their indices in the original 'surface_map'. This results in the addition of N
    3231             :   // extra null 'DAGSurface' objects in 'surfaces', where N is the number of DAGMC surfaces in the
    3232             :   // geometry. These null surfaces aren't linked to a DAGMC universe and so they do not participate
    3233             :   // in particle transport, they just take up memory. CSGCell::region_ and Region::expression_ need
    3234             :   // to be made public in OpenMC to avoid this, or an appropriate series of C-API functions / member
    3235             :   // functions need to be added to OpenMC.
    3236          26 :   if (openmc::model::surfaces.size() > 0)
    3237             :   {
    3238          13 :     for (int i = openmc::model::surfaces.size(); i < _initial_num_openmc_surfaces; ++i)
    3239          11 :       openmc::model::surfaces.push_back(
    3240          22 :           std::move(std::make_unique<openmc::DAGSurface>(nullptr, 0)));
    3241          14 :     for (const auto & [id, index] : openmc::model::surface_map)
    3242             :     {
    3243             :       // If the surface at the index exists and the id is the same, do nothing.
    3244          12 :       if (openmc::model::surfaces[index]->id_ == id)
    3245          12 :         continue;
    3246             :       else
    3247             :       {
    3248             :         // Otherwise we need to find the filter and swap it with the filter at the current location.
    3249           0 :         for (int i = 0; i < openmc::model::surfaces.size(); ++i)
    3250             :         {
    3251           0 :           if (openmc::model::surfaces[i]->id_ == id)
    3252             :           {
    3253             :             auto temp = std::move(openmc::model::surfaces[index]);
    3254             :             openmc::model::surfaces[index] = std::move(openmc::model::surfaces[i]);
    3255             :             openmc::model::surfaces[i] = std::move(temp);
    3256             :             break;
    3257           0 :           }
    3258             :         }
    3259             :       }
    3260             :     }
    3261             : 
    3262             :     // Sanity check by looping over the surface_map to make sure the indices correspond to the
    3263             :     // surface ids.
    3264          14 :     for (const auto & [id, index] : openmc::model::surface_map)
    3265          12 :       if (openmc::model::surfaces[index]->id_ != id)
    3266           0 :         mooseError("Internal error: mismatch between surfaces[surface_map[id]]->id_ and id.");
    3267             :   }
    3268             : #endif
    3269          26 : }
    3270             : 
    3271             : void
    3272          26 : OpenMCCellAverageProblem::updateMaterials()
    3273             : {
    3274             : #ifdef ENABLE_DAGMC
    3275             :   // We currently only re-init the materials one time, because we create one new
    3276             :   // material for every density bin, even if that density bin doesn't actually
    3277             :   // appear in the problem. TODO: we could probably reduce memory usage
    3278             :   // if we only re-generated materials we strictly needed for the model.
    3279          26 :   if (!_first_transfer)
    3280          24 :     return;
    3281             : 
    3282             :   // only need to create new materials if we have density skinning
    3283          14 :   if (_skinner->nDensityBins() == 1)
    3284             :     return;
    3285             : 
    3286             :   // map from IDs to names (names used by the skinner, not necessarily any internal
    3287             :   // name in OpenMC, because you're not strictly required to add names for materials
    3288             :   // with the OpenMC input files)
    3289             :   std::map<int32_t, std::string> ids_to_names;
    3290           6 :   for (const auto & m : openmc::model::material_map)
    3291             :   {
    3292           4 :     auto id = m.first;
    3293           4 :     auto idx = m.second;
    3294             :     if (ids_to_names.count(id))
    3295           0 :       mooseError("Internal error: material_map has more than one material with the same ID");
    3296             : 
    3297           8 :     ids_to_names[id] = materialName(idx);
    3298             :   }
    3299             : 
    3300             :   // append _0 to all existing material names
    3301           6 :   for (const auto & mat : openmc::model::materials)
    3302           8 :     mat->set_name(ids_to_names[mat->id()] + "_0");
    3303             : 
    3304             :   // Then, create the copies of each material
    3305             :   int n_mats = openmc::model::materials.size();
    3306           6 :   for (unsigned int n = 0; n < n_mats; ++n)
    3307             :   {
    3308           4 :     auto name = ids_to_names[openmc::model::materials[n]->id()];
    3309          16 :     for (unsigned int j = 1; j < _skinner->nDensityBins(); ++j)
    3310             :     {
    3311          12 :       openmc::Material & new_mat = openmc::model::materials[n]->clone();
    3312          24 :       new_mat.set_name(name + "_" + std::to_string(j));
    3313             :     }
    3314             :   }
    3315             : #endif
    3316           0 : }
    3317             : 
    3318             : bool
    3319      699000 : OpenMCCellAverageProblem::cellMapsToSubdomain(const cellInfo & cell_info,
    3320             :                                               const std::unordered_set<SubdomainID> & id) const
    3321             : {
    3322      699000 :   auto s = _cell_to_elem_subdomain.at(cell_info);
    3323      699024 :   for (const auto & i : id)
    3324      699000 :     if (s.find(i) != s.end())
    3325             :       return true;
    3326             : 
    3327             :   return false;
    3328             : }
    3329             : 
    3330             : bool
    3331     8258410 : OpenMCCellAverageProblem::cellHasIdenticalFill(const cellInfo & cell_info) const
    3332             : {
    3333             :   // material cells are discounted as identical fill
    3334     8258410 :   const auto & cell = openmc::model::cells[cell_info.first];
    3335     8258410 :   if (!_has_identical_cell_fills || cell->type_ == openmc::Fill::MATERIAL)
    3336             :     return false;
    3337             : 
    3338      699000 :   return cellMapsToSubdomain(cell_info, _identical_cell_fill_blocks);
    3339             : }
    3340             : 
    3341             : OpenMCCellAverageProblem::containedCells
    3342      690048 : OpenMCCellAverageProblem::shiftCellInstances(const cellInfo & cell_info) const
    3343             : {
    3344      690048 :   if (!_has_identical_cell_fills)
    3345           0 :     mooseError("Internal error: should not call shiftCellInstances!");
    3346             : 
    3347      690048 :   auto offset = _n_offset.at(cell_info);
    3348             : 
    3349             :   containedCells contained_cells;
    3350      690048 :   const auto & first_cell_cc = _cell_to_contained_material_cells.at(_first_identical_cell);
    3351   244919196 :   for (const auto & cc : first_cell_cc)
    3352             :   {
    3353   244229148 :     const auto & index = cc.first;
    3354             :     const auto & instances = cc.second;
    3355             :     auto n_instances = instances.size();
    3356             :     const auto & shifts = _instance_offsets.at(index);
    3357             : 
    3358             :     std::vector<int32_t> shifted_instances;
    3359 22380277176 :     for (unsigned int inst = 0; inst < n_instances; ++inst)
    3360 22136048028 :       shifted_instances.push_back(instances[inst] + offset * shifts[inst]);
    3361             : 
    3362   244229148 :     contained_cells[index] = shifted_instances;
    3363             :   }
    3364             : 
    3365      690048 :   return contained_cells;
    3366             : }
    3367             : 
    3368             : OpenMCCellAverageProblem::containedCells
    3369     8180848 : OpenMCCellAverageProblem::containedMaterialCells(const cellInfo & cell_info) const
    3370             : {
    3371     8180848 :   if (!cellHasIdenticalFill(cell_info))
    3372     7490800 :     return _cell_to_contained_material_cells.at(cell_info);
    3373             :   else
    3374      690048 :     return shiftCellInstances(cell_info);
    3375             : }
    3376             : 
    3377             : std::vector<int32_t>
    3378       34293 : OpenMCCellAverageProblem::materialsInCells(const containedCells & contained_cells) const
    3379             : {
    3380             :   std::vector<int32_t> mats;
    3381     1301274 :   for (const auto & contained : contained_cells)
    3382             :   {
    3383    87944202 :     for (const auto & instance : contained.second)
    3384             :     {
    3385             :       // we know this is a material cell, so we don't need to check that the fill is material
    3386             :       int32_t material_index;
    3387             :       cellInfo cell_info = {contained.first, instance};
    3388    86677221 :       materialFill(cell_info, material_index);
    3389    86677221 :       mats.push_back(material_index);
    3390             :     }
    3391             :   }
    3392             : 
    3393       34293 :   return mats;
    3394             : }
    3395             : 
    3396             : Point
    3397     8631008 : OpenMCCellAverageProblem::transformPointToOpenMC(const Point & pt) const
    3398             : {
    3399     8631008 :   Point pnt_out = transformPoint(pt);
    3400             : 
    3401             :   // scale point to OpenMC domain
    3402     8631008 :   pnt_out *= _scaling;
    3403             : 
    3404     8631008 :   return pnt_out;
    3405             : }
    3406             : #endif

Generated by: LCOV version 1.14