LCOV - code coverage report
Current view: top level - src/userobjects - MoabSkinner.C (source / functions) Hit Total Coverage
Test: neams-th-coe/cardinal: faa471 Lines: 671 689 97.4 %
Date: 2026-08-05 13:00:46 Functions: 52 52 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : #ifdef ENABLE_DAGMC
       2             : 
       3             : #include "MoabSkinner.h"
       4             : #include "VariadicTable.h"
       5             : #include "AuxiliarySystem.h"
       6             : #include "BinUtility.h"
       7             : #include "GeometryUtils.h"
       8             : #include "UserErrorChecking.h"
       9             : #include "DisplacedProblem.h"
      10             : #include "MooseMeshElementConversionUtils.h"
      11             : 
      12             : #include "libmesh/elem.h"
      13             : #include "libmesh/enum_io_package.h"
      14             : #include "libmesh/enum_order.h"
      15             : #include "libmesh/enum_fe_family.h"
      16             : #include "libmesh/equation_systems.h"
      17             : #include "libmesh/system.h"
      18             : #include "libmesh/mesh_tools.h"
      19             : 
      20             : registerMooseObject("CardinalApp", MoabSkinner);
      21             : 
      22             : InputParameters
      23         148 : MoabSkinner::validParams()
      24             : {
      25         148 :   InputParameters params = GeneralUserObject::validParams();
      26         296 :   params.addParam<bool>("verbose", false, "Whether to print diagnostic information");
      27             : 
      28             :   // temperature binning
      29         296 :   params.addRequiredParam<std::string>("temperature",
      30             :                                        "Temperature variable by which to bin elements");
      31         444 :   params.addRangeCheckedParam<Real>(
      32         296 :       "temperature_min", 0.0, "temperature_min >= 0.0", "Lower bound of temperature bins");
      33         296 :   params.addRequiredParam<Real>("temperature_max", "Upper bound of temperature bins");
      34         296 :   params.addRequiredRangeCheckedParam<unsigned int>(
      35             :       "n_temperature_bins", "n_temperature_bins > 0", "Number of temperature bins");
      36             : 
      37             :   // density binning
      38         296 :   params.addParam<std::string>("density", "Density variable by which to bin elements");
      39         444 :   params.addRangeCheckedParam<Real>(
      40         296 :       "density_min", 0.0, "density_min >= 0.0", "Lower bound of density bins");
      41         296 :   params.addParam<Real>("density_max", "Upper bound of density bins");
      42         296 :   params.addRangeCheckedParam<unsigned int>(
      43             :       "n_density_bins", "n_density_bins > 0", "Number of density bins");
      44         296 :   params.addParam<std::vector<SubdomainName>>(
      45             :       "material_blocks",
      46             :       "List of mesh subdomain names (or IDs) for which to assign material names in the generated "
      47             :       "DAGMC geometry. Must be provided together with 'material_names' where both have the same "
      48             :       "length and are listed in the same order."
      49             :       "Any subdomain not listed in 'material_blocks' will have no material assignment and OpenMC "
      50             :       "will"
      51             :       "default to void for that region. This parameter is optional when used in combination with "
      52             :       "OpenMCCellAverageProblem"
      53             :       "because in this case, the default is to auto-detect the materials in the original h5m file "
      54             :       "and re-apply them during skinning.");
      55         296 :   params.addParam<std::vector<std::string>>(
      56             :       "material_names",
      57             :       "Material names (or IDs) to assign to subdomains in the generated DAGMC geometry. "
      58             :       "Must be provided together with 'material_blocks' where both have the same length"
      59             :       "and listed in the same order. This parameter is optional when used in combination with "
      60             :       "OpenMCCellAverageProblem because in this case, the default is to auto-detect the materials "
      61             :       "in the original h5m file and re-apply them during skinning.");
      62         444 :   params.addRangeCheckedParam<Real>(
      63         296 :       "faceting_tol", 1e-4, "faceting_tol > 0", "Faceting tolerance for DagMC");
      64         444 :   params.addRangeCheckedParam<Real>(
      65         296 :       "geom_tol", 1e-6, "geom_tol > 0", "Geometry tolerance for DagMC");
      66             : 
      67         296 :   params.addParam<bool>(
      68         296 :       "build_graveyard", false, "Whether to build a graveyard around the geometry");
      69         444 :   params.addRangeCheckedParam<Real>(
      70             :       "graveyard_scale_inner",
      71         296 :       1.01,
      72             :       "graveyard_scale_inner > 1",
      73             :       "Multiplier on mesh bounding box to form inner graveyard surface");
      74         296 :   params.addParam<Real>("graveyard_scale_outer",
      75         296 :                         1.10,
      76             :                         "Multiplier on mesh bounding box to form outer graveyard surface");
      77         296 :   params.addParam<std::string>("implicit_complement_material",
      78             :                                "Assigns OpenMC material name or ID to the implicit complement "
      79             :                                "region. If not provided, void material is assigned by default.");
      80             :   // TODO: would be nice to support other file formats as well, like exodus
      81         296 :   params.addParam<bool>(
      82             :       "output_skins",
      83         296 :       false,
      84             :       "Whether the skinned MOAB mesh (skins generated from the "
      85             :       "libMesh [Mesh]) should be written to a file. The files will be named moab_skins_<n>.h5m, "
      86             :       "where <n> is the time step index. You can then visualize these files by running "
      87             :       "'mbconvert'.");
      88         296 :   params.addParam<bool>("output_full",
      89         296 :                         false,
      90             :                         "Whether the MOAB mesh (copied from the libMesh [Mesh]) should "
      91             :                         "be written to a file. The files will be named moab_full_<n>.h5m, where "
      92             :                         "<n> is the time step index. "
      93             :                         "You can then visualize these files by running 'mbconvert'.");
      94         296 :   params.addParam<bool>("use_displaced_mesh",
      95         296 :                         false,
      96             :                         "Whether the skinned mesh should be generated from a displaced mesh ");
      97         296 :   params.addParam<std::vector<BoundaryName>>(
      98             :       "vacuum_bcs_surfaces",
      99             :       "Mesh sideset names or numeric sideset IDs to assign DAGMC vacuum boundary conditions to. "
     100             :       "Both string names and integer IDs are accepted. "
     101             :       "If not specified, the surface defaults to transmission.");
     102         296 :   params.addParam<std::vector<BoundaryName>>(
     103             :       "reflective_bcs_surfaces",
     104             :       "Mesh sideset names or numeric sideset IDs to assign DAGMC reflective boundary conditions "
     105             :       "to. Both string names and integer IDs are accepted. "
     106             :       "If not specified, the surface defaults to transmission.");
     107         148 :   params.addClassDescription("Re-generate the OpenMC geometry on-the-fly according to changes in "
     108             :                              "the mesh geometry and/or contours in temperature and density");
     109         148 :   return params;
     110           0 : }
     111             : 
     112          74 : MoabSkinner::MoabSkinner(const InputParameters & parameters)
     113             :   : GeneralUserObject(parameters),
     114          74 :     _serialized_solution(NumericVector<Number>::build(_communicator).release()),
     115         148 :     _verbose(getParam<bool>("verbose")),
     116         148 :     _temperature_name(getParam<std::string>("temperature")),
     117         148 :     _temperature_min(getParam<Real>("temperature_min")),
     118         148 :     _temperature_max(getParam<Real>("temperature_max")),
     119         148 :     _n_temperature_bins(getParam<unsigned int>("n_temperature_bins")),
     120          74 :     _temperature_bin_width((_temperature_max - _temperature_min) / _n_temperature_bins),
     121         148 :     _bin_by_density(isParamValid("density")),
     122         148 :     _faceting_tol(getParam<Real>("faceting_tol")),
     123         148 :     _geom_tol(getParam<Real>("geom_tol")),
     124         148 :     _graveyard_scale_inner(getParam<double>("graveyard_scale_inner")),
     125         148 :     _graveyard_scale_outer(getParam<double>("graveyard_scale_outer")),
     126         148 :     _output_skins(getParam<bool>("output_skins")),
     127         148 :     _output_full(getParam<bool>("output_full")),
     128          74 :     _scaling(1.0),
     129          74 :     _n_write(0),
     130          74 :     _standalone(true),
     131          74 :     _tet_mesh_built(false),
     132         342 :     _set_bcs(isParamSetByUser("vacuum_bcs_surfaces") || isParamSetByUser("reflective_bcs_surfaces"))
     133             : {
     134         148 :   _build_graveyard = getParam<bool>("build_graveyard");
     135         148 :   _use_displaced = getParam<bool>("use_displaced_mesh");
     136             : 
     137         148 :   if (isParamSetByUser("implicit_complement_material"))
     138             :   {
     139             :     // If the user specify a material that doesn't exist in materials.xml file, OpenMC
     140             :     // will catch the mistake.
     141           2 :     _set_implicit_complement_material = true;
     142             :     _implicit_complement_group_name =
     143           8 :         "mat:" + getParam<std::string>("implicit_complement_material") + "_comp";
     144             :   }
     145             : 
     146             :   // we can probably support this in the future, it's just not implemented yet
     147          74 :   if (!getMooseMesh().getMesh().is_serial())
     148           0 :     mooseError("MoabSkinner does not yet support distributed meshes!");
     149             : 
     150             :   // Create MOAB interface
     151          74 :   _moab = std::make_shared<moab::Core>();
     152             : 
     153             :   // Create a skinner
     154         148 :   skinner = std::make_unique<moab::Skinner>(_moab.get());
     155             : 
     156             :   // Create a geom topo tool
     157         148 :   gtt = std::make_unique<moab::GeomTopoTool>(_moab.get());
     158             : 
     159          74 :   if (_bin_by_density)
     160             :   {
     161          48 :     checkRequiredParam(parameters, "density_min", "binning by density");
     162          48 :     checkRequiredParam(parameters, "density_max", "binning by density");
     163          48 :     checkRequiredParam(parameters, "n_density_bins", "binning by density");
     164             : 
     165          48 :     _density_min = getParam<Real>("density_min");
     166          48 :     _density_max = getParam<Real>("density_max");
     167          48 :     _n_density_bins = getParam<unsigned int>("n_density_bins");
     168          48 :     _density_name = getParam<std::string>("density");
     169          24 :     _density_bin_width = (_density_max - _density_min) / _n_density_bins;
     170             : 
     171          24 :     if (_density_max < _density_min)
     172           1 :       paramError("density_max", "'density_max' must be greater than 'density_min'");
     173             :   }
     174             :   else
     175             :   {
     176         100 :     checkUnusedParam(parameters, "density_min", "not binning by density");
     177         100 :     checkUnusedParam(parameters, "density_max", "not binning by density");
     178         100 :     checkUnusedParam(parameters, "n_density_bins", "not binning by density");
     179             : 
     180          50 :     _n_density_bins = 1;
     181             :   }
     182             : 
     183          73 :   if (_build_graveyard)
     184             :   {
     185          39 :     if (_graveyard_scale_outer < _graveyard_scale_inner)
     186           1 :       paramError("graveyard_scale_outer",
     187             :                  "'graveyard_scale_outer' must be greater than 'graveyard_scale_inner'!");
     188             :   }
     189             :   else
     190             :   {
     191          68 :     checkUnusedParam(parameters, "graveyard_scale_inner", "'build_graveyard' is false");
     192          68 :     checkUnusedParam(parameters, "graveyard_scale_outer", "'build_graveyard' is false");
     193             :   }
     194             : 
     195             :   // get variable numbers
     196          72 :   _temperature_var_num = getAuxiliaryVariableNumber(_temperature_name, "temperature");
     197          70 :   if (_bin_by_density)
     198             :   {
     199          21 :     if (_temperature_name == _density_name)
     200           1 :       mooseError("The 'temperature' and 'density' variables cannot be the same!");
     201             : 
     202          20 :     _density_var_num = getAuxiliaryVariableNumber(_density_name, "density");
     203             :   }
     204             : 
     205          68 :   if (_temperature_max <= _temperature_min)
     206           1 :     paramError("temperature_max", "'temperature_max' must be greater than 'temperature_min'");
     207             : 
     208         305 :   for (unsigned int i = 0; i < _n_temperature_bins + 1; ++i)
     209         238 :     _temperature_bin_bounds.push_back(_temperature_min + i * _temperature_bin_width);
     210             : 
     211         263 :   for (unsigned int i = 0; i < _n_density_bins + 1; ++i)
     212         196 :     _density_bin_bounds.push_back(_density_min + i * _density_bin_width);
     213             : 
     214             :   // node numberings for first-order tets
     215         134 :   _tet4_nodes.push_back({0, 1, 2, 3});
     216             : 
     217             :   // node numbers for second-order tets
     218         134 :   _tet10_nodes.push_back({0, 4, 6, 7});
     219         134 :   _tet10_nodes.push_back({1, 5, 4, 8});
     220         134 :   _tet10_nodes.push_back({2, 6, 5, 9});
     221         134 :   _tet10_nodes.push_back({7, 8, 9, 3});
     222         134 :   _tet10_nodes.push_back({4, 9, 7, 8});
     223         134 :   _tet10_nodes.push_back({4, 5, 9, 8});
     224         134 :   _tet10_nodes.push_back({4, 7, 9, 6});
     225         134 :   _tet10_nodes.push_back({4, 9, 5, 6});
     226             : 
     227          67 :   moab::MBErrorHandler_Init();
     228          67 : }
     229             : 
     230             : void
     231          68 : MoabSkinner::finalize()
     232             : {
     233          68 :   moab::MBErrorHandler_Finalize();
     234          68 : }
     235             : 
     236             : moab::ErrorCode
     237     2123428 : MoabSkinner::check(const moab::ErrorCode input) const
     238             : {
     239             : #ifdef DEBUG
     240             :   MB_CHK_ERR(input);
     241             : #endif
     242     2123428 :   return moab::MB_SUCCESS;
     243             : }
     244             : 
     245             : unsigned int
     246          92 : MoabSkinner::getAuxiliaryVariableNumber(const std::string & name,
     247             :                                         const std::string & param_name) const
     248             : {
     249          92 :   if (!_fe_problem.getAuxiliarySystem().hasVariable(name))
     250           2 :     paramError(param_name, "Cannot find auxiliary variable '", name, "'!");
     251             : 
     252             :   // we require these variables to be constant monomial
     253          90 :   auto type = _fe_problem.getAuxiliarySystem().getFieldVariable<Real>(0, name).feType();
     254          90 :   if (type.family != MONOMIAL || type.order != 0)
     255           1 :     paramError(param_name, "Auxiliary variable '", name, "' must be a CONSTANT MONOMIAL type!");
     256             : 
     257          89 :   return _fe_problem.getAuxiliarySystem().getFieldVariable<Real>(0, name).number();
     258             : }
     259             : 
     260             : MooseMesh &
     261      121934 : MoabSkinner::getMooseMesh()
     262             : {
     263      156510 :   if (_use_displaced && _fe_problem.getDisplacedProblem() == nullptr)
     264           0 :     mooseError("Displaced mesh was requested but the displaced problem does not exist. "
     265             :                "set use_displaced_mesh = False");
     266       34576 :   return ((_use_displaced && _fe_problem.getDisplacedProblem())
     267      278444 :               ? _fe_problem.getDisplacedProblem()->mesh()
     268      209292 :               : _fe_problem.mesh());
     269             : }
     270             : 
     271             : MeshBase &
     272       83925 : MoabSkinner::getDAGMCGeometryMesh()
     273             : {
     274       83925 :   if (_tet_mesh)
     275             :     return *_tet_mesh;
     276       34519 :   return getMooseMesh().getMesh();
     277             : }
     278             : 
     279             : void
     280         136 : MoabSkinner::buildTetMesh()
     281             : {
     282             :   _tet_mesh.reset();
     283         136 :   _tet_mesh_built = true;
     284             : 
     285         136 :   MeshBase & source = getMooseMesh().getMesh();
     286             : 
     287             :   // Decide whether conversion is needed and validate element types
     288             :   bool needs_conversion = false;
     289      132268 :   for (const auto * elem : source.active_element_ptr_range())
     290             :   {
     291       66014 :     if (elem->type() != TET4 && elem->type() != TET10)
     292             :     {
     293             :       needs_conversion = true;
     294             :       break;
     295             :     }
     296         136 :   }
     297             : 
     298         136 :   if (!needs_conversion)
     299         120 :     return;
     300             : 
     301          16 :   if (_verbose)
     302          16 :     _console << "MoabSkinner: non-tetrahedral elements detected. "
     303          16 :              << "Building internal TET4 copy of the mesh..." << std::endl;
     304             : 
     305          32 :   _tet_mesh = source.clone();
     306          16 :   _tet_mesh->all_first_order();
     307          16 :   _tet_mesh->prepare_for_use();
     308             : 
     309             :   // Stamp each element with its original ID; convert3DMeshToAllTet4 copies element extra
     310             :   // integers onto every child tet, so each tet can report the element it came from.
     311          32 :   const unsigned int orig_id_tag = _tet_mesh->add_elem_integer("orig_id");
     312             :   std::vector<std::pair<dof_id_type, bool>> elems_to_process;
     313       13038 :   for (auto * elem : _tet_mesh->active_element_ptr_range())
     314             :   {
     315       13006 :     elem->set_extra_integer(orig_id_tag, elem->id());
     316       13006 :     elems_to_process.emplace_back(elem->id(), true);
     317          16 :   }
     318             : 
     319             :   std::set<subdomain_id_type> sids;
     320          16 :   _tet_mesh->subdomain_ids(sids);
     321          16 :   const subdomain_id_type tmp_remove_sid = *sids.rbegin() + 1;
     322             : 
     323             :   // convert3DMeshToAllTet4() contracts and re-prepares the mesh internally.
     324             :   std::vector<dof_id_type> converted_ids;
     325          16 :   MooseMeshElementConversionUtils::convert3DMeshToAllTet4(
     326             :       *_tet_mesh, elems_to_process, converted_ids, tmp_remove_sid, true);
     327             : 
     328          16 :   if (_verbose)
     329          16 :     _console << "MoabSkinner: internal TET4 mesh has " << _tet_mesh->n_active_elem()
     330          16 :              << " elements (from " << source.n_active_elem() << " original elements)." << std::endl;
     331          32 : }
     332             : 
     333             : void
     334         193 : MoabSkinner::initialize()
     335             : {
     336         193 :   if (!_tet_mesh_built)
     337         136 :     buildTetMesh();
     338             : 
     339         193 :   findBlocks();
     340             :   _block_id_to_material_name.clear();
     341             : 
     342         193 :   const bool has_material_blocks = isParamValid("material_blocks");
     343         193 :   const bool has_material_names = isParamValid("material_names");
     344         193 :   if (has_material_blocks != has_material_names)
     345           1 :     mooseError("'material_blocks' and 'material_names' must both be provided if specifying "
     346             :                "materials for the skinner; you have provided only one of these parameters");
     347             : 
     348         192 :   if (has_material_blocks)
     349             :   {
     350          57 :     const auto & block_names = getParam<std::vector<SubdomainName>>("material_blocks");
     351         114 :     const auto & mat_names = getParam<std::vector<std::string>>("material_names");
     352          57 :     if (block_names.size() != mat_names.size())
     353           4 :       paramError("material_names",
     354           2 :                  "'material_names' (length " + Moose::stringify(mat_names.size()) +
     355           2 :                      ") must be the same length as 'material_blocks' (length " +
     356             :                      Moose::stringify(block_names.size()) + ")");
     357             : 
     358             :     std::set<SubdomainID> seen_ids;
     359         147 :     for (const auto i : index_range(block_names))
     360             :     {
     361          94 :       if (!MooseMeshUtils::hasSubdomainName(getMooseMesh().getMesh(), block_names[i]))
     362           2 :         paramError("material_blocks",
     363           1 :                    "Subdomain '" + std::string(block_names[i]) + "' not found in the mesh");
     364          93 :       auto id = getMooseMesh().getSubdomainID(block_names[i]);
     365             :       if (seen_ids.count(id))
     366             :         // A repeated entry for blocks (including if the same block is passed by its name then later
     367             :         // by its ID) should error here
     368           2 :         paramError("material_blocks",
     369           1 :                    "Subdomain '" + std::string(block_names[i]) +
     370             :                        "' was already listed in 'material_blocks' (either by name or ID). Each "
     371             :                        "block can only be listed one time.");
     372          92 :       seen_ids.insert(id);
     373             :       auto it = _blocks.find(id);
     374          92 :       _block_id_to_material_name[it->first] = mat_names[i];
     375             :     }
     376             :   }
     377             :   else
     378             :   {
     379             :     // Neither 'material_blocks' nor 'material_names' was supplied in the input.
     380             :     // This is only valid when the skinner is controlled externally, e.g. an .h5m was
     381             :     // prepared outside of Cardinal and material names is set in setMaterialNames().
     382         135 :     if (_standalone)
     383           1 :       mooseError("When using the MoabSkinner independent of an OpenMCCellAverageProblem "
     384             :                  "it requires both 'material_blocks' and 'material_names' parameters.");
     385         600 :     for (const auto & [subdomain_id, block_index] : _blocks)
     386         466 :       _block_id_to_material_name[subdomain_id] = _material_names[block_index];
     387             :   }
     388             : 
     389             :   std::vector<std::string> unassigned_blocks;
     390         747 :   for (const auto & [subdomain_id, block_index] : _blocks)
     391         560 :     if (_block_id_to_material_name.find(subdomain_id) == _block_id_to_material_name.end())
     392           4 :       unassigned_blocks.push_back(getMooseMesh().getSubdomainName(subdomain_id));
     393             : 
     394         187 :   if (!unassigned_blocks.empty())
     395           4 :     mooseWarning("The following mesh subdomains have no material assignment in MoabSkinner "
     396             :                  "and will default to void in OpenMC:\n  ",
     397           7 :                  Moose::stringify(unassigned_blocks, "\n  "));
     398             : 
     399             :   // Set spatial dimension in MOAB
     400         186 :   check(_moab->set_dimension(getMooseMesh().getMesh().spatial_dimension()));
     401             : 
     402             :   // Create a meshset representing all of the MOAB tets
     403         186 :   check(_moab->create_meshset(moab::MESHSET_SET, _all_tets));
     404             : 
     405         186 :   createTags();
     406             : 
     407         186 :   createMOABElems();
     408             : 
     409             :   // Resolve sideset names/IDs to boundary IDs and check that no boundary appears in both
     410             :   // 'vacuum_bcs_surfaces' and 'reflective_bcs_surfaces'
     411         186 :   if (_set_bcs)
     412             :   {
     413          72 :     if (isParamSetByUser("vacuum_bcs_surfaces"))
     414          90 :       _vacuum_bcs_surface_ids = boundaryNamesToIDs(
     415             :           getParam<std::vector<BoundaryName>>("vacuum_bcs_surfaces"), "vacuum_bcs_surfaces");
     416          66 :     if (isParamSetByUser("reflective_bcs_surfaces"))
     417             :       _reflective_bcs_surface_ids =
     418          84 :           boundaryNamesToIDs(getParam<std::vector<BoundaryName>>("reflective_bcs_surfaces"),
     419             :                              "reflective_bcs_surfaces");
     420          33 :     checkBoundaryConditionOverlap();
     421             :   }
     422         182 : }
     423             : 
     424             : void
     425          72 : MoabSkinner::execute()
     426             : {
     427          72 :   if (_standalone)
     428          23 :     update();
     429          68 : }
     430             : 
     431             : void
     432          39 : MoabSkinner::setUseDisplacedMesh(const bool & use)
     433             : {
     434          39 :   if ((use != _use_displaced) && isParamSetByUser("use_displaced_mesh"))
     435           0 :     mooseWarning("Overriding 'use_displaced_mesh' to " + std::to_string(use) +
     436             :                  " to match the displaced problem action.");
     437          39 :   _use_displaced = use;
     438          39 : }
     439             : 
     440             : void
     441          76 : MoabSkinner::update()
     442             : {
     443          76 :   _console << "Skinning geometry into " << _n_temperature_bins << " temperature bins, "
     444          76 :            << _n_density_bins << " density bins, and " << _n_block_bins << " block bins... "
     445          76 :            << std::endl;
     446             : 
     447          76 :   if (_use_displaced && _standalone)
     448             :   {
     449             :     // we are responsible for updating the mesh if running in standalone mode; otherwise, the
     450             :     // OpenMCCellAverageProblem class does it
     451           4 :     _fe_problem.getDisplacedProblem()->updateMesh();
     452             :   }
     453             : 
     454             :   // Clear MOAB mesh data from last timestep
     455          76 :   reset();
     456             : 
     457          76 :   _serialized_solution->init(_fe_problem.getAuxiliarySystem().sys().n_dofs(), false, SERIAL);
     458          76 :   _fe_problem.getAuxiliarySystem().solution().localize(*_serialized_solution);
     459             : 
     460             :   // Re-initialise the mesh data
     461          76 :   initialize();
     462             : 
     463         202 :   if (isParamValid("material_blocks") && isParamValid("material_names"))
     464          25 :     _console << "MoabSkinner updating material assignments..." << std::endl;
     465             : 
     466             :   // Sort libMesh elements into bins
     467          76 :   sortElemsByResults();
     468             : 
     469             :   // Find the surfaces of local temperature regions
     470          72 :   findSurfaces();
     471          68 : }
     472             : 
     473             : void
     474         193 : MoabSkinner::findBlocks()
     475             : {
     476             :   _blocks.clear();
     477             : 
     478             :   int i = 0;
     479         764 :   for (const auto & b : getMooseMesh().meshSubdomains())
     480         571 :     _blocks[b] = i++;
     481             : 
     482         193 :   _n_block_bins = _blocks.size();
     483         193 : }
     484             : 
     485             : void
     486         186 : MoabSkinner::createMOABElems()
     487             : {
     488             :   // Clear prior results
     489             :   _id_to_elem_handles.clear();
     490             :   _node_id_to_handle.clear();
     491             :   _elem_handle_to_id.clear();
     492             : 
     493             :   double coords[3];
     494             : 
     495         186 :   MeshBase & geom_mesh = getDAGMCGeometryMesh();
     496             : 
     497             :   // Save all the node information
     498       55404 :   for (const auto & node : geom_mesh.node_ptr_range())
     499             :   {
     500             :     // Fetch coords (and scale to correct units)
     501       55032 :     coords[0] = _scaling * (*node)(0);
     502       55032 :     coords[1] = _scaling * (*node)(1);
     503       55032 :     coords[2] = _scaling * (*node)(2);
     504             : 
     505             :     // Add node to MOAB database and get handle
     506       55032 :     moab::EntityHandle ent(0);
     507       55032 :     check(_moab->create_vertex(coords, ent));
     508             : 
     509             :     // Save mapping of libMesh IDs to MOAB vertex handles
     510       55032 :     _node_id_to_handle[node->id()] = ent;
     511         186 :   }
     512             : 
     513             :   moab::Range all_elems;
     514             : 
     515             :   // Iterate over elements in the mesh
     516      380530 :   for (const auto & elem : geom_mesh.active_element_ptr_range())
     517             :   {
     518      190079 :     auto nodeSets = getTetSets(elem->type());
     519             : 
     520             :     // Get the connectivity
     521             :     std::vector<dof_id_type> conn_libmesh;
     522      190079 :     elem->connectivity(0, libMesh::IOPackage::VTK, conn_libmesh);
     523             : 
     524             :     // Loop over sub tets
     525      402782 :     for (const auto & nodeSet : nodeSets)
     526             :     {
     527             :       // Set MOAB connectivity
     528      212703 :       std::vector<moab::EntityHandle> conn(NODES_PER_MOAB_TET);
     529     1063515 :       for (unsigned int i = 0; i < NODES_PER_MOAB_TET; ++i)
     530             :       {
     531             :         // Get the elem node index of the ith node of the sub-tet
     532      850812 :         unsigned int nodeIndex = nodeSet.at(i);
     533     1701624 :         conn[i] = _node_id_to_handle[conn_libmesh.at(nodeIndex)];
     534             :       }
     535             : 
     536             :       // Create an element in MOAB database
     537      212703 :       moab::EntityHandle ent(0);
     538      212703 :       check(_moab->create_element(moab::MBTET, conn.data(), NODES_PER_MOAB_TET, ent));
     539             : 
     540             :       // Save mapping between libMesh ids and moab handles
     541      212703 :       auto id = elem->id();
     542      212703 :       if (_id_to_elem_handles.find(id) == _id_to_elem_handles.end())
     543      380158 :         _id_to_elem_handles[id] = std::vector<moab::EntityHandle>();
     544             : 
     545      212703 :       _id_to_elem_handles[id].push_back(ent);
     546      212703 :       _elem_handle_to_id[ent] = id;
     547             : 
     548             :       // Save the handle for adding to entity sets
     549      212703 :       all_elems.insert(ent);
     550      212703 :     }
     551      190265 :   }
     552             : 
     553             :   // Add the elems to the full meshset
     554         186 :   check(_moab->add_entities(_all_tets, all_elems));
     555             : 
     556             :   // Save the first elem
     557         186 :   offset = all_elems.front();
     558         186 : }
     559             : 
     560             : const std::vector<std::vector<unsigned int>> &
     561      190079 : MoabSkinner::getTetSets(ElemType type) const
     562             : {
     563      190079 :   if (type == TET4)
     564      186847 :     return _tet4_nodes;
     565        3232 :   else if (type == TET10)
     566        3232 :     return _tet10_nodes;
     567             :   else
     568           0 :     mooseError("The MoabSkinner can only be used with a tetrahedral [Mesh]! If your mesh "
     569             :                "contains other element types, MoabSkinner should convert them automatically.");
     570             : }
     571             : 
     572             : void
     573         186 : MoabSkinner::createTags()
     574             : {
     575             :   // First some built-in MOAB tag types
     576         186 :   check(_moab->tag_get_handle(GEOM_DIMENSION_TAG_NAME,
     577             :                               1,
     578             :                               moab::MB_TYPE_INTEGER,
     579         186 :                               geometry_dimension_tag,
     580             :                               moab::MB_TAG_DENSE | moab::MB_TAG_CREAT));
     581             : 
     582         186 :   check(_moab->tag_get_handle(GLOBAL_ID_TAG_NAME,
     583             :                               1,
     584             :                               moab::MB_TYPE_INTEGER,
     585         186 :                               id_tag,
     586             :                               moab::MB_TAG_DENSE | moab::MB_TAG_CREAT));
     587             : 
     588         186 :   check(_moab->tag_get_handle(CATEGORY_TAG_NAME,
     589             :                               CATEGORY_TAG_SIZE,
     590             :                               moab::MB_TYPE_OPAQUE,
     591         186 :                               category_tag,
     592             :                               moab::MB_TAG_SPARSE | moab::MB_TAG_CREAT));
     593             : 
     594         186 :   check(_moab->tag_get_handle(NAME_TAG_NAME,
     595             :                               NAME_TAG_SIZE,
     596             :                               moab::MB_TYPE_OPAQUE,
     597         186 :                               name_tag,
     598             :                               moab::MB_TAG_SPARSE | moab::MB_TAG_CREAT));
     599             : 
     600             :   // Some tags needed for DagMC
     601         186 :   check(_moab->tag_get_handle("FACETING_TOL",
     602             :                               1,
     603             :                               moab::MB_TYPE_DOUBLE,
     604         186 :                               faceting_tol_tag,
     605             :                               moab::MB_TAG_SPARSE | moab::MB_TAG_CREAT));
     606             : 
     607         186 :   check(_moab->tag_get_handle("GEOMETRY_RESABS",
     608             :                               1,
     609             :                               moab::MB_TYPE_DOUBLE,
     610         186 :                               geometry_resabs_tag,
     611             :                               moab::MB_TAG_SPARSE | moab::MB_TAG_CREAT));
     612             : 
     613             :   // Set the values for DagMC faceting / geometry tolerance tags on the mesh entity set
     614         186 :   check(_moab->tag_set_data(faceting_tol_tag, &_all_tets, 1, &_faceting_tol));
     615         186 :   check(_moab->tag_set_data(geometry_resabs_tag, &_all_tets, 1, &_geom_tol));
     616         186 : }
     617             : 
     618             : void
     619        1121 : MoabSkinner::createGroup(const unsigned int & id,
     620             :                          const std::string & name,
     621             :                          moab::EntityHandle & group_set)
     622             : {
     623        1121 :   check(_moab->create_meshset(moab::MESHSET_SET, group_set));
     624        2242 :   setTags(group_set, name, "Group", id, 4);
     625        1121 : }
     626             : 
     627             : void
     628        1895 : MoabSkinner::createVol(const unsigned int & id,
     629             :                        moab::EntityHandle & volume_set,
     630             :                        moab::EntityHandle group_set)
     631             : {
     632        1895 :   check(_moab->create_meshset(moab::MESHSET_SET, volume_set));
     633             : 
     634        3790 :   setTags(volume_set, "", "Volume", id, 3);
     635             : 
     636             :   // Add the volume to group
     637        1895 :   check(_moab->add_entities(group_set, &volume_set, 1));
     638        1895 : }
     639             : 
     640             : void
     641        4371 : MoabSkinner::createSurf(const unsigned int & id,
     642             :                         moab::EntityHandle & surface_set,
     643             :                         moab::Range & faces,
     644             :                         const std::vector<VolData> & voldata,
     645             :                         BoundaryConditionType bc_type)
     646             : {
     647             :   // Create meshset
     648        4371 :   check(_moab->create_meshset(moab::MESHSET_SET, surface_set));
     649             : 
     650             :   // Set tags
     651        8742 :   setTags(surface_set, "", "Surface", id, 2);
     652             : 
     653             :   // Add tris to the surface
     654        4371 :   check(_moab->add_entities(surface_set, faces));
     655             : 
     656             :   // Create entry in map
     657        8742 :   surfsToVols[surface_set] = std::vector<VolData>();
     658             : 
     659             :   // Add volume to list associated with this surface
     660       11156 :   for (const auto & data : voldata)
     661        6785 :     updateSurfData(surface_set, data);
     662             : 
     663        4371 :   recordBoundaryConditionSurface(surface_set, bc_type);
     664        4371 : }
     665             : 
     666             : void
     667        7649 : MoabSkinner::updateSurfData(moab::EntityHandle surface_set, const VolData & data)
     668             : {
     669             :   // Add the surface to the volume set
     670        7649 :   check(_moab->add_parent_child(data.vol, surface_set));
     671             : 
     672             :   // Set the surfaces sense
     673        7649 :   gtt->set_sense(surface_set, data.vol, int(data.sense));
     674             : 
     675        7649 :   surfsToVols[surface_set].push_back(data);
     676        7649 : }
     677             : 
     678             : void
     679        7387 : MoabSkinner::setTags(
     680             :     moab::EntityHandle ent, std::string name, std::string category, unsigned int id, int dim)
     681             : {
     682             :   // Set the name tag
     683        7387 :   if (name != "")
     684        2242 :     setTagData(name_tag, ent, name, NAME_TAG_SIZE);
     685             : 
     686             :   // Set the category tag
     687        7387 :   if (category != "")
     688       14774 :     setTagData(category_tag, ent, category, CATEGORY_TAG_SIZE);
     689             : 
     690             :   // Set the dimension tag
     691        7387 :   setTagData(geometry_dimension_tag, ent, &dim);
     692             : 
     693             :   // Set the id tag
     694        7387 :   setTagData(id_tag, ent, &id);
     695        7387 : }
     696             : 
     697             : void
     698        8508 : MoabSkinner::setTagData(moab::Tag tag, moab::EntityHandle ent, std::string data, unsigned int SIZE)
     699             : {
     700        8508 :   auto namebuf = new char[SIZE];
     701        8508 :   memset(namebuf, '\0', SIZE); // fill C char array with null
     702        8508 :   strncpy(namebuf, data.c_str(), SIZE - 1);
     703        8508 :   check(_moab->tag_set_data(tag, &ent, 1, namebuf));
     704        8508 :   delete[] namebuf;
     705        8508 : }
     706             : 
     707             : void
     708       14774 : MoabSkinner::setTagData(moab::Tag tag, moab::EntityHandle ent, void * data)
     709             : {
     710       14774 :   check(_moab->tag_set_data(tag, &ent, 1, data));
     711       14774 : }
     712             : 
     713             : unsigned int
     714         134 : MoabSkinner::nBins() const
     715             : {
     716         134 :   return _n_block_bins * _n_density_bins * _n_temperature_bins;
     717             : }
     718             : 
     719             : void
     720          76 : MoabSkinner::sortElemsByResults()
     721             : {
     722          76 :   _elem_bins.clear();
     723          76 :   _elem_bins.resize(nBins());
     724             : 
     725             :   // accumulate information for printing diagnostics
     726          76 :   std::vector<unsigned int> n_block_hits(_n_block_bins, 0);
     727          76 :   std::vector<unsigned int> n_temp_hits(_n_temperature_bins, 0);
     728          76 :   std::vector<unsigned int> n_density_hits(_n_density_bins, 0);
     729             : 
     730          76 :   if (!_tet_mesh)
     731             :   {
     732       34523 :     for (unsigned int e = 0; e < getMooseMesh().nElem(); ++e)
     733             :     {
     734       34461 :       const Elem * const elem = getMooseMesh().queryElemPtr(e);
     735       34461 :       if (!elem)
     736           0 :         continue;
     737             : 
     738       34461 :       auto iMat = getSubdomainBin(elem);
     739       34461 :       n_block_hits[iMat] += 1;
     740             : 
     741       34461 :       auto iDenBin = getDensityBin(elem);
     742       34459 :       n_density_hits[iDenBin] += 1;
     743             : 
     744       34459 :       auto iBin = getTemperatureBin(elem);
     745       34457 :       n_temp_hits[iBin] += 1;
     746             : 
     747       34457 :       _elem_bins.at(getBin(iBin, iDenBin, iMat)).insert(elem->id());
     748             :     }
     749             :   }
     750             :   else
     751             :   {
     752             :     // aux vars live on the original mesh; _elem_bins
     753             :     // must hold tet-mesh IDs (keyed the same as _id_to_elem_handles).
     754             : 
     755             :     // compute bin index for every original element.
     756             :     std::unordered_map<dof_id_type, unsigned int> orig_id_to_bin;
     757             : 
     758        8638 :     for (unsigned int e = 0; e < getMooseMesh().nElem(); ++e)
     759             :     {
     760        8628 :       const Elem * const elem = getMooseMesh().queryElemPtr(e);
     761        8628 :       if (!elem)
     762           0 :         continue;
     763             : 
     764        8628 :       auto iMat = getSubdomainBin(elem);
     765        8628 :       n_block_hits[iMat] += 1;
     766             : 
     767        8628 :       auto iDenBin = getDensityBin(elem);
     768        8628 :       n_density_hits[iDenBin] += 1;
     769             : 
     770        8628 :       auto iBin = getTemperatureBin(elem);
     771        8628 :       n_temp_hits[iBin] += 1;
     772             : 
     773        8628 :       orig_id_to_bin[elem->id()] = getBin(iBin, iDenBin, iMat);
     774             :     }
     775             : 
     776             :     // insert each tet ID into its parent's bin.
     777          10 :     const unsigned int orig_id_tag = _tet_mesh->get_elem_integer_index("orig_id");
     778       49388 :     for (const auto * tet : _tet_mesh->active_element_ptr_range())
     779             :     {
     780       49368 :       const dof_id_type orig_id = tet->get_extra_integer(orig_id_tag);
     781             : 
     782             :       auto bin_it = orig_id_to_bin.find(orig_id);
     783       49368 :       if (bin_it == orig_id_to_bin.end())
     784           0 :         mooseError("MoabSkinner::sortElemsByResults(): original elem id=",
     785             :                    orig_id,
     786             :                    " not found in bin map");
     787             : 
     788       49368 :       _elem_bins.at(bin_it->second).insert(tet->id());
     789          10 :     }
     790             :   }
     791             : 
     792          72 :   if (_verbose)
     793             :   {
     794          69 :     VariadicTable<unsigned int, std::string, unsigned int> vtt({"Bin", "Range (K)", "# Elems"});
     795          69 :     VariadicTable<unsigned int, std::string, unsigned int> vtd({"Bin", "Range (kg/m3)", "# Elems"});
     796             : 
     797         266 :     for (unsigned int i = 0; i < _n_temperature_bins; ++i)
     798         197 :       vtt.addRow(i,
     799         591 :                  std::to_string(_temperature_bin_bounds[i]) + " to " +
     800         394 :                      std::to_string(_temperature_bin_bounds[i + 1]),
     801         197 :                  n_temp_hits[i]);
     802             : 
     803         232 :     for (unsigned int i = 0; i < _n_density_bins; ++i)
     804         163 :       vtd.addRow(i,
     805         489 :                  std::to_string(_density_bin_bounds[i]) + " to " +
     806         326 :                      std::to_string(_density_bin_bounds[i + 1]),
     807         163 :                  n_density_hits[i]);
     808             : 
     809          69 :     _console << "\nMapping of Elements to Temperature Bins:" << std::endl;
     810          69 :     vtt.print(_console);
     811          69 :     _console << std::endl;
     812             : 
     813          69 :     if (_bin_by_density)
     814             :     {
     815          21 :       _console << "\n\nMapping of Elements to Density Bins:" << std::endl;
     816          21 :       vtd.print(_console);
     817          21 :       _console << std::endl;
     818             :     }
     819          69 :   }
     820          72 : }
     821             : 
     822             : unsigned int
     823      100468 : MoabSkinner::getTemperatureBin(const Elem * const elem) const
     824             : {
     825      100468 :   auto dof = elem->dof_number(_fe_problem.getAuxiliarySystem().number(), _temperature_var_num, 0);
     826      100468 :   auto value = (*_serialized_solution)(dof);
     827             : 
     828             :   // TODO: add option to truncate instead
     829      100468 :   if ((_temperature_min - value) > BIN_TOLERANCE)
     830           1 :     mooseError("Variable '",
     831           1 :                _temperature_name,
     832             :                "' has value below minimum range of bins. "
     833             :                "Please decrease 'temperature_min'.\n\n"
     834             :                "  value: ",
     835             :                value,
     836             :                "\n  temperature_min: ",
     837             :                _temperature_min);
     838             : 
     839      100467 :   if ((value - _temperature_max) > BIN_TOLERANCE)
     840           1 :     mooseError("Variable '",
     841           1 :                _temperature_name,
     842             :                "' has value above maximum range of bins. "
     843             :                "Please increase 'temperature_max'.\n\n"
     844             :                "  value: ",
     845             :                value,
     846             :                "\n  temperature_max: ",
     847             :                _temperature_max);
     848             : 
     849      100466 :   return bin_utility::linearBin(value, _temperature_bin_bounds);
     850             : }
     851             : 
     852             : unsigned int
     853       98604 : MoabSkinner::getDensityBin(const Elem * const elem) const
     854             : {
     855       98604 :   if (!_bin_by_density)
     856             :     return 0;
     857             : 
     858       56106 :   auto dof = elem->dof_number(_fe_problem.getAuxiliarySystem().number(), _density_var_num, 0);
     859       56106 :   auto value = (*_serialized_solution)(dof);
     860             : 
     861             :   // TODO: add option to truncate instead
     862       56106 :   if ((_density_min - value) > BIN_TOLERANCE)
     863           1 :     mooseError("Variable '",
     864           1 :                _density_name,
     865             :                "' has value below minimum range of bins. "
     866             :                "Please decrease 'density_min'.\n\n"
     867             :                "  value: ",
     868             :                value,
     869             :                "\n  density_min: ",
     870           1 :                _density_min);
     871             : 
     872       56105 :   if ((value - _density_max) > BIN_TOLERANCE)
     873           1 :     mooseError("Variable '",
     874           1 :                _density_name,
     875             :                "' has value above maximum range of bins. "
     876             :                "Please increase 'density_max'.\n\n"
     877             :                "  value: ",
     878             :                value,
     879             :                "\n  density_max: ",
     880           1 :                _density_max);
     881             : 
     882       56104 :   return bin_utility::linearBin(value, _density_bin_bounds);
     883             : }
     884             : 
     885             : std::string
     886        1057 : MoabSkinner::materialName(const unsigned int & block,
     887             :                           const unsigned int & density,
     888             :                           const unsigned int & temp) const
     889             : {
     890        2385 :   for (const auto & [subdomain_id, block_index] : _blocks)
     891             :   {
     892        2385 :     if (block_index == block)
     893             :     {
     894             :       auto it = _block_id_to_material_name.find(subdomain_id);
     895        1057 :       if (it != _block_id_to_material_name.end())
     896        1053 :         return "mat:" + it->second;
     897             : 
     898           4 :       return "mat:void";
     899             :     }
     900             :   }
     901             :   mooseAssert(false, "could not find subdomain for block index " + Moose::stringify(block));
     902           0 :   return "";
     903             : }
     904             : 
     905             : std::set<BoundaryID>
     906          59 : MoabSkinner::boundaryNamesToIDs(const std::vector<BoundaryName> & names,
     907             :                                 const std::string & param_name)
     908             : {
     909             :   std::set<BoundaryID> ids;
     910         223 :   for (const auto & name : names)
     911             :   {
     912         167 :     const BoundaryID id = getMooseMesh().getBoundaryID(name);
     913         167 :     if (!getMooseMesh().meshSidesetIds().count(id))
     914             :     {
     915           3 :       if (getMooseMesh().meshNodesetIds().count(id))
     916           1 :         paramError(param_name,
     917             :                    "Boundary '",
     918             :                    name,
     919             :                    "' is a nodeset; boundary conditions can only be assigned to sidesets.");
     920             :       else
     921           2 :         paramError(param_name, "Boundary '", name, "' does not exist in the mesh.");
     922             :     }
     923         164 :     ids.insert(id);
     924             :   }
     925          56 :   return ids;
     926             : }
     927             : 
     928             : void
     929          33 : MoabSkinner::checkBoundaryConditionOverlap() const
     930             : {
     931             :   std::set<BoundaryID> overlap;
     932          33 :   std::set_intersection(_vacuum_bcs_surface_ids.begin(),
     933             :                         _vacuum_bcs_surface_ids.end(),
     934             :                         _reflective_bcs_surface_ids.begin(),
     935             :                         _reflective_bcs_surface_ids.end(),
     936             :                         std::inserter(overlap, overlap.begin()));
     937          33 :   if (overlap.empty())
     938          32 :     return;
     939             : 
     940             :   std::string ids_str;
     941           2 :   for (const auto id : overlap)
     942           2 :     ids_str += (ids_str.empty() ? "" : ", ") + std::to_string(id);
     943             : 
     944           1 :   paramError("vacuum_bcs_surfaces",
     945             :              "The following sideset ID(s) appear in both 'vacuum_bcs_surfaces' and "
     946             :              "'reflective_bcs_surfaces': ",
     947             :              ids_str);
     948             : }
     949             : 
     950             : MoabSkinner::BoundaryConditionType
     951       25357 : MoabSkinner::boundaryConditionType(const Elem * const elem,
     952             :                                    const unsigned int side,
     953             :                                    const libMesh::BoundaryInfo & boundary_info) const
     954             : {
     955             :   std::vector<boundary_id_type> side_bids;
     956       25357 :   boundary_info.boundary_ids(elem, side, side_bids);
     957             : 
     958             :   std::vector<boundary_id_type> vacuum_bids, reflective_bids;
     959       31082 :   for (const auto bid : side_bids)
     960             :   {
     961             :     if (_vacuum_bcs_surface_ids.count(bid))
     962         657 :       vacuum_bids.push_back(bid);
     963             :     if (_reflective_bcs_surface_ids.count(bid))
     964        2321 :       reflective_bids.push_back(bid);
     965             :   }
     966             : 
     967       25357 :   if (!vacuum_bids.empty() && !reflective_bids.empty())
     968             :   {
     969             :     // Assemble sideset names (resolves to IDs) for a helpful error message
     970           2 :     auto names = [&boundary_info](const std::vector<boundary_id_type> & bids)
     971             :     {
     972             :       std::string out;
     973           4 :       for (const auto bid : bids)
     974             :       {
     975           2 :         const auto & name = boundary_info.get_sideset_name(bid);
     976           2 :         out += (out.empty() ? "" : ", ");
     977           4 :         out += "'" + (name.empty() ? std::to_string(bid) : name) + "'";
     978             :       }
     979           2 :       return out;
     980           1 :     };
     981             : 
     982           2 :     mooseError("Element ",
     983           1 :                elem->id(),
     984             :                ", side ",
     985             :                side,
     986             :                " is assigned both vacuum and reflective boundary conditions, because this side "
     987             :                "belongs to sideset(s) ",
     988           1 :                names(vacuum_bids),
     989             :                " (vacuum) and ",
     990           1 :                names(reflective_bids),
     991             :                " (reflective). An element side cannot be assigned competing types of surface "
     992             :                "boundary conditions.");
     993             :   }
     994             : 
     995       25356 :   if (!vacuum_bids.empty())
     996             :     return BoundaryConditionType::Vacuum;
     997       24700 :   if (!reflective_bids.empty())
     998        2320 :     return BoundaryConditionType::Reflective;
     999             :   return BoundaryConditionType::Transmission;
    1000       25356 : }
    1001             : 
    1002             : void
    1003        3693 : MoabSkinner::splitSkinByBoundaryCondition(const moab::Range & region,
    1004             :                                           const moab::Range & skin,
    1005             :                                           moab::Range & transmission_tris,
    1006             :                                           moab::Range & vacuum_tris,
    1007             :                                           moab::Range & reflective_tris)
    1008             : {
    1009             :   // all triangles have transmission BC until sorted
    1010        3693 :   transmission_tris = skin;
    1011             : 
    1012        3693 :   if (!_set_bcs || skin.empty())
    1013             :     return;
    1014             : 
    1015          34 :   MeshBase & geom_mesh = getDAGMCGeometryMesh();
    1016             :   const auto & boundary_info = geom_mesh.get_boundary_info();
    1017             : 
    1018             :   // Number of entities passed to get_adjacencies per call
    1019             :   constexpr int num_entities = 1;
    1020             :   // Topological dimension of the faces we want (2 = triangles for a 3D tet mesh)
    1021             :   constexpr int surface_dimension = 2;
    1022             : 
    1023       12713 :   for (const auto tet : region)
    1024             :   {
    1025             :     const auto elem_id_it = _elem_handle_to_id.find(tet);
    1026             :     mooseAssert(elem_id_it != _elem_handle_to_id.end(),
    1027             :                 "Could not map a MOAB tet back to a libMesh element while assigning "
    1028             :                 "DAGMC boundary conditions.");
    1029             : 
    1030        6340 :     const Elem * const elem = geom_mesh.query_elem_ptr(elem_id_it->second);
    1031             :     mooseAssert(elem,
    1032             :                 "Could not find libMesh element " + std::to_string(elem_id_it->second) +
    1033             :                     " while assigning DAGMC boundary conditions.");
    1034             : 
    1035       31696 :     for (const auto side : make_range(elem->n_sides()))
    1036             :     {
    1037       25357 :       const auto bc_type = boundaryConditionType(elem, side, boundary_info);
    1038       25356 :       if (bc_type == BoundaryConditionType::Transmission)
    1039       22380 :         continue;
    1040             : 
    1041             :       // Build the set of MOAB vertex handles covering this libMesh side
    1042        2976 :       std::unique_ptr<const Elem> side_elem = elem->build_side_ptr(side);
    1043             :       std::set<moab::EntityHandle> side_verts;
    1044       11904 :       for (const auto i : make_range(side_elem->n_nodes()))
    1045             :       {
    1046        8928 :         const auto node_it = _node_id_to_handle.find(side_elem->node_id(i));
    1047             :         mooseAssert(node_it != _node_id_to_handle.end(),
    1048             :                     "Could not map libMesh node " + std::to_string(side_elem->node_id(i)) +
    1049             :                         " to a MOAB vertex while assigning DAGMC boundary conditions.");
    1050        8928 :         side_verts.insert(node_it->second);
    1051             :       }
    1052             : 
    1053             :       // Retrieve the MOAB triangle faces adjacent to this tet
    1054             :       moab::Range tri_faces;
    1055        2976 :       check(_moab->get_adjacencies(&tet, num_entities, surface_dimension, false, tri_faces));
    1056             : 
    1057       10676 :       for (const auto tri : tri_faces)
    1058             :       {
    1059        3850 :         if (skin.find(tri) == skin.end())
    1060        1930 :           continue;
    1061             : 
    1062        2588 :         const moab::EntityHandle * conn = nullptr;
    1063        2588 :         int nconn = 0;
    1064        2588 :         check(_moab->get_connectivity(tri, conn, nconn));
    1065             : 
    1066             :         // A skin triangle lies on this libMesh side if all three of its MOAB vertices
    1067             :         // are contained in the side's vertex set.
    1068        2588 :         if (!side_verts.count(conn[0]) || !side_verts.count(conn[1]) || !side_verts.count(conn[2]))
    1069         668 :           continue;
    1070             : 
    1071        1920 :         switch (bc_type)
    1072             :         {
    1073             :           case BoundaryConditionType::Transmission:
    1074             :             // Nothing to do - tris stay in transmission_tris
    1075             :             break;
    1076         448 :           case BoundaryConditionType::Vacuum:
    1077         448 :             transmission_tris.erase(tri);
    1078         448 :             vacuum_tris.insert(tri);
    1079             :             break;
    1080        1472 :           case BoundaryConditionType::Reflective:
    1081        1472 :             transmission_tris.erase(tri);
    1082        1472 :             reflective_tris.insert(tri);
    1083             :             break;
    1084           0 :           default:
    1085           0 :             mooseError("Unhandled boundary condition type!");
    1086             :         }
    1087             :       }
    1088        2976 :     }
    1089             :   }
    1090             : }
    1091             : 
    1092             : void
    1093        3693 : MoabSkinner::createSurfacesFromSkin(const moab::Range & region,
    1094             :                                     moab::Range & skin,
    1095             :                                     VolData & voldata,
    1096             :                                     unsigned int & surf_id)
    1097             : {
    1098             :   moab::Range transmission_tris, vacuum_tris, reflective_tris;
    1099        3693 :   splitSkinByBoundaryCondition(region, skin, transmission_tris, vacuum_tris, reflective_tris);
    1100             : 
    1101             :   // Create surfaces for each BC class separately so every surface meshset carries
    1102             :   //  one BC type. Transmission is the DAGMC default and needs no group.
    1103        3692 :   createSurfaces(transmission_tris, voldata, surf_id, BoundaryConditionType::Transmission);
    1104        3692 :   createSurfaces(vacuum_tris, voldata, surf_id, BoundaryConditionType::Vacuum);
    1105        3692 :   createSurfaces(reflective_tris, voldata, surf_id, BoundaryConditionType::Reflective);
    1106        3691 : }
    1107             : 
    1108             : void
    1109        5235 : MoabSkinner::recordBoundaryConditionSurface(moab::EntityHandle surface_set,
    1110             :                                             BoundaryConditionType bc_type)
    1111             : {
    1112             :   // transmission is the DAGMC default - we don't need to tag the transmission surface
    1113        5235 :   if (bc_type == BoundaryConditionType::Transmission)
    1114        5207 :     return;
    1115             : 
    1116          28 :   const auto [it, inserted] = _surface_bc_types.emplace(surface_set, bc_type);
    1117          28 :   if (!inserted && it->second != bc_type)
    1118           1 :     mooseError("A DAGMC surface was assigned two different boundary conditions ('",
    1119           1 :                boundaryConditionGroupName(it->second),
    1120             :                "' and '",
    1121           1 :                boundaryConditionGroupName(bc_type),
    1122             :                "').");
    1123             : }
    1124             : 
    1125             : MoabSkinner::BoundaryConditionType
    1126        2414 : MoabSkinner::recordedBoundaryCondition(moab::EntityHandle surface_set) const
    1127             : {
    1128             :   const auto it = _surface_bc_types.find(surface_set);
    1129        2414 :   return it == _surface_bc_types.end() ? BoundaryConditionType::Transmission : it->second;
    1130             : }
    1131             : 
    1132             : std::string
    1133          16 : MoabSkinner::boundaryConditionGroupName(BoundaryConditionType bc_type) const
    1134             : {
    1135          16 :   switch (bc_type)
    1136             :   {
    1137             :     case BoundaryConditionType::Vacuum:
    1138           8 :       return "boundary:Vacuum";
    1139             :     case BoundaryConditionType::Reflective:
    1140           8 :       return "boundary:Reflecting";
    1141             :     case BoundaryConditionType::Transmission:
    1142             :       mooseAssert(bc_type != BoundaryConditionType::Transmission,
    1143             :                   "Surface set groups should not be created for transmission BCs since "
    1144             :                   "transmission is the DAGMC default.");
    1145           0 :       return "";
    1146           0 :     default:
    1147           0 :       mooseError("No DAGMC boundary condition group exists for this boundary condition type");
    1148             :   }
    1149             : }
    1150             : 
    1151             : unsigned int
    1152           8 : MoabSkinner::firstBoundaryConditionGroupID() const
    1153             : {
    1154             :   // IDs used by material groups.
    1155             :   // buildGraveyard() and the implicit complement each consume one additional ID when enabled.
    1156           8 :   unsigned int gid = nBins() + 1;
    1157           8 :   gid += _build_graveyard + _set_implicit_complement_material;
    1158           8 :   return gid;
    1159             : }
    1160             : 
    1161             : void
    1162          70 : MoabSkinner::createBoundaryConditionGroups()
    1163             : {
    1164          70 :   if (!_set_bcs)
    1165          60 :     return;
    1166             : 
    1167             :   std::map<BoundaryConditionType, std::vector<moab::EntityHandle>> surfaces_by_type;
    1168          36 :   for (const auto & [surf, bc_type] : _surface_bc_types)
    1169          26 :     surfaces_by_type[bc_type].push_back(surf);
    1170             : 
    1171          17 :   if (!_vacuum_bcs_surface_ids.empty() && !surfaces_by_type.count(BoundaryConditionType::Vacuum))
    1172           1 :     paramError("vacuum_bcs_surfaces",
    1173             :                "'vacuum_bcs_surfaces' was specified but no skinned DAGMC surfaces were assigned "
    1174             :                "vacuum boundary conditions. Verify the sideset names or IDs correspond to "
    1175             :                "boundary faces of the mesh.");
    1176             : 
    1177           9 :   if (!_reflective_bcs_surface_ids.empty() &&
    1178           8 :       !surfaces_by_type.count(BoundaryConditionType::Reflective))
    1179           1 :     paramError("reflective_bcs_surfaces",
    1180             :                "'reflective_bcs_surfaces' was specified but no skinned DAGMC surfaces were "
    1181             :                "assigned reflective boundary conditions. Verify the sideset names or IDs "
    1182             :                "correspond to boundary faces of the mesh.");
    1183             : 
    1184           8 :   unsigned int gid = firstBoundaryConditionGroupID();
    1185          22 :   for (const auto & [bc_type, surfs] : surfaces_by_type)
    1186             :   {
    1187          14 :     moab::EntityHandle group = 0;
    1188          14 :     createGroup(gid++, boundaryConditionGroupName(bc_type), group);
    1189          40 :     for (const auto surf_set : surfs)
    1190          26 :       check(_moab->add_entities(group, &surf_set, 1));
    1191             :   }
    1192             : }
    1193             : 
    1194             : void
    1195          72 : MoabSkinner::findSurfaces()
    1196             : {
    1197             :   _surface_bc_types.clear();
    1198             : 
    1199             :   // Find all neighbours in mesh
    1200          72 :   getDAGMCGeometryMesh().find_neighbors();
    1201             : 
    1202             :   // Counter for volumes
    1203          72 :   unsigned int vol_id = 0;
    1204             : 
    1205             :   // Counter for surfaces
    1206          72 :   unsigned int surf_id = 0;
    1207             : 
    1208             :   // Loop over material bins
    1209         301 :   for (unsigned int iMat = 0; iMat < _n_block_bins; iMat++)
    1210             :   {
    1211             :     // Loop over density bins
    1212         630 :     for (unsigned int iDen = 0; iDen < _n_density_bins; iDen++)
    1213             :     {
    1214             :       // Loop over temperature bins
    1215        1456 :       for (unsigned int iVar = 0; iVar < _n_temperature_bins; iVar++)
    1216             :       {
    1217             :         // Update material name
    1218        1057 :         auto updated_mat_name = materialName(iMat, iDen, iVar);
    1219             : 
    1220             :         // Create a material group
    1221        1057 :         int iSortBin = getBin(iVar, iDen, iMat);
    1222             : 
    1223             :         // For DagMC to fill a cell with a material, we first create a group
    1224             :         // with that name, and then assign it with createVol (called inside findSurface)
    1225             :         moab::EntityHandle group_set;
    1226        1057 :         unsigned int group_id = iSortBin + 1;
    1227        1057 :         createGroup(group_id, updated_mat_name, group_set);
    1228             : 
    1229             :         // Sort elems in this mat-density-temp bin into local regions
    1230             :         std::vector<moab::Range> regions;
    1231        3171 :         groupLocalElems(_elem_bins.at(iSortBin), regions);
    1232             : 
    1233             :         // Loop over all regions and find surfaces
    1234        2902 :         for (const auto & region : regions)
    1235             :         {
    1236             :           moab::EntityHandle volume_set;
    1237        1847 :           findSurface(region, group_set, vol_id, surf_id, volume_set);
    1238             :         }
    1239        1055 :       }
    1240             :     }
    1241             :   }
    1242             : 
    1243          70 :   if (_build_graveyard)
    1244          48 :     buildGraveyard(vol_id, surf_id);
    1245             : 
    1246          70 :   if (_set_implicit_complement_material)
    1247             :   {
    1248             :     moab::EntityHandle comp_group;
    1249           2 :     unsigned int comp_id = nBins() + 1 + _build_graveyard;
    1250           2 :     createGroup(comp_id, _implicit_complement_group_name, comp_group);
    1251           2 :     moab::EntityHandle arbitray_volume = 0;
    1252           2 :     for (const auto & surf_pair : surfsToVols)
    1253             :     {
    1254             :       const auto & vols = surf_pair.second;
    1255           2 :       arbitray_volume = vols.front().vol;
    1256           2 :       break;
    1257             :     }
    1258           2 :     check(_moab->add_entities(comp_group, &arbitray_volume, 1));
    1259             :   }
    1260             : 
    1261          70 :   createBoundaryConditionGroups();
    1262             : 
    1263             :   // Write MOAB volume and/or skin meshes to file
    1264          68 :   write();
    1265          68 : }
    1266             : 
    1267             : void
    1268          68 : MoabSkinner::write()
    1269             : {
    1270             :   // Only write to file on root process
    1271          68 :   if (processor_id() != 0)
    1272           0 :     return;
    1273             : 
    1274          68 :   std::string extension = std::to_string(_n_write) + ".h5m";
    1275             : 
    1276          68 :   if (_output_skins)
    1277             :   {
    1278             :     // Generate list of surfaces to write
    1279             :     std::vector<moab::EntityHandle> surfs;
    1280        3549 :     for (const auto & itsurf : surfsToVols)
    1281        3532 :       surfs.push_back(itsurf.first);
    1282             : 
    1283          17 :     std::string filename = "moab_skins_" + extension;
    1284             : 
    1285          17 :     if (_verbose)
    1286          17 :       _console << "Writing MOAB skins to " << filename << "...";
    1287             : 
    1288          17 :     check(_moab->write_mesh(filename.c_str(), surfs.data(), surfs.size()));
    1289          17 :   }
    1290             : 
    1291          68 :   if (_output_full)
    1292             :   {
    1293           2 :     std::string filename = "moab_mesh_" + extension;
    1294             : 
    1295           2 :     if (_verbose)
    1296           1 :       _console << "Writing MOAB mesh to " << filename << std::endl;
    1297             : 
    1298           2 :     check(_moab->write_mesh(filename.c_str()));
    1299             :   }
    1300             : 
    1301          68 :   _n_write++;
    1302             : }
    1303             : 
    1304             : void
    1305        1057 : MoabSkinner::groupLocalElems(std::set<dof_id_type> elems, std::vector<moab::Range> & localElems)
    1306             : {
    1307        2904 :   while (!elems.empty())
    1308             :   {
    1309             : 
    1310             :     // Create a new local range of moab handles
    1311             :     moab::Range local;
    1312             : 
    1313             :     // Retrieve and remove the fisrt elem
    1314             :     auto it = elems.begin();
    1315        1847 :     dof_id_type next = *it;
    1316        1847 :     elems.erase(it);
    1317             : 
    1318             :     std::set<dof_id_type> neighbors;
    1319        1847 :     neighbors.insert(next);
    1320             : 
    1321       11657 :     while (!neighbors.empty())
    1322             :     {
    1323             : 
    1324             :       std::set<dof_id_type> new_neighbors;
    1325             : 
    1326             :       // Loop over all the new neighbors
    1327       93443 :       for (auto & next : neighbors)
    1328             :       {
    1329             : 
    1330             :         // Get the MOAB handles, and add to local set
    1331             :         // (May be more than one if this libMesh elem has sub-tetrahedra)
    1332       83633 :         if (_id_to_elem_handles.find(next) == _id_to_elem_handles.end())
    1333           0 :           mooseError("No entity handles found for libmesh id.");
    1334             : 
    1335       83633 :         std::vector<moab::EntityHandle> ents = _id_to_elem_handles[next];
    1336      178578 :         for (const auto ent : ents)
    1337       94945 :           local.insert(ent);
    1338             : 
    1339             :         // Get the libMesh element
    1340       83633 :         Elem & elem = getDAGMCGeometryMesh().elem_ref(next);
    1341             : 
    1342             :         // How many nearest neighbors (general element)?
    1343             :         unsigned int NN = elem.n_neighbors();
    1344             : 
    1345             :         // Loop over neighbors
    1346      418165 :         for (unsigned int i = 0; i < NN; i++)
    1347             :         {
    1348             :           const Elem * nnptr = elem.neighbor_ptr(i);
    1349             :           // If on boundary, some may be null ptrs
    1350      334532 :           if (nnptr == nullptr)
    1351       24500 :             continue;
    1352             : 
    1353      310032 :           dof_id_type idnn = nnptr->id();
    1354             : 
    1355             :           // Select only those that are in the current bin
    1356      310032 :           if (elems.find(idnn) != elems.end())
    1357             :           {
    1358       81786 :             new_neighbors.insert(idnn);
    1359             :             // Remove from those still available
    1360             :             elems.erase(idnn);
    1361             :           }
    1362             :         }
    1363       83633 :       }
    1364             : 
    1365             :       // Found all the new neighbors, done with current set.
    1366             :       neighbors = new_neighbors;
    1367             :     }
    1368             : 
    1369             :     // Save this moab range of local neighbors
    1370        1847 :     localElems.push_back(local);
    1371             :   }
    1372        1057 : }
    1373             : 
    1374             : void
    1375          76 : MoabSkinner::reset()
    1376             : {
    1377          76 :   _moab.reset(new moab::Core());
    1378          76 :   skinner.reset(new moab::Skinner(_moab.get()));
    1379          76 :   gtt.reset(new moab::GeomTopoTool(_moab.get()));
    1380             : 
    1381             :   _tet_mesh.reset();
    1382          76 :   _tet_mesh_built = false;
    1383             : 
    1384             :   // Clear entity set maps
    1385             :   surfsToVols.clear();
    1386          76 : }
    1387             : 
    1388             : unsigned int
    1389       80903 : MoabSkinner::getBin(const unsigned int & i_temp,
    1390             :                     const unsigned int & i_density,
    1391             :                     const unsigned int & i_block) const
    1392             : {
    1393       80903 :   return _n_temperature_bins * (_n_density_bins * i_block + i_density) + i_temp;
    1394             : }
    1395             : 
    1396             : void
    1397        1847 : MoabSkinner::findSurface(const moab::Range & region,
    1398             :                          moab::EntityHandle group,
    1399             :                          unsigned int & vol_id,
    1400             :                          unsigned int & surf_id,
    1401             :                          moab::EntityHandle & volume_set)
    1402             : {
    1403             :   // Create a volume set
    1404        1847 :   vol_id++;
    1405        1847 :   createVol(vol_id, volume_set, group);
    1406             : 
    1407             :   // Find surfaces from these regions
    1408             :   moab::Range tris;  // The tris of the surfaces
    1409             :   moab::Range rtris; // The tris which are reversed with respect to their surfaces
    1410        1847 :   skinner->find_skin(0, region, false, tris, &rtris);
    1411             : 
    1412             :   // Create surface sets, classifying by boundary condition. BC sorting happens here,
    1413             :   // while the current region and its skin result are in hand, rather than in a
    1414             :   // separate post-processing pass.
    1415        1847 :   VolData vdata = {volume_set, Sense::FORWARDS};
    1416        1847 :   createSurfacesFromSkin(region, tris, vdata, surf_id);
    1417             : 
    1418             :   // Create surface sets for the reversed tris
    1419        1846 :   vdata.sense = Sense::BACKWARDS;
    1420        1846 :   createSurfacesFromSkin(region, rtris, vdata, surf_id);
    1421        1845 : }
    1422             : 
    1423             : void
    1424       11076 : MoabSkinner::createSurfaces(moab::Range & faces,
    1425             :                             VolData & voldata,
    1426             :                             unsigned int & surf_id,
    1427             :                             BoundaryConditionType bc_type)
    1428             : {
    1429       11076 :   if (faces.empty())
    1430             :     return;
    1431             : 
    1432             :   // Loop over the surfaces we have already created
    1433     1800979 :   for (const auto & surfpair : surfsToVols)
    1434             :   {
    1435             :     // Local copies of surf/vols
    1436     1799118 :     moab::EntityHandle surf = surfpair.first;
    1437     1799118 :     std::vector<VolData> vols = surfpair.second;
    1438             : 
    1439             :     // First get the entities in this surface
    1440             :     moab::Range tris;
    1441     1799118 :     check(_moab->get_entities_by_handle(surf, tris));
    1442             : 
    1443             :     // Find any tris that live in both surfs
    1444     1799118 :     moab::Range overlap = moab::intersect(tris, faces);
    1445     1799118 :     if (!overlap.empty())
    1446             :     {
    1447             :       // Check if the tris are a subset or the entire surf
    1448        3278 :       if (tris.size() == overlap.size())
    1449             :       {
    1450             :         // Whole surface -> just update the volume relationships and BC record
    1451         864 :         updateSurfData(surf, voldata);
    1452         864 :         recordBoundaryConditionSurface(surf, bc_type);
    1453             :       }
    1454             :       else
    1455             :       {
    1456             :         // Overlap is a subset: remove shared tris from this surface and create a new
    1457             :         // shared surface carrying both volume relationships and the BC type
    1458        2414 :         check(_moab->remove_entities(surf, overlap));
    1459             : 
    1460             :         // Append our new volume to the list that share this surf
    1461        2414 :         vols.push_back(voldata);
    1462             : 
    1463             :         // The shared tris may have been assigned a BC when 'surf' was created (e.g. by
    1464             :         // the region on the other side of an internal surface); merge that record with
    1465             :         // the current classification so the BC is not lost when the tris move to the
    1466             :         // new shared surface
    1467             :         auto merged_bc = bc_type;
    1468        2414 :         const auto existing_bc = recordedBoundaryCondition(surf);
    1469        2414 :         if (merged_bc == BoundaryConditionType::Transmission)
    1470             :           merged_bc = existing_bc;
    1471           0 :         else if (existing_bc != BoundaryConditionType::Transmission && existing_bc != merged_bc)
    1472           0 :           mooseError("A DAGMC surface was assigned both vacuum and reflective boundary "
    1473             :                      "conditions. This surface is shared between two skinned regions "
    1474             :                      "(e.g. an internal surface between two blocks) and received a "
    1475             :                      "different boundary condition from each side. Check 'vacuum_bcs_surfaces' "
    1476             :                      "and 'reflective_bcs_surfaces' for sidesets that cover the same mesh faces "
    1477             :                      "from opposite sides.");
    1478             : 
    1479             :         moab::EntityHandle shared_surf;
    1480        2414 :         surf_id++;
    1481        2414 :         createSurf(surf_id, shared_surf, overlap, vols, merged_bc);
    1482             :       }
    1483             : 
    1484             :       // Subtract from the input list
    1485       24149 :       for (auto & shared : overlap)
    1486       20872 :         faces.erase(shared);
    1487             : 
    1488        3277 :       if (faces.empty())
    1489             :         break;
    1490             :     }
    1491     1799117 :   }
    1492             : 
    1493        3590 :   if (!faces.empty())
    1494             :   {
    1495             :     moab::EntityHandle surface_set;
    1496        1861 :     std::vector<VolData> voldatavec(1, voldata);
    1497        1861 :     surf_id++;
    1498        1861 :     createSurf(surf_id, surface_set, faces, voldatavec, bc_type);
    1499        1861 :   }
    1500             : }
    1501             : 
    1502             : void
    1503          48 : MoabSkinner::buildGraveyard(unsigned int & vol_id, unsigned int & surf_id)
    1504             : {
    1505             :   // Create the graveyard group
    1506             :   moab::EntityHandle graveyard;
    1507          48 :   unsigned int id = nBins() + 1;
    1508          48 :   createGroup(id, "mat:Graveyard", graveyard);
    1509             : 
    1510             :   // Create a volume set
    1511             :   moab::EntityHandle volume_set;
    1512          48 :   createVol(++vol_id, volume_set, graveyard);
    1513             : 
    1514             :   // Set up for the volume data to pass to surfs
    1515          48 :   VolData vdata = {volume_set, Sense::FORWARDS};
    1516             : 
    1517             :   // Find a bounding box
    1518          48 :   BoundingBox bbox = MeshTools::create_bounding_box(getMooseMesh().getMesh());
    1519             : 
    1520             :   // Build the two cubic surfaces defining the graveyard
    1521          48 :   createSurfaceFromBox(
    1522             :       bbox, vdata, surf_id, false /* normals point into box */, _graveyard_scale_inner);
    1523          48 :   createSurfaceFromBox(
    1524             :       bbox, vdata, surf_id, true /* normals point out of box */, _graveyard_scale_outer);
    1525          48 : }
    1526             : 
    1527             : void
    1528          96 : MoabSkinner::createSurfaceFromBox(const BoundingBox & box,
    1529             :                                   const VolData & voldata,
    1530             :                                   unsigned int & surf_id,
    1531             :                                   bool normalout,
    1532             :                                   const Real & factor)
    1533             : {
    1534          96 :   std::vector<moab::EntityHandle> vert_handles = createNodesFromBox(box, factor);
    1535             : 
    1536             :   // Create the tris in 4 groups of 3 (4 open tetrahedra)
    1537             :   moab::Range tris;
    1538          96 :   createCornerTris(vert_handles, 0, 1, 2, 4, normalout, tris);
    1539          96 :   createCornerTris(vert_handles, 3, 2, 1, 7, normalout, tris);
    1540          96 :   createCornerTris(vert_handles, 6, 4, 2, 7, normalout, tris);
    1541          96 :   createCornerTris(vert_handles, 5, 1, 4, 7, normalout, tris);
    1542             : 
    1543             :   moab::EntityHandle surface_set;
    1544          96 :   std::vector<VolData> voldatavec(1, voldata);
    1545          96 :   surf_id++;
    1546          96 :   createSurf(surf_id, surface_set, tris, voldatavec);
    1547         192 : }
    1548             : 
    1549             : std::vector<moab::EntityHandle>
    1550          96 : MoabSkinner::createNodesFromBox(const BoundingBox & box, const Real & factor) const
    1551             : {
    1552             :   std::vector<moab::EntityHandle> vert_handles;
    1553             : 
    1554             :   // Fetch the vertices of the box
    1555          96 :   auto verts = geom_utils::boxCorners(box, factor);
    1556             : 
    1557             :   // Array to represent a coord in MOAB
    1558             :   double coord[3];
    1559             : 
    1560             :   // Create the vertices in MOAB and get the handles
    1561         864 :   for (const auto & vert : verts)
    1562             :   {
    1563         768 :     coord[0] = vert(0) * _scaling;
    1564         768 :     coord[1] = vert(1) * _scaling;
    1565         768 :     coord[2] = vert(2) * _scaling;
    1566             : 
    1567             :     moab::EntityHandle ent;
    1568         768 :     check(_moab->create_vertex(coord, ent));
    1569         768 :     vert_handles.push_back(ent);
    1570             :   }
    1571             : 
    1572          96 :   return vert_handles;
    1573          96 : }
    1574             : 
    1575             : void
    1576         384 : MoabSkinner::createCornerTris(const std::vector<moab::EntityHandle> & verts,
    1577             :                               unsigned int corner,
    1578             :                               unsigned int v1,
    1579             :                               unsigned int v2,
    1580             :                               unsigned int v3,
    1581             :                               bool normalout,
    1582             :                               moab::Range & surface_tris)
    1583             : {
    1584             :   // Create 3 tris stemming from one corner (i.e. an open tetrahedron)
    1585             :   // Assume first is the central corner, and the others are labelled clockwise looking down on the
    1586             :   // corner
    1587         384 :   unsigned int indices[3] = {v1, v2, v3};
    1588             : 
    1589             :   // Create each tri by a cyclic permutation of indices
    1590             :   // Values of i1, i2 in the loop: 0,1; 1,2; 2;0
    1591        1536 :   for (unsigned int i = 0; i < 3; i++)
    1592             :   {
    1593        1152 :     int i1 = indices[i % 3];
    1594        1152 :     int i2 = indices[(i + 1) % 3];
    1595        1152 :     if (normalout) // anti-clockwise: normal points outwards
    1596         576 :       surface_tris.insert(createTri(verts, corner, i2, i1));
    1597             :     else // clockwise: normal points inwards
    1598         576 :       surface_tris.insert(createTri(verts, corner, i1, i2));
    1599             :   }
    1600         384 : }
    1601             : 
    1602             : moab::EntityHandle
    1603        1152 : MoabSkinner::createTri(const std::vector<moab::EntityHandle> & vertices,
    1604             :                        unsigned int v1,
    1605             :                        unsigned int v2,
    1606             :                        unsigned int v3)
    1607             : {
    1608             :   moab::EntityHandle triangle;
    1609        1152 :   moab::EntityHandle connectivity[3] = {vertices[v1], vertices[v2], vertices[v3]};
    1610        1152 :   check(_moab->create_element(moab::MBTRI, connectivity, 3, triangle));
    1611        1152 :   return triangle;
    1612             : }
    1613             : 
    1614             : void
    1615          37 : MoabSkinner::setGraveyard(bool build)
    1616             : {
    1617          37 :   if (build != _build_graveyard)
    1618             :   {
    1619          34 :     std::string original = _build_graveyard ? "true" : "false";
    1620          34 :     std::string change = _build_graveyard ? "false" : "true";
    1621          17 :     mooseWarning("Overriding graveyard setting from ",
    1622             :                  original,
    1623             :                  " to ",
    1624             :                  change,
    1625             :                  ".\n"
    1626             :                  "To hide this warning, set 'build_graveyard = ",
    1627             :                  change,
    1628             :                  "'");
    1629             :   }
    1630             : 
    1631          36 :   _build_graveyard = build;
    1632          36 : }
    1633             : #endif

Generated by: LCOV version 1.14