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.
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:
Define Rays - Only done on the first execution of the study.
Claim Rays - Done on the first execution of the study and after all mesh changes.
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)