LCOV - code coverage report
Current view: top level - src/userobjects - RayTracingStudy.C (source / functions) Hit Total Coverage
Test: idaholab/moose ray_tracing: #32971 (54bef8) with base c6cf66 Lines: 760 786 96.7 %
Date: 2026-05-29 20:39:07 Functions: 80 80 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : //* This file is part of the MOOSE framework
       2             : //* https://mooseframework.inl.gov
       3             : //*
       4             : //* All rights reserved, see COPYRIGHT for full restrictions
       5             : //* https://github.com/idaholab/moose/blob/master/COPYRIGHT
       6             : //*
       7             : //* Licensed under LGPL 2.1, please see LICENSE for details
       8             : //* https://www.gnu.org/licenses/lgpl-2.1.html
       9             : 
      10             : #include "RayTracingStudy.h"
      11             : 
      12             : #include "AuxRayKernel.h"
      13             : #include "RayBoundaryConditionBase.h"
      14             : #include "RayKernel.h"
      15             : #include "TraceRay.h"
      16             : #include "TraceRayTools.h"
      17             : #include "PeriodicRayBC.h"
      18             : 
      19             : #include "AuxiliarySystem.h"
      20             : #include "Assembly.h"
      21             : #include "NonlinearSystemBase.h"
      22             : 
      23             : #include "libmesh/enum_to_string.h"
      24             : #include "libmesh/mesh_tools.h"
      25             : #include "libmesh/parallel_sync.h"
      26             : #include "libmesh/remote_elem.h"
      27             : #include "libmesh/periodic_boundary.h"
      28             : #include "libmesh/periodic_boundaries.h"
      29             : 
      30             : using namespace libMesh;
      31             : 
      32             : InputParameters
      33        3980 : RayTracingStudy::validParams()
      34             : {
      35        3980 :   auto params = GeneralUserObject::validParams();
      36             : 
      37             :   // Parameters for the execution of the rays
      38        3980 :   params += ParallelRayStudy::validParams();
      39             : 
      40       11940 :   params.addRangeCheckedParam<Real>("ray_distance",
      41        7960 :                                     std::numeric_limits<Real>::max(),
      42             :                                     "ray_distance > 0",
      43             :                                     "The maximum distance all Rays can travel");
      44             : 
      45        7960 :   params.addParam<bool>(
      46        7960 :       "tolerate_failure", false, "Whether or not to tolerate a ray tracing failure");
      47             : 
      48        7960 :   MooseEnum work_buffers("lifo circular", "circular");
      49        7960 :   params.addParam<MooseEnum>("work_buffer_type", work_buffers, "The work buffer type to use");
      50             : 
      51        7960 :   params.addParam<bool>(
      52        7960 :       "ray_kernel_coverage_check", true, "Whether or not to perform coverage checks on RayKernels");
      53        7960 :   params.addParam<bool>("warn_non_planar",
      54        7960 :                         true,
      55             :                         "Whether or not to produce a warning if any element faces are non-planar.");
      56             : 
      57        7960 :   params.addParam<bool>(
      58             :       "always_cache_traces",
      59        7960 :       false,
      60             :       "Whether or not to cache the Ray traces on every execution, primarily for use in output. "
      61             :       "Warning: this can get expensive very quick with a large number of rays!");
      62        7960 :   params.addParam<bool>("data_on_cache_traces",
      63        7960 :                         false,
      64             :                         "Whether or not to also cache the Ray's data when caching its traces");
      65        7960 :   params.addParam<bool>("aux_data_on_cache_traces",
      66        7960 :                         false,
      67             :                         "Whether or not to also cache the Ray's aux data when caching its traces");
      68        7960 :   params.addParam<bool>(
      69             :       "segments_on_cache_traces",
      70        7960 :       true,
      71             :       "Whether or not to cache individual segments when trace caching is enabled. If false, we "
      72             :       "will instead cache a segment for each part of the trace where the direction is the same. "
      73             :       "This minimizes the number of segments requied to represent the Ray's path, but removes the "
      74             :       "ability to show Ray field data on each segment through an element.");
      75             : 
      76        7960 :   params.addParam<bool>("use_internal_sidesets",
      77        7960 :                         false,
      78             :                         "Whether or not to use internal sidesets for RayBCs in ray tracing");
      79             : 
      80        7960 :   params.addParam<bool>("warn_subdomain_hmax",
      81        7960 :                         true,
      82             :                         "Whether or not to warn if the approximated hmax (constant on subdomain) "
      83             :                         "varies significantly for an element");
      84             : 
      85        7960 :   params.addParam<bool>(
      86             :       "verify_rays",
      87        7960 :       true,
      88             :       "Whether or not to verify the generated Rays. This includes checking their "
      89             :       "starting information and the uniqueness of Rays before and after execution. This is also "
      90             :       "used by derived studies for more specific verification.");
      91        7960 :   params.addParam<bool>("verify_trace_intersections",
      92        7960 :                         true,
      93             :                         "Whether or not to verify the trace intersections in devel and dbg modes. "
      94             :                         "Trace intersections are not verified regardless of this parameter in "
      95             :                         "optimized modes (opt, oprof).");
      96             : 
      97        7960 :   params.addParam<bool>("allow_other_flags_with_prekernels",
      98        7960 :                         false,
      99             :                         "Whether or not to allow the list of execution flags to have PRE_KERNELS "
     100             :                         "mixed with other flags. If this parameter is not set then if PRE_KERNELS "
     101             :                         "is provided it must be the only execution flag.");
     102             : 
     103        3980 :   ExecFlagEnum & exec_enum = params.set<ExecFlagEnum>("execute_on", true);
     104        3980 :   exec_enum.addAvailableFlags(EXEC_PRE_KERNELS);
     105             : 
     106        7960 :   params.addParamNamesToGroup(
     107             :       "always_cache_traces data_on_cache_traces aux_data_on_cache_traces segments_on_cache_traces",
     108             :       "Trace cache");
     109        7960 :   params.addParamNamesToGroup("warn_non_planar warn_subdomain_hmax", "Tracing Warnings");
     110        7960 :   params.addParamNamesToGroup("ray_kernel_coverage_check verify_rays verify_trace_intersections",
     111             :                               "Checks and verifications");
     112             : 
     113             :   // Whether or not each Ray must be registered using the registerRay() API
     114        3980 :   params.addPrivateParam<bool>("_use_ray_registration", true);
     115             :   // Whether or not to bank Rays on completion
     116        3980 :   params.addPrivateParam<bool>("_bank_rays_on_completion", true);
     117             :   /// Whether or not subdomain setup is dependent on the Ray
     118        7960 :   params.addPrivateParam<bool>("_ray_dependent_subdomain_setup", true);
     119             : 
     120             :   // Add a point neighbor relationship manager
     121        7960 :   params.addRelationshipManager("ElementPointNeighborLayers",
     122             :                                 Moose::RelationshipManagerType::GEOMETRIC |
     123             :                                     Moose::RelationshipManagerType::ALGEBRAIC,
     124        5891 :                                 [](const InputParameters &, InputParameters & rm_params)
     125        5891 :                                 { rm_params.set<unsigned short>("layers") = 1; });
     126             : 
     127        3980 :   return params;
     128        3980 : }
     129             : 
     130        2029 : RayTracingStudy::RayTracingStudy(const InputParameters & parameters)
     131             :   : GeneralUserObject(parameters),
     132        2029 :     _mesh(_fe_problem.mesh()),
     133        2029 :     _comm(_mesh.comm()),
     134        2029 :     _pid(_comm.rank()),
     135             : 
     136        4058 :     _ray_kernel_coverage_check(getParam<bool>("ray_kernel_coverage_check")),
     137        4058 :     _warn_non_planar(getParam<bool>("warn_non_planar")),
     138        4058 :     _use_ray_registration(getParam<bool>("_use_ray_registration")),
     139        4058 :     _use_internal_sidesets(getParam<bool>("use_internal_sidesets")),
     140        4058 :     _tolerate_failure(getParam<bool>("tolerate_failure")),
     141        4058 :     _bank_rays_on_completion(getParam<bool>("_bank_rays_on_completion")),
     142        4058 :     _ray_dependent_subdomain_setup(getParam<bool>("_ray_dependent_subdomain_setup")),
     143             : 
     144        4058 :     _always_cache_traces(getParam<bool>("always_cache_traces")),
     145        4058 :     _data_on_cache_traces(getParam<bool>("data_on_cache_traces")),
     146        4058 :     _aux_data_on_cache_traces(getParam<bool>("aux_data_on_cache_traces")),
     147        4058 :     _segments_on_cache_traces(getParam<bool>("segments_on_cache_traces")),
     148        4058 :     _ray_max_distance(getParam<Real>("ray_distance")),
     149        4058 :     _verify_rays(getParam<bool>("verify_rays")),
     150             : #ifndef NDEBUG
     151             :     _verify_trace_intersections(getParam<bool>("verify_trace_intersections")),
     152             : #endif
     153             : 
     154        2029 :     _threaded_elem_side_builders(libMesh::n_threads()),
     155             : 
     156        2029 :     _registered_ray_map(
     157        2029 :         declareRestartableData<std::unordered_map<std::string, RayID>>("registered_ray_map")),
     158        2029 :     _reverse_registered_ray_map(
     159        4058 :         declareRestartableData<std::vector<std::string>>("reverse_registered_ray_map")),
     160             : 
     161        2029 :     _threaded_cached_traces(libMesh::n_threads()),
     162             : 
     163        2029 :     _num_cached(libMesh::n_threads(), 0),
     164             : 
     165        2029 :     _has_non_planar_sides(true),
     166        2029 :     _has_same_level_active_elems(sameLevelActiveElems()),
     167             : 
     168        2029 :     _b_box(MeshTools::create_nodal_bounding_box(_mesh.getMesh())),
     169        2029 :     _domain_max_length(1.01 * (_b_box.max() - _b_box.min()).norm()),
     170        2029 :     _total_volume(computeTotalVolume()),
     171             : 
     172        2029 :     _threaded_cache_ray_kernel(libMesh::n_threads()),
     173        2029 :     _threaded_cache_ray_bc(libMesh::n_threads()),
     174        2029 :     _threaded_ray_object_registration(libMesh::n_threads()),
     175        2029 :     _threaded_current_ray_kernels(libMesh::n_threads()),
     176        2029 :     _threaded_trace_ray(libMesh::n_threads()),
     177        2029 :     _threaded_fe_face(libMesh::n_threads()),
     178        2029 :     _threaded_q_face(libMesh::n_threads()),
     179        2029 :     _threaded_cached_normals(libMesh::n_threads()),
     180        2029 :     _threaded_next_ray_id(libMesh::n_threads()),
     181             : 
     182        2029 :     _parallel_ray_study(std::make_unique<ParallelRayStudy>(*this, _threaded_trace_ray)),
     183             : 
     184        2029 :     _local_trace_ray_results(TraceRay::FAILED_TRACES + 1, 0),
     185             : 
     186        2029 :     _called_initial_setup(false),
     187             : 
     188        8116 :     _elem_index_helper(_mesh.getMesh(), name() + "_elem_index")
     189             : {
     190             :   // Initialize a tracing object for each thread
     191        4395 :   for (THREAD_ID tid = 0; tid < libMesh::n_threads(); ++tid)
     192             :   {
     193             :     // Initialize a tracing object for each thread
     194        4732 :     _threaded_trace_ray[tid] = std::make_shared<TraceRay>(*this, tid);
     195             : 
     196             :     // Setup the face FEs for normal computation on the fly
     197        4732 :     _threaded_fe_face[tid] = FEBase::build(_mesh.dimension(), FEType(CONSTANT, MONOMIAL));
     198        4732 :     _threaded_q_face[tid] = QBase::build(QGAUSS, _mesh.dimension() - 1, CONSTANT);
     199             :     _threaded_fe_face[tid]->attach_quadrature_rule(_threaded_q_face[tid].get());
     200             :     _threaded_fe_face[tid]->get_normals();
     201             :   }
     202             : 
     203             :   // Evaluating on residual and Jacobian evaluation
     204        2029 :   if (_execute_enum.isValueSet(EXEC_PRE_KERNELS))
     205             :   {
     206         282 :     if (!getParam<bool>("allow_other_flags_with_prekernels") && _execute_enum.size() > 1)
     207           2 :       paramError("execute_on",
     208             :                  "PRE_KERNELS cannot be mixed with any other execution flag.\nThat is, you cannot "
     209             :                  "currently "
     210             :                  "mix RayKernels that contribute to the Jacobian/residual with those that do not.");
     211             : 
     212          92 :     if (_app.useEigenvalue())
     213           2 :       mooseError("Execution on residual and Jacobian evaluation (execute_on = PRE_KERNELS)\n",
     214             :                  "is not supported for an eigenvalue solve.");
     215             :   }
     216             : 
     217        2025 :   resetUniqueRayIDs();
     218        2025 :   resetReplicatedRayIDs();
     219             : 
     220             :   // Scale the bounding box for loose checking
     221             :   _loose_b_box = _b_box;
     222        2025 :   _loose_b_box.scale(TOLERANCE * TOLERANCE);
     223        2025 : }
     224             : 
     225             : void
     226        1911 : RayTracingStudy::initialSetup()
     227             : {
     228             :   // Keep track of initialSetup call to avoid registration of various things
     229        1911 :   _called_initial_setup = true;
     230             : 
     231             :   // Sets up a local index for each elem this proc knows about
     232        1911 :   localElemIndexSetup();
     233             : 
     234             :   // Check for RayKernel coverage
     235        1911 :   coverageChecks();
     236             : 
     237             :   // Make sure the dependencies exist, if any
     238        1909 :   dependencyChecks();
     239             : 
     240             :   // Check for traceable element types
     241        1905 :   traceableMeshChecks();
     242             : 
     243             :   // Check for sane periodic boundaries
     244        1903 :   periodicBoundaryChecks();
     245             : 
     246             :   // Setup for internal sidesets
     247        1901 :   internalSidesetSetup();
     248             : 
     249        1897 :   nonPlanarSideSetup();
     250             : 
     251             :   // Setup approximate hmax for each subdomain
     252        1895 :   subdomainHMaxSetup();
     253             : 
     254             :   // Call initial setup on all of the objects
     255        5952 :   for (auto & rto : getRayTracingObjects())
     256        5952 :     rto->initialSetup();
     257             : 
     258             :   // Check for proper exec flags with RayKernels
     259             :   std::vector<RayKernelBase *> ray_kernels;
     260        1893 :   getRayKernels(ray_kernels, 0);
     261        3475 :   for (const auto & rkb : ray_kernels)
     262        1584 :     if (dynamic_cast<RayKernel *>(rkb) && !_execute_enum.isValueSet(EXEC_PRE_KERNELS))
     263           2 :       mooseError("This study has RayKernel objects that contribute to residuals and Jacobians.",
     264             :                  "\nIn this case, the study must use the execute_on = PRE_KERNELS");
     265             : 
     266             :   // Build 1D quadrature rule for along a segment
     267             :   _segment_qrule =
     268        3782 :       QBase::build(QGAUSS, 1, _fe_problem.getSystemBase(_sys.number()).getMinQuadratureOrder());
     269        1891 : }
     270             : 
     271             : void
     272        3328 : RayTracingStudy::residualSetup()
     273             : {
     274             :   for (THREAD_ID tid = 0; tid < libMesh::n_threads(); ++tid)
     275             :     mooseAssert(_num_cached[tid] == 0, "Cached residuals/Jacobians not empty");
     276             : 
     277        8595 :   for (auto & rto : getRayTracingObjects())
     278        8595 :     rto->residualSetup();
     279        3328 : }
     280             : 
     281             : void
     282         180 : RayTracingStudy::jacobianSetup()
     283             : {
     284             :   for (THREAD_ID tid = 0; tid < libMesh::n_threads(); ++tid)
     285             :     mooseAssert(_num_cached[tid] == 0, "Cached residuals/Jacobians not empty");
     286             : 
     287         636 :   for (auto & rto : getRayTracingObjects())
     288         636 :     rto->jacobianSetup();
     289         180 : }
     290             : 
     291             : void
     292        1955 : RayTracingStudy::timestepSetup()
     293             : {
     294        5923 :   for (auto & rto : getRayTracingObjects())
     295        5923 :     rto->timestepSetup();
     296        1955 : }
     297             : 
     298             : void
     299         132 : RayTracingStudy::meshChanged()
     300             : {
     301         132 :   localElemIndexSetup();
     302         132 :   internalSidesetSetup();
     303         132 :   nonPlanarSideSetup();
     304         132 :   subdomainHMaxSetup();
     305             : 
     306         132 :   _has_same_level_active_elems = sameLevelActiveElems();
     307             : 
     308         282 :   for (const auto & trace_ray : _threaded_trace_ray)
     309         150 :     trace_ray->meshChanged();
     310         132 : }
     311             : 
     312             : void
     313        5360 : RayTracingStudy::execute()
     314             : {
     315        5360 :   executeStudy();
     316        5260 : }
     317             : 
     318             : void
     319        1911 : RayTracingStudy::coverageChecks()
     320             : {
     321             :   // Check for coverage of RayKernels on domain
     322        1911 :   if (_ray_kernel_coverage_check)
     323             :   {
     324             :     std::vector<RayKernelBase *> ray_kernels;
     325        1406 :     getRayKernels(ray_kernels, 0);
     326             : 
     327             :     std::set<SubdomainID> ray_kernel_blocks;
     328        2978 :     for (const auto & rk : ray_kernels)
     329        3144 :       ray_kernel_blocks.insert(rk->blockIDs().begin(), rk->blockIDs().end());
     330             : 
     331             :     std::set<SubdomainID> missing;
     332        2812 :     std::set_difference(_mesh.meshSubdomains().begin(),
     333        1406 :                         _mesh.meshSubdomains().end(),
     334             :                         ray_kernel_blocks.begin(),
     335             :                         ray_kernel_blocks.end(),
     336             :                         std::inserter(missing, missing.begin()));
     337             : 
     338        1406 :     if (!missing.empty() && !ray_kernel_blocks.count(Moose::ANY_BLOCK_ID))
     339             :     {
     340           2 :       std::ostringstream error;
     341           2 :       error << "Subdomains { ";
     342           2 :       std::copy(missing.begin(), missing.end(), std::ostream_iterator<SubdomainID>(error, " "));
     343           2 :       error << "} do not have RayKernels defined!";
     344             : 
     345           2 :       mooseError(error.str());
     346           0 :     }
     347        1404 :   }
     348        1909 : }
     349             : 
     350             : void
     351        1909 : RayTracingStudy::dependencyChecks()
     352             : {
     353             :   std::vector<RayTracingObject *> ray_tracing_objects;
     354             : 
     355        1909 :   getRayKernels(ray_tracing_objects, 0);
     356        1909 :   verifyDependenciesExist(ray_tracing_objects);
     357             : 
     358        1907 :   getRayBCs(ray_tracing_objects, 0);
     359        1907 :   verifyDependenciesExist(ray_tracing_objects);
     360        1905 : }
     361             : 
     362             : void
     363        3816 : RayTracingStudy::verifyDependenciesExist(const std::vector<RayTracingObject *> & rtos)
     364             : {
     365        7429 :   for (const auto & rto : rtos)
     366        3677 :     for (const auto & dep_name : rto->getRequestedItems())
     367             :     {
     368             :       bool found = false;
     369         136 :       for (const auto & rto_search : rtos)
     370         132 :         if (rto_search->name() == dep_name)
     371             :         {
     372             :           found = true;
     373             :           break;
     374             :         }
     375             : 
     376          64 :       if (!found)
     377           4 :         rto->paramError("depends_on", "The ", rto->getBase(), " '", dep_name, "' does not exist");
     378             :     }
     379        3812 : }
     380             : 
     381             : void
     382        1905 : RayTracingStudy::traceableMeshChecks()
     383             : {
     384      270851 :   for (const auto & elem : *_mesh.getActiveLocalElementRange())
     385             :   {
     386      268948 :     if (_fe_problem.adaptivity().isOn())
     387             :     {
     388        3310 :       if (!TraceRayTools::isAdaptivityTraceableElem(elem))
     389           2 :         mooseError("Element type ",
     390           2 :                    Utility::enum_to_string(elem->type()),
     391             :                    " is not supported in ray tracing with adaptivity");
     392             :     }
     393      265638 :     else if (!TraceRayTools::isTraceableElem(elem))
     394           0 :       mooseError("Element type ",
     395           0 :                  Utility::enum_to_string(elem->type()),
     396             :                  " is not supported in ray tracing");
     397             :   }
     398        1903 : }
     399             : 
     400             : void
     401        1903 : RayTracingStudy::periodicBoundaryChecks()
     402             : {
     403             :   // Collect the PeriodicRayBCs
     404             :   std::vector<const RayBoundaryConditionBase *> rbc_ptrs;
     405        1903 :   getRayBCs(rbc_ptrs, 0);
     406             :   std::vector<const PeriodicRayBC *> prbc_ptrs;
     407        3930 :   for (const auto rbc_ptr : rbc_ptrs)
     408        2027 :     if (const auto prbc_ptr = dynamic_cast<const PeriodicRayBC *>(rbc_ptr))
     409          27 :       prbc_ptrs.push_back(prbc_ptr);
     410        1903 :   if (prbc_ptrs.empty())
     411             :     return;
     412             : 
     413             :   // Collect each of the periodic boundaries
     414             :   std::map<boundary_id_type,
     415             :            std::tuple<const PeriodicRayBC *,
     416             :                       const libMesh::PeriodicBoundaryBase *,
     417             :                       std::unordered_set<dof_id_type>>>
     418             :       boundary_map;
     419          50 :   for (const auto prbc_ptr : prbc_ptrs)
     420             :   {
     421         123 :     for (const auto & [bid, pb] : prbc_ptr->getPeriodicBoundaries())
     422             :     {
     423          98 :       const auto [it, inserted] = boundary_map.emplace(
     424             :           std::piecewise_construct,
     425          98 :           std::tuple{bid},
     426          98 :           std::forward_as_tuple(prbc_ptr, pb.get(), std::unordered_set<dof_id_type>()));
     427          98 :       if (!inserted)
     428           2 :         prbc_ptr->mooseError("The periodic boundary '",
     429           2 :                              _mesh.getBoundaryString(bid),
     430             :                              "' has been defined in both ",
     431           2 :                              prbc_ptr->typeAndName(),
     432             :                              " and ",
     433           2 :                              std::get<0>(it->second)->typeAndName());
     434             :     }
     435             :   }
     436             : 
     437             :   // Because we don't have ghosting setup correctly yet, we need to check if any
     438             :   // of the periodic boundaries are neighbors with distributed mesh. If the
     439             :   // mesh is replicated, we don't need to check this. See #31280.
     440          23 :   if (comm().size() == 1 || !_mesh.isDistributedMesh())
     441             :     return;
     442             : 
     443             :   // Collect all of the nodes that are on each periodic boundary
     444           2 :   const auto & sideset_map = _mesh.getMesh().get_boundary_info().get_sideset_map();
     445           6 :   for (const auto & [elem, side_bid_pair] : sideset_map)
     446             :   {
     447           4 :     const auto [side, bid] = side_bid_pair;
     448           4 :     if (auto it = boundary_map.find(bid); it != boundary_map.end())
     449           8 :       for (const auto n : elem->nodes_on_side(side))
     450           8 :         std::get<2>(it->second).insert(elem->node_ref(n).id());
     451             :   }
     452             : 
     453             :   // Distributed meshes have distributed boundary information, so sync
     454           6 :   for (auto & bid_tuple_pair : boundary_map)
     455           4 :     comm().set_union(std::get<2>(bid_tuple_pair.second));
     456             : 
     457             :   // Check for periodic boundaries that share nodes
     458             :   std::map<std::pair<boundary_id_type, boundary_id_type>,
     459             :            std::pair<const PeriodicRayBC *, const PeriodicRayBC *>>
     460             :       warn_boundaries;
     461           6 :   for (auto it = boundary_map.begin(); it != boundary_map.end(); ++it)
     462             :   {
     463             :     const auto & [bid, tup] = *it;
     464             :     const auto [prbc_ptr, pb, node_ids] = tup;
     465             : 
     466           6 :     for (auto other_it = std::next(it); other_it != boundary_map.end(); ++other_it)
     467             :     {
     468             :       const auto & [other_bid, other_tup] = *other_it;
     469             : 
     470             :       // Don't check against boundaries that are paried together
     471           2 :       if (pb->pairedboundary == other_bid)
     472             :         continue;
     473             : 
     474           0 :       const auto other_prbc_ptr = std::get<0>(other_tup);
     475             :       const auto & other_node_ids = std::get<2>(other_tup);
     476           0 :       for (const auto node_id : node_ids)
     477             :         if (other_node_ids.count(node_id))
     478             :         {
     479           0 :           if (!warn_boundaries.count(std::make_pair(other_bid, bid)))
     480           0 :             warn_boundaries.emplace(std::make_pair(bid, other_bid),
     481           0 :                                     std::make_pair(prbc_ptr, other_prbc_ptr));
     482             :           break;
     483             :         }
     484             :     }
     485             :   }
     486             : 
     487           2 :   if (warn_boundaries.size())
     488             :   {
     489           0 :     std::ostringstream oss;
     490             :     oss << warn_boundaries.size()
     491           0 :         << " ray tracing periodic boundaries were found to be neighbors:\n\n";
     492           0 :     for (const auto & [bids_pair, prbc_ptrs_pair] : warn_boundaries)
     493             :     {
     494           0 :       const auto [bid, paired_bid] = bids_pair;
     495           0 :       const auto [prbc_ptr, paired_prbc_ptr] = prbc_ptrs_pair;
     496           0 :       oss << "  '" << _mesh.getBoundaryString(bid) << "' (in " << prbc_ptr->typeAndName()
     497           0 :           << ") <-> '" << _mesh.getBoundaryString(paired_bid) << "' (in "
     498           0 :           << paired_prbc_ptr->typeAndName() << ")\n";
     499             :     }
     500             :     oss << "\nThe periodic propagation of rays at points where two or more periodic"
     501             :         << "\nboundaries meet is not fully supported with a distributed mesh."
     502           0 :         << "\n\nIf you encounter trace failures, you should use a replicated mesh.";
     503           0 :     mooseWarning(oss.str());
     504           0 :   }
     505        1901 : }
     506             : 
     507             : void
     508        2043 : RayTracingStudy::localElemIndexSetup()
     509             : {
     510             :   // TODO: We could probably minimize this to local active elements followed by
     511             :   // boundary point neighbors, but if using distibuted mesh it really shouldn't matter
     512        2043 :   _elem_index_helper.initialize(_mesh.getMesh().active_element_ptr_range());
     513        2043 : }
     514             : 
     515             : void
     516        2033 : RayTracingStudy::internalSidesetSetup()
     517             : {
     518             :   // Even if we have _use_internal_sidesets == false, we will make sure the user didn't add RayBCs
     519             :   // on internal boundaries
     520             : 
     521             :   // Clear the data structures and size the map based on the elements that we know about
     522             :   _internal_sidesets.clear();
     523        2033 :   _internal_sidesets_map.clear();
     524        2033 :   _internal_sidesets_map.resize(_elem_index_helper.maxIndex() + 1);
     525             : 
     526             :   // First, we are going to store all elements with internal sidesets (if any) that have active
     527             :   // RayBCs on them as elem -> vector of (side, vector of boundary ids)
     528      148361 :   for (const auto & bnd_elem : *_mesh.getBoundaryElementRange())
     529             :   {
     530      146330 :     Elem * elem = bnd_elem->_elem;
     531      146330 :     const unsigned int side = bnd_elem->_side;
     532      146330 :     const auto bnd_id = bnd_elem->_bnd_id;
     533             : 
     534             :     // Not internal
     535             :     const Elem * const neighbor = elem->neighbor_ptr(side);
     536      146330 :     if (!neighbor || neighbor == remote_elem)
     537      142760 :       continue;
     538             : 
     539             :     // No RayBCs on this sideset
     540             :     std::vector<RayBoundaryConditionBase *> result;
     541        3570 :     getRayBCs(result, bnd_id, 0);
     542        3570 :     if (result.empty())
     543             :       continue;
     544             : 
     545        3570 :     if (neighbor->subdomain_id() == elem->subdomain_id())
     546           2 :       mooseError("RayBCs exist on internal sidesets that are not bounded by a different",
     547             :                  "\nsubdomain on each side.",
     548             :                  "\n\nIn order to use RayBCs on internal sidesets, said sidesets must have",
     549             :                  "\na different subdomain on each side.");
     550             : 
     551             :     // Mark that this boundary is an internal sideset with RayBC(s)
     552        3568 :     _internal_sidesets.insert(bnd_id);
     553             : 
     554             :     // Get elem's entry in the internal sidset data structure
     555             :     const auto index = _elem_index_helper.getIndex(elem);
     556             :     auto & entry = _internal_sidesets_map[index];
     557             : 
     558             :     // Initialize this elem's sides if they have not been already
     559        3568 :     if (entry.empty())
     560        2772 :       entry.resize(elem->n_sides(), std::vector<BoundaryID>());
     561             : 
     562             :     // Add the internal boundary to the side entry
     563        3568 :     entry[side].push_back(bnd_id);
     564        3568 :   }
     565             : 
     566        2031 :   if (!_use_internal_sidesets && _internal_sidesets.size())
     567           2 :     mooseError("RayBCs are defined on internal sidesets, but the study is not set to use ",
     568             :                "internal sidesets during tracing.",
     569             :                "\n\nSet the parameter use_internal_sidesets = true to enable this capability.");
     570        2029 : }
     571             : 
     572             : void
     573        2029 : RayTracingStudy::nonPlanarSideSetup()
     574             : {
     575        2029 :   _has_non_planar_sides = false;
     576        2029 :   bool warned = !_warn_non_planar;
     577             : 
     578             :   // Nothing to do here for 2D or 1D
     579        2029 :   if (_mesh.dimension() != 3)
     580             :     return;
     581             : 
     582             :   // Clear the data structure and size it based on the elements that we know about
     583         765 :   _non_planar_sides.clear();
     584         765 :   _non_planar_sides.resize(_elem_index_helper.maxIndex() + 1);
     585             : 
     586      907772 :   for (const Elem * elem : _mesh.getMesh().active_element_ptr_range())
     587             :   {
     588             :     const auto index = _elem_index_helper.getIndex(elem);
     589             :     auto & entry = _non_planar_sides[index];
     590      302082 :     entry.resize(elem->n_sides(), 0);
     591             : 
     592     1713816 :     for (const auto s : elem->side_index_range())
     593             :     {
     594     1411736 :       const auto & side = elemSide(*elem, s);
     595     1411736 :       if (side.n_vertices() < 4)
     596     1018080 :         continue;
     597             : 
     598      393656 :       if (!side.has_affine_map())
     599             :       {
     600        6318 :         entry[s] = 1;
     601        6318 :         _has_non_planar_sides = true;
     602             : 
     603        6318 :         if (!warned)
     604             :         {
     605           2 :           mooseWarning("The mesh contains non-planar faces.\n\n",
     606             :                        "Ray tracing on non-planar faces is an approximation and may fail.\n\n",
     607             :                        "Use at your own risk! You can disable this warning by setting the\n",
     608             :                        "parameter 'warn_non_planar' to false.");
     609             :           warned = true;
     610             :         }
     611             :       }
     612             :     }
     613         763 :   }
     614             : }
     615             : 
     616             : void
     617        2027 : RayTracingStudy::subdomainHMaxSetup()
     618             : {
     619             :   // Setup map with subdomain keys
     620             :   _subdomain_hmax.clear();
     621        4427 :   for (const auto subdomain_id : _mesh.meshSubdomains())
     622        2400 :     _subdomain_hmax[subdomain_id] = std::numeric_limits<Real>::min();
     623             : 
     624             :   // Set local max for each subdomain
     625      275965 :   for (const auto & elem : *_mesh.getActiveLocalElementRange())
     626             :   {
     627      273938 :     auto & entry = _subdomain_hmax.at(elem->subdomain_id());
     628      276655 :     entry = std::max(entry, elem->hmax());
     629             :   }
     630             : 
     631             :   // Accumulate global max for each subdomain
     632        2027 :   _communicator.max(_subdomain_hmax);
     633             : 
     634        4054 :   if (getParam<bool>("warn_subdomain_hmax"))
     635             :   {
     636        2027 :     const auto warn_prefix = type() + " '" + name() + "': ";
     637        2027 :     const auto warn_suffix =
     638             :         "\n\nRay tracing uses an approximate element size for each subdomain to scale the\n"
     639             :         "tolerances used in computing ray intersections. This warning suggests that the\n"
     640             :         "approximate element size is not a good approximation. This is likely due to poor\n"
     641             :         "element aspect ratios.\n\n"
     642             :         "This warning is only output for the first element affected.\n"
     643             :         "To disable this warning, set warn_subdomain_hmax = false.\n";
     644             : 
     645      275963 :     for (const auto & elem : *_mesh.getActiveLocalElementRange())
     646             :     {
     647      273938 :       const auto hmin = elem->hmin();
     648      273938 :       const auto hmax = elem->hmax();
     649      273938 :       const auto max_hmax = subdomainHmax(elem->subdomain_id());
     650             : 
     651      273938 :       const auto hmax_rel = hmax / max_hmax;
     652      273938 :       if (hmax_rel < 1.e-2 || hmax_rel > 1.e2)
     653           0 :         mooseDoOnce(mooseWarning(warn_prefix,
     654             :                                  "Element hmax varies significantly from subdomain hmax.\n",
     655             :                                  warn_suffix,
     656             :                                  "First element affected:\n",
     657             :                                  Moose::stringify(*elem)););
     658             : 
     659      273938 :       const auto h_rel = max_hmax / hmin;
     660      273938 :       if (h_rel > 1.e2)
     661           2 :         mooseDoOnce(mooseWarning(warn_prefix,
     662             :                                  "Element hmin varies significantly from subdomain hmax.\n",
     663             :                                  warn_suffix,
     664             :                                  "First element affected:\n",
     665             :                                  Moose::stringify(*elem)););
     666             :     }
     667             :   }
     668        2025 : }
     669             : 
     670             : void
     671        5294 : RayTracingStudy::registeredRaySetup()
     672             : {
     673             :   // First, clear the objects associated with each Ray on each thread
     674        5294 :   const auto num_rays = _registered_ray_map.size();
     675       11396 :   for (auto & entry : _threaded_ray_object_registration)
     676             :   {
     677        6102 :     entry.clear();
     678        6102 :     entry.resize(num_rays);
     679             :   }
     680             : 
     681        5294 :   const auto rtos = getRayTracingObjects();
     682             : 
     683        5294 :   if (_use_ray_registration)
     684             :   {
     685             :     // All of the registered ray names - used when a RayTracingObject did not specify
     686             :     // any Rays so it should be associated with all Rays.
     687             :     std::vector<std::string> all_ray_names;
     688        3563 :     all_ray_names.reserve(_registered_ray_map.size());
     689       11622 :     for (const auto & pair : _registered_ray_map)
     690        8059 :       all_ray_names.push_back(pair.first);
     691             : 
     692        8974 :     for (auto & rto : rtos)
     693             :     {
     694             :       // The Ray names associated with this RayTracingObject
     695        5413 :       const auto & ray_names = rto->parameters().get<std::vector<std::string>>("rays");
     696             :       // The registration for RayTracingObjects for the thread rto is on
     697        5413 :       const auto tid = rto->parameters().get<THREAD_ID>("_tid");
     698        5413 :       auto & registration = _threaded_ray_object_registration[tid];
     699             : 
     700             :       // Register each Ray for this object in the registration
     701       17197 :       for (const auto & ray_name : (ray_names.empty() ? all_ray_names : ray_names))
     702             :       {
     703       10401 :         const auto id = registeredRayID(ray_name, /* graceful = */ true);
     704       10401 :         if (ray_names.size() && id == Ray::INVALID_RAY_ID)
     705           2 :           rto->paramError(
     706           2 :               "rays", "Supplied ray '", ray_name, "' is not a registered Ray in ", typeAndName());
     707       10399 :         registration[id].insert(rto);
     708             :       }
     709             :     }
     710        3561 :   }
     711             :   // Not using Ray registration
     712             :   else
     713             :   {
     714        5205 :     for (const auto & rto : rtos)
     715        3476 :       if (rto->parameters().get<std::vector<std::string>>("rays").size())
     716           2 :         rto->paramError(
     717             :             "rays",
     718             :             "Rays cannot be supplied when the study does not require Ray registration.\n\n",
     719             :             type(),
     720             :             " does not require Ray registration.");
     721             :   }
     722        5290 : }
     723             : 
     724             : void
     725        5360 : RayTracingStudy::zeroAuxVariables()
     726             : {
     727             :   std::set<std::string> vars_to_be_zeroed;
     728             :   std::vector<RayKernelBase *> ray_kernels;
     729        5360 :   getRayKernels(ray_kernels, 0);
     730       10832 :   for (auto & rk : ray_kernels)
     731             :   {
     732        5472 :     AuxRayKernel * aux_rk = dynamic_cast<AuxRayKernel *>(rk);
     733        5472 :     if (aux_rk)
     734          40 :       vars_to_be_zeroed.insert(aux_rk->variable().name());
     735             :   }
     736             : 
     737             :   std::vector<std::string> vars_to_be_zeroed_vec(vars_to_be_zeroed.begin(),
     738        5360 :                                                  vars_to_be_zeroed.end());
     739        5360 :   _fe_problem.getAuxiliarySystem().zeroVariables(vars_to_be_zeroed_vec);
     740       10720 : }
     741             : 
     742             : void
     743     2997087 : RayTracingStudy::segmentSubdomainSetup(const SubdomainID subdomain,
     744             :                                        const THREAD_ID tid,
     745             :                                        const RayID ray_id)
     746             : {
     747             :   mooseAssert(currentlyPropagating(), "Should not call while not propagating");
     748             : 
     749             :   // Call subdomain setup on FE
     750     2997087 :   _fe_problem.subdomainSetup(subdomain, tid);
     751             : 
     752             :   std::set<MooseVariableFEBase *> needed_moose_vars;
     753             :   std::unordered_set<unsigned int> needed_mat_props;
     754             : 
     755             :   // Get RayKernels and their dependencies and call subdomain setup
     756     2997087 :   getRayKernels(_threaded_current_ray_kernels[tid], subdomain, tid, ray_id);
     757     5998762 :   for (auto & rkb : _threaded_current_ray_kernels[tid])
     758             :   {
     759     3001675 :     rkb->subdomainSetup();
     760             : 
     761     3001675 :     const auto & mv_deps = rkb->getMooseVariableDependencies();
     762     3001675 :     needed_moose_vars.insert(mv_deps.begin(), mv_deps.end());
     763             : 
     764     3001675 :     const auto & mp_deps = rkb->getMatPropDependencies();
     765     3001675 :     needed_mat_props.insert(mp_deps.begin(), mp_deps.end());
     766             :   }
     767             : 
     768             :   // Prepare aux vars
     769     3022169 :   for (auto & var : needed_moose_vars)
     770       25082 :     if (var->kind() == Moose::VarKindType::VAR_AUXILIARY)
     771        5156 :       var->prepareAux();
     772             : 
     773     2997087 :   _fe_problem.setActiveElementalMooseVariables(needed_moose_vars, tid);
     774     2997087 :   _fe_problem.prepareMaterials(needed_mat_props, subdomain, tid);
     775     2997087 : }
     776             : 
     777             : void
     778    22560690 : RayTracingStudy::reinitSegment(
     779             :     const Elem * elem, const Point & start, const Point & end, const Real length, THREAD_ID tid)
     780             : {
     781             :   mooseAssert(MooseUtils::absoluteFuzzyEqual((start - end).norm(), length), "Invalid length");
     782             :   mooseAssert(currentlyPropagating(), "Should not call while not propagating");
     783             : 
     784    22560690 :   _fe_problem.setCurrentSubdomainID(elem, tid);
     785             : 
     786             :   // If we have any variables or material properties that are active, we definitely need to reinit
     787    45044792 :   bool reinit = _fe_problem.hasActiveElementalMooseVariables(tid) ||
     788    22484102 :                 _fe_problem.hasActiveMaterialProperties(tid);
     789             :   // If not, make sure that the RayKernels have not requested a reinit (this could happen when a
     790             :   // RayKernel doesn't have variables or materials but still does an integration and needs qps)
     791             :   if (!reinit)
     792    44967508 :     for (const RayKernelBase * rk : currentRayKernels(tid))
     793    22483886 :       if (rk->needSegmentReinit())
     794             :       {
     795             :         reinit = true;
     796             :         break;
     797             :       }
     798             : 
     799    22560690 :   if (reinit)
     800             :   {
     801       77068 :     _fe_problem.prepare(elem, tid);
     802             : 
     803             :     std::vector<Point> points;
     804             :     std::vector<Real> weights;
     805       77068 :     buildSegmentQuadrature(start, end, length, points, weights);
     806       77068 :     _fe_problem.reinitElemPhys(elem, points, tid);
     807       77068 :     _fe_problem.assembly(tid, _sys.number()).modifyArbitraryWeights(weights);
     808             : 
     809       77068 :     _fe_problem.reinitMaterials(elem->subdomain_id(), tid);
     810       77068 :   }
     811    22560690 : }
     812             : 
     813             : void
     814       77068 : RayTracingStudy::buildSegmentQuadrature(const Point & start,
     815             :                                         const Point & end,
     816             :                                         const Real length,
     817             :                                         std::vector<Point> & points,
     818             :                                         std::vector<Real> & weights) const
     819             : {
     820       77068 :   points.resize(_segment_qrule->n_points());
     821       77068 :   weights.resize(_segment_qrule->n_points());
     822             : 
     823             :   const Point diff = end - start;
     824             :   const Point sum = end + start;
     825             :   mooseAssert(MooseUtils::absoluteFuzzyEqual(length, diff.norm()), "Invalid length");
     826             : 
     827             :   // The standard quadrature rule should be on x = [-1, 1]
     828             :   // To scale the points, you...
     829             :   //  - Scale to size of the segment in 3D
     830             :   //    initial_scaled_qp = x_qp * 0.5 * (end - start) = 0.5 * x_qp * diff
     831             :   //  - Shift quadrature midpoint to segment midpoint
     832             :   //    final_qp = initial_scaled_qp + 0.5 * (end - start) = initial_scaled_qp + 0.5 * sum
     833             :   //             = 0.5 * (x_qp * diff + sum)
     834      216188 :   for (unsigned int qp = 0; qp < _segment_qrule->n_points(); ++qp)
     835             :   {
     836      139120 :     points[qp] = 0.5 * (_segment_qrule->qp(qp)(0) * diff + sum);
     837      139120 :     weights[qp] = 0.5 * _segment_qrule->w(qp) * length;
     838             :   }
     839       77068 : }
     840             : 
     841             : void
     842    23617483 : RayTracingStudy::postOnSegment(const THREAD_ID tid, const std::shared_ptr<Ray> & /* ray */)
     843             : {
     844             :   mooseAssert(currentlyPropagating(), "Should not call while not propagating");
     845    23617483 :   if (!_fe_problem.currentlyComputingJacobian() && !_fe_problem.currentlyComputingResidual())
     846             :     mooseAssert(_num_cached[tid] == 0,
     847             :                 "Values should only be cached when computing Jacobian/residual");
     848             : 
     849             :   // Fill into cached Jacobian/residuals if necessary
     850    23617483 :   if (_fe_problem.currentlyComputingJacobian())
     851             :   {
     852        6096 :     _fe_problem.cacheJacobian(tid);
     853             : 
     854        6096 :     if (++_num_cached[tid] == 20)
     855             :     {
     856             :       Threads::spin_mutex::scoped_lock lock(_spin_mutex);
     857         250 :       _fe_problem.addCachedJacobian(tid);
     858         250 :       _num_cached[tid] = 0;
     859             :     }
     860             :   }
     861    23611387 :   else if (_fe_problem.currentlyComputingResidual())
     862             :   {
     863       51968 :     _fe_problem.cacheResidual(tid);
     864             : 
     865       51968 :     if (++_num_cached[tid] == 20)
     866             :     {
     867             :       Threads::spin_mutex::scoped_lock lock(_spin_mutex);
     868        1445 :       _fe_problem.addCachedResidual(tid);
     869        1445 :       _num_cached[tid] = 0;
     870             :     }
     871             :   }
     872    23617483 : }
     873             : 
     874             : void
     875        5360 : RayTracingStudy::executeStudy()
     876             : {
     877       10720 :   TIME_SECTION("executeStudy", 2, "Executing Study");
     878             : 
     879             :   mooseAssert(_called_initial_setup, "Initial setup not called");
     880             : 
     881             :   // Reset ray start/complete timers
     882        5360 :   _total_intersections = 0;
     883        5360 :   _max_intersections = 0;
     884        5360 :   _max_trajectory_changes = 0;
     885             : 
     886             :   // Reset physical tracing stats
     887       80400 :   for (auto & val : _local_trace_ray_results)
     888       75040 :     val = 0;
     889             : 
     890             :   // Reset crossing and intersection
     891        5360 :   _ending_processor_crossings = 0;
     892        5360 :   _total_processor_crossings = 0;
     893        5360 :   _ending_max_processor_crossings = 0;
     894        5360 :   _ending_intersections = 0;
     895        5360 :   _ending_max_intersections = 0;
     896        5360 :   _ending_max_trajectory_changes = 0;
     897        5360 :   _ending_distance = 0;
     898        5360 :   _total_distance = 0;
     899             : 
     900             :   // Zero the AuxVariables that our AuxRayKernels contribute to before they accumulate
     901        5360 :   zeroAuxVariables();
     902             : 
     903        5360 :   preExecuteStudy();
     904       14251 :   for (auto & rto : getRayTracingObjects())
     905       14251 :     rto->preExecuteStudy();
     906             : 
     907        5360 :   _ray_bank.clear();
     908             : 
     909       11561 :   for (THREAD_ID tid = 0; tid < libMesh::n_threads(); ++tid)
     910             :   {
     911        6201 :     _threaded_trace_ray[tid]->preExecute();
     912             :     _threaded_cached_normals[tid].clear();
     913             :   }
     914             : 
     915        5360 :   _communicator.barrier();
     916        5360 :   _execution_start_time = std::chrono::steady_clock::now();
     917             : 
     918        5360 :   _parallel_ray_study->preExecute();
     919             : 
     920             :   {
     921             :     {
     922        5360 :       auto generation_start_time = std::chrono::steady_clock::now();
     923             : 
     924       10720 :       TIME_SECTION("generateRays", 2, "Generating Rays");
     925             : 
     926        5360 :       generateRays();
     927             : 
     928        5298 :       _generation_time = std::chrono::steady_clock::now() - generation_start_time;
     929        5298 :     }
     930             : 
     931             :     // At this point, nobody is working so this is good time to make sure
     932             :     // Rays are unique across all processors in the working buffer
     933        5298 :     if (verifyRays())
     934             :     {
     935        5298 :       verifyUniqueRays(_parallel_ray_study->workBuffer().begin(),
     936             :                        _parallel_ray_study->workBuffer().end(),
     937             :                        /* error_suffix = */ "after generateRays()");
     938             : 
     939       10590 :       verifyUniqueRayIDs(_parallel_ray_study->workBuffer().begin(),
     940             :                          _parallel_ray_study->workBuffer().end(),
     941             :                          /* global = */ true,
     942             :                          /* error_suffix = */ "after generateRays()");
     943             :     }
     944             : 
     945        5294 :     registeredRaySetup();
     946             : 
     947             :     {
     948       10580 :       TIME_SECTION("propagateRays", 2, "Propagating Rays");
     949             : 
     950        5290 :       const auto propagation_start_time = std::chrono::steady_clock::now();
     951             : 
     952        5290 :       _parallel_ray_study->execute();
     953             : 
     954        5264 :       _propagation_time = std::chrono::steady_clock::now() - propagation_start_time;
     955        5264 :     }
     956             :   }
     957             : 
     958        5264 :   _execution_time = std::chrono::steady_clock::now() - _execution_start_time;
     959             : 
     960        5264 :   if (verifyRays())
     961             :   {
     962       10528 :     verifyUniqueRays(_parallel_ray_study->workBuffer().begin(),
     963             :                      _parallel_ray_study->workBuffer().end(),
     964             :                      /* error_suffix = */ "after tracing completed");
     965             : 
     966             : #ifndef NDEBUG
     967             :     // Outside of debug, _ray_bank always holds all of the Rays that have ended on this processor
     968             :     // We can use this as a global point to check for unique IDs for every Ray that has traced
     969             :     verifyUniqueRayIDs(_ray_bank.begin(),
     970             :                        _ray_bank.end(),
     971             :                        /* global = */ true,
     972             :                        /* error_suffix = */ "after tracing completed");
     973             : #endif
     974             :   }
     975             : 
     976             :   // Update counters from the threaded trace objects
     977       11321 :   for (const auto & tr : _threaded_trace_ray)
     978       90855 :     for (std::size_t i = 0; i < _local_trace_ray_results.size(); ++i)
     979       84798 :       _local_trace_ray_results[i] += tr->results()[i];
     980             : 
     981             :   // Update local ending counters
     982        5264 :   _total_processor_crossings = _ending_processor_crossings;
     983        5264 :   _max_processor_crossings = _ending_max_processor_crossings;
     984        5264 :   _total_intersections = _ending_intersections;
     985        5264 :   _max_intersections = _ending_max_intersections;
     986        5264 :   _max_trajectory_changes = _ending_max_trajectory_changes;
     987        5264 :   _total_distance = _ending_distance;
     988             :   // ...and communicate the global values
     989        5264 :   _communicator.sum(_total_processor_crossings);
     990        5264 :   _communicator.max(_max_processor_crossings);
     991        5264 :   _communicator.sum(_total_intersections);
     992        5264 :   _communicator.max(_max_intersections);
     993        5264 :   _communicator.max(_max_trajectory_changes);
     994        5264 :   _communicator.sum(_total_distance);
     995             : 
     996             :   // Throw a warning with the number of failed (tolerated) traces
     997        5264 :   if (_tolerate_failure)
     998             :   {
     999           6 :     auto failures = _local_trace_ray_results[TraceRay::FAILED_TRACES];
    1000           6 :     _communicator.sum(failures);
    1001           6 :     if (failures)
    1002           6 :       mooseWarning(
    1003             :           type(), " '", name(), "': ", failures, " ray tracing failures were tolerated.\n");
    1004             :   }
    1005             : 
    1006             :   // Clear the current RayKernels
    1007       11321 :   for (THREAD_ID tid = 0; tid < libMesh::n_threads(); ++tid)
    1008        6057 :     _threaded_current_ray_kernels[tid].clear();
    1009             : 
    1010             :   // Move the threaded cache trace information into the full cached trace vector
    1011             :   // Here, we only clear the cached vectors so that we might not have to
    1012             :   // reallocate on future traces
    1013             :   std::size_t num_entries = 0;
    1014       11321 :   for (THREAD_ID tid = 0; tid < libMesh::n_threads(); ++tid)
    1015        6057 :     num_entries += _threaded_cached_traces[tid].size();
    1016        5264 :   _cached_traces.clear();
    1017        5264 :   _cached_traces.reserve(num_entries);
    1018       11321 :   for (THREAD_ID tid = 0; tid < libMesh::n_threads(); ++tid)
    1019             :   {
    1020       14633 :     for (const auto & entry : _threaded_cached_traces[tid])
    1021        8576 :       _cached_traces.emplace_back(std::move(entry));
    1022        6057 :     _threaded_cached_traces[tid].clear();
    1023             :   }
    1024             : 
    1025             :   // Add any stragglers that contribute to the Jacobian or residual
    1026       11321 :   for (THREAD_ID tid = 0; tid < libMesh::n_threads(); ++tid)
    1027        6057 :     if (_num_cached[tid] != 0)
    1028             :     {
    1029             :       mooseAssert(_fe_problem.currentlyComputingJacobian() ||
    1030             :                       _fe_problem.currentlyComputingResidual(),
    1031             :                   "Should not have cached values without Jacobian/residual computation");
    1032             : 
    1033        3297 :       if (_fe_problem.currentlyComputingJacobian())
    1034         146 :         _fe_problem.addCachedJacobian(tid);
    1035             :       else
    1036        3151 :         _fe_problem.addCachedResidual(tid);
    1037             : 
    1038        3297 :       _num_cached[tid] = 0;
    1039             :     }
    1040             : 
    1041             :   // AuxRayKernels may have modified AuxVariables
    1042        5264 :   _fe_problem.getAuxiliarySystem().solution().close();
    1043        5264 :   _fe_problem.getAuxiliarySystem().update();
    1044             : 
    1045             :   // Clear FE
    1046       11321 :   for (THREAD_ID tid = 0; tid < libMesh::n_threads(); ++tid)
    1047             :   {
    1048        6057 :     _fe_problem.clearActiveElementalMooseVariables(tid);
    1049        6057 :     _fe_problem.clearActiveMaterialProperties(tid);
    1050             :   }
    1051             : 
    1052        5264 :   postExecuteStudy();
    1053       14082 :   for (auto & rto : getRayTracingObjects())
    1054       14082 :     rto->postExecuteStudy();
    1055        5260 : }
    1056             : 
    1057             : void
    1058     2755265 : RayTracingStudy::onCompleteRay(const std::shared_ptr<Ray> & ray)
    1059             : {
    1060             :   mooseAssert(currentlyPropagating(), "Should only be called during Ray propagation");
    1061             : 
    1062     2755265 :   _ending_processor_crossings += ray->processorCrossings();
    1063     2755265 :   _ending_max_processor_crossings =
    1064     2758098 :       std::max(_ending_max_processor_crossings, ray->processorCrossings());
    1065     2755265 :   _ending_intersections += ray->intersections();
    1066     2766258 :   _ending_max_intersections = std::max(_ending_max_intersections, ray->intersections());
    1067     2755265 :   _ending_max_trajectory_changes =
    1068     2755562 :       std::max(_ending_max_trajectory_changes, ray->trajectoryChanges());
    1069     2755265 :   _ending_distance += ray->distance();
    1070             : 
    1071             : #ifdef NDEBUG
    1072             :   // In non-opt modes, we will always bank the Rays for debugging
    1073     2755265 :   if (_bank_rays_on_completion)
    1074             : #endif
    1075     2755263 :     _ray_bank.emplace_back(ray);
    1076     2755265 : }
    1077             : 
    1078             : RayDataIndex
    1079        1289 : RayTracingStudy::registerRayDataInternal(const std::string & name, const bool aux)
    1080             : {
    1081             :   Threads::spin_mutex::scoped_lock lock(_spin_mutex);
    1082             : 
    1083        1289 :   if (_called_initial_setup)
    1084           4 :     mooseError("Cannot register Ray ", (aux ? "aux " : ""), "data after initialSetup()");
    1085             : 
    1086        1287 :   auto & map = aux ? _ray_aux_data_map : _ray_data_map;
    1087             :   const auto find = map.find(name);
    1088        1287 :   if (find != map.end())
    1089          27 :     return find->second;
    1090             : 
    1091        1260 :   auto & other_map = aux ? _ray_data_map : _ray_aux_data_map;
    1092        1260 :   if (other_map.find(name) != other_map.end())
    1093           4 :     mooseError("Cannot register Ray aux data with name ",
    1094             :                name,
    1095             :                " because Ray ",
    1096           2 :                (aux ? "(non-aux)" : "aux"),
    1097             :                " data already exists with said name.");
    1098             : 
    1099             :   // Add into the name -> index map
    1100        1258 :   map.emplace(name, map.size());
    1101             : 
    1102             :   // Add into the index -> names vector
    1103        1258 :   auto & vector = aux ? _ray_aux_data_names : _ray_data_names;
    1104        1258 :   vector.push_back(name);
    1105             : 
    1106        1258 :   return map.size() - 1;
    1107             : }
    1108             : 
    1109             : std::vector<RayDataIndex>
    1110         144 : RayTracingStudy::registerRayDataInternal(const std::vector<std::string> & names, const bool aux)
    1111             : {
    1112         144 :   std::vector<RayDataIndex> indices(names.size());
    1113         315 :   for (std::size_t i = 0; i < names.size(); ++i)
    1114         171 :     indices[i] = registerRayDataInternal(names[i], aux);
    1115         144 :   return indices;
    1116           0 : }
    1117             : 
    1118             : RayDataIndex
    1119         788 : RayTracingStudy::getRayDataIndexInternal(const std::string & name,
    1120             :                                          const bool aux,
    1121             :                                          const bool graceful) const
    1122             : {
    1123             :   Threads::spin_mutex::scoped_lock lock(_spin_mutex);
    1124             : 
    1125         788 :   const auto & map = aux ? _ray_aux_data_map : _ray_data_map;
    1126             :   const auto find = map.find(name);
    1127         788 :   if (find != map.end())
    1128         774 :     return find->second;
    1129             : 
    1130          14 :   if (graceful)
    1131             :     return Ray::INVALID_RAY_DATA_INDEX;
    1132             : 
    1133           6 :   const auto & other_map = aux ? _ray_data_map : _ray_aux_data_map;
    1134           6 :   if (other_map.find(name) != other_map.end())
    1135           8 :     mooseError("Ray data with name '",
    1136             :                name,
    1137             :                "' was not found.\n\n",
    1138             :                "However, Ray ",
    1139           4 :                (aux ? "non-aux" : "aux"),
    1140             :                " data with said name was found.\n",
    1141             :                "Did you mean to use ",
    1142           6 :                (aux ? "getRayDataIndex()/getRayDataIndices()?"
    1143             :                     : "getRayAuxDataIndex()/getRayAuxDataIndices()"),
    1144             :                "?");
    1145             : 
    1146           4 :   mooseError("Unknown Ray ", (aux ? "aux " : ""), "data with name ", name);
    1147             : }
    1148             : 
    1149             : std::vector<RayDataIndex>
    1150         326 : RayTracingStudy::getRayDataIndicesInternal(const std::vector<std::string> & names,
    1151             :                                            const bool aux,
    1152             :                                            const bool graceful) const
    1153             : {
    1154         326 :   std::vector<RayDataIndex> indices(names.size());
    1155         386 :   for (std::size_t i = 0; i < names.size(); ++i)
    1156          60 :     indices[i] = getRayDataIndexInternal(names[i], aux, graceful);
    1157         326 :   return indices;
    1158           0 : }
    1159             : 
    1160             : const std::string &
    1161        4898 : RayTracingStudy::getRayDataNameInternal(const RayDataIndex index, const bool aux) const
    1162             : {
    1163             :   Threads::spin_mutex::scoped_lock lock(_spin_mutex);
    1164             : 
    1165        9796 :   if ((aux ? rayAuxDataSize() : rayDataSize()) < index)
    1166           4 :     mooseError("Unknown Ray ", aux ? "aux " : "", "data with index ", index);
    1167        9792 :   return aux ? _ray_aux_data_names[index] : _ray_data_names[index];
    1168             : }
    1169             : 
    1170             : RayDataIndex
    1171         494 : RayTracingStudy::registerRayData(const std::string & name)
    1172             : {
    1173         494 :   return registerRayDataInternal(name, /* aux = */ false);
    1174             : }
    1175             : 
    1176             : std::vector<RayDataIndex>
    1177          76 : RayTracingStudy::registerRayData(const std::vector<std::string> & names)
    1178             : {
    1179          76 :   return registerRayDataInternal(names, /* aux = */ false);
    1180             : }
    1181             : 
    1182             : RayDataIndex
    1183         632 : RayTracingStudy::getRayDataIndex(const std::string & name, const bool graceful /* = false */) const
    1184             : {
    1185         632 :   return getRayDataIndexInternal(name, /* aux = */ false, graceful);
    1186             : }
    1187             : 
    1188             : std::vector<RayDataIndex>
    1189         163 : RayTracingStudy::getRayDataIndices(const std::vector<std::string> & names,
    1190             :                                    const bool graceful /* = false */) const
    1191             : {
    1192         163 :   return getRayDataIndicesInternal(names, /* aux = */ false, graceful);
    1193             : }
    1194             : 
    1195             : const std::string &
    1196        2450 : RayTracingStudy::getRayDataName(const RayDataIndex index) const
    1197             : {
    1198        2450 :   return getRayDataNameInternal(index, /* aux = */ false);
    1199             : }
    1200             : 
    1201             : RayDataIndex
    1202         624 : RayTracingStudy::registerRayAuxData(const std::string & name)
    1203             : {
    1204         624 :   return registerRayDataInternal(name, /* aux = */ true);
    1205             : }
    1206             : 
    1207             : std::vector<RayDataIndex>
    1208          68 : RayTracingStudy::registerRayAuxData(const std::vector<std::string> & names)
    1209             : {
    1210          68 :   return registerRayDataInternal(names, /* aux = */ true);
    1211             : }
    1212             : 
    1213             : RayDataIndex
    1214          96 : RayTracingStudy::getRayAuxDataIndex(const std::string & name,
    1215             :                                     const bool graceful /* = false */) const
    1216             : {
    1217          96 :   return getRayDataIndexInternal(name, /* aux = */ true, graceful);
    1218             : }
    1219             : 
    1220             : std::vector<RayDataIndex>
    1221         163 : RayTracingStudy::getRayAuxDataIndices(const std::vector<std::string> & names,
    1222             :                                       const bool graceful /* = false */) const
    1223             : {
    1224         163 :   return getRayDataIndicesInternal(names, /* aux = */ true, graceful);
    1225             : }
    1226             : 
    1227             : const std::string &
    1228        2448 : RayTracingStudy::getRayAuxDataName(const RayDataIndex index) const
    1229             : {
    1230        2448 :   return getRayDataNameInternal(index, /* aux = */ true);
    1231             : }
    1232             : 
    1233             : bool
    1234        6201 : RayTracingStudy::hasRayKernels(const THREAD_ID tid)
    1235             : {
    1236             :   std::vector<RayKernelBase *> result;
    1237        6201 :   getRayKernels(result, tid);
    1238        6201 :   return result.size();
    1239        6201 : }
    1240             : 
    1241             : void
    1242     2997089 : RayTracingStudy::getRayKernels(std::vector<RayKernelBase *> & result, SubdomainID id, THREAD_ID tid)
    1243             : {
    1244             :   // If the cache doesn't have any attributes yet, it means that we haven't set
    1245             :   // the conditions yet. We do this so that it can be generated on the fly on first use.
    1246     2997089 :   if (!_threaded_cache_ray_kernel[tid].numAttribs())
    1247             :   {
    1248        1396 :     if (!_called_initial_setup)
    1249           2 :       mooseError("Should not call getRayKernels() before initialSetup()");
    1250             : 
    1251        1394 :     auto query = _fe_problem.theWarehouse()
    1252        1394 :                      .query()
    1253        2788 :                      .condition<AttribRayTracingStudy>(this)
    1254        1394 :                      .condition<AttribSystem>("RayKernel")
    1255        1394 :                      .condition<AttribThread>(tid);
    1256        1394 :     _threaded_cache_ray_kernel[tid] = query.clone();
    1257        1394 :   }
    1258             : 
    1259             :   _threaded_cache_ray_kernel[tid].queryInto(result, id);
    1260     2997087 : }
    1261             : 
    1262             : void
    1263     2997087 : RayTracingStudy::getRayKernels(std::vector<RayKernelBase *> & result,
    1264             :                                SubdomainID id,
    1265             :                                THREAD_ID tid,
    1266             :                                RayID ray_id)
    1267             : {
    1268             :   // No Ray registration: no need to sift through objects
    1269     2997087 :   if (!_use_ray_registration)
    1270             :   {
    1271     2990007 :     getRayKernels(result, id, tid);
    1272             :   }
    1273             :   // Has Ray registration: only pick the objects associated with ray_id
    1274             :   else
    1275             :   {
    1276             :     // Get all of the kernels on this block
    1277             :     std::vector<RayKernelBase *> rkbs;
    1278        7080 :     getRayKernels(rkbs, id, tid);
    1279             : 
    1280             :     // The RayTracingObjects associated with this ray
    1281        7080 :     const auto & ray_id_rtos = _threaded_ray_object_registration[tid][ray_id];
    1282             : 
    1283             :     // The result is the union of all of the kernels and the objects associated with this Ray
    1284        7080 :     result.clear();
    1285       17088 :     for (auto rkb : rkbs)
    1286       10008 :       if (ray_id_rtos.count(rkb))
    1287        7168 :         result.push_back(rkb);
    1288        7080 :   }
    1289     2997087 : }
    1290             : 
    1291             : void
    1292      913198 : RayTracingStudy::getRayBCs(std::vector<RayBoundaryConditionBase *> & result,
    1293             :                            BoundaryID id,
    1294             :                            THREAD_ID tid)
    1295             : {
    1296             :   // If the cache doesn't have any attributes yet, it means that we haven't set
    1297             :   // the conditions yet. We do this so that it can be generated on the fly on first use.
    1298      913198 :   if (!_threaded_cache_ray_bc[tid].numAttribs())
    1299             :   {
    1300        1327 :     if (!_called_initial_setup)
    1301           2 :       mooseError("Should not call getRayBCs() before initialSetup()");
    1302             : 
    1303        1325 :     auto query = _fe_problem.theWarehouse()
    1304        1325 :                      .query()
    1305        2650 :                      .condition<AttribRayTracingStudy>(this)
    1306        1325 :                      .condition<AttribSystem>("RayBoundaryCondition")
    1307        1325 :                      .condition<AttribThread>(tid);
    1308        1325 :     _threaded_cache_ray_bc[tid] = query.clone();
    1309        1325 :   }
    1310             : 
    1311      913196 :   _threaded_cache_ray_bc[tid].queryInto(result, std::make_tuple(id, false));
    1312      913196 : }
    1313             : 
    1314             : void
    1315      973271 : RayTracingStudy::getRayBCs(std::vector<RayBoundaryConditionBase *> & result,
    1316             :                            const std::vector<TraceRayBndElement> & bnd_elems,
    1317             :                            THREAD_ID tid,
    1318             :                            RayID ray_id)
    1319             : {
    1320             :   // No Ray registration: no need to sift through objects
    1321      973271 :   if (!_use_ray_registration)
    1322             :   {
    1323      971604 :     if (bnd_elems.size() == 1)
    1324      908200 :       getRayBCs(result, bnd_elems[0].bnd_id, tid);
    1325             :     else
    1326             :     {
    1327       63404 :       std::vector<BoundaryID> bnd_ids(bnd_elems.size());
    1328      195468 :       for (MooseIndex(bnd_elems.size()) i = 0; i < bnd_elems.size(); ++i)
    1329      132064 :         bnd_ids[i] = bnd_elems[i].bnd_id;
    1330       63404 :       getRayBCs(result, bnd_ids, tid);
    1331       63404 :     }
    1332             :   }
    1333             :   // Has Ray registration: only pick the objects associated with ray_id
    1334             :   else
    1335             :   {
    1336             :     // Get all of the RayBCs on these boundaries
    1337             :     std::vector<RayBoundaryConditionBase *> rbcs;
    1338        1667 :     if (bnd_elems.size() == 1)
    1339        1426 :       getRayBCs(rbcs, bnd_elems[0].bnd_id, tid);
    1340             :     else
    1341             :     {
    1342         241 :       std::vector<BoundaryID> bnd_ids(bnd_elems.size());
    1343         831 :       for (MooseIndex(bnd_elems.size()) i = 0; i < bnd_elems.size(); ++i)
    1344         590 :         bnd_ids[i] = bnd_elems[i].bnd_id;
    1345         241 :       getRayBCs(rbcs, bnd_ids, tid);
    1346         241 :     }
    1347             : 
    1348             :     // The RayTracingObjects associated with this ray
    1349             :     mooseAssert(ray_id < _threaded_ray_object_registration[tid].size(), "Not in registration");
    1350        1667 :     const auto & ray_id_rtos = _threaded_ray_object_registration[tid][ray_id];
    1351             : 
    1352             :     // The result is the union of all of the kernels and the objects associated with this Ray
    1353        1667 :     result.clear();
    1354        4646 :     for (auto rbc : rbcs)
    1355        2979 :       if (ray_id_rtos.count(rbc))
    1356        1693 :         result.push_back(rbc);
    1357        1667 :   }
    1358      973271 : }
    1359             : 
    1360             : std::vector<RayTracingObject *>
    1361       23270 : RayTracingStudy::getRayTracingObjects()
    1362             : {
    1363             :   std::vector<RayTracingObject *> result;
    1364       46540 :   _fe_problem.theWarehouse().query().condition<AttribRayTracingStudy>(this).queryInto(result);
    1365       23270 :   return result;
    1366           0 : }
    1367             : 
    1368             : const std::vector<std::shared_ptr<Ray>> &
    1369         664 : RayTracingStudy::rayBank() const
    1370             : {
    1371         664 :   if (!_bank_rays_on_completion)
    1372           2 :     mooseError("The Ray bank is not available because the private parameter "
    1373             :                "'_bank_rays_on_completion' is set to false.");
    1374         662 :   if (currentlyGenerating() || currentlyPropagating())
    1375           2 :     mooseError("Cannot get the Ray bank during generation or propagation.");
    1376             : 
    1377         660 :   return _ray_bank;
    1378             : }
    1379             : 
    1380             : std::shared_ptr<Ray>
    1381         426 : RayTracingStudy::getBankedRay(const RayID ray_id) const
    1382             : {
    1383             :   // This is only a linear search - can be improved on with a map in the future
    1384             :   // if this is used on a larger scale
    1385         426 :   std::shared_ptr<Ray> ray;
    1386         778 :   for (const std::shared_ptr<Ray> & possible_ray : rayBank())
    1387         670 :     if (possible_ray->id() == ray_id)
    1388             :     {
    1389             :       ray = possible_ray;
    1390             :       break;
    1391             :     }
    1392             : 
    1393             :   // Make sure one and only one processor has the Ray
    1394         426 :   unsigned int have_ray = ray ? 1 : 0;
    1395         426 :   _communicator.sum(have_ray);
    1396         426 :   if (have_ray == 0)
    1397           2 :     mooseError("Could not find a Ray with the ID ", ray_id, " in the Ray banks.");
    1398             : 
    1399             :   // This should never happen... but let's make sure
    1400             :   mooseAssert(have_ray == 1, "Multiple rays with the same ID were found in the Ray banks");
    1401             : 
    1402         424 :   return ray;
    1403             : }
    1404             : 
    1405             : RayData
    1406         426 : RayTracingStudy::getBankedRayDataInternal(const RayID ray_id,
    1407             :                                           const RayDataIndex index,
    1408             :                                           const bool aux) const
    1409             : {
    1410             :   // Will be a nullptr shared_ptr if this processor doesn't own the Ray
    1411         426 :   const std::shared_ptr<Ray> ray = getBankedRay(ray_id);
    1412             : 
    1413         424 :   Real value = ray ? (aux ? ray->auxData(index) : ray->data(index)) : 0;
    1414         424 :   _communicator.sum(value);
    1415         742 :   return value;
    1416             : }
    1417             : 
    1418             : RayData
    1419         394 : RayTracingStudy::getBankedRayData(const RayID ray_id, const RayDataIndex index) const
    1420             : {
    1421         394 :   return getBankedRayDataInternal(ray_id, index, /* aux = */ false);
    1422             : }
    1423             : 
    1424             : RayData
    1425          32 : RayTracingStudy::getBankedRayAuxData(const RayID ray_id, const RayDataIndex index) const
    1426             : {
    1427          32 :   return getBankedRayDataInternal(ray_id, index, /* aux = */ true);
    1428             : }
    1429             : 
    1430             : RayID
    1431        1602 : RayTracingStudy::registerRay(const std::string & name)
    1432             : {
    1433             :   libmesh_parallel_only(comm());
    1434             : 
    1435             :   Threads::spin_mutex::scoped_lock lock(_spin_mutex);
    1436             : 
    1437        1602 :   if (!_use_ray_registration)
    1438           2 :     mooseError("Cannot use registerRay() with Ray registration disabled");
    1439             : 
    1440             :   // This is parallel only for now. We could likely stagger the ID building like we do with
    1441             :   // the unique IDs, but it would require a sync point which isn't there right now
    1442             :   libmesh_parallel_only(comm());
    1443             : 
    1444        1600 :   const auto & it = _registered_ray_map.find(name);
    1445        1600 :   if (it != _registered_ray_map.end())
    1446           0 :     return it->second;
    1447             : 
    1448        1600 :   const auto id = _reverse_registered_ray_map.size();
    1449        1600 :   _registered_ray_map.emplace(name, id);
    1450        1600 :   _reverse_registered_ray_map.push_back(name);
    1451        1600 :   return id;
    1452             : }
    1453             : 
    1454             : RayID
    1455       10801 : RayTracingStudy::registeredRayID(const std::string & name, const bool graceful /* = false */) const
    1456             : {
    1457             :   Threads::spin_mutex::scoped_lock lock(_spin_mutex);
    1458             : 
    1459       10801 :   if (!_use_ray_registration)
    1460           2 :     mooseError("Should not use registeredRayID() with Ray registration disabled");
    1461             : 
    1462       10799 :   const auto search = _registered_ray_map.find(name);
    1463       10799 :   if (search != _registered_ray_map.end())
    1464       10791 :     return search->second;
    1465             : 
    1466           8 :   if (graceful)
    1467             :     return Ray::INVALID_RAY_ID;
    1468             : 
    1469           2 :   mooseError("Attempted to obtain ID of registered Ray ",
    1470             :              name,
    1471             :              ", but a Ray with said name is not registered.");
    1472             : }
    1473             : 
    1474             : const std::string &
    1475          40 : RayTracingStudy::registeredRayName(const RayID ray_id) const
    1476             : {
    1477             :   Threads::spin_mutex::scoped_lock lock(_spin_mutex);
    1478             : 
    1479          40 :   if (!_use_ray_registration)
    1480           2 :     mooseError("Should not use registeredRayName() with Ray registration disabled");
    1481             : 
    1482          38 :   if (_reverse_registered_ray_map.size() > ray_id)
    1483          36 :     return _reverse_registered_ray_map[ray_id];
    1484             : 
    1485           2 :   mooseError("Attempted to obtain name of registered Ray with ID ",
    1486             :              ray_id,
    1487             :              ", but a Ray with said ID is not registered.");
    1488             : }
    1489             : 
    1490             : Real
    1491        2029 : RayTracingStudy::computeTotalVolume()
    1492             : {
    1493        2029 :   Real volume = 0;
    1494      271139 :   for (const auto & elem : *_mesh.getActiveLocalElementRange())
    1495      269110 :     volume += elem->volume();
    1496        2029 :   _communicator.sum(volume);
    1497        2029 :   return volume;
    1498             : }
    1499             : 
    1500             : const std::vector<std::vector<BoundaryID>> &
    1501       10274 : RayTracingStudy::getInternalSidesets(const Elem * elem) const
    1502             : {
    1503             :   mooseAssert(_use_internal_sidesets, "Not using internal sidesets");
    1504             :   mooseAssert(hasInternalSidesets(), "Processor does not have internal sidesets");
    1505             :   mooseAssert(_internal_sidesets_map.size() > _elem_index_helper.getIndex(elem),
    1506             :               "Internal sideset map not initialized");
    1507             : 
    1508             :   const auto index = _elem_index_helper.getIndex(elem);
    1509       10274 :   return _internal_sidesets_map[index];
    1510             : }
    1511             : 
    1512             : TraceData &
    1513        8576 : RayTracingStudy::initThreadedCachedTrace(const std::shared_ptr<Ray> & ray, THREAD_ID tid)
    1514             : {
    1515             :   mooseAssert(shouldCacheTrace(ray), "Not caching trace");
    1516             :   mooseAssert(currentlyPropagating(), "Should only use while tracing");
    1517             : 
    1518        8576 :   _threaded_cached_traces[tid].emplace_back(ray);
    1519        8576 :   return _threaded_cached_traces[tid].back();
    1520             : }
    1521             : 
    1522             : void
    1523        5732 : RayTracingStudy::verifyUniqueRayIDs(const std::vector<std::shared_ptr<Ray>>::const_iterator begin,
    1524             :                                     const std::vector<std::shared_ptr<Ray>>::const_iterator end,
    1525             :                                     const bool global,
    1526             :                                     const std::string & error_suffix) const
    1527             : {
    1528             :   // Determine the unique set of Ray IDs on this processor,
    1529             :   // and if not locally unique throw an error. Once we build this set,
    1530             :   // we will send it to rank 0 to verify globally
    1531             :   std::set<RayID> local_rays;
    1532     2708261 :   for (const std::shared_ptr<Ray> & ray : as_range(begin, end))
    1533             :   {
    1534             :     mooseAssert(ray, "Null ray");
    1535             : 
    1536             :     // Try to insert into the set; the second entry in the pair
    1537             :     // will be false if it was not inserted
    1538     2702533 :     if (!local_rays.insert(ray->id()).second)
    1539             :     {
    1540           4 :       for (const std::shared_ptr<Ray> & other_ray : as_range(begin, end))
    1541           4 :         if (ray.get() != other_ray.get() && ray->id() == other_ray->id())
    1542           8 :           mooseError("Multiple Rays exist with ID ",
    1543           4 :                      ray->id(),
    1544             :                      " on processor ",
    1545           4 :                      _pid,
    1546             :                      " ",
    1547             :                      error_suffix,
    1548             :                      "\n\nOffending Ray information:\n\n",
    1549           4 :                      ray->getInfo(),
    1550             :                      "\n",
    1551           4 :                      other_ray->getInfo());
    1552             :     }
    1553             :   }
    1554             : 
    1555             :   // Send IDs from all procs to rank 0 and verify on rank 0
    1556        5728 :   if (global)
    1557             :   {
    1558             :     // Package our local IDs and send to rank 0
    1559             :     std::map<processor_id_type, std::vector<RayID>> send_ids;
    1560        5294 :     if (local_rays.size())
    1561             :       send_ids.emplace(std::piecewise_construct,
    1562        4149 :                        std::forward_as_tuple(0),
    1563        4149 :                        std::forward_as_tuple(local_rays.begin(), local_rays.end()));
    1564             :     local_rays.clear();
    1565             : 
    1566             :     // Mapping on rank 0 from ID -> processor ID
    1567             :     std::map<RayID, processor_id_type> global_map;
    1568             : 
    1569             :     // Verify another processor's IDs against the global map on rank 0
    1570             :     const auto check_ids =
    1571        4149 :         [this, &global_map, &error_suffix](processor_id_type pid, const std::vector<RayID> & ids)
    1572             :     {
    1573     2704482 :       for (const RayID id : ids)
    1574             :       {
    1575     2700333 :         const auto emplace_pair = global_map.emplace(id, pid);
    1576             : 
    1577             :         // Means that this ID already exists in the map
    1578     2700333 :         if (!emplace_pair.second)
    1579           0 :           mooseError("Ray with ID ",
    1580             :                      id,
    1581             :                      " exists on ranks ",
    1582           0 :                      emplace_pair.first->second,
    1583             :                      " and ",
    1584             :                      pid,
    1585             :                      "\n",
    1586             :                      error_suffix);
    1587             :       }
    1588        9443 :     };
    1589             : 
    1590        5294 :     Parallel::push_parallel_vector_data(_communicator, send_ids, check_ids);
    1591             :   }
    1592        5728 : }
    1593             : 
    1594             : void
    1595       10562 : RayTracingStudy::verifyUniqueRays(const std::vector<std::shared_ptr<Ray>>::const_iterator begin,
    1596             :                                   const std::vector<std::shared_ptr<Ray>>::const_iterator end,
    1597             :                                   const std::string & error_suffix)
    1598             : {
    1599             :   std::set<const Ray *> rays;
    1600     2710901 :   for (const std::shared_ptr<Ray> & ray : as_range(begin, end))
    1601     2700341 :     if (!rays.insert(ray.get()).second) // false if not inserted into rays
    1602           2 :       mooseError("Multiple shared_ptrs were found that point to the same Ray ",
    1603             :                  error_suffix,
    1604             :                  "\n\nOffending Ray:\n",
    1605           2 :                  ray->getInfo());
    1606       10560 : }
    1607             : 
    1608             : void
    1609     2699651 : RayTracingStudy::moveRayToBuffer(std::shared_ptr<Ray> & ray)
    1610             : {
    1611             :   mooseAssert(currentlyGenerating(), "Can only use while generating");
    1612             :   mooseAssert(ray, "Null ray");
    1613             :   mooseAssert(ray->shouldContinue(), "Ray is not continuing");
    1614             : 
    1615     2699651 :   _parallel_ray_study->moveWorkToBuffer(ray, /* tid = */ 0);
    1616     2699651 : }
    1617             : 
    1618             : void
    1619         180 : RayTracingStudy::moveRaysToBuffer(std::vector<std::shared_ptr<Ray>> & rays)
    1620             : {
    1621             :   mooseAssert(currentlyGenerating(), "Can only use while generating");
    1622             : #ifndef NDEBUG
    1623             :   for (const std::shared_ptr<Ray> & ray : rays)
    1624             :   {
    1625             :     mooseAssert(ray, "Null ray");
    1626             :     mooseAssert(ray->shouldContinue(), "Ray is not continuing");
    1627             :   }
    1628             : #endif
    1629             : 
    1630         180 :   _parallel_ray_study->moveWorkToBuffer(rays, /* tid = */ 0);
    1631         180 : }
    1632             : 
    1633             : void
    1634       54960 : RayTracingStudy::moveRayToBufferDuringTrace(std::shared_ptr<Ray> & ray,
    1635             :                                             const THREAD_ID tid,
    1636             :                                             const AcquireMoveDuringTraceKey &)
    1637             : {
    1638             :   mooseAssert(ray, "Null ray");
    1639             :   mooseAssert(currentlyPropagating(), "Can only use while tracing");
    1640             : 
    1641       54960 :   _parallel_ray_study->moveWorkToBuffer(ray, tid);
    1642       54960 : }
    1643             : 
    1644             : void
    1645        5042 : RayTracingStudy::reserveRayBuffer(const std::size_t size)
    1646             : {
    1647        5042 :   if (!currentlyGenerating())
    1648           2 :     mooseError("Can only reserve in Ray buffer during generateRays()");
    1649             : 
    1650        5040 :   _parallel_ray_study->reserveBuffer(size);
    1651        5040 : }
    1652             : 
    1653             : const Point &
    1654     4429805 : RayTracingStudy::getSideNormal(const Elem * elem, unsigned short side, const THREAD_ID tid)
    1655             : {
    1656             :   std::unordered_map<std::pair<const Elem *, unsigned short>, Point> & cache =
    1657     4429805 :       _threaded_cached_normals[tid];
    1658             : 
    1659             :   // See if we've already cached this side normal
    1660     4429805 :   const auto elem_side_pair = std::make_pair(elem, side);
    1661             :   const auto search = cache.find(elem_side_pair);
    1662             : 
    1663             :   // Haven't cached this side normal: compute it and then cache it
    1664     4429805 :   if (search == cache.end())
    1665             :   {
    1666      936906 :     _threaded_fe_face[tid]->reinit(elem, side);
    1667             :     const auto & normal = _threaded_fe_face[tid]->get_normals()[0];
    1668             :     cache.emplace(elem_side_pair, normal);
    1669      936906 :     return normal;
    1670             :   }
    1671             : 
    1672             :   // Have cached this side normal: simply return it
    1673     3492899 :   return search->second;
    1674             : }
    1675             : 
    1676             : bool
    1677        2161 : RayTracingStudy::sameLevelActiveElems() const
    1678             : {
    1679        2161 :   unsigned int min_level = std::numeric_limits<unsigned int>::max();
    1680        2161 :   unsigned int max_level = std::numeric_limits<unsigned int>::min();
    1681             : 
    1682      276647 :   for (const auto & elem : *_mesh.getActiveLocalElementRange())
    1683             :   {
    1684      274486 :     const auto level = elem->level();
    1685      274486 :     min_level = std::min(level, min_level);
    1686      274486 :     max_level = std::max(level, max_level);
    1687             :   }
    1688             : 
    1689        2161 :   _communicator.min(min_level);
    1690        2161 :   _communicator.max(max_level);
    1691             : 
    1692        2161 :   return min_level == max_level;
    1693             : }
    1694             : 
    1695             : Real
    1696     3544866 : RayTracingStudy::subdomainHmax(const SubdomainID subdomain_id) const
    1697             : {
    1698             :   const auto find = _subdomain_hmax.find(subdomain_id);
    1699     3544866 :   if (find == _subdomain_hmax.end())
    1700           2 :     mooseError("Subdomain ", subdomain_id, " not found in subdomain hmax map");
    1701     3544864 :   return find->second;
    1702             : }
    1703             : 
    1704             : bool
    1705        7198 : RayTracingStudy::isRectangularDomain() const
    1706             : {
    1707             :   Real bbox_volume = 1;
    1708       22771 :   for (unsigned int d = 0; d < _mesh.dimension(); ++d)
    1709       15573 :     bbox_volume *= std::abs(_b_box.max()(d) - _b_box.min()(d));
    1710             : 
    1711        7198 :   return MooseUtils::absoluteFuzzyEqual(bbox_volume, totalVolume(), TOLERANCE);
    1712             : }
    1713             : 
    1714             : void
    1715        2025 : RayTracingStudy::resetUniqueRayIDs()
    1716             : {
    1717             :   libmesh_parallel_only(comm());
    1718             : 
    1719             :   Threads::spin_mutex::scoped_lock lock(_spin_mutex);
    1720             : 
    1721             :   mooseAssert(!currentlyGenerating() && !currentlyPropagating(),
    1722             :               "Cannot be reset during generation or propagation");
    1723             : 
    1724        4385 :   for (THREAD_ID tid = 0; tid < libMesh::n_threads(); ++tid)
    1725        2360 :     _threaded_next_ray_id[tid] = (RayID)_pid * (RayID)libMesh::n_threads() + (RayID)tid;
    1726        2025 : }
    1727             : 
    1728             : RayID
    1729     2709316 : RayTracingStudy::generateUniqueRayID(const THREAD_ID tid)
    1730             : {
    1731             :   // Get the current ID to return
    1732     2709316 :   const auto id = _threaded_next_ray_id[tid];
    1733             : 
    1734             :   // Advance so that the next call has the correct ID
    1735     2709316 :   _threaded_next_ray_id[tid] += (RayID)n_processors() * (RayID)libMesh::n_threads();
    1736             : 
    1737     2709316 :   return id;
    1738             : }
    1739             : 
    1740             : void
    1741        2025 : RayTracingStudy::resetReplicatedRayIDs()
    1742             : {
    1743             :   libmesh_parallel_only(comm());
    1744             : 
    1745             :   Threads::spin_mutex::scoped_lock lock(_spin_mutex);
    1746             : 
    1747             :   mooseAssert(!currentlyGenerating() && !currentlyPropagating(),
    1748             :               "Cannot be reset during generation or propagation");
    1749             : 
    1750        2025 :   _replicated_next_ray_id = 0;
    1751        2025 : }
    1752             : 
    1753             : RayID
    1754         632 : RayTracingStudy::generateReplicatedRayID()
    1755             : {
    1756         632 :   return _replicated_next_ray_id++;
    1757             : }
    1758             : 
    1759             : bool
    1760     2053515 : RayTracingStudy::sideIsIncoming(const Elem * const elem,
    1761             :                                 const unsigned short side,
    1762             :                                 const Point & direction,
    1763             :                                 const THREAD_ID tid)
    1764             : {
    1765     2053515 :   const auto & normal = getSideNormal(elem, side, tid);
    1766             :   const auto dot = normal * direction;
    1767     2053515 :   return dot < TraceRayTools::TRACE_TOLERANCE;
    1768             : }
    1769             : 
    1770             : std::shared_ptr<Ray>
    1771     2643796 : RayTracingStudy::acquireRay()
    1772             : {
    1773             :   mooseAssert(currentlyGenerating(), "Can only use during generateRays()");
    1774             : 
    1775     2643796 :   return _parallel_ray_study->acquireParallelData(
    1776             :       /* tid = */ 0,
    1777     5287592 :       this,
    1778     2643796 :       generateUniqueRayID(/* tid = */ 0),
    1779     5287592 :       rayDataSize(),
    1780     2643796 :       rayAuxDataSize(),
    1781     2643796 :       /* reset = */ true,
    1782     2643796 :       Ray::ConstructRayKey());
    1783             : }
    1784             : 
    1785             : std::shared_ptr<Ray>
    1786       10560 : RayTracingStudy::acquireUnsizedRay()
    1787             : {
    1788             :   mooseAssert(currentlyGenerating(), "Can only use during generateRays()");
    1789             : 
    1790       10560 :   return _parallel_ray_study->acquireParallelData(/* tid = */ 0,
    1791       21120 :                                                   this,
    1792       10560 :                                                   generateUniqueRayID(/* tid = */ 0),
    1793       21120 :                                                   /* data_size = */ 0,
    1794       21120 :                                                   /* aux_data_size = */ 0,
    1795       10560 :                                                   /* reset = */ true,
    1796       10560 :                                                   Ray::ConstructRayKey());
    1797             : }
    1798             : 
    1799             : std::shared_ptr<Ray>
    1800         632 : RayTracingStudy::acquireReplicatedRay()
    1801             : {
    1802             :   mooseAssert(currentlyGenerating(), "Can only use during generateRays()");
    1803             :   libmesh_parallel_only(comm());
    1804             : 
    1805         632 :   return _parallel_ray_study->acquireParallelData(
    1806             :       /* tid = */ 0,
    1807        1264 :       this,
    1808         632 :       generateReplicatedRayID(),
    1809        1264 :       rayDataSize(),
    1810         632 :       rayAuxDataSize(),
    1811         632 :       /* reset = */ true,
    1812         632 :       Ray::ConstructRayKey());
    1813             : }
    1814             : 
    1815             : std::shared_ptr<Ray>
    1816        1602 : RayTracingStudy::acquireRegisteredRay(const std::string & name)
    1817             : {
    1818             :   mooseAssert(currentlyGenerating(), "Can only use during generateRays()");
    1819             : 
    1820             :   // Either register a Ray or get an already registered Ray id
    1821        1602 :   const RayID id = registerRay(name);
    1822             : 
    1823             :   // Acquire a Ray with the properly sized data initialized to zero
    1824        1600 :   return _parallel_ray_study->acquireParallelData(
    1825             :       /* tid = */ 0,
    1826        3200 :       this,
    1827             :       id,
    1828        3200 :       rayDataSize(),
    1829        1600 :       rayAuxDataSize(),
    1830        1600 :       /* reset = */ true,
    1831        1600 :       Ray::ConstructRayKey());
    1832             : }
    1833             : 
    1834             : std::shared_ptr<Ray>
    1835     2698423 : RayTracingStudy::acquireCopiedRay(const Ray & ray)
    1836             : {
    1837             :   mooseAssert(currentlyGenerating(), "Can only use during generateRays()");
    1838     2698423 :   return _parallel_ray_study->acquireParallelData(
    1839     5396846 :       /* tid = */ 0, &ray, Ray::ConstructRayKey());
    1840             : }
    1841             : 
    1842             : std::shared_ptr<Ray>
    1843       54960 : RayTracingStudy::acquireRayDuringTrace(const THREAD_ID tid, const AcquireMoveDuringTraceKey &)
    1844             : {
    1845             :   mooseAssert(currentlyPropagating(), "Can only use during propagation");
    1846       54960 :   return _parallel_ray_study->acquireParallelData(tid,
    1847      109920 :                                                   this,
    1848       54960 :                                                   generateUniqueRayID(tid),
    1849      109920 :                                                   rayDataSize(),
    1850       54960 :                                                   rayAuxDataSize(),
    1851       54960 :                                                   /* reset = */ true,
    1852       54960 :                                                   Ray::ConstructRayKey());
    1853             : }

Generated by: LCOV version 1.14