Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-11-13 11:00:07

0001 // This file is part of VecGeom and is distributed under the
0002 // conditions in the file LICENSE.txt in the top directory.
0003 // For the full list of authors see CONTRIBUTORS.txt and `git log`.
0004 
0005 /// \brief Lightweight touchable path for validating encoded navigation data.
0006 /// \file management/TouchablePath.h
0007 /// \author OpenAI Codex
0008 
0009 #ifndef VECGEOM_MANAGEMENT_TOUCHABLEPATH_H_
0010 #define VECGEOM_MANAGEMENT_TOUCHABLEPATH_H_
0011 
0012 #include "VecGeom/base/Global.h"
0013 #include "VecGeom/base/Transformation3D.h"
0014 #include "VecGeom/volumes/PlacedVolume.h"
0015 
0016 namespace vecgeom {
0017 inline namespace VECGEOM_IMPL_NAMESPACE {
0018 
0019 /**
0020  * @brief Error codes returned when validating encoded navigation data against
0021  *        geometry truth.
0022  *
0023  * The numeric values intentionally match the historical validation diagnostics
0024  * used by the navigation-table checks so that existing callers can continue to
0025  * treat the values as stable integer error codes
0026  */
0027 enum class ReferenceNavValidationError : int {
0028   kNone                   = 0, ///< Validation succeeded.
0029   kIncompatibleDaughter   = 1, ///< The geometry daughter pointer cannot be used for a valid descent.
0030   kIncompatibleScene      = 2, ///< The encoded child state stayed in the wrong scene.
0031   kTopVolumeMismatch      = 3, ///< The encoded top volume does not match the geometry traversal.
0032   kChildIdMismatch        = 4, ///< The encoded child id does not match the placed volume.
0033   kLogicalIdMismatch      = 5, ///< The encoded logical-volume id does not match the placed volume.
0034   kLevelMismatch          = 6, ///< The encoded depth does not match the traversal depth.
0035   kPushPopMismatch        = 7, ///< A Pop followed by Push does not reconstruct the encoded state.
0036   kDaughterCountMismatch  = 8, ///< The encoded number of daughters does not match the logical volume.
0037   kTransformationMismatch = 9  ///< The encoded top transformation does not match the reference transform.
0038 };
0039 
0040 /**
0041  * @brief Returns a short textual description for a validation error code.
0042  * @param error Validation error code.
0043  * @return Human-readable description suitable for host logs and test diagnostics.
0044  */
0045 inline char const *ToString(ReferenceNavValidationError error)
0046 {
0047   switch (error) {
0048   case ReferenceNavValidationError::kNone:
0049     return "success";
0050   case ReferenceNavValidationError::kIncompatibleDaughter:
0051     return "incompatible daughter pointer";
0052   case ReferenceNavValidationError::kIncompatibleScene:
0053     return "incompatible scene index";
0054   case ReferenceNavValidationError::kTopVolumeMismatch:
0055     return "top placed volume pointer mismatch";
0056   case ReferenceNavValidationError::kChildIdMismatch:
0057     return "top placed volume child id mismatch";
0058   case ReferenceNavValidationError::kLogicalIdMismatch:
0059     return "logical volume id mismatch";
0060   case ReferenceNavValidationError::kLevelMismatch:
0061     return "level mismatch";
0062   case ReferenceNavValidationError::kPushPopMismatch:
0063     return "navigation index inconsistency for Push/Pop";
0064   case ReferenceNavValidationError::kDaughterCountMismatch:
0065     return "number of daughters mismatch";
0066   case ReferenceNavValidationError::kTransformationMismatch:
0067     return "transformation matrix mismatch";
0068   }
0069   return "unknown validation error";
0070 }
0071 
0072 /**
0073  * @brief Compact host/device touchable path derived directly from the geometry tree.
0074  *
0075  * The helper stores only the explicit touchable path:
0076  * - `fPath[0]` is the world placed volume when the state is non-empty,
0077  * - `fPath[fCurrentLevel - 1]` is the current top placed volume,
0078  * - `fCurrentLevel` is the number of valid entries in `fPath`, not the
0079  *   zero-based depth.
0080  *
0081  * Consequently:
0082  * - an empty state has `fCurrentLevel == 0`,
0083  * - the world-only state has `fCurrentLevel == 1` and `GetLevel() == 0`,
0084  * - a daughter of the world has `fCurrentLevel == 2` and `GetLevel() == 1`.
0085  *
0086  * The helper does not cache or incrementally update any derived transform.
0087  * Instead, `TopMatrix()` reconstructs the full global-to-top transform from the
0088  * stored placed-volume sequence each time it is requested. This keeps the
0089  * validation oracle tied to the explicit path data and avoids a second,
0090  * independent transform-accumulation implementation.
0091  */
0092 class TouchablePath {
0093 private:
0094   static constexpr unsigned int kMaxPathEntries = 256; ///< Supports geometry depths from 0 to 255.
0095 
0096   VPlacedVolume const *fPath[kMaxPathEntries] = {}; ///< Touchable path, with world at index 0 when non-empty.
0097   unsigned short fCurrentLevel                = 0;  ///< Count of valid entries currently stored in @ref fPath.
0098 
0099 public:
0100   /// @brief Constructs an empty touchable path.
0101   VECCORE_ATT_HOST_DEVICE
0102   TouchablePath() = default;
0103 
0104   /**
0105    * @brief Creates the touchable path for the world volume.
0106    * @param world Top placed volume of the geometry tree.
0107    * @return Touchable path whose stack contains exactly the world volume.
0108    *
0109    * If @p world is non-null, the returned state satisfies:
0110    * - `Top() == world`
0111    * - `GetLevel() == 0`
0112    * - `fCurrentLevel == 1`
0113    *
0114    * This mirrors the standard VecGeom interpretation that the world touchable
0115    * lives at depth 0.
0116    */
0117   VECCORE_ATT_HOST_DEVICE
0118   static TouchablePath MakeWorld(VPlacedVolume const *world)
0119   {
0120     TouchablePath state;
0121     if (world) {
0122       state.fPath[0]      = world;
0123       state.fCurrentLevel = 1;
0124     }
0125     return state;
0126   }
0127 
0128   /**
0129    * @brief Pushes a placed volume onto the reference stack.
0130    * @param volume Placed volume to append.
0131    *
0132    * Callers are expected to push volumes in traversal order, meaning the
0133    * parent is already the current top and @p volume is its direct daughter.
0134    * The helper does not verify family relationships; it simply records the
0135    * explicit path presented by the caller.
0136    */
0137   VECCORE_ATT_HOST_DEVICE
0138   VECGEOM_FORCE_INLINE
0139   void Push(VPlacedVolume const *volume)
0140   {
0141     VECGEOM_VALIDATE(volume != nullptr, << "TouchablePath cannot push a null placed volume");
0142     VECGEOM_VALIDATE(fCurrentLevel < kMaxPathEntries, << "TouchablePath does not support geometry depths beyond 255");
0143     fPath[fCurrentLevel++] = volume;
0144   }
0145 
0146   /**
0147    * @brief Pops the current top placed volume from the touchable path.
0148    *
0149    * This removes the last path entry and restores the previous top volume.
0150    * Popping the world entry returns the helper to the empty-state representation.
0151    */
0152   VECCORE_ATT_HOST_DEVICE
0153   VECGEOM_FORCE_INLINE
0154   void Pop()
0155   {
0156     VECGEOM_VALIDATE(fCurrentLevel > 0, << "TouchablePath cannot pop an empty path");
0157     fPath[--fCurrentLevel] = nullptr;
0158   }
0159 
0160   /// @brief Returns the placed volume at the end of the current reference path.
0161   VECCORE_ATT_HOST_DEVICE
0162   VECGEOM_FORCE_INLINE
0163   VPlacedVolume const *Top() const { return (fCurrentLevel > 0) ? fPath[fCurrentLevel - 1] : nullptr; }
0164 
0165   /// @brief Returns the placed volume stored at a given path index.
0166   ///
0167   /// The index is a path position, not a daughter id:
0168   /// - `At(0)` is the world when the state is non-empty
0169   /// - `At(GetLevel())` is the same volume as `Top()`
0170   VECCORE_ATT_HOST_DEVICE
0171   VECGEOM_FORCE_INLINE
0172   VPlacedVolume const *At(unsigned char level) const { return (level < fCurrentLevel) ? fPath[level] : nullptr; }
0173 
0174   /// @brief Returns the zero-based depth of the current top volume.
0175   ///
0176   /// This is `fCurrentLevel - 1` for any non-empty state, so the world has
0177   /// level 0, its daughters level 1, and so on.
0178   VECCORE_ATT_HOST_DEVICE
0179   VECGEOM_FORCE_INLINE
0180   unsigned char GetLevel() const { return (fCurrentLevel > 0) ? static_cast<unsigned char>(fCurrentLevel - 1) : 0; }
0181 
0182   /// @brief Returns whether the helper is not currently anchored on a placed volume.
0183   VECCORE_ATT_HOST_DEVICE
0184   VECGEOM_FORCE_INLINE
0185   bool IsOutside() const { return fCurrentLevel == 0; }
0186 
0187   /**
0188    * @brief Reconstructs the global-to-top transform from the stored volume stack.
0189    * @param matrix Destination transform receiving the global-to-top transform.
0190    *
0191    * The reconstruction rule is intentionally explicit:
0192    * - start from the identity transform,
0193    * - ignore `fPath[0]` because the world entry does not contribute a local
0194    *   placement transform,
0195    * - then multiply the daughter transforms in reverse path order:
0196    *   `fPath[fCurrentLevel - 1]`, `fPath[fCurrentLevel - 2]`, ..., `fPath[1]`.
0197    *
0198    * In other words, for a path `[world, a, b, c]`, the resulting transform is
0199    * reconstructed as:
0200    * `T_c * T_b * T_a`
0201    *
0202    * This is the exact convention required by the encoded navigation-state
0203    * validation in this file and matches the way VecGeom reconstructs the
0204    * global-to-local transform from an explicit touchable path.
0205    */
0206   VECCORE_ATT_HOST_DEVICE
0207   VECGEOM_FORCE_INLINE
0208   void TopMatrix(Transformation3D &matrix) const
0209   {
0210     matrix.Clear();
0211     for (int i = fCurrentLevel - 1; i > 0; --i)
0212       matrix *= *(fPath[i]->GetTransformation());
0213   }
0214 
0215   /// @brief Prints the current touchable path for host diagnostics.
0216   void Print() const
0217   {
0218     printf("TouchablePath: level=%u, path=<", unsigned(GetLevel()));
0219     for (unsigned int i = 0; i < fCurrentLevel; ++i)
0220       printf("/%s", fPath[i] ? fPath[i]->GetLabel().c_str() : "NULL");
0221     printf(">\n");
0222   }
0223 };
0224 
0225 /**
0226  * @brief Validates an encoded navigation state against the current reference traversal point.
0227  *
0228  * @tparam EncodedNavState Navigation-state class providing the static `...Impl`
0229  *         validation interface, for example `NavStateIndex` or `NavStateTuple`.
0230  * @tparam EncodedState Encoded state handle type used by @p EncodedNavState.
0231  * @param reference Geometry-truth touchable path for the current touchable.
0232  * @param encoded_state Encoded state to be validated.
0233  * @return Validation result code.
0234  */
0235 template <typename EncodedNavState, typename EncodedState>
0236 VECCORE_ATT_HOST_DEVICE VECGEOM_FORCE_INLINE ReferenceNavValidationError
0237 ValidateEncodedState(TouchablePath const &reference, EncodedState encoded_state)
0238 {
0239   if (reference.IsOutside()) return ReferenceNavValidationError::kNone;
0240 
0241   if (EncodedNavState::TopImpl(encoded_state) != reference.Top())
0242     return ReferenceNavValidationError::kTopVolumeMismatch;
0243 
0244   if (EncodedNavState::GetChildIdImpl(encoded_state) != reference.Top()->GetChildId())
0245     return ReferenceNavValidationError::kChildIdMismatch;
0246 
0247   if (EncodedNavState::GetLogicalIdImpl(encoded_state) != reference.Top()->GetLogicalVolume()->id())
0248     return ReferenceNavValidationError::kLogicalIdMismatch;
0249 
0250   if (EncodedNavState::GetLevelImpl(encoded_state) != reference.GetLevel())
0251     return ReferenceNavValidationError::kLevelMismatch;
0252 
0253   auto roundtrip_state = encoded_state;
0254   if (reference.GetLevel() > 0) {
0255     EncodedNavState::PopImpl(roundtrip_state);
0256     EncodedNavState::PushImpl(roundtrip_state, reference.Top());
0257     if (roundtrip_state != encoded_state) return ReferenceNavValidationError::kPushPopMismatch;
0258   }
0259 
0260   if (EncodedNavState::GetNdaughtersImpl(encoded_state) != reference.Top()->GetDaughters().size())
0261     return ReferenceNavValidationError::kDaughterCountMismatch;
0262 
0263   Transformation3D reference_matrix;
0264   Transformation3D encoded_matrix;
0265   reference.TopMatrix(reference_matrix);
0266   EncodedNavState::TopMatrixImpl(encoded_state, encoded_matrix);
0267   if (!reference_matrix.ApproxEqual(encoded_matrix)) return ReferenceNavValidationError::kTransformationMismatch;
0268 
0269   return ReferenceNavValidationError::kNone;
0270 }
0271 
0272 /**
0273  * @brief Validates that a child encoded state moved into the expected scene.
0274  *
0275  * The check is meaningful for scene-aware encoded states such as
0276  * `NavStateTuple`. For scene-less states such as `NavStateIndex`, the static
0277  * `GetSceneIdImpl` interface simply reports no active scene and the validation
0278  * succeeds.
0279  *
0280  * @tparam EncodedNavState Navigation-state class providing the static scene
0281  *         accessors.
0282  * @tparam EncodedState Encoded state handle type used by @p EncodedNavState.
0283  * @param parent_state Encoded state before descending to the child.
0284  * @param child_state Encoded state after descending to the child.
0285  * @return Validation result code.
0286  */
0287 template <typename EncodedNavState, typename EncodedState>
0288 VECCORE_ATT_HOST_DEVICE VECGEOM_FORCE_INLINE ReferenceNavValidationError
0289 ValidateSceneTransition(EncodedState parent_state, EncodedState child_state)
0290 {
0291   unsigned short parent_scene = 0, parent_new_scene = 0;
0292   bool inside_scene          = EncodedNavState::GetSceneIdImpl(parent_state, parent_scene, parent_new_scene);
0293   unsigned short child_scene = 0, child_new_scene = 0;
0294   EncodedNavState::GetSceneIdImpl(child_state, child_scene, child_new_scene);
0295   if (inside_scene && child_scene == parent_scene) return ReferenceNavValidationError::kIncompatibleScene;
0296   return ReferenceNavValidationError::kNone;
0297 }
0298 
0299 /**
0300  * @brief Prints detailed diagnostics for a failed encoded-state validation.
0301  *
0302  * The output includes both the observed encoded value and the geometry-truth
0303  * value carried by the touchable path so that callers can immediately see what
0304  * the validator expected.
0305  *
0306  * @tparam EncodedNavState Navigation-state class providing the static `...Impl`
0307  *         validation interface.
0308  * @tparam EncodedState Encoded state handle type used by @p EncodedNavState.
0309  * @param error Validation error code to describe.
0310  * @param reference Geometry-truth touchable path for the current touchable.
0311  * @param encoded_state Encoded state that failed validation.
0312  */
0313 template <typename EncodedNavState, typename EncodedState>
0314 void PrintValidationFailure(ReferenceNavValidationError error, TouchablePath const &reference,
0315                             EncodedState encoded_state)
0316 {
0317   printf("=== EEE === Validation detail: error code %d\n", static_cast<int>(error));
0318   switch (error) {
0319   case ReferenceNavValidationError::kTopVolumeMismatch: {
0320     auto const expected_top_id = reference.Top() ? static_cast<long long>(reference.Top()->id()) : -1LL;
0321     printf("    expected top placed volume id %lld, got %d\n", expected_top_id,
0322            EncodedNavState::TopIdImpl(encoded_state));
0323     break;
0324   }
0325   case ReferenceNavValidationError::kChildIdMismatch:
0326     printf("    expected child id %d, got %d\n", reference.Top()->GetChildId(),
0327            EncodedNavState::GetChildIdImpl(encoded_state));
0328     break;
0329   case ReferenceNavValidationError::kLogicalIdMismatch:
0330     printf("    expected logical volume id %d, got %u\n", reference.Top()->GetLogicalVolume()->id(),
0331            EncodedNavState::GetLogicalIdImpl(encoded_state));
0332     break;
0333   case ReferenceNavValidationError::kLevelMismatch:
0334     printf("    expected level %u, got %u\n", unsigned(reference.GetLevel()),
0335            unsigned(EncodedNavState::GetLevelImpl(encoded_state)));
0336     break;
0337   case ReferenceNavValidationError::kPushPopMismatch: {
0338     auto roundtrip_state = encoded_state;
0339     EncodedNavState::PopImpl(roundtrip_state);
0340     EncodedNavState::PushImpl(roundtrip_state, reference.Top());
0341     auto const expected_top_id = reference.Top() ? static_cast<long long>(reference.Top()->id()) : -1LL;
0342     printf("    expected Pop/Push to reconstruct the same encoded state for top id %lld\n", expected_top_id);
0343     printf("    original top id %d, roundtrip top id %d\n", EncodedNavState::TopIdImpl(encoded_state),
0344            EncodedNavState::TopIdImpl(roundtrip_state));
0345     break;
0346   }
0347   case ReferenceNavValidationError::kDaughterCountMismatch:
0348     printf("    expected daughter count %zu, got %u\n", reference.Top()->GetDaughters().size(),
0349            EncodedNavState::GetNdaughtersImpl(encoded_state));
0350     break;
0351   case ReferenceNavValidationError::kTransformationMismatch: {
0352     Transformation3D reference_matrix;
0353     Transformation3D encoded_matrix;
0354     reference.TopMatrix(reference_matrix);
0355     EncodedNavState::TopMatrixImpl(encoded_state, encoded_matrix);
0356     printf("    expected translation (%.8g, %.8g, %.8g), got (%.8g, %.8g, %.8g)\n", reference_matrix.Translation(0),
0357            reference_matrix.Translation(1), reference_matrix.Translation(2), encoded_matrix.Translation(0),
0358            encoded_matrix.Translation(1), encoded_matrix.Translation(2));
0359     break;
0360   }
0361   default:
0362     break;
0363   }
0364 }
0365 
0366 /**
0367  * @brief Prints detailed diagnostics for a failed scene transition.
0368  *
0369  * @tparam EncodedNavState Navigation-state class providing the static scene
0370  *         accessors.
0371  * @tparam EncodedState Encoded state handle type used by @p EncodedNavState.
0372  * @param parent_state Encoded state before descending to the child.
0373  * @param child_state Encoded state after descending to the child.
0374  * @param parent_volume Geometry parent used for the attempted descent.
0375  * @param child_volume Geometry child used for the attempted descent.
0376  */
0377 template <typename EncodedNavState, typename EncodedState>
0378 void PrintSceneTransitionFailure(EncodedState parent_state, EncodedState child_state,
0379                                  VPlacedVolume const *parent_volume, VPlacedVolume const *child_volume)
0380 {
0381   unsigned short parent_scene = 0, parent_new_scene = 0;
0382   unsigned short child_scene = 0, child_new_scene = 0;
0383   EncodedNavState::GetSceneIdImpl(parent_state, parent_scene, parent_new_scene);
0384   EncodedNavState::GetSceneIdImpl(child_state, child_scene, child_new_scene);
0385   auto const parent_id = parent_volume ? static_cast<long long>(parent_volume->id()) : -1LL;
0386   auto const child_id  = child_volume ? static_cast<long long>(child_volume->id()) : -1LL;
0387   printf("=== EEE === Validation detail: error code %d\n",
0388          static_cast<int>(ReferenceNavValidationError::kIncompatibleScene));
0389   printf("    expected child scene different from parent scene %u, got %u for %lld/%lld\n", parent_scene, child_scene,
0390          parent_id, child_id);
0391 }
0392 
0393 } // namespace VECGEOM_IMPL_NAMESPACE
0394 } // namespace vecgeom
0395 
0396 #endif // VECGEOM_MANAGEMENT_TOUCHABLEPATH_H_