Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-09-16 08:57:15

0001 //------------------------------- -*- C++ -*- -------------------------------//
0002 // Copyright Celeritas contributors: see top-level COPYRIGHT file for details
0003 // SPDX-License-Identifier: (Apache-2.0 OR MIT)
0004 //---------------------------------------------------------------------------//
0005 //! \file geocel/g4/GeantGeoTrackView.hh
0006 //---------------------------------------------------------------------------//
0007 #pragma once
0008 
0009 #include <algorithm>
0010 #include <type_traits>
0011 #include <G4LogicalVolume.hh>
0012 #include <G4Navigator.hh>
0013 #include <G4TouchableHandle.hh>
0014 #include <G4TouchableHistory.hh>
0015 #include <G4VPhysicalVolume.hh>
0016 
0017 #include "corecel/Macros.hh"
0018 #include "corecel/math/Algorithms.hh"
0019 #include "corecel/math/ArrayUtils.hh"
0020 #include "corecel/math/SoftEqual.hh"
0021 #include "geocel/Types.hh"
0022 
0023 #include "Convert.hh"
0024 #include "GeantGeoData.hh"
0025 
0026 namespace celeritas
0027 {
0028 //---------------------------------------------------------------------------//
0029 /*!
0030  * Navigate through a Geant4 geometry on a single thread.
0031  *
0032  * This wraps a Geant4 geometry navigator and volume hierarchy state with the
0033  * same Celeritas tracker interface. It's not going to be the most efficient
0034  * code since the \c G4Navigator includes a lot of helper functions for
0035  * managing safety distance, tracking through a field, etc. We also
0036  * independently store a "celeritas" native position and direction, as well as
0037  * duplicating the "geant4" position and direction that are also stored under
0038  * the hood in the heavyweight navigator.
0039  *
0040  * For a description of ordering requirements, see: \sa OrangeTrackView .
0041  */
0042 class GeantGeoTrackView
0043 {
0044   public:
0045     //!@{
0046     //! \name Type aliases
0047     using Initializer_t = GeoTrackInitializer;
0048     using ParamsRef = NativeCRef<GeantGeoParamsData>;
0049     using StateRef = NativeRef<GeantGeoStateData>;
0050     using real_type = double;
0051     using Real3 = Array<real_type, 3>;
0052     //!@}
0053 
0054     //! Helper struct for initializing from an existing geometry state
0055     struct DetailedInitializer
0056     {
0057         GeantGeoTrackView const& other;  //!< Existing geometry
0058         Real3 dir;  //!< New direction
0059     };
0060 
0061   public:
0062     // Construct from params and state data
0063     inline GeantGeoTrackView(ParamsRef const& params,
0064                              StateRef const& state,
0065                              TrackSlotId tid);
0066 
0067     // Initialize the state
0068     inline GeantGeoTrackView& operator=(Initializer_t const& init);
0069     // Initialize the state from a parent state and new direction
0070     inline GeantGeoTrackView& operator=(DetailedInitializer const& init);
0071 
0072     //// STATIC ACCESSORS ////
0073 
0074     //! A tiny push to make sure tracks do not get stuck at boundaries
0075     static constexpr real_type extra_push()
0076     {
0077         return 1e-12 * lengthunits::millimeter;
0078     }
0079 
0080     //// ACCESSORS ////
0081 
0082     //!@{
0083     //! State accessors
0084     CELER_FORCEINLINE Real3 const& pos() const { return pos_; }
0085     CELER_FORCEINLINE Real3 const& dir() const { return dir_; }
0086     //!@}
0087 
0088     // Get the volume ID in the lowest level volume.
0089     inline VolumeId volume_id() const;
0090     // Get the physical volume ID in the current cell
0091     inline VolumeInstanceId volume_instance_id() const;
0092     // Get the depth in the geometry hierarchy
0093     inline LevelId level() const;
0094     // Get the volume instance ID for all levels
0095     inline void volume_instance_id(Span<VolumeInstanceId> levels) const;
0096 
0097     //!@{
0098     //! Geant4 states are never "on" a surface
0099     SurfaceId surface_id() const { return {}; }
0100     SurfaceId next_surface_id() const { return {}; }
0101     //!@}
0102 
0103     // Whether the track is outside the valid geometry region
0104     inline bool is_outside() const;
0105     // Whether the track is exactly on a surface
0106     inline bool is_on_boundary() const;
0107     //! Whether the last operation resulted in an error
0108     CELER_FORCEINLINE bool failed() const { return false; }
0109 
0110     // Get the Geant4 navigation state
0111     inline G4NavigationHistory const* nav_history() const;
0112 
0113     //// OPERATIONS ////
0114 
0115     // Find the distance to the next boundary (infinite max)
0116     inline Propagation find_next_step();
0117 
0118     // Find the distance to the next boundary, up to and including a step
0119     inline Propagation find_next_step(real_type max_step);
0120 
0121     // Find the safety at the current position
0122     inline real_type find_safety();
0123 
0124     // Find the safety at the current position up to a maximum step distance
0125     inline CELER_FUNCTION real_type find_safety(real_type max_step);
0126 
0127     // Move to the boundary in preparation for crossing it
0128     inline void move_to_boundary();
0129 
0130     // Move within the volume
0131     inline void move_internal(real_type step);
0132 
0133     // Move within the volume to a specific point
0134     inline void move_internal(Real3 const& pos);
0135 
0136     // Cross from one side of the current surface to the other
0137     inline void cross_boundary();
0138 
0139     // Change direction
0140     inline void set_dir(Real3 const& newdir);
0141 
0142   private:
0143     //// DATA ////
0144 
0145     //!@{
0146     //! Referenced thread-local data
0147     Real3& pos_;
0148     Real3& dir_;
0149     real_type& next_step_;
0150     real_type& safety_radius_;
0151     G4TouchableHandle& touch_handle_;
0152     G4Navigator& navi_;
0153     //!@}
0154 
0155     // Temporary data
0156     G4ThreeVector g4pos_;
0157     G4ThreeVector g4dir_;  // [mm]
0158     real_type g4safety_;  // [mm]
0159 
0160     //// HELPER FUNCTIONS ////
0161 
0162     // Whether any next distance-to-boundary has been found
0163     inline bool has_next_step() const;
0164 
0165     //! Get a pointer to the current volume; null if outside
0166     inline G4LogicalVolume const* volume() const;
0167 };
0168 
0169 //---------------------------------------------------------------------------//
0170 // INLINE DEFINITIONS
0171 //---------------------------------------------------------------------------//
0172 /*!
0173  * Construct from params and state data.
0174  */
0175 GeantGeoTrackView::GeantGeoTrackView(ParamsRef const&,
0176                                      StateRef const& states,
0177                                      TrackSlotId tid)
0178     : pos_(states.pos[tid])
0179     , dir_(states.dir[tid])
0180     , next_step_(states.next_step[tid])
0181     , safety_radius_(states.safety_radius[tid])
0182     , touch_handle_(states.nav_state.touch_handle(tid))
0183     , navi_(states.nav_state.navigator(tid))
0184     , g4pos_(convert_to_geant(pos_, clhep_length))
0185     , g4dir_(convert_to_geant(dir_, 1))
0186     , g4safety_(convert_to_geant(safety_radius_, clhep_length))
0187 {
0188 }
0189 
0190 //---------------------------------------------------------------------------//
0191 /*!
0192  * Construct the state.
0193  */
0194 GeantGeoTrackView& GeantGeoTrackView::operator=(Initializer_t const& init)
0195 {
0196     CELER_EXPECT(is_soft_unit_vector(init.dir));
0197 
0198     // Initialize position/direction
0199     std::copy(init.pos.begin(), init.pos.end(), pos_.begin());
0200     std::copy(init.dir.begin(), init.dir.end(), dir_.begin());
0201     next_step_ = 0;
0202     safety_radius_ = -1;  // Assume *not* on a boundary
0203 
0204     g4pos_ = convert_to_geant(pos_, clhep_length);
0205     g4dir_ = convert_to_geant(dir_, 1);
0206     g4safety_ = -1;
0207 
0208     navi_.LocateGlobalPointAndUpdateTouchable(g4pos_,
0209                                               g4dir_,
0210                                               touch_handle_(),
0211                                               /* relative_search = */ false);
0212 
0213     CELER_ENSURE(!this->has_next_step());
0214     return *this;
0215 }
0216 
0217 //---------------------------------------------------------------------------//
0218 /*!
0219  * Construct the state from a direction and a copy of the parent state.
0220  *
0221  * \c G4Track::SetTouchableHandle from \c G4VEmProcess::PostStepDoIt
0222  *
0223  * maybe see \c G4SteppingManager::Stepping
0224  */
0225 GeantGeoTrackView& GeantGeoTrackView::operator=(DetailedInitializer const& init)
0226 {
0227     CELER_EXPECT(is_soft_unit_vector(init.dir));
0228 
0229     if (this != &init.other)
0230     {
0231         // Copy values from the parent state
0232         pos_ = init.other.pos_;
0233         safety_radius_ = init.other.safety_radius_;
0234         g4pos_ = init.other.g4pos_;
0235         g4dir_ = init.other.g4dir_;
0236         g4safety_ = init.other.g4safety_;
0237 
0238         // Update the touchable and navigator
0239         touch_handle_ = init.other.touch_handle_;
0240         navi_.ResetHierarchyAndLocate(
0241             g4pos_, g4dir_, dynamic_cast<G4TouchableHistory&>(*touch_handle_()));
0242     }
0243 
0244     // Set up the next state and initialize the direction
0245     dir_ = init.dir;
0246     next_step_ = 0;
0247 
0248     CELER_ENSURE(!this->has_next_step());
0249     return *this;
0250 }
0251 
0252 //---------------------------------------------------------------------------//
0253 /*!
0254  * Get the volume ID in the current cell.
0255  */
0256 VolumeId GeantGeoTrackView::volume_id() const
0257 {
0258     CELER_EXPECT(!this->is_outside());
0259     return id_cast<VolumeId>(this->volume()->GetInstanceID());
0260 }
0261 
0262 //---------------------------------------------------------------------------//
0263 /*!
0264  * Get the physical volume ID in the current cell.
0265  */
0266 VolumeInstanceId GeantGeoTrackView::volume_instance_id() const
0267 {
0268     CELER_EXPECT(!this->is_outside());
0269     G4VPhysicalVolume* pv = touch_handle_()->GetVolume(0);
0270     if (!pv)
0271         return {};
0272     return id_cast<VolumeInstanceId>(pv->GetInstanceID());
0273 }
0274 
0275 //---------------------------------------------------------------------------//
0276 /*!
0277  * Get the depth in the geometry hierarchy.
0278  */
0279 LevelId GeantGeoTrackView::level() const
0280 {
0281     auto* touch = touch_handle_();
0282     return id_cast<LevelId>(touch->GetHistoryDepth());
0283 }
0284 
0285 //---------------------------------------------------------------------------//
0286 /*!
0287  * Get the volume instance ID at every level.
0288  *
0289  * The input span size must be equal to the value of "level" plus one. The
0290  * top-most level ("world" or level zero) starts at index zero and moves
0291  * downward. Note that Geant4 uses the \em reverse nomenclature.
0292  */
0293 void GeantGeoTrackView::volume_instance_id(Span<VolumeInstanceId> levels) const
0294 {
0295     CELER_EXPECT(levels.size() == this->level().get() + 1);
0296 
0297     auto* touch = touch_handle_();
0298     auto const max_depth = static_cast<size_type>(touch->GetHistoryDepth());
0299     for (auto lev : range(levels.size()))
0300     {
0301         G4VPhysicalVolume* pv = touch->GetVolume(max_depth - lev);
0302         levels[lev] = pv ? id_cast<VolumeInstanceId>(pv->GetInstanceID())
0303                          : VolumeInstanceId{};
0304     }
0305 }
0306 
0307 //---------------------------------------------------------------------------//
0308 /*!
0309  * Whether the track is outside the valid geometry region.
0310  */
0311 CELER_FORCEINLINE bool GeantGeoTrackView::is_outside() const
0312 {
0313     return this->volume() == nullptr;
0314 }
0315 
0316 //---------------------------------------------------------------------------//
0317 /*!
0318  * Whether the track is on the boundary of a volume.
0319  */
0320 CELER_FORCEINLINE bool GeantGeoTrackView::is_on_boundary() const
0321 {
0322     return safety_radius_ == 0.0;
0323 }
0324 
0325 //---------------------------------------------------------------------------//
0326 /*!
0327  * Get the navigation state.
0328  */
0329 G4NavigationHistory const* GeantGeoTrackView::nav_history() const
0330 {
0331     auto* touch = touch_handle_();
0332     CELER_ASSERT(touch);
0333     return touch->GetHistory();
0334 }
0335 
0336 //---------------------------------------------------------------------------//
0337 /*!
0338  * Find the distance to the next geometric boundary.
0339  */
0340 CELER_FORCEINLINE Propagation GeantGeoTrackView::find_next_step()
0341 {
0342     return this->find_next_step(numeric_limits<real_type>::infinity());
0343 }
0344 
0345 //---------------------------------------------------------------------------//
0346 /*!
0347  * Find the distance to the next geometric boundary.
0348  *
0349  * It seems that ComputeStep cannot be called twice in a row without an
0350  * intermediate call to \c LocateGlobalPointWithinVolume: the safety will be
0351  * set to zero.
0352  */
0353 Propagation GeantGeoTrackView::find_next_step(real_type max_step)
0354 {
0355     CELER_EXPECT(!this->is_outside());
0356     CELER_EXPECT(max_step > 0);
0357 
0358     // Compute the step
0359     real_type g4step = convert_to_geant(max_step, clhep_length);
0360     g4step = navi_.ComputeStep(g4pos_, g4dir_, g4step, g4safety_);
0361 
0362     if (g4safety_ != 0 && !this->is_on_boundary())
0363     {
0364         // Save the resulting safety distance if computed: allow to be
0365         // "negative" to prevent accidentally changing the boundary state
0366         safety_radius_ = convert_from_geant(g4safety_, clhep_length);
0367         CELER_ASSERT(!this->is_on_boundary());
0368     }
0369 
0370     // Update result
0371     Propagation result;
0372     result.distance = convert_from_geant(g4step, clhep_length);
0373     if (result.distance <= max_step)
0374     {
0375         result.boundary = true;
0376         result.distance
0377             = celeritas::max<real_type>(result.distance, this->extra_push());
0378         CELER_ENSURE(result.distance > 0);
0379     }
0380     else
0381     {
0382         // No intersection in range -> G4Navigator returns kInfinity
0383         result.distance = max_step;
0384         CELER_ENSURE(result.distance > 0);
0385     }
0386 
0387     // Save the next step
0388     next_step_ = result.distance;
0389 
0390     CELER_ENSURE(result.distance > 0);
0391     CELER_ENSURE(result.distance <= max(max_step, this->extra_push()));
0392     CELER_ENSURE(result.boundary || result.distance == max_step
0393                  || max_step < this->extra_push());
0394     CELER_ENSURE(this->has_next_step());
0395     return result;
0396 }
0397 
0398 //---------------------------------------------------------------------------//
0399 /*!
0400  * Find the safety at the current position.
0401  */
0402 CELER_FORCEINLINE auto GeantGeoTrackView::find_safety() -> real_type
0403 {
0404     return this->find_safety(numeric_limits<real_type>::infinity());
0405 }
0406 
0407 //---------------------------------------------------------------------------//
0408 /*!
0409  * Find the safety at the current position.
0410  *
0411  * \warning This can change the boundary state if the track was moved to or
0412  * initialized a point on the boundary.
0413  */
0414 auto GeantGeoTrackView::find_safety(real_type max_step) -> real_type
0415 {
0416     CELER_EXPECT(!this->is_on_boundary());
0417     CELER_EXPECT(max_step > 0);
0418     if (safety_radius_ < max_step)
0419     {
0420         real_type g4step = convert_to_geant(max_step, clhep_length);
0421         g4safety_ = navi_.ComputeSafety(g4pos_, g4step);
0422         safety_radius_ = max(convert_from_geant(g4safety_, clhep_length), 0.0);
0423     }
0424 
0425     return safety_radius_;
0426 }
0427 
0428 //---------------------------------------------------------------------------//
0429 /*!
0430  * Move to the next boundary but don't cross yet.
0431  */
0432 void GeantGeoTrackView::move_to_boundary()
0433 {
0434     CELER_EXPECT(this->has_next_step());
0435 
0436     // Move next step
0437     axpy(next_step_, dir_, &pos_);
0438     axpy(convert_to_geant(next_step_, clhep_length), g4dir_, &g4pos_);
0439     next_step_ = 0;
0440     safety_radius_ = 0;
0441     g4safety_ = 0;
0442     navi_.SetGeometricallyLimitedStep();
0443 
0444     CELER_ENSURE(this->is_on_boundary());
0445 }
0446 
0447 //---------------------------------------------------------------------------//
0448 /*!
0449  * Cross from one side of the current surface to the other.
0450  *
0451  * The position *must* be on the boundary following a move-to-boundary.
0452  */
0453 void GeantGeoTrackView::cross_boundary()
0454 {
0455     CELER_EXPECT(this->is_on_boundary());
0456 
0457     navi_.LocateGlobalPointAndUpdateTouchableHandle(
0458         g4pos_,
0459         g4dir_,
0460         touch_handle_,
0461         /* relative_search = */ true);
0462 
0463     CELER_ENSURE(this->is_on_boundary());
0464 }
0465 
0466 //---------------------------------------------------------------------------//
0467 /*!
0468  * Move within the current volume.
0469  *
0470  * The straight-line distance *must* be less than the distance to the
0471  * boundary.
0472  */
0473 void GeantGeoTrackView::move_internal(real_type dist)
0474 {
0475     CELER_EXPECT(this->has_next_step());
0476     CELER_EXPECT(dist > 0 && dist <= next_step_);
0477 
0478     // Move and update next_step
0479     axpy(dist, dir_, &pos_);
0480     axpy(convert_to_geant(dist, clhep_length), g4dir_, &g4pos_);
0481     next_step_ -= dist;
0482     navi_.LocateGlobalPointWithinVolume(g4pos_);
0483 
0484     safety_radius_ = -1;
0485     g4safety_ = 0;
0486 }
0487 
0488 //---------------------------------------------------------------------------//
0489 /*!
0490  * Move within the current volume to a nearby point.
0491  *
0492  * See \c G4PathFinder::ReLocate from \c G4SafetyHelper::ReLocateWithinVolume
0493  * from \c G4VMultipleScattering::AlongStepDoIt .
0494  */
0495 void GeantGeoTrackView::move_internal(Real3 const& pos)
0496 {
0497     pos_ = pos;
0498     g4pos_ = convert_to_geant(pos_, clhep_length);
0499     next_step_ = 0;
0500     navi_.LocateGlobalPointWithinVolume(g4pos_);
0501 
0502     safety_radius_ = -1;
0503     g4safety_ = 0;
0504 }
0505 
0506 //---------------------------------------------------------------------------//
0507 /*!
0508  * Change the track's direction.
0509  *
0510  * This happens after a scattering event or movement inside a magnetic field.
0511  * It resets the calculated distance-to-boundary.
0512  */
0513 void GeantGeoTrackView::set_dir(Real3 const& newdir)
0514 {
0515     CELER_EXPECT(is_soft_unit_vector(newdir));
0516     dir_ = newdir;
0517     g4dir_ = convert_to_geant(newdir, 1);
0518     next_step_ = 0;
0519 }
0520 
0521 //---------------------------------------------------------------------------//
0522 // PRIVATE MEMBER FUNCTIONS
0523 //---------------------------------------------------------------------------//
0524 /*!
0525  * Whether a next step has been calculated.
0526  */
0527 CELER_FORCEINLINE bool GeantGeoTrackView::has_next_step() const
0528 {
0529     return next_step_ != 0;
0530 }
0531 
0532 //---------------------------------------------------------------------------//
0533 /*!
0534  * Get a reference to the current volume, or to world volume if outside.
0535  */
0536 auto GeantGeoTrackView::volume() const -> G4LogicalVolume const*
0537 {
0538     CELER_EXPECT(touch_handle_());
0539     G4VPhysicalVolume* pv = touch_handle_()->GetVolume(0);
0540     if (!pv)
0541         return nullptr;
0542 
0543     return pv->GetLogicalVolume();
0544 }
0545 
0546 //---------------------------------------------------------------------------//
0547 }  // namespace celeritas