Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-12-23 09:49:35

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