RepeatableRayStudyBase

The RepeatableRayStudyBase is a specialized RayTracingStudy that simplifies the Ray generation process. To describe how exactly it simplifies this process, we will describe the difficulties of Ray generation and how this study overcomes many of those issues.

schooltip

The use of the RepeatableRayStudyBase is considered an advanced use of the Ray Tracing Module. Please consider the RepeatableRayStudy before using this object.

Finding a Ray's Starting Element

The Ray Tracing Module requires that a Ray be on the processor that contains the element that it starts in when it is moved into the buffer to be traced. If one wants to only specify a set of starting points for rays, it becomes necessary to determine which elements said points are contained in and then to communicate ahead of time the Ray to the processor that owns each element.

The RepeatableRayStudyBase has an option that only requires the defined rays to have their starting points set. Internally, the rays will be "claimed" and communicated to the processor that owns their starting element based on the user-set starting points. In addition, the incoming side on said starting elements will be set (if any).

The "claiming" of rays after they are defined is controlled by the _claim_after_define_rays private parameter. An example of a study that uses this claiming is the RepeatableRayStudy.

Tracing After Mesh Changes

In the case of mesh changes (for example, mesh adaptivity steps), the element that a Ray starts in may change, which may also result in a change of the processor that a Ray starts on.

The RepeatableRayStudyBase keeps a copy of the user-generated rays such that at any time the mesh changes, the information is available to trace rays with the same information (start point, direction, data, etc). Internally, when the mesh changes the RepeatableRayStudyBase will re-claim the users' rays to ensure that they are again on the correct processor with the correct starting elements.

Process

The process by which the RepeatableRayStudyBase generates rays follows:

  1. Define Rays - Only done on the first execution of the study.

  2. Claim Rays - Done on the first execution of the study and after all mesh changes.

  3. Copy Rays - Done on every execution of the study.

Define Rays

The user-derived object will overload the defineRays() method. Upon first execution of the study, this method will be called. Within defineRays(), you are to create rays (see Defining a Ray Trajectory) and move them into the _rays member variable. This action by default is only performed once, when generateRays() is first called.

If you are defining rays that need to be "claimed", that is they are being defined with only their start points (and not starting elements or starting incoming sides), ensure that the _claim_after_define_rays parameter is set to true. When this parameter is true, it is assumed that the starting elements and starting incoming sides have not been set and that the rays need to be "claimed". After claiming, internally they will be placed on the correct processors with a starting element that contains their starting points.

If you are defining rays that:

  • have their starting point set,

  • have their starting element set (which contains the starting point),

  • have their starting incoming sides set (if any - the rays can also start within an element),

  • are filled into _rays on the processor that contains their respective starting elements,

then it is not necessary to utilize claiming. You would set the _claim_after_define_rays parameter to false.

Any Ray data or auxiliary data that is set at this point will also be used in any further executions of this study.

The other important parameter that can be changed is the _define_rays_replicated private parameter. If this parameter is true, the rays that are filled into _rays during defineRays() are replicated. That is, the same rays were filled into _rays across all processors. If _claim_after_define_rays == false, the _define_rays_replicated parameter is set to false regardless of the user's setting because it is not possible for rays that are on their correct processors with their correct starting elements to be replicated.

Example

For an example of the define process, see RepeatableRayStudy:

void
RepeatableRayStudy::defineRays()
{
  for (std::size_t i = 0; i < _names.size(); ++i)
  {
    std::shared_ptr<Ray> ray = acquireRegisteredRay(_names[i]);

    ray->setStart(_start_points[i]);
    if (_end_points) // user set end point
      ray->setStartingEndPoint((*_end_points)[i]);
    else // user set direction
      ray->setStartingDirection((*_directions)[i]);

    // Set the data if the user requested so
    const auto set_data = [this, &ray, &i](const bool aux)
    {
      const auto indices = aux ? _ray_aux_data_indices : _ray_data_indices;
      const auto data = aux ? _initial_ray_aux_data : _initial_ray_data;
      if (data)
      {
        mooseAssert(data->size() == _names.size(), "Size mismatch");
        mooseAssert((*data)[i].size() == indices.size(), "Size mismatch");
        for (const auto index_i : indices)
        {
          const auto data_index = indices[index_i];
          const auto value = (*data)[i][index_i];
          if (aux)
            ray->auxData(data_index) = value;
          else
            ray->data(data_index) = value;
        }
      }
    };
    set_data(false);
    set_data(true);

    // User set max-distances
    if (_max_distances)
      ray->setStartingMaxDistance((*_max_distances)[i]);

    _rays.emplace_back(std::move(ray));
  }
}
(moose/modules/ray_tracing/src/userobjects/RepeatableRayStudy.C)

In this case, the rays defined during defineRays() are replicated across all processors. Their start points are set but the starting elements and starting incoming sides are not set, therefore claiming is required.

Claim Rays

Note that the actions that follow in this section are performed on the first execution of the study and thereafter only after each mesh change, because claiming afterwards is only needed when the mesh changes.

If the private parameter _claim_after_define_rays == true, the rays within _rays do not have their starting elements set and are not necessarily on the correct starting processor. The _rays are passed to the ClaimRays object and the result is _local_rays being filled with the rays that can be started in the local processor. This claiming is only performed in the first call of generateRays() and thereafter is only called after each mesh change to re-determine the starting elements and starting processors.

If the private parameter _claim_after_define_rays == false, the rays within _rays are already on their starting processor with the starting elements set. The _rays are then simply copied into _local_rays. Because all of the Ray objects are actually shared pointers (std::shared_ptr<Ray>), this copying process does not actually "copy" the rays, it just points to the same objects that are in _rays. This "copying" is seen as:

  // The Rays in _rays are ready to go as is: they have their starting element
  // set, their incoming set (if any), and are on the processor that owns said
  // starting element. Therefore, we move them right into _local_rays and
  // set that we don't need to claim.
  if (!_claim_after_define_rays)
  {
    _local_rays.reserve(_rays.size());
    for (const std::shared_ptr<Ray> & ray : _rays)
      _local_rays.emplace_back(ray);

    _should_claim_rays = false;
  }
(moose/modules/ray_tracing/src/userobjects/RepeatableRayStudyBase.C)

Copy Rays

In every execution of the study, all of the rays in _local_rays are copied and inserted into the buffer to be traced. An actual copy takes place here - we want the rays within _local_rays to always be valid so that on later executions of the study, we can produce repeatable behavior in terms of the rays that are being traced.

  // Reserve ahead of time how many Rays we are adding to the buffer
  reserveRayBuffer(_local_rays.size());

  // To make this study "repeatable", we will not trace the Rays that
  // are ready to go in _local_rays. We will instead create new Rays
  // that are duplicates of the ones in _local_rays, and trace those.
  // This ensures that on multiple executions of this study, we always
  // have the information to create the same Rays.
  for (const auto & ray : _local_rays)
  {
    // This acquires a new ray that is copied from a Ray that has already
    // been claimed to begin on this processor with the user-defined trajectory
    std::shared_ptr<Ray> copied_ray = acquireCopiedRay(*ray);

    // This calls std::move() on the ray, which means that copied_ray in this context
    // is no longer valid. We use the move method because copied_ray is a shared_ptr
    // and otherwise we would increase the count as we add it to the buffer and also
    // decrease the count once this goes out of scope.
    moveRayToBuffer(copied_ray);
  }
(moose/modules/ray_tracing/src/userobjects/RepeatableRayStudyBase.C)