These classes manage access to geometric information throughout the codebase.
They work in tandem with celeritas::GeantGeoParams to associate
Celeritas identifiers with Geant4 runtime data structures.
Define and manage a hierarchy of volumes and instances thereof.
See the introduction to the Geometry API section for a detailed description of volumes in the detector geometry description. This class abstracts the graph of user-defined volumes, relating nodes (VolumeId, aka logical volume) to edges (VolumeInstanceId, aka physical volume) and providing the means to determine the path (isomorphic to a VolumeUniqueInstanceId, aka touchable history) of a track state. The root of the graph is the world volume, and the level of a volume in the path is the distance to the root: zero for the root volume, one for its direct child, etc. The maximum value of the level in any path is one less than num_volume_levels : an array of VolumeInstanceId with that size can represent any path. (When explicitly constructing this array, we omit the world volume since it is implicit.) The total number of paths to any node in the geometry is num_unique_instances .
In conjunction with GeantGeoParams, this class allows conversion between the Celeritas geometry implementation and the Geant4 geometry navigation.
Label-based lookup (volume and volume instance names) is provided through the volume_labels and volume_instance_labels accessors. Graph properties (material, connectivity, world) are stored in the underlying VolumeParamsData and accessed efficiently via VolumeView.
Incrementally compute the unique ID of a path in the volume hierarchy.
Each VolumeUniqueInstanceId uniquely identifies a root-to-node path (i.e., a Geant4 “touchable”) in the volume DAG. This class computes the ID on the fly without allocating a path buffer: call operator() once for each VolumeInstanceId encountered while descending from the world volume, passing the current accumulated ID and receiving the updated one.
For a volume instance vi at position k in its parent volume’s children list, the offset is the sum of num_desc(volume(vj)) for all preceding siblings vj (positions 0..k-1), where num_desc(V) counts the total number of unique paths ending at any node in V’s subtree (including V itself). For a path \([vi_0, vi_1, \ldots, vi_k]\) the unique instance ID is
The empty path (the world volume itself, with no enclosing instance) maps to ID 0 (see world_unique_instance ).
Example:
VolumePathAccumulatoraccum{params.host_ref()};VolumeUniqueInstanceIduid=world_unique_instance;for(VolumeInstanceIdvi:path_below_world){uid=accum(uid,vi);// unique ID for the node reached via this step}
Reconstruct the volume-instance path for a VolumeUniqueInstanceId.
Each VolumeUniqueInstanceId uniquely identifies a root-to-node path (i.e., a Geant4 “touchable”) in the volume DAG. This class performs the inverse of VolumePathAccumulator: given an ID it fills a caller-supplied scratch buffer with the VolumeInstanceId sequence and returns a (possibly shorter) span of the result.
VolumeUniqueInstanceId{0} (or world_unique_instance ) always denotes the world volume itself (empty path), and VolumeUniqueInstanceId{} (null/invalid) is rejected with a precondition failure. The valid range is \([0,\, N_\text{unique})\).
The algorithm descends from the world volume level by level, always seeding from the world’s direct children. At each level it scans the current volume’s children to find the unique child whose subtree contains the remaining UID, exploiting the fact that sibling offsets are strictly increasing. The cost is \(O(D \log C)\) where \(D\) is the path depth and \(C\) is the maximum number of children of any volume.
The scratch buffer must be at least num_volume_levels - 1 long (the maximum possible path depth). Successive calls reuse the same buffer, so callers must consume the returned span before the next call.
Map volumetric geometry information to surface IDs.
See the introduction to the Geometry API section for a detailed description of surfaces in the detector geometry description.
The specification of surfaces using volume relationships is required by volume-based geometries such as Geant4 and VecGeom 1, so it is not currently possible to define different properties for the different faces of a volume unless those faces are surrounded by distinct geometric volumes. Since ORANGE and VecGeom 2 support true surface definitions, a future extension will allow the user to attach surface properties to, for example, different sides of a cube.
Interface class for accessing host geometry metadata.
This class is implemented by OrangeParams to allow navigation with the ORANGE geometry implementation, VecgeomParams for using VecGeom, and GeantGeoParams for testing with the Geant4-provided navigator.
A few helper functions can be used to build collections (see
Data model) of ImplVolumeId for runtime tracking (used
internally by fields, physics, etc.).
A geometry “track view” is the key class used to access and/or modify a
geometry state. (See Data model for more discussion of views and
states.) Most states are a combination of physical properties (position,
direction) and logical properties (volume path, surface, surface side).
The track view provides access to the thread-local state.
Standard interface to geometry navigation for a track for testing on CPU.
Initialization is performed via the assignment operator using a GeoTrackInitializer.
Initialization may fail, leaving the track with an “error” status.
Some implementations allow initialization on a boundary, leaving the track in an “outgoing from boundary” state (i.e., the next step is into the volume the track initializes inside).
Some implementations allow initialization to succeed but be in an unphysical part of the geometry (e.g., ORANGE exterior volume). In this case, the canonical volume ID will be null, indicating no corresponding user-defined geometry region.
Tracking to and across volumes along a straight line requires a specific sequence of calls.
Locate the boundary crossing along the current direction with find_next_step.
Move within the current volume, not crossing a boundary, via move_internal or move_to_boundary.
If on a boundary, normal can be used to calculate the current surface normal, but its dot product with the track direction may not be meaningful. Use geo_status to determine whether the track is incident to or outgoing from the boundary.
If incident into the boundary, change volumes (“relocate”) with cross_boundary. The post-crossing status can be error, boundary_out, or invalid. Some geometries represent the exterior as a null canonical VolumeId, and some as an invalid state.
Movement to a nearby but arbitrary point can be done inside a “safety” distance:
Locate the closest point on the boundary in any direction with find_safety.
Change the direction with set_dir. (Note that this will always invalidate the linear “next step”.)
Move to a point with move_internal.
Neither accessors nor mutators should be called in an invalid state. It is allowable but potentially dangerous to call accessors in an error state.
Note
This class is for illustrative and testing purposes only (see celeritas::test::WrappedGeoTrackView) and is not used during the main Celeritas execution. The geometry there is determined by the CELERITAS_CORE_GEO configuration variable and defined as a type alias celeritas::CoreGeoTrackView .
Note
The free function is_on_boundary will be true of the geo status both before and after the call to cross_boundary, and the surface normal can be calculated in both cases.
Calculate the normal vector on the current surface.
Returns a global-coordinate direction perpendicular to the volume at the local point when on a boundary. The sign of the surface normal is implementation-dependent; it may change based on the track state (previous volume, direction, surface sign) or geometry construction.
Derive the geometry state from the existing state flags.
This is a shim for geometry implementations that do not natively track the full GeoStatus. When on a boundary, it assumes the normal vector is oriented “outward” from the current volume. The dot product of the track direction and the surface normal determines whether the track is incident to the boundary (positive: headed outward from the volume) or incident (negative: headed into the volume).
Find the distance to the next boundary, up to and including a step.
Determines the distance to the next boundary along the track’s current direction, up to a given distance. Queries may be more efficient for small distances.
Move to the boundary in preparation for crossing it.
Moves the track to the boundary of the current volume along the current direction, updating its logical state to indicate that it is on the boundary of the current volume.
Takes a GeoTrackInitializer object to locate the point in the geometry hierarchy.
Post:
geo_status() is never GeoStatus::boundary_inc : the result is interior (placed inside a volume), boundary_out (placed on a boundary with direction heading away from it), or error (volume not found).
Geometry state as a track moves across boundaries through the geometry.
The “incoming” (incident) and “outgoing” (exiting) states are relative to the boundary (surface) that the track is on, not the volume.
Note
The numeric values of the enumeration are chosen to optimize the free functions is_valid (invalid values are strictly negative) and is_on_boundary (values on the boundary are strictly positive).