Skip to content

Commit ca02ce4

Browse files
committed
faster mat LUT
Signed-off-by: Felix Schlepper <felix.schlepper@cern.ch>
1 parent 407208a commit ca02ce4

5 files changed

Lines changed: 132 additions & 26 deletions

File tree

Detectors/Base/include/DetectorsBase/MatLayerCyl.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ class MatLayerCyl : public o2::gpu::FlatObject
9696
// obtain material cell, cell ID must be valid
9797
GPUd() const MatCell& getCellPhiBin(int iphi, int iz) const { return mCells[getCellIDPhiBin(iphi, iz)]; }
9898
GPUd() const MatCell& getCell(int iphiSlice, int iz) const { return mCells[getCellID(iphiSlice, iz)]; }
99+
GPUd() const MatCell* getCellRow(int iphiSlice) const { return mCells + iphiSlice * getNZBins(); }
99100

100101
#ifndef GPUCA_ALIGPUCODE // this part is unvisible on GPU version
101102
MatCell& getCellPhiBin(int iphi, int iz)

Detectors/Base/include/DetectorsBase/MatLayerCylSet.h

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,10 @@ struct MatLayerCylSetLayout {
4141
float mRMin2; ///< precalculater rmin^2
4242
float mRMax2; ///< precalculater rmax^2
4343
int mNLayers; ///< number of layers
44-
int mNRIntervals; ///< number of R interval boundaries (gaps are possible)
44+
int mNRIntervals; ///< number of R interval boundaries, one more than the number of intervals (gaps are possible)
4545
MatLayerCyl* mLayers; //[mNLayers] set of cylinrical layers
46-
float* mR2Intervals; //[mNRIntervals+1] limits of layers
47-
int* mInterval2LrID; //[mNRIntervals] mapping from r2 interval to layer ID
46+
float* mR2Intervals; //[mNRIntervals] limits of layers
47+
int* mInterval2LrID; //[mNRIntervals-1] mapping from r2 interval to layer ID
4848
};
4949

5050
class MatLayerCylSet : public o2::gpu::FlatObject
@@ -115,6 +115,16 @@ class MatLayerCylSet : public o2::gpu::FlatObject
115115
/// searches a layer based on r2 input, using a lookup table
116116
GPUd() int searchLayerFast(float r2, int low = -1, int high = -1) const;
117117

118+
/// resolves a layer from an already loaded lookup-table entry
119+
GPUd() int resolveLayerRange(float r2, int voxel, uint16_t entry) const;
120+
121+
/// voxel holding this radius; the caller must have checked that r2 is inside the LUT
122+
GPUd() int voxelIndex(float r2) const { return int(o2::gpu::CAMath::Sqrt(r2) * InvVoxelRDelta); }
123+
124+
/// Radial boundaries of a voxel.
125+
GPUd() static constexpr float voxelRMin(int voxel) { return voxel * VoxelRDelta; }
126+
GPUd() static constexpr float voxelRMax(int voxel) { return (voxel + 1) * VoxelRDelta; }
127+
118128
#ifndef GPUCA_GPUCODE
119129
//-----------------------------------------------------------
120130
std::size_t estimateFlatBufferSize() const;
@@ -139,8 +149,10 @@ class MatLayerCylSet : public o2::gpu::FlatObject
139149
static constexpr float VoxelRDelta = 0.05; // voxel spacing for layer lookup; seems a natural choice - corresponding ~ to smallest spacing
140150
static constexpr float InvVoxelRDelta = 1.f / VoxelRDelta;
141151
static constexpr int NumVoxels = int(LayerRMax / VoxelRDelta);
152+
static constexpr uint16_t VoxelAmbiguousBit = 0x8000u;
153+
static constexpr uint16_t VoxelSegmentMask = 0x7fffu;
142154

143-
uint16_t mLayerVoxelLU[2 * NumVoxels]; //! helper structure to lookup a layer based on known radius (static dimension for easy copy to GPU)
155+
uint16_t mLayerVoxelLU[NumVoxels]; //! first interval based on known radius, plus the ambiguity flag (static dimension for easy copy to GPU)
144156
bool mInitializedLayerVoxelLU = false; //! if the voxels have been initialized
145157

146158
ClassDefNV(MatLayerCylSet, 1);

Detectors/Base/src/MatLayerCylSet.cxx

Lines changed: 49 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ void MatLayerCylSet::finalizeStructures()
234234
o2::gpu::FlatObject::resizeArray(get()->mR2Intervals, 0, nR2Int);
235235
o2::gpu::FlatObject::resizeArray(get()->mInterval2LrID, 0, nR2Int);
236236
get()->mR2Intervals[0] = get()->mRMin2;
237-
get()->mR2Intervals[1] = get()->mRMax2;
237+
get()->mR2Intervals[1] = getLayer(0).getRMax2();
238238
get()->mInterval2LrID[0] = 0;
239239
auto& nRIntervals = get()->mNRIntervals;
240240
nRIntervals = 1;
@@ -319,14 +319,17 @@ void MatLayerCylSet::initLayerVoxelLU()
319319
if (LayerRMax < get()->mRMax) {
320320
LOG(fatal) << "Cannot initialized layer voxel lookup due to dimension problem (fix constants in MatLayerCylSet.h)";
321321
}
322+
// the top bit of an entry carries the ambiguity flag, so the interval index has one bit less
323+
if (get()->mNRIntervals > VoxelSegmentMask) {
324+
LOG(fatal) << "Too many R intervals (" << get()->mNRIntervals << ") to pack into a layer voxel lookup entry";
325+
}
322326
for (int voxel = 0; voxel < NumVoxels; ++voxel) {
323327
// check the 2 extremes of this voxel "covering"
324-
const auto lowerR = voxel * VoxelRDelta;
325-
const auto upperR = lowerR + VoxelRDelta;
328+
const auto lowerR = voxelRMin(voxel);
329+
const auto upperR = voxelRMax(voxel);
326330
const auto lowerSegment = searchSegment(lowerR * lowerR);
327331
const auto upperSegment = searchSegment(upperR * upperR);
328-
mLayerVoxelLU[2 * voxel] = lowerSegment;
329-
mLayerVoxelLU[2 * voxel + 1] = upperSegment;
332+
mLayerVoxelLU[voxel] = uint16_t(lowerSegment) | (lowerSegment != upperSegment ? VoxelAmbiguousBit : uint16_t{0});
330333
}
331334
mInitializedLayerVoxelLU = true;
332335
}
@@ -477,7 +480,10 @@ GPUd() MatBudget MatLayerCylSet::getMatBudget(float x0, float y0, float z0, floa
477480
tEndPhi = cross2;
478481
checkMorePhi = false;
479482
} else { // last phi slice still not reached
480-
tEndPhi = ray.crossRadial(lr, (stepPhiID > 0 ? phiID + 1 : phiID) % nphiSlices);
483+
const int boundaryPhiID = stepPhiID > 0 ? phiID + 1 : phiID;
484+
// phiID may be offset by one revolution to handle wrapping, but never by more.
485+
const int wrappedBoundaryPhiID = boundaryPhiID < nphiSlices ? boundaryPhiID : boundaryPhiID - nphiSlices;
486+
tEndPhi = ray.crossRadial(lr, wrappedBoundaryPhiID);
481487
if (tEndPhi == Ray::InvalidT) {
482488
break; // ray parallel to radial line, abandon check for phi bin change
483489
}
@@ -490,6 +496,8 @@ GPUd() MatBudget MatLayerCylSet::getMatBudget(float x0, float y0, float z0, floa
490496
}
491497
auto zID = lr.getZBinID(ray.getZ(tStartPhi));
492498
auto zIDLast = lr.getZBinID(ray.getZ(tEndPhi));
499+
const int wrappedPhiID = phiID < nphiSlices ? phiID : phiID - nphiSlices;
500+
const auto* cellRow = lr.getCellRow(wrappedPhiID);
493501
// check if Zbins are crossed
494502

495503
#ifdef _DBG_LOC_
@@ -512,7 +520,7 @@ GPUd() MatBudget MatLayerCylSet::getMatBudget(float x0, float y0, float z0, floa
512520
}
513521
// account materials of this step
514522
float step = tEndZ > tStartZ ? tEndZ - tStartZ : tStartZ - tEndZ; // the real step is ray.getDist(tEnd-tStart), will rescale all later
515-
const auto& cell = lr.getCell(phiID % nphiSlices, zID);
523+
const auto& cell = cellRow[zID];
516524
rval.meanRho += cell.meanRho * step;
517525
rval.meanX2X0 += cell.meanX2X0 * step;
518526
rval.length += step;
@@ -523,7 +531,7 @@ GPUd() MatBudget MatLayerCylSet::getMatBudget(float x0, float y0, float z0, floa
523531
printf(
524532
"Lr#%3d / cross#%d : account %f<t<%f at phiSlice %d | Zbin: %3d (%3d) |[%+e %+e +%e]:[%+e %+e %+e] "
525533
"Step: %.3e StrpCor: %.3e\n",
526-
lrID, ic, tEndZ, tStartZ, phiID % nphiSlices, zID, zIDLast,
534+
lrID, ic, tEndZ, tStartZ, wrappedPhiID, zID, zIDLast,
527535
pos0[0], pos0[1], pos0[2], pos1[0], pos1[1], pos1[2], step, ray.getDist(step));
528536
#endif
529537

@@ -532,7 +540,7 @@ GPUd() MatBudget MatLayerCylSet::getMatBudget(float x0, float y0, float z0, floa
532540
} while (checkMoreZ);
533541
} else {
534542
float step = tEndPhi > tStartPhi ? tEndPhi - tStartPhi : tStartPhi - tEndPhi; // the real step is |ray.getDist(tEnd-tStart)|, will rescale all later
535-
const auto& cell = lr.getCell(phiID % nphiSlices, zID);
543+
const auto& cell = cellRow[zID];
536544
rval.meanRho += cell.meanRho * step;
537545
rval.meanX2X0 += cell.meanX2X0 * step;
538546
rval.length += step;
@@ -543,7 +551,7 @@ GPUd() MatBudget MatLayerCylSet::getMatBudget(float x0, float y0, float z0, floa
543551
printf(
544552
"Lr#%3d / cross#%d : account %f<t<%f at phiSlice %d | Zbin: %3d ----- |[%+e %+e +%e]:[%+e %+e %+e]"
545553
"Step: %.3e StrpCor: %.3e\n",
546-
lrID, ic, tEndPhi, tStartPhi, phiID % nphiSlices, zID,
554+
lrID, ic, tEndPhi, tStartPhi, wrappedPhiID, zID,
547555
pos0[0], pos0[1], pos0[2], pos1[0], pos1[1], pos1[2], step, ray.getDist(step));
548556
#endif
549557
}
@@ -585,8 +593,15 @@ GPUd() bool MatLayerCylSet::getLayersRange(const Ray& ray, short& lmin, short& l
585593
lmxInt = rmax2 < getRMax2() ? searchSegment(rmax2, 0) : get()->mNRIntervals - 2;
586594
lmnInt = rmin2 >= getRMin2() ? searchSegment(rmin2, 0, lmxInt + 1) : 0;
587595
} else {
588-
lmxInt = rmax2 < getRMax2() ? searchLayerFast(rmax2, 0) : get()->mNRIntervals - 2;
589-
lmnInt = rmin2 >= getRMin2() ? searchLayerFast(rmin2, 0, lmxInt + 1) : 0;
596+
// The two lookups are independent so overlapping the pair is worth the clumsier shape.
597+
const bool useMax = rmax2 < getRMax2();
598+
const bool useMin = rmin2 >= getRMin2();
599+
const int ixMax = useMax ? voxelIndex(rmax2) : NumVoxels - 1;
600+
const int ixMin = useMin ? voxelIndex(rmin2) : 0;
601+
const uint16_t eMax = mLayerVoxelLU[ixMax];
602+
const uint16_t eMin = mLayerVoxelLU[ixMin];
603+
lmxInt = useMax ? resolveLayerRange(rmax2, ixMax, eMax) : get()->mNRIntervals - 2;
604+
lmnInt = useMin ? resolveLayerRange(rmin2, ixMin, eMin) : 0;
590605
}
591606

592607
const auto* interval2LrID = get()->mInterval2LrID;
@@ -605,11 +620,17 @@ GPUd() bool MatLayerCylSet::getLayersRange(const Ray& ray, short& lmin, short& l
605620
GPUd() int MatLayerCylSet::searchLayerFast(float r2, int low, int high) const
606621
{
607622
// we can avoid the sqrt .. at the cost of more memory in the lookup
608-
const auto index = 2 * int(o2::gpu::CAMath::Sqrt(r2) * InvVoxelRDelta);
609-
const auto layersfirst = mLayerVoxelLU[index];
610-
const auto layerslast = mLayerVoxelLU[index + 1];
611-
if (layersfirst != layerslast) {
612-
// this means the voxel is undecided and we revert to search
623+
const auto index = voxelIndex(r2);
624+
return resolveLayerRange(r2, index, mLayerVoxelLU[index]);
625+
}
626+
627+
GPUd() int MatLayerCylSet::resolveLayerRange(float r2, int voxel, uint16_t entry) const
628+
{
629+
const int layersfirst = entry & VoxelSegmentMask;
630+
if (entry & VoxelAmbiguousBit) {
631+
// Recreate the upper candidate only for the small fraction of undecided voxels
632+
const auto upperR = voxelRMax(voxel);
633+
const auto layerslast = searchSegment(upperR * upperR);
613634
return searchSegment(r2, layersfirst, layerslast + 1);
614635
}
615636
return layersfirst;
@@ -663,12 +684,13 @@ void MatLayerCylSet::flatten()
663684
offs = alignSize(offs + nLr * sizeof(MatLayerCyl), MatLayerCyl::getClassAlignmentBytes()); // account for the alignment
664685

665686
// move array of R2 boundaries to the flat array
666-
delete[] o2::gpu::FlatObject::resizeArray(get()->mR2Intervals, nLr + 1, nLr + 1, (float*)(mFlatBufferPtr + offs));
667-
offs = alignSize(offs + (nLr + 1) * sizeof(float), getBufferAlignmentBytes()); // account for the alignment
687+
const int nRBound = get()->mNRIntervals;
688+
delete[] o2::gpu::FlatObject::resizeArray(get()->mR2Intervals, nRBound, nRBound, (float*)(mFlatBufferPtr + offs));
689+
offs = alignSize(offs + nRBound * sizeof(float), getBufferAlignmentBytes()); // account for the alignment
668690

669-
// move array of R2 boundaries to the flat array
670-
delete[] o2::gpu::FlatObject::resizeArray(get()->mInterval2LrID, nLr, nLr, (int*)(mFlatBufferPtr + offs));
671-
offs = alignSize(offs + nLr * sizeof(int), getBufferAlignmentBytes()); // account for the alignment
691+
// move array of interval -> layer ID to the flat array
692+
delete[] o2::gpu::FlatObject::resizeArray(get()->mInterval2LrID, nRBound - 1, nRBound - 1, (int*)(mFlatBufferPtr + offs));
693+
offs = alignSize(offs + (nRBound - 1) * sizeof(int), getBufferAlignmentBytes()); // account for the alignment
672694

673695
for (int il = 0; il < nLr; il++) {
674696
MatLayerCyl& lr = get()->mLayers[il];
@@ -710,6 +732,11 @@ void MatLayerCylSet::cloneFromObject(const MatLayerCylSet& obj, char* newFlatBuf
710732
/// Initializes from another object, copies data to newBufferPtr
711733
flatObject::cloneFromObject(obj, newFlatBufferPtr);
712734
fixPointers(mFlatBufferPtr);
735+
// the voxel lookup lives outside the flat buffer
736+
if (obj.mInitializedLayerVoxelLU) {
737+
std::copy(obj.mLayerVoxelLU, obj.mLayerVoxelLU + NumVoxels, mLayerVoxelLU);
738+
mInitializedLayerVoxelLU = true;
739+
}
713740
}
714741

715742
//______________________________________________

Detectors/Base/test/buildMatBudLUT.C

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include <TFile.h>
2222
#include <TSystem.h>
2323
#include <TStopwatch.h>
24+
#include <TRandom.h>
2425
#endif
2526

2627
using MatbudGeomBackend = o2::base::MatbudGeomBackend;
@@ -30,6 +31,11 @@ o2::base::MatLayerCylSet mbLUT;
3031
bool testMBLUT(const std::string& lutFile = "matbud.root");
3132
MatbudGeomBackend parseBackend(const std::string& s);
3233

34+
/// mR2Intervals must be non-decreasing
35+
bool testMBLUTIntervalsSorted(const o2::base::MatLayerCylSet* lut);
36+
/// getLayersRange() must agree with and without the voxel lookup
37+
bool testMBLUTVoxelConsistency(o2::base::MatLayerCylSet* lut, int nRays = 5000);
38+
3339
/// Build the material budget LUT. nThreads < 0 takes the thread count from NTHREADS_MATBUD.
3440
/// geomBackend is "ROOT" (default) or "VECGEOM" (requires O2 built against TGeo2VecGeom).
3541
bool buildMatBudLUT(int nTst = 60, int maxLr = -1, const std::string& outFile = "matbud.root",
@@ -186,6 +192,61 @@ bool testMBLUT(const std::string& lutFile)
186192
return true;
187193
}
188194

195+
//_______________________________________________________________________
196+
bool testMBLUTIntervalsSorted(const o2::base::MatLayerCylSet* lut)
197+
{
198+
// searchSegment() is a binary search over mR2Intervals, enfore order
199+
const auto* layout = lut->get();
200+
for (int i = 1; i < layout->mNRIntervals; i++) { // mNRIntervals counts boundaries, last index is mNRIntervals-1
201+
if (layout->mR2Intervals[i] < layout->mR2Intervals[i - 1]) {
202+
LOGP(error, "mR2Intervals not monotonic at {}: {} > {}", i, layout->mR2Intervals[i - 1], layout->mR2Intervals[i]);
203+
return false;
204+
}
205+
}
206+
return true;
207+
}
208+
209+
//_______________________________________________________________________
210+
bool testMBLUTVoxelConsistency(o2::base::MatLayerCylSet* lut, int nRays)
211+
{
212+
// The voxel lookup is only a shortcut into searchSegment(), so it must not change the answer.
213+
if (!lut->mInitializedLayerVoxelLU) {
214+
LOG(error) << "voxel lookup is not initialized, nothing to compare against";
215+
return false;
216+
}
217+
const float rMax = lut->getRMax(), zMax = lut->getZMax();
218+
TRandom rnd(20260825);
219+
int nBad = 0, nInside = 0;
220+
for (int i = 0; i < nRays; i++) {
221+
float x0 = rnd.Uniform(-rMax, rMax), y0 = rnd.Uniform(-rMax, rMax), z0 = rnd.Uniform(-zMax, zMax);
222+
float x1 = rnd.Uniform(-rMax, rMax), y1 = rnd.Uniform(-rMax, rMax), z1 = rnd.Uniform(-zMax, zMax);
223+
o2::base::Ray ray(x0, y0, z0, x1, y1, z1);
224+
short lmin = -1, lmax = -1, lminRef = -1, lmaxRef = -1;
225+
const bool ok = lut->getLayersRange(ray, lmin, lmax);
226+
lut->mInitializedLayerVoxelLU = false; // force the plain binary search
227+
const bool okRef = lut->getLayersRange(ray, lminRef, lmaxRef);
228+
lut->mInitializedLayerVoxelLU = true;
229+
if (ok) {
230+
nInside++;
231+
}
232+
if (ok != okRef || (ok && (lmin != lminRef || lmax != lmaxRef))) {
233+
if (++nBad < 10) {
234+
LOGP(error, "ray {} ({:.3f},{:.3f},{:.3f})->({:.3f},{:.3f},{:.3f}): voxel LU gives {} [{},{}], search gives {} [{},{}]",
235+
i, x0, y0, z0, x1, y1, z1, ok, lmin, lmax, okRef, lminRef, lmaxRef);
236+
}
237+
}
238+
}
239+
if (nInside < nRays / 10) {
240+
LOGP(error, "only {} of {} test rays crossed the LUT, the comparison is not meaningful", nInside, nRays);
241+
return false;
242+
}
243+
if (nBad) {
244+
LOGP(error, "{} of {} rays disagree between the voxel lookup and searchSegment()", nBad, nRays);
245+
return false;
246+
}
247+
return true;
248+
}
249+
189250
//_______________________________________________________________________
190251
void configLayers()
191252
{

Detectors/Base/test/testMatBudLUT.cxx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,10 @@ BOOST_AUTO_TEST_CASE(MatBudLUT)
2828
matBudFile += std::to_string(getpid()) + ".root";
2929
BOOST_CHECK(buildMatBudLUT(2, 20, matBudFile, geomPrefix + std::to_string(getpid()), "align-geom.mDetectors=none")); // generate LUT
3030
BOOST_CHECK(testMBLUT(matBudFile)); // test LUT manipulations
31+
32+
o2::base::MatLayerCylSet* lut = o2::base::MatLayerCylSet::loadFromFile(matBudFile);
33+
BOOST_REQUIRE(lut != nullptr);
34+
BOOST_CHECK(testMBLUTIntervalsSorted(lut)); // mR2Intervals is monotonic
35+
BOOST_CHECK(testMBLUTVoxelConsistency(lut)); // voxel lookup agrees with the plain search
3136
}
3237
} // namespace o2

0 commit comments

Comments
 (0)