https://mooseframework.inl.gov
RayTracingMeshOutput.C
Go to the documentation of this file.
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 // TODO: remove ignore warnings once std::tuple<> is instantiated as a StandardType
11 // Using push_parallel_vector_data with std::tuple<> leads to a -Wextra error
12 // https://github.com/libMesh/TIMPI/issues/52
13 #include "libmesh/ignore_warnings.h"
14 #include "RayTracingMeshOutput.h"
15 #include "libmesh/restore_warnings.h"
16 
17 // Local Includes
18 #include "RayTracingStudy.h"
19 #include "TraceData.h"
20 
21 // libMesh includes
22 #include "libmesh/mesh_tools.h"
23 #include "libmesh/parallel_algebra.h"
24 #include "libmesh/parallel_sync.h"
25 #include "libmesh/remote_elem.h"
26 
27 using namespace libMesh;
28 
31 {
33 
34  params.addRequiredParam<UserObjectName>("study", "The RayTracingStudy to get the segments from");
35 
36  params.addParam<bool>("output_data", false, "Whether or not to also output the Ray's data");
37  params.addParam<std::vector<std::string>>("output_data_names",
38  "The names of specific data to output");
39  params.addParam<bool>("output_data_nodal",
40  false,
41  "Whether or not to output the Ray's data in a nodal sense, in which the "
42  "data is interpolated linearly across segments");
43 
44  params.addParam<bool>(
45  "output_aux_data", false, "Whether or not to also output the Ray's aux data");
46  params.addParam<std::vector<std::string>>("output_aux_data_names",
47  "The names of specific aux data to output");
48 
49  MultiMooseEnum props("ray_id intersections pid processor_crossings trajectory_changes",
50  "ray_id intersections");
51  params.addParam<MultiMooseEnum>("output_properties", props, "Which Ray properties to output");
52 
53  return params;
54 }
55 
57  : FileOutput(params),
58  UserObjectInterface(this),
59  _study(getUserObject<RayTracingStudy>("study")),
60  _output_data(getParam<bool>("output_data")),
61  _output_data_names(isParamValid("output_data_names")
62  ? &getParam<std::vector<std::string>>("output_data_names")
63  : nullptr),
64  _output_data_nodal(getParam<bool>("output_data_nodal")),
65  _output_aux_data(getParam<bool>("output_aux_data")),
66  _output_aux_data_names(isParamValid("output_aux_data_names")
67  ? &getParam<std::vector<std::string>>("output_aux_data_names")
68  : nullptr),
69  _ray_id_var(invalid_uint),
70  _intersections_var(invalid_uint),
71  _pid_var(invalid_uint),
72  _processor_crossings_var(invalid_uint),
73  _trajectory_changes_var(invalid_uint),
74  _segmented_rays(false)
75 {
77  mooseError("In order to output Ray data in output '",
78  name(),
79  "', the RayTracingStudy '",
80  _study.name(),
81  "' must set data_on_cache_traces = true");
83  mooseError("In order to output Ray aux data in output '",
84  name(),
85  "', the RayTracingStudy '",
86  _study.name(),
87  "' must set aux_data_on_cache_traces = true");
89  paramError("output_data_nodal",
90  "Cannot be used unless there is data to output; in addition set either "
91  "'output_data' or 'output_data_names");
93  paramError("output_data_nodal", "Not supported when study segments_on_cache_traces = false");
95  paramError("output_data", "Cannot be used in addition to 'output_data_names'; choose one");
97  paramError("output_aux_data",
98  "Cannot be used in addition to 'output_aux_data_names'; choose one");
99 }
100 
101 std::string
103 {
104  // Append the .e extension on the base file name
105  std::ostringstream output;
106  output << _file_base << ".e";
107 
108  // Add the _000x extension to the file
109  if (_file_num > 0)
110  output << "-s" << std::setw(_padding) << std::setprecision(0) << std::setfill('0') << std::right
111  << _file_num;
112 
113  // Return the filename
114  return output.str();
115 }
116 
117 void
119 {
120  // Do we even have any traces?
121  auto num_segments = _study.getCachedTraces().size();
122  _communicator.sum(num_segments);
123  if (!num_segments)
124  mooseError("No cached trace segments were found in the study '", _study.name(), "'.");
125 
126  // Build the _inflated_neighbor_bboxes
128 
129  // Build the _segment_mesh
131 
132  // Setup the system to store the Ray field data
134 
135  // Fill the field data
136  fillFields();
137 
138  // And output
139  outputMesh();
140 
141  // Done with these
142  // We don't necessarily need to create a new mesh every time, but it's easier than
143  // checking if the Rays have changed from last time we built a mesh
144  _es = nullptr;
145  _sys = nullptr;
146  _segment_mesh = nullptr;
147 }
148 
149 void
151 {
152  TIME_SECTION("buildIDMap", 3, "Building RayTracing ID Map");
153 
154  // Build the maximum number of nodes required to represent each one of my local Rays
155  std::map<RayID, dof_id_type> local_ray_needed_nodes;
156  for (const auto & trace_data : _study.getCachedTraces())
157  {
158  const auto find = local_ray_needed_nodes.find(trace_data._ray_id);
159  if (find == local_ray_needed_nodes.end())
160  local_ray_needed_nodes[trace_data._ray_id] = neededNodes(trace_data);
161  else
162  {
163  auto & value = find->second;
164  value = std::max(value, neededNodes(trace_data));
165  }
166  }
167 
168  // Fill all of my local Ray maxima to be sent to processor 0
169  std::map<processor_id_type, std::vector<std::pair<RayID, dof_id_type>>> send_needed_nodes;
170  if (local_ray_needed_nodes.size())
171  {
172  auto & root_entry = send_needed_nodes[0];
173  for (const auto & id_nodes_pair : local_ray_needed_nodes)
174  root_entry.emplace_back(id_nodes_pair);
175  }
176 
177  // The global map of ray -> required nodes needed to be filled on processor 0
178  std::map<RayID, dof_id_type> global_needed_nodes;
179  // Keep track of what Ray IDs we received from what processors so we know who to send back to
180  std::unordered_map<processor_id_type, std::set<RayID>> pid_received_ids;
181  // Take the required nodes for each Ray and determine on processor 0 the global maximum
182  // nodes needed to represent each Ray
183  const auto append_global_max =
184  [&global_needed_nodes, &pid_received_ids](
185  processor_id_type pid, const std::vector<std::pair<RayID, dof_id_type>> & pairs)
186  {
187  auto & pid_received_entry = pid_received_ids[pid];
188  for (const auto & id_max_pair : pairs)
189  {
190  const RayID ray_id = id_max_pair.first;
191  const dof_id_type max = id_max_pair.second;
192 
193  const auto find = global_needed_nodes.find(ray_id);
194  if (find == global_needed_nodes.end())
195  global_needed_nodes.emplace(ray_id, max);
196  else
197  {
198  auto & current_max = find->second;
199  current_max = std::max(current_max, max);
200  }
201 
202  pid_received_entry.insert(ray_id);
203  }
204  };
205  Parallel::push_parallel_vector_data(comm(), send_needed_nodes, append_global_max);
206 
207  // Decide on the starting representative starting node and elem ID for each Ray
208  std::map<RayID, std::pair<dof_id_type, dof_id_type>> global_ids;
209  dof_id_type current_node_id = 0;
210  dof_id_type current_elem_id = 0;
211  for (auto & pair : global_needed_nodes)
212  {
213  const RayID ray_id = pair.first;
214  const dof_id_type max_nodes = pair.second;
215 
216  global_ids.emplace(ray_id, std::make_pair(current_node_id, current_elem_id));
217 
218  current_node_id += max_nodes;
219  if (max_nodes > 1)
220  current_elem_id += max_nodes - 1;
221  else // stationary
222  current_elem_id += 1;
223  }
224 
225  // Share the max node IDs so that we have a starting point for unique IDs for elems
226  _max_node_id = current_node_id;
227  comm().max(_max_node_id);
228 
229  // Fill the starting ID information to each processor that needs it from processor 0
230  std::unordered_map<processor_id_type, std::vector<std::tuple<RayID, dof_id_type, dof_id_type>>>
231  send_ids;
232  for (auto & pid_ids_pair : pid_received_ids)
233  {
234  const processor_id_type pid = pid_ids_pair.first;
235  const std::set<RayID> & ray_ids = pid_ids_pair.second;
236 
237  auto & pid_send = send_ids[pid];
238  for (const RayID ray_id : ray_ids)
239  {
240  const auto & global_entry = global_ids.at(ray_id);
241  const dof_id_type node_id = global_entry.first;
242  const dof_id_type elem_id = global_entry.second;
243  pid_send.emplace_back(ray_id, node_id, elem_id);
244  }
245  }
246 
247  // Take the starting ID information from processor 0 and store it locally
248  const auto append_ids =
249  [&](processor_id_type,
250  const std::vector<std::tuple<RayID, dof_id_type, dof_id_type>> & tuples)
251  {
252  for (const auto & tuple : tuples)
253  {
254  const RayID ray_id = std::get<0>(tuple);
255  const dof_id_type node_id = std::get<1>(tuple);
256  const dof_id_type elem_id = std::get<2>(tuple);
257  _ray_starting_id_map.emplace(ray_id, std::make_pair(node_id, elem_id));
258  }
259  };
260 
261  _ray_starting_id_map.clear();
262  Parallel::push_parallel_vector_data(comm(), send_ids, append_ids);
263 }
264 
265 void
267 {
268  TIME_SECTION("buildSegmentMesh", 3, "Building RayTracing Mesh Output");
269 
270  _segmented_rays = false;
271 
272  // Tally nodes and elems for the local mesh ahead of time so we can reserve
273  // Each segment requires an element, and we need one more node than elems
274  dof_id_type num_nodes = 0;
275  dof_id_type num_elems = 0;
276  for (const auto & entry : _study.getCachedTraces())
277  {
278  const auto num_segments = entry.numSegments();
279  num_nodes += num_segments + 1;
280  if (num_segments >= 1)
281  {
282  _segmented_rays = true;
283  num_elems += num_segments;
284  }
285  else if (entry.stationary())
286  num_elems += 1;
287  }
288 
290 
291  // Build the segment mesh
292  mooseAssert(!_segment_mesh, "Not cleared");
293  if (_communicator.size() == 1)
294  _segment_mesh = std::make_unique<ReplicatedMesh>(_communicator, _mesh_ptr->dimension());
295  else
296  {
297  _segment_mesh = std::make_unique<DistributedMesh>(_communicator, _mesh_ptr->dimension());
298  _segment_mesh->set_distributed();
299  }
300  // We set neighbor links
301  _segment_mesh->allow_find_neighbors(false);
302  // Don't renumber so that we can remain consistent between processor counts
303  _segment_mesh->allow_renumbering(false);
304  // As we're building segments for just this partiton, we're partitioning on our own
305  _segment_mesh->skip_partitioning(true);
306  // Reserve nodes and elems
307  _segment_mesh->reserve_nodes(num_nodes);
308  _segment_mesh->reserve_elem(num_elems);
309 
310  buildIDMap();
311 
312  // Decide on a starting ID for each processor
313  // Build a mesh segment for each local Ray segment
314  // Each one of these objects represents a single Ray's path through this processor
315  for (const auto & trace_data : _study.getCachedTraces())
316  {
317  // If we have no segments, this is a trace that skimmed the corner of a processor boundary and
318  // didn't contribute anything
319  if (trace_data.numSegments() == 0 && !trace_data.stationary())
320  continue;
321 
322  dof_id_type node_id, elem_id;
323  startingIDs(trace_data, node_id, elem_id);
324 
325  // Add the start point
326  mooseAssert(!_segment_mesh->query_node_ptr(node_id), "Node already exists");
327  Node * last_node =
328  _segment_mesh->add_point(trace_data._point_data[0]._point, node_id, processor_id());
329  last_node->set_unique_id(node_id++);
330 
331  // Stationary, add a NodeElem
332  if (trace_data.stationary())
333  {
334  mooseAssert(!_segment_mesh->query_elem_ptr(elem_id), "Elem already exists");
335  auto elem = _segment_mesh->add_elem(Elem::build_with_id(NODEELEM, elem_id));
336  elem->processor_id(processor_id());
337  elem->set_unique_id(_max_node_id + elem_id++);
338  elem->set_node(0, last_node);
339  }
340  // Not stationary; add a point and element for each segment
341  else
342  {
343  Elem * last_elem = nullptr;
344 
345  for (std::size_t i = 1; i < trace_data._point_data.size(); ++i)
346  {
347  const auto & point = trace_data._point_data[i]._point;
348 
349  // Add next point on the trace
350  mooseAssert(!_segment_mesh->query_node_ptr(node_id), "Node already exists");
351  Node * node = _segment_mesh->add_point(point, node_id, processor_id());
352  node->set_unique_id(node_id++);
353 
354  // Build a segment from this point to the last
355  mooseAssert(!_segment_mesh->query_elem_ptr(elem_id), "Elem already exists");
356  Elem * elem = _segment_mesh->add_elem(Elem::build_with_id(EDGE2, elem_id));
357  elem->processor_id(processor_id());
358  elem->set_unique_id(_max_node_id + elem_id++);
359  elem->set_node(0, last_node);
360  elem->set_node(1, node);
361 
362  // Set neighbor links
363  if (last_elem)
364  {
365  elem->set_neighbor(0, last_elem);
366  last_elem->set_neighbor(1, elem);
367  }
368 
369  last_elem = elem;
370  last_node = node;
371  }
372  }
373  }
374 
375  // If the mesh is replicated, everything that follows is unnecessary. Prepare and be done.
376  if (_segment_mesh->is_replicated())
377  {
378  _segment_mesh->prepare_for_use();
379  return;
380  }
381 
382  // Find the Nodes on processor boundaries that we need to decide on owners for. Also set up
383  // remote_elem links for neighbors we clearly don't have. We don't build the neighbor maps at
384  // all, so all we have are nullptr and remote_elem. We can only do all of this after the mesh is
385  // built because the mesh isn't necessarily built in the order Rays are traced
386  std::vector<Node *> need_node_owners;
387  for (const auto & trace_data : _study.getCachedTraces())
388  {
389  const auto num_segments = trace_data.numSegments();
390 
391  if (num_segments == 0)
392  continue;
393 
394  dof_id_type start_node_id, start_elem_id;
395  startingIDs(trace_data, start_node_id, start_elem_id);
396 
397  // Another part of the trace for this Ray happened before this one
398  if ((_study.segmentsOnCacheTraces() ? trace_data._intersections
399  : (unsigned long int)(trace_data._processor_crossings +
400  trace_data._trajectory_changes)) != 0)
401  {
402  // The element before start_elem (may be nullptr, meaning it didn't trace on this proc)
403  Elem * previous_elem = _segment_mesh->query_elem_ptr(start_elem_id - 1);
404 
405  // We don't have the previous element segment, so it exists on another processor
406  // Set the remote_elem and mark that we need to find out who owns the first node on this
407  // trace
408  if (!previous_elem)
409  {
410  Elem * start_elem = _segment_mesh->elem_ptr(start_elem_id);
411  start_elem->set_neighbor(0, const_cast<RemoteElem *>(remote_elem));
412  start_elem->node_ptr(0)->invalidate_processor_id();
413  need_node_owners.push_back(start_elem->node_ptr(0));
414  }
415  }
416 
417  // If you have multiple sets of segments on a single processor, it is possible
418  // to have part of the trace on another processor within the segments we know about.
419  // In this case, we have to do some magic to the neighbor links so that the mesh
420  // doesn't fail in assertions.
421  if (!trace_data._last)
422  {
423  // The element after end_elem (may be nullptr, meaning it didn't happen on this proc)
424  Elem * next_elem = _segment_mesh->query_elem_ptr(start_elem_id + num_segments);
425 
426  // We have the next element from another trace, set neighbors as we own both
427  if (!next_elem)
428  {
429  Elem * end_elem = _segment_mesh->elem_ptr(start_elem_id + num_segments - 1);
430  end_elem->set_neighbor(1, const_cast<RemoteElem *>(remote_elem));
431  end_elem->node_ptr(1)->invalidate_processor_id();
432  need_node_owners.push_back(end_elem->node_ptr(1));
433  }
434  }
435  }
436 
437  // Sort through the neighboring bounding boxes and prepare requests to each processor that /may/
438  // also have one of the nodes that we need to decide on an owner for
439  std::unordered_map<processor_id_type, std::vector<dof_id_type>> need_node_owners_sends;
440  for (const Node * node : need_node_owners)
441  for (const auto & neighbor_pid_bbox_pair : _inflated_neighbor_bboxes)
442  {
443  const auto neighbor_pid = neighbor_pid_bbox_pair.first;
444  const auto & neighbor_bbox = neighbor_pid_bbox_pair.second;
445  if (neighbor_bbox.contains_point(*node))
446  need_node_owners_sends[neighbor_pid].push_back(node->id());
447  }
448 
449  // Functor that takes in a set of incoming node IDs from a processor and decides on an owner.
450  // For every node that we need an owner for, this should be called for said node on both
451  // processors. Therefore, just pick the minimum processor ID as the owner. This should never
452  // happen more than once for a single node because we're building 1D elems and we can only ever
453  // have two procs that touch a node.
454  std::unordered_map<processor_id_type, std::vector<dof_id_type>> confirm_node_owners_sends;
455  auto decide_node_owners_functor =
456  [this, &confirm_node_owners_sends](processor_id_type pid,
457  const std::vector<dof_id_type> & incoming_node_ids)
458  {
459  auto & confirm_pid = confirm_node_owners_sends[pid];
460  for (const auto & node_id : incoming_node_ids)
461  {
462  Node * node = _segment_mesh->query_node_ptr(node_id);
463  if (node)
464  {
465  mooseAssert(!node->valid_processor_id(), "Should be invalid");
466  node->processor_id() = std::min(pid, processor_id());
467  confirm_pid.push_back(node_id);
468  }
469  }
470  };
471 
472  // Ship the nodes that need owners for and pick an owner when we receive
473  Parallel::push_parallel_vector_data(
474  _communicator, need_node_owners_sends, decide_node_owners_functor);
475 
476  // At this point, any nodes that we actually need ghosts for should have their processor_ids
477  // satisfied. Anything that is left isn't actually ghosted and is ours. Therefore, set
478  // everything left that is invalid to be owned by this proc.
479  for (Node * node : need_node_owners)
480  if (!node->valid_processor_id())
481  node->processor_id() = processor_id();
482 
483  // We're done!
484  _segment_mesh->prepare_for_use();
485 }
486 
487 void
489 {
490  TIME_SECTION("setupEquationSystem", 3, "Setting Up Ray Tracing MeshOutput Equation System");
491 
492  _es = std::make_unique<EquationSystems>(*_segment_mesh);
493  _sys = &_es->add_system<libMesh::ExplicitSystem>("sys");
494  _data_vars.clear();
495  _aux_data_vars.clear();
496 
497  // Add variables for the basic properties if enabled
503  for (auto & prop : _pars.get<MultiMooseEnum>("output_properties"))
504  switch (prop)
505  {
506  case 0: // ray_id (stationary and segments)
508  break;
509  case 1: // intersections (segments only)
510  if (_segmented_rays)
511  _intersections_var = _sys->add_variable("intersections", CONSTANT, MONOMIAL);
512  break;
513  case 2: // pid (stationary and segments)
515  break;
516  case 3: // processor_crossings (segments only)
517  if (_segmented_rays)
518  _processor_crossings_var = _sys->add_variable("processor_crossings", CONSTANT, MONOMIAL);
519  break;
520  case 4: // trajectory_changes (segments only)
521  if (_segmented_rays)
522  _trajectory_changes_var = _sys->add_variable("trajectory_changes", CONSTANT, MONOMIAL);
523  break;
524  default:
525  mooseError("Invalid property");
526  }
527 
528  const auto get_data_vars = [this](const bool aux)
529  {
530  // The data index -> variable result
531  std::vector<std::pair<RayDataIndex, unsigned int>> vars;
532 
533  const auto from_names =
536 
537  // Nothing to output
538  if (!from_names)
539  return vars;
540 
541  const auto output_data_nodal = aux ? false : _output_data_nodal;
542 
543  for (const auto & name : *from_names)
544  {
545  const auto data_index =
547  if (data_index == Ray::INVALID_RAY_DATA_INDEX)
548  {
549  const std::string names_param = aux ? "output_aux_data_names" : "output_data_names";
550  const std::string data_prefix = aux ? "aux " : "";
551  paramError(names_param, "The ray ", data_prefix, "data '", name, "' is not registered");
552  }
553 
554  const std::string var_prefix = aux ? "aux_" : "";
555  if (output_data_nodal)
556  _sys->add_variable(var_prefix + name, FIRST, LAGRANGE);
557  else
558  _sys->add_variable(var_prefix + name, CONSTANT, MONOMIAL);
559  const auto var_num = _sys->variable_number(var_prefix + name);
560  vars.emplace_back(data_index, var_num);
561  }
562 
563  return vars;
564  };
565 
566  _data_vars = get_data_vars(false);
567  _aux_data_vars = get_data_vars(true);
568 
569  // All done
570  _es->init();
571 }
572 
573 void
575 {
576  TIME_SECTION("fillFields", 3, "Filling RayTracing MeshOutput Fields");
577 
578  // Helper for setting the solution (if enabled)
579  const auto set_solution =
580  [this](const DofObject * const dof, const unsigned int var, const Real value)
581  {
582  mooseAssert(dof, "Nullptr dof");
583  if (var != invalid_uint)
584  {
585  const auto dof_number = dof->dof_number(_sys->number(), var, 0);
586  _sys->solution->set(dof_number, value);
587  }
588  };
589 
590  // Helper for filling data and aux data (if enabled)
591  const auto fill_data =
592  [&set_solution](const DofObject * const dof, const auto & vars, const auto & data)
593  {
594  mooseAssert(dof, "Nullptr dof");
595  for (const auto & [data_index, var_num] : vars)
596  set_solution(dof, var_num, data[data_index]);
597  };
598 
599  for (const auto & trace_data : _study.getCachedTraces())
600  {
601  auto intersection = trace_data._intersections;
602 
603  // No segments and not stationary; means this ray bounced off
604  // this processor and never actually moved on this processor
605  if (!trace_data.numSegments() && !trace_data.stationary())
606  continue;
607 
608  dof_id_type node_id, elem_id;
609  startingIDs(trace_data, node_id, elem_id);
610 
611  const Elem * elem = _segment_mesh->elem_ptr(elem_id);
612 
613  // Fill first node's nodal data if we need it; the loop that follows will handle
614  // the rest of the nodes
615  if (_output_data_nodal && _study.hasRayData() && !trace_data.stationary())
616  fill_data(elem->node_ptr(0), _data_vars, trace_data._point_data[0]._data);
617 
618  const std::size_t start = trace_data.stationary() ? 0 : 1;
619  for (const auto i : make_range(start, trace_data._point_data.size()))
620  {
621  mooseAssert(elem, "Nullptr elem");
622 
623  // Elemental ID and pid
624  set_solution(elem, _ray_id_var, trace_data._ray_id);
625  set_solution(elem, _pid_var, processor_id());
626  // Elemental properties that only apply to segments
627  if (!trace_data.stationary())
628  {
629  set_solution(elem, _intersections_var, intersection++);
630  set_solution(elem, _processor_crossings_var, trace_data._processor_crossings);
631  set_solution(elem, _trajectory_changes_var, trace_data._trajectory_changes);
632  }
633  const auto & point_data = trace_data._point_data[i];
634  // Data fields
635  if (_output_data_nodal && !trace_data.stationary())
636  fill_data(elem->node_ptr(1), _data_vars, point_data._data);
637  else
638  fill_data(elem, _data_vars, point_data._data);
639  // Aux data fields
640  fill_data(elem, _aux_data_vars, point_data._aux_data);
641 
642  // Advance to the next element
643  if (!trace_data.stationary())
644  elem = elem->neighbor_ptr(1);
645  }
646  }
647 
648  _sys->solution->close();
649  _sys->update();
650 }
651 
652 void
654 {
655  TIME_SECTION("buildBoundingBoxes", 3, "Building Bounding Boxes for RayTracing Mesh Output");
656 
657  // Not used in the one proc case
658  if (_communicator.size() == 1)
659  return;
660 
661  // Local bounding box
662  _bbox = MeshTools::create_local_bounding_box(_mesh_ptr->getMesh());
663 
664  // Gather the bounding boxes of all processors
665  std::vector<std::pair<Point, Point>> bb_points = {static_cast<std::pair<Point, Point>>(_bbox)};
666  _communicator.allgather(bb_points, true);
668  for (processor_id_type pid = 0; pid < _communicator.size(); ++pid)
669  {
670  BoundingBox pid_bbox = static_cast<BoundingBox>(bb_points[pid]);
671  pid_bbox.scale(0.01);
672  _inflated_bboxes[pid] = pid_bbox;
673  }
674 
675  // Find intersecting (neighbor) bounding boxes
677  for (processor_id_type pid = 0; pid < _communicator.size(); ++pid)
678  if (pid != processor_id())
679  {
680  // Insert if the searched processor's bbox intersects my bbox
681  const auto & pid_bbox = _inflated_bboxes[pid];
682  if (_bbox.intersects(pid_bbox))
683  _inflated_neighbor_bboxes.emplace_back(pid, pid_bbox);
684  }
685 }
686 
687 void
689  dof_id_type & start_node_id,
690  dof_id_type & start_elem_id) const
691 {
692  const auto [begin_node_id, begin_elem_id] = _ray_starting_id_map.at(trace_data._ray_id);
693 
694  const auto offset =
695  trace_data.stationary()
696  ? 1
698  ? trace_data._intersections
699  : (trace_data._processor_crossings + trace_data._trajectory_changes));
700 
701  start_node_id = begin_node_id + offset;
702  start_elem_id = begin_elem_id + offset;
703 }
704 
707 {
709  return trace_data._intersections + trace_data._point_data.size();
710  return trace_data._processor_crossings + trace_data._trajectory_changes +
711  trace_data._point_data.size();
712 }
void buildSegmentMesh()
Build the mesh that contains the ray tracing segments.
LAGRANGE
bool stationary() const
Definition: TraceData.h:59
void fillFields()
Fill the Ray field data.
void allgather(const T &send_data, std::vector< T, A > &recv_data) const
bool hasRayData() const
Whether or not any Ray data are registered.
dof_id_type dof_number(const unsigned int s, const unsigned int var, const unsigned int comp) const
bool segmentsOnCacheTraces() const
Whether or not to cache individual element segments when _cache_traces = true.
void fill_data(std::map< processor_id_type, std::vector< std::set< unsigned int >>> &data, int M)
static const RayDataIndex INVALID_RAY_DATA_INDEX
Invalid index into a Ray&#39;s data.
Definition: Ray.h:200
const bool _output_data
Whether or not to output all of the Ray&#39;s data.
unsigned long int RayID
Type for a Ray&#39;s ID.
Definition: Ray.h:43
const unsigned int invalid_uint
const bool _output_data_nodal
Whether or not to output the Ray&#39;s data in a nodal, linear sense.
void addParam(const std::string &name, const std::initializer_list< typename T::value_type > &value, const std::string &doc_string)
Data structure that stores information for output of a partial trace of a Ray on a processor...
Definition: TraceData.h:42
std::vector< std::pair< R1, R2 > > get(const std::string &param1, const std::string &param2) const
void startingIDs(const TraceData &trace_data, dof_id_type &start_node_id, dof_id_type &start_elem_id) const
Gets the starting node and element IDs in the EDGE2 mesh for a given trace.
bool intersects(const BoundingBox &) const
const std::vector< TraceData > & getCachedTraces() const
Get the cached trace data structure.
FIRST
const std::vector< std::string > & rayDataNames() const
The Ray data names.
char ** vars
const Parallel::Communicator & comm() const
std::string _file_base
std::vector< std::pair< RayDataIndex, unsigned int > > _data_vars
The ray data index -> variable index map.
const Parallel::Communicator & _communicator
std::unordered_map< RayID, std::pair< dof_id_type, dof_id_type > > _ray_starting_id_map
The map from RayID to the starting element and node ID of the mesh element for said Ray...
The following methods are specializations for using the Parallel::packed_range_* routines for a vecto...
dof_id_type _max_node_id
The max node ID for the ray tracing mesh for creating unique elem IDs.
const RayTracingStudy & _study
The RayTracingStudy.
const unsigned long int _intersections
The number of intersections thus far.
Definition: TraceData.h:70
bool _segmented_rays
Whether or not we have segmented rays.
virtual const std::string & name() const
void addRequiredParam(const std::string &name, const std::string &doc_string)
auto max(const L &left, const R &right)
void buildIDMap()
Builds a map for each Ray to starting element and node ID for the EDGE2 mesh that will represent said...
RayDataIndex getRayAuxDataIndex(const std::string &name, const bool graceful=false) const
Gets the index associated with a registered value in the Ray aux data.
unsigned int variable_number(std::string_view var) const
unsigned int _padding
processor_id_type size() const
uint8_t processor_id_type
CONSTANT
unsigned int number() const
const bool _output_aux_data
Whether or not to output the Ray&#39;s aux data.
Real value(unsigned n, unsigned alpha, unsigned beta, Real x)
MeshBase & getMesh()
bool auxDataOnCacheTraces() const
Whether or not to store the Ray aux data on the cached Ray traces.
virtual unsigned int dimension() const
std::vector< std::pair< RayDataIndex, unsigned int > > _aux_data_vars
The ray aux data index -> variable index map.
bool valid_processor_id() const
const std::vector< std::string > *const _output_data_names
Specific Ray data to output.
std::unique_ptr< NumericVector< Number > > solution
static InputParameters validParams()
static InputParameters validParams()
void setupEquationSystem()
Setup the equation system that stores the segment-wise field data.
unsigned int add_variable(std::string_view var, const FEType &type, const std::set< subdomain_id_type > *const active_subdomains=nullptr)
const std::vector< std::string > *const _output_aux_data_names
Specific Ray Aux data to output.
std::unique_ptr< libMesh::EquationSystems > _es
The EquationSystems.
dof_id_type neededNodes(const TraceData &trace_data) const
Gets the number of nodes needed to represent a given trace.
void paramError(const std::string &param, Args... args) const
const unsigned int _processor_crossings
Number of processor crossings thus far.
Definition: TraceData.h:72
void set_neighbor(const unsigned int i, Elem *n)
unsigned int _intersections_var
The variable index in _sys for the intersection ID (if any)
void invalidate_processor_id()
const std::vector< std::string > & rayAuxDataNames() const
The Ray aux data names.
RayTracingMeshOutput(const InputParameters &parameters)
MONOMIAL
virtual void outputMesh()=0
Output the mesh - to be overridden.
unsigned int _ray_id_var
The variable index in _sys for the Ray&#39;s ID (if any)
const Elem * neighbor_ptr(unsigned int i) const
virtual void update()
libMesh::ExplicitSystem * _sys
The system that stores the field data.
std::vector< std::pair< processor_id_type, BoundingBox > > _inflated_neighbor_bboxes
Inflated bounding boxes that are neighboring to this processor (pid : bbox for each entry) ...
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
EDGE2
std::unique_ptr< MeshBase > _segment_mesh
The mesh that contains the segments.
void max(const T &r, T &o, Request &req) const
const unsigned int _trajectory_changes
Number of trajectory changes thus far.
Definition: TraceData.h:74
const Node * node_ptr(const unsigned int i) const
IntRange< T > make_range(T beg, T end)
MooseMesh * _mesh_ptr
void mooseError(Args &&... args) const
const RayID _ray_id
The Ray ID.
Definition: TraceData.h:68
const InputParameters & _pars
unsigned int & _file_num
std::vector< BoundingBox > _inflated_bboxes
The inflated bounding boxes for all processors.
BoundingBox _bbox
The bounding box for this processor.
std::vector< TracePointData > _point_data
The data for each point along the track.
Definition: TraceData.h:78
void scale(const Real factor)
processor_id_type processor_id() const
unsigned int _processor_crossings_var
The variable index in _sys for the Ray&#39;s processor crossings (if any)
processor_id_type processor_id() const
bool dataOnCacheTraces() const
Whether or not to store the Ray data on the cached Ray traces.
RayDataIndex getRayDataIndex(const std::string &name, const bool graceful=false) const
Gets the index associated with a registered value in the Ray data.
unsigned int _pid_var
The variable index in _sys for the Ray&#39;s processor id (if any)
void ErrorVector unsigned int
void buildBoundingBoxes()
Build the inflated neighbor bounding boxes stored in _inflated_neighbor_bboxes for the purposes of id...
virtual std::string filename() override
NODEELEM
unsigned int _trajectory_changes_var
The variable index in _sys for the Ray&#39;s trajectory changes (if any)
uint8_t dof_id_type
Base class for Ray tracing studies that will generate Rays and then propagate all of them to terminat...
const RemoteElem * remote_elem