Skip to content

Commit afe49d3

Browse files
Tristan Wenzelmatbud agent
authored andcommitted
Support to use VecGeom geometry navigation in the material scan
This commit provides support to use VecGeom as an alternative geometry backend to perform the material budget LUT creation. It is the first time that ALICE interfaces full VecGeom navigation. This should be a useful milestone towards adoption at detector simulation level. First scans indicate performance advantages of 2x over TGeo but a precise performance investigation will be done later. This commit will be the foundation for this and/or serve for debugging issues or driving further development in VecGeom itself. Note that the original method name `populateFromTGeo` remains untouched for API stability, despite the fact that the method can now internally use VecGeom.
1 parent 0dc82cf commit afe49d3

11 files changed

Lines changed: 363 additions & 53 deletions

File tree

Detectors/Base/CMakeLists.txt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,13 @@
1010
# or submit itself to any jurisdiction.
1111
#add_compile_options(-O0 -g -fPIC)
1212

13+
# Optional VecGeom backend for material-budget LUT filling, off by default when TGeo2VecGeom
14+
# isn't installed (guarded by O2_WITH_VECGEOM). Linked PRIVATE: VecGeom types never appear in
15+
# DetectorsBase's public headers, so consumers need neither VecGeom headers nor VecGeom itself.
16+
find_package(TGeo2VecGeom CONFIG QUIET)
17+
1318
o2_add_library(DetectorsBase
19+
TARGETVARNAME targetDetectorsBase
1420
SOURCES src/Detector.cxx
1521
src/GeometryManager.cxx
1622
src/MaterialManager.cxx
@@ -51,6 +57,13 @@ o2_add_library(DetectorsBase
5157
ROOT::Gdml
5258
)
5359

60+
if(TGeo2VecGeom_FOUND)
61+
target_compile_definitions(${targetDetectorsBase} PRIVATE O2_WITH_VECGEOM)
62+
target_link_libraries(${targetDetectorsBase} PRIVATE TGeo2VecGeom::TGeo2VecGeom)
63+
else()
64+
message(STATUS "TGeo2VecGeom not found: DetectorsBase built without the optional VecGeom material-budget backend")
65+
endif()
66+
5467
o2_target_root_dictionary(DetectorsBase
5568
HEADERS include/DetectorsBase/Detector.h
5669
include/DetectorsBase/GeometryManager.h

Detectors/Base/include/DetectorsBase/GeometryManager.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ class AlignParam;
4040

4141
namespace base
4242
{
43+
/// Backend used to compute material budget for LUT filling: ROOT/TGeo (default, always
44+
/// available) or VecGeom (requires O2 to be built against the optional TGeo2VecGeom
45+
/// package; see GeometryManager::isVecGeomAvailable()).
46+
enum class MatbudGeomBackend : int { ROOT = 0,
47+
VECGEOM = 1 };
48+
4349
/// Class for interfacing to the geometry; it also builds and manages the look-up tables for fast
4450
/// access to geometry and alignment information for sensitive alignable volumes:
4551
/// 1) the look-up table mapping unique volume ids to TGeoPNEntries. This allows to access
@@ -121,6 +127,17 @@ class GeometryManager : public TObject
121127
return meanMaterialBudgetExt(start.X(), start.Y(), start.Z(), end.X(), end.Y(), end.Z());
122128
}
123129

130+
/// Whether this build of O2 was configured with the optional VecGeom material-budget
131+
/// backend (i.e. TGeo2VecGeom was found at CMake configure time).
132+
#ifdef O2_WITH_VECGEOM
133+
static constexpr bool isVecGeomAvailable() { return true; }
134+
/// Mean material budget between two points, using the VecGeom backend. On first call,
135+
/// lazily converts the currently loaded TGeo geometry to VecGeom (once per process).
136+
static o2::base::MatBudget vecGeomMaterialBudget(float x0, float y0, float z0, float x1, float y1, float z1);
137+
#else
138+
static constexpr bool isVecGeomAvailable() { return false; }
139+
#endif
140+
124141
private:
125142
/// Default constructor
126143
GeometryManager() = default;

Detectors/Base/include/DetectorsBase/MatLayerCyl.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020
#include <cstring>
2121
#endif
2222
#include "GPUCommonDef.h"
23+
#ifndef GPUCA_ALIGPUCODE
24+
#include "DetectorsBase/GeometryManager.h" // for MatbudGeomBackend
25+
#endif
2326
#include "FlatObject.h"
2427
#include "GPUCommonRtypes.h"
2528
#include "GPUCommonMath.h"
@@ -67,8 +70,8 @@ class MatLayerCyl : public o2::gpu::FlatObject
6770

6871
void initSegmentation(float rMin, float rMax, float zHalfSpan, int nz, int nphi);
6972
void initSegmentation(float rMin, float rMax, float zHalfSpan, float dzMin, float drphiMin);
70-
void populateFromTGeo(int ntrPerCell = 10);
71-
void populateFromTGeo(int ip, int iz, int ntrPerCell, TGeoNavigator* nav = nullptr);
73+
void populateFromTGeo(int ntrPerCell = 10, MatbudGeomBackend backend = MatbudGeomBackend::ROOT);
74+
void populateFromTGeo(int ip, int iz, int ntrPerCell, TGeoNavigator* nav = nullptr, MatbudGeomBackend backend = MatbudGeomBackend::ROOT);
7275
void print(bool data = false) const;
7376
#endif // !GPUCA_ALIGPUCODE
7477

Detectors/Base/include/DetectorsBase/MatLayerCylSet.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,11 @@ class MatLayerCylSet : public o2::gpu::FlatObject
7373
#ifndef GPUCA_ALIGPUCODE // this part is unvisible on GPU version
7474
void print(bool data = false) const;
7575
void addLayer(float rmin, float rmax, float zmax, float dz, float drphi);
76-
/// Populate the LUT from TGeo. nThreads > 1 fills the cells in parallel (one TGeoNavigator
77-
/// per thread); nThreads < 0 takes the count from the NTHREADS_MATBUD environment variable.
78-
void populateFromTGeo(int ntrPerCel = 10, int nThreads = -1);
76+
/// Populate the LUT from TGeo or VecGeom. nThreads > 1 fills cells in parallel (one
77+
/// TGeoNavigator per thread for ROOT; VecGeom navigation is thread-safe on its own);
78+
/// nThreads < 0 takes the count from NTHREADS_MATBUD. VECGEOM requires O2 built against
79+
/// TGeo2VecGeom, see GeometryManager::isVecGeomAvailable().
80+
void populateFromTGeo(int ntrPerCel = 10, int nThreads = -1, MatbudGeomBackend backend = MatbudGeomBackend::ROOT);
7981
static int getNThreadsFromEnv();
8082
void optimizePhiSlices(float maxRelDiff = 0.05);
8183

Detectors/Base/src/DetectorsBaseLinkDef.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
#pragma link C++ class o2::base::GeometryManager + ;
2626
#pragma link C++ class o2::base::GeometryManager::MatBudgetExt + ;
27+
#pragma link C++ enum o2::base::MatbudGeomBackend;
2728
#pragma link C++ class o2::base::MaterialManager + ;
2829
#pragma link C++ class o2::MaterialManagerParam + ;
2930
#pragma link C++ class o2::GeometryManagerParam + ;

Detectors/Base/src/GeometryManager.cxx

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,22 @@
3030
#include "CommonUtils/NameConf.h"
3131
#include "DetectorsBase/Aligner.h"
3232

33+
#ifdef O2_WITH_VECGEOM
34+
#include "TGeo2VecGeom/RootGeoManager.h"
35+
#include <VecGeom/management/GeoManager.h>
36+
#include <VecGeom/management/ABBoxManager.h>
37+
#include <VecGeom/management/BVHManager.h>
38+
#include <VecGeom/navigation/GlobalLocator.h>
39+
#include <VecGeom/navigation/NavigationState.h>
40+
#include <VecGeom/navigation/NewSimpleNavigator.h>
41+
#include <VecGeom/navigation/BVHNavigator.h>
42+
#include <VecGeom/navigation/SimpleLevelLocator.h>
43+
#include <VecGeom/navigation/BVHLevelLocator.h>
44+
#include <VecGeom/navigation/VNavigator.h>
45+
#include <VecGeom/volumes/LogicalVolume.h>
46+
#include <mutex>
47+
#endif
48+
3349
using namespace o2::detectors;
3450
using namespace o2::base;
3551

@@ -536,3 +552,129 @@ void GeometryManager::loadGeometry(std::string_view simPrefix, bool applyMisalig
536552
applyMisalignent(applyMisalignment);
537553
}
538554
}
555+
556+
#ifdef O2_WITH_VECGEOM
557+
558+
namespace
559+
{
560+
/// Converts the currently loaded TGeo geometry to VecGeom and sets up navigators, once per
561+
/// process, the first time the VecGeom backend is requested. Not part of loadGeometry(),
562+
/// which every job calls regardless of whether it ever uses the VecGeom backend.
563+
void ensureVecGeomWorldBuilt()
564+
{
565+
static std::once_flag onceFlag;
566+
std::call_once(onceFlag, []() {
567+
if (!gGeoManager) {
568+
LOG(fatal) << "Cannot build VecGeom geometry: no TGeo geometry loaded (call GeometryManager::loadGeometry() first)";
569+
}
570+
// Translate geometry and material pointers, then build acceleration structures.
571+
tgeo2vecgeom::RootGeoManager::Instance().SetMaterialConversionHook([](TGeoMaterial const* m) { return (void*)m; });
572+
tgeo2vecgeom::RootGeoManager::Instance().SetFlattenAssemblies(true);
573+
tgeo2vecgeom::RootGeoManager::Instance().LoadRootGeometry();
574+
575+
// Acceleration structures must be built before the navigators/locators reference them.
576+
vecgeom::ABBoxManager::Instance().InitABBoxesForCompleteGeometry();
577+
// Builds a BVH per logical volume from the ABBoxes computed above.
578+
vecgeom::BVHManager::Init();
579+
580+
// For each logical volume, set both a navigator (used for ComputeStep) and a matched
581+
// level locator (used for point relocation after a boundary crossing via GlobalLocator);
582+
// volumes with very few daughters are cheaper to brute-force than to accelerate.
583+
for (auto& lvol : vecgeom::GeoManager::Instance().GetLogicalVolumesMap()) {
584+
auto* vol = lvol.second;
585+
if (vol->GetDaughtersp()->size() <= 2) {
586+
vol->SetNavigator(vecgeom::NewSimpleNavigator<>::Instance());
587+
vol->SetLevelLocator(vecgeom::SimpleLevelLocator::GetInstance());
588+
} else {
589+
vol->SetNavigator(vecgeom::BVHNavigator<>::Instance());
590+
vol->SetLevelLocator(vecgeom::BVHLevelLocator::GetInstance());
591+
}
592+
}
593+
});
594+
}
595+
} // namespace
596+
597+
//_____________________________________________________________________________________
598+
o2::base::MatBudget GeometryManager::vecGeomMaterialBudget(float x0, float y0, float z0, float x1, float y1, float z1)
599+
{
600+
// Mean material budget between "0" and "1" via VecGeom's BVH-accelerated ray/boundary
601+
// intersection, instead of TGeo.
602+
ensureVecGeomWorldBuilt();
603+
604+
using Vector3D = vecgeom::Vector3D<vecgeom::Precision>;
605+
606+
double length, start[3] = {x0, y0, z0};
607+
double dir[3] = {x1 - x0, y1 - y0, z1 - z0};
608+
if ((length = dir[0] * dir[0] + dir[1] * dir[1] + dir[2] * dir[2]) < TGeoShape::Tolerance() * TGeoShape::Tolerance()) {
609+
return o2::base::MatBudget(); // return empty struct
610+
}
611+
length = std::sqrt(length);
612+
double invlen = 1. / length;
613+
for (int i = 3; i--;) {
614+
dir[i] *= invlen;
615+
}
616+
617+
thread_local static vecgeom::NavigationState* newnavstate = vecgeom::NavigationState::MakeInstance(vecgeom::GeoManager::Instance().getMaxDepth());
618+
thread_local static vecgeom::NavigationState* currnavstate = vecgeom::NavigationState::MakeInstance(vecgeom::GeoManager::Instance().getMaxDepth());
619+
thread_local static vecgeom::NavigationState* startCache = vecgeom::NavigationState::MakeInstance(vecgeom::GeoManager::Instance().getMaxDepth());
620+
thread_local static bool startCacheValid = false;
621+
622+
Vector3D currPoint(x0, y0, z0);
623+
Vector3D dirr(dir[0], dir[1], dir[2]);
624+
constexpr double kPush = 1.E-6; // mimick the nudging of TGeo's FindNextBoundaryAndStep
625+
auto world = vecgeom::GeoManager::Instance().GetWorld();
626+
o2::base::MatBudget budTot, budStep;
627+
budStep.length = length;
628+
629+
// Locate the starting volume, reusing the path from the previous call when still valid.
630+
if (startCacheValid && !startCache->IsOutside()) {
631+
startCache->CopyTo(currnavstate);
632+
vecgeom::Transformation3D m;
633+
currnavstate->TopMatrix(m);
634+
vecgeom::GlobalLocator::RelocatePointFromPath(m.Transform(currPoint), *currnavstate);
635+
} else {
636+
currnavstate->Clear();
637+
vecgeom::GlobalLocator::LocateGlobalPoint(world, currPoint, *currnavstate, true);
638+
}
639+
if (currnavstate->IsOutside() || currnavstate->Top() == nullptr) {
640+
LOG(error) << "start point out of geometry: " << x0 << ':' << y0 << ':' << z0;
641+
startCacheValid = false;
642+
return o2::base::MatBudget();
643+
}
644+
currnavstate->CopyTo(startCache);
645+
startCacheValid = true;
646+
647+
double stepTot = 0.;
648+
double remainingDist = length;
649+
Int_t nzero = 0;
650+
while (remainingDist > 1.E-10) {
651+
auto* lvol = currnavstate->Top()->GetLogicalVolume();
652+
accountMaterial(static_cast<TGeoMaterial*>(lvol->GetMaterialPtr()), budStep);
653+
vecgeom::VNavigator const* navigator = lvol->GetNavigator();
654+
double step = static_cast<double>(navigator->ComputeStepAndPropagatedState(currPoint, dirr, remainingDist, *currnavstate, *newnavstate));
655+
if (step < 2.E-10) {
656+
nzero++;
657+
} else {
658+
nzero = 0;
659+
}
660+
if (nzero > 3) {
661+
// This means navigation has problems on one boundary
662+
LOG(warning) << "Cannot cross boundary at (" << currPoint[0] << ',' << currPoint[1] << ',' << currPoint[2] << ')';
663+
budTot.meanRho /= stepTot;
664+
budTot.length = stepTot;
665+
return o2::base::MatBudget(budTot);
666+
}
667+
668+
remainingDist -= step;
669+
stepTot += step;
670+
budTot.meanRho += step * budStep.meanRho;
671+
budTot.meanX2X0 += step / budStep.meanX2X0;
672+
currPoint = currPoint + (step + kPush) * dirr;
673+
std::swap(currnavstate, newnavstate);
674+
}
675+
budTot.meanRho /= stepTot;
676+
budTot.length = stepTot;
677+
return o2::base::MatBudget(budTot);
678+
}
679+
680+
#endif // O2_WITH_VECGEOM

Detectors/Base/src/MatLayerCyl.cxx

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -109,21 +109,21 @@ void MatLayerCyl::initSegmentation(float rMin, float rMax, float zHalfSpan, int
109109
}
110110

111111
//________________________________________________________________________________
112-
void MatLayerCyl::populateFromTGeo(int ntrPerCell)
112+
void MatLayerCyl::populateFromTGeo(int ntrPerCell, MatbudGeomBackend backend)
113113
{
114114
/// populate layer with info extracted from TGeometry, using ntrPerCell test tracks per cell
115115
assert(mConstructionMask != Constructed);
116116
mConstructionMask = InProgress;
117117
ntrPerCell = ntrPerCell > 1 ? ntrPerCell : 1;
118118
for (int iz = getNZBins(); iz--;) {
119119
for (int ip = getNPhiBins(); ip--;) {
120-
populateFromTGeo(ip, iz, ntrPerCell);
120+
populateFromTGeo(ip, iz, ntrPerCell, nullptr, backend);
121121
}
122122
}
123123
}
124124

125125
//________________________________________________________________________________
126-
void MatLayerCyl::populateFromTGeo(int ip, int iz, int ntrPerCell, TGeoNavigator* nav)
126+
void MatLayerCyl::populateFromTGeo(int ip, int iz, int ntrPerCell, TGeoNavigator* nav, MatbudGeomBackend backend)
127127
{
128128
/// populate cell with info extracted from TGeometry, using ntrPerCell test tracks per cell
129129

@@ -136,7 +136,16 @@ void MatLayerCyl::populateFromTGeo(int ip, int iz, int ntrPerCell, TGeoNavigator
136136
float dzt = zs > 0.f ? 0.25 * dz : -0.25 * dz; // to avoid 90 degree polar angle
137137
for (int isp = ntrPerCell; isp--;) {
138138
o2::math_utils::sincos(phmn + (isp + 0.5) * getDPhi() / ntrPerCell, sn, cs);
139-
auto bud = o2::base::GeometryManager::meanMaterialBudget(rMin * cs, rMin * sn, zs - dzt, rMax * cs, rMax * sn, zs + dzt, nav);
139+
o2::base::MatBudget bud;
140+
if (backend == MatbudGeomBackend::ROOT) {
141+
bud = o2::base::GeometryManager::meanMaterialBudget(rMin * cs, rMin * sn, zs - dzt, rMax * cs, rMax * sn, zs + dzt, nav);
142+
} else {
143+
#ifdef O2_WITH_VECGEOM
144+
bud = o2::base::GeometryManager::vecGeomMaterialBudget(rMin * cs, rMin * sn, zs - dzt, rMax * cs, rMax * sn, zs + dzt);
145+
#else
146+
LOG(fatal) << "MatbudGeomBackend::VECGEOM requested but O2 was built without VecGeom support (TGeo2VecGeom not found at configure time)";
147+
#endif
148+
}
140149
if (bud.length > 0.) {
141150
meanRho += bud.length * bud.meanRho;
142151
meanX2X0 += bud.meanX2X0; // we store actually not X2X0 but 1./X0

0 commit comments

Comments
 (0)