CbmRoot
Loading...
Searching...
No Matches
KfMaterialMapFactory.cxx
Go to the documentation of this file.
1/* Copyright (C) 2024 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
2 SPDX-License-Identifier: GPL-3.0-only
3 Authors: Sergei Zharko [committer] */
4
10
12
13#include "KfMaterialMap.h"
14#include "TGeoManager.h"
15#include "TGeoMedium.h"
16#include "TGeoNavigator.h"
17#include "TGeoNode.h"
18#include "TGeoPhysicalNode.h"
19#include "TGeoVoxelFinder.h"
20
21#include <Logger.h>
22
23#include <sstream>
24#include <thread>
25
28
29// ---------------------------------------------------------------------------------------------------------------------
30//
31MaterialMapFactory::MaterialMapFactory(int verbose) : fVerbose(verbose)
32{
33 // constructor
34
35 // save the current number of threads to restore it when the work is done
36 fNthreadsOld = gGeoManager->GetMaxThreads();
37
38 // set defaut n threads to 1
39
40 fNthreads = 1;
41
42 std::stringstream msg;
43 msg << "\n------------------------------------------------------------------------------------";
44 msg << "\n Material budget map creator";
45 msg << '\n';
46 msg << "\n !! To run it with the full speed, set: \"export OMP_NUM_THREADS=<n CPUs>\" before running !!";
47 msg << '\n';
48 // if possible, read the allowed n threads from the environment variable
49 {
50 const char* env = std::getenv("OMP_NUM_THREADS");
51 if (env) {
52 fNthreads = TString(env).Atoi();
53 msg << "\n found environment variable OMP_NUM_THREADS = \"" << env << "\", read as integer: " << fNthreads;
54 }
55 }
56
57 // ensure that at least one thread is set
58
59 if (fNthreads < 1) {
60 fNthreads = 1;
61 }
62
63 msg << "\n Maps will be created using " << fNthreads << " CPU threads";
64 msg << '\n';
65 msg << "\n It might crash because of a ROOT bug. In this case do one of the following: ";
66 msg << '\n';
67 msg << "\n - fix illegal overlaps in the geometry via gGeoManager->CheckOverlaps()";
68 msg << "\n - run with CbmL1::SetSafeMaterialInitialization() option. ";
69 msg << "\n It fixes the crash, but slows down the initialization significantly.";
70 msg << "\n - manually disable voxel finders for problematic volumes in CaToolsMaterialMapFactory.cxx";
71 msg << '\n';
72 msg << "\n------------------------------------------------------------------------------------";
73 LOG_IF(info, fVerbose > 0) << msg.str();
74
76
77 //
78 // It looks like TGeoVoxelFinder contains a bug and therefore sometimes produces a crash.
79 //
80 // Currently, the crash only happens on some of TGeoVolumeAssembly volumes
81 // and only when an (inproper?) alignment matrices are applied.
82 //
83 // Here we try to catch this case and disable those finders.
84 //
85 // Disabling all the voxel finders leads to a very long initialization, try to avoid it!
86 //
87 // TODO: make a bug report to the ROOT team; remove the following code once the bug is fixed.
88 //
89
90 if (fDoSafeInitialization) { // disable all voxel finders
91 TObjArray* volumes = gGeoManager->GetListOfVolumes();
92 if (volumes) {
93 for (int iv = 0; iv < volumes->GetEntriesFast(); iv++) {
94 TGeoVolume* vol = dynamic_cast<TGeoVolume*>(volumes->At(iv));
95 if (!vol) {
96 continue;
97 }
98 TGeoVoxelFinder* finder = vol->GetVoxels();
99 if (finder) {
100 finder->SetInvalid();
101 }
102 }
103 }
104 }
105 else { // disable some voxel finders in some cases
106
107 // check if any alignment was applied
108
109 bool isAlignmentApplied = false;
110 {
111 TObjArray* nodes = gGeoManager->GetListOfPhysicalNodes();
112 if (nodes) {
113 for (int in = 0; in < nodes->GetEntriesFast(); in++) {
114 TGeoPhysicalNode* node = dynamic_cast<TGeoPhysicalNode*>(nodes->At(in));
115 if (!node) continue;
116 if (node->IsAligned()) {
117 isAlignmentApplied = true;
118 break;
119 }
120 }
121 }
122 }
123
124 // disable potentially problematic voxel finders
125
126 if (isAlignmentApplied) {
127 TObjArray* volumes = gGeoManager->GetListOfVolumes();
128 if (volumes) {
129 for (int iv = 0; iv < volumes->GetEntriesFast(); iv++) {
130 TGeoVolumeAssembly* vol = dynamic_cast<TGeoVolumeAssembly*>(volumes->At(iv));
131 if (!vol) {
132 continue;
133 }
134 TGeoVoxelFinder* finder = vol->GetVoxels();
135 if (finder) {
136 finder->SetInvalid();
137 }
138 }
139 }
140 }
141 }
142}
143
144// ---------------------------------------------------------------------------------------------------------------------
145//
146MaterialMapFactory::~MaterialMapFactory()
147{
148 // destructor
149
151
152 // delete the navigators, created in this class
153
154 for (auto i = fNavigators.begin(); i != fNavigators.end(); i++) {
155 gGeoManager->RemoveNavigator(*i);
156 }
157
158 // once TGeoManager is swithched in multithreaded mode, there is no way to make it non-mltithreaded again
159 // therefore we should set SetMaxThreads( >=0 )
160
161 if (fNthreadsOld > 0) {
162 gGeoManager->SetMaxThreads(fNthreadsOld);
163 }
164 else {
165 gGeoManager->SetMaxThreads(1);
166 }
167
168 // ensure that the default navigator exists
169
171}
172
173// ---------------------------------------------------------------------------------------------------------------------
174//
175TGeoNavigator* MaterialMapFactory::GetCurrentNavigator(int iThread)
176{
177 // Get navigator for current thread, create it if it doesnt exist.
178 // Produce an error when anything goes wrong
179
180 TGeoNavigator* navi = gGeoManager->GetCurrentNavigator();
181 if (!navi) {
182
183 navi = gGeoManager->AddNavigator();
184
185 if (iThread >= 0) {
186 fNavigators.push_back(navi);
187 }
188
189 if (navi != gGeoManager->GetCurrentNavigator()) {
190 LOG(fatal) << "ca::tools::MaterialMapFactory: unexpected behavior of TGeoManager::AddNavigator() !!";
191 }
192 }
193
194 if (!navi) {
195 LOG(fatal) << "ca::tools::MaterialMapFactory: can not find / create TGeoNavigator for thread " << iThread;
196 }
197
198 return navi;
199}
200
201// ---------------------------------------------------------------------------------------------------------------------
202//
203void MaterialMapFactory::CleanUpThreads()
204{
205 // clean up thread data in TGeoManager
206
207 gGeoManager->ClearThreadsMap();
208
209 // create a default navigator for multithreaded mode
210 // otherwise gGeoManager->GetCurrentNavigator() returns nullptr that might produces crashes in other code
211
213}
214
215void MaterialMapFactory::InitThreads()
216{
217 // (re)initialise the number of threads in TGeoManager
218
219 // reserve enough threads in TGeoManager
220 if (fNthreads > gGeoManager->GetMaxThreads()) {
221 gGeoManager->SetMaxThreads(fNthreads);
222 // in case the number of threads is truncated by TGeoManager (must not happen)
223 fNthreads = gGeoManager->GetMaxThreads();
224 if (fNthreads < 1) {
225 fNthreads = 1;
226 }
227 }
229}
230
231// ---------------------------------------------------------------------------------------------------------------------
232//
233MaterialMap MaterialMapFactory::GenerateMaterialMap(double zRef, double zMin, double zMax, double xyMax, int nBinsDim)
234{
236 if (fTargetZ + 0.05 >= zRef) {
237 LOG(warn)
238 << "kf::tools::MaterialMapFactory: the material reference z-coordinate must be at least 0.1 cm downstream "
239 "the target. Shifting the reference z-coordinate to the lower limit: target Z = "
240 << fTargetZ << ", material reference z = " << zRef;
241 }
242 zRef = std::max(zRef, fTargetZ + 0.05);
243 zMin = std::max(fTargetZ, zMin);
244 zMax = std::max(zMax, zRef + 0.05);
245 }
246
247 if (!(zMin <= zRef && zRef <= zMax)) {
248 LOG(fatal) << "kf::tools::MaterialMapFactory: the material parameters are inconsistent. It must be: zMin " << zMin
249 << " <= zRef " << zRef << " <= zMax " << zMax;
250 }
251
252 MaterialMap matBudget(nBinsDim, xyMax, zRef, zMin, zMax);
253 double binSizeX = 2. * xyMax / nBinsDim;
254 double binSizeY = 2. * xyMax / nBinsDim;
255
256 // call it every time. for the case the number of threads was reset in some other code
257 InitThreads();
258
259 long nThreadCrosses[fNthreads];
260 long nThreadRays[fNthreads];
261 long nThreadRaysExpected = nBinsDim * nBinsDim * fNraysBinPerDim * fNraysBinPerDim / fNthreads;
262 for (int i = 0; i < fNthreads; i++) {
263 nThreadCrosses[i] = 0;
264 nThreadRays[i] = 0;
265 }
266
267 LOG_IF(info, fVerbose > -1) << "kf::tools::MaterialMapFactory: Generate material map using " << fNthreads
268 << " threads..";
269
270 auto naviThread = [&](int iThread) {
271 // create a navigator for this thread
272 TGeoNavigator* navi = GetCurrentNavigator(iThread);
273
274 for (int iBinX = iThread; iBinX < nBinsDim; iBinX += fNthreads) {
275 for (int iBinY = 0; iBinY < nBinsDim; ++iBinY) {
276 double radThick = 0.;
277 int nRays = 0;
278 double d = 1. / (fNraysBinPerDim);
279 for (int iStepX = 0; iStepX < fNraysBinPerDim; iStepX++) {
280 for (int iStepY = 0; iStepY < fNraysBinPerDim; iStepY++) {
281
282 // ray position at zRef
283 double rayX = -xyMax + (iBinX + d * (iStepX + 0.5)) * binSizeX;
284 double rayY = -xyMax + (iBinY + d * (iStepY + 0.5)) * binSizeY;
285
286 // ray slopes
287 double tx = 0.;
288 double ty = 0.;
289 double t = 1.;
291 tx = rayX / (zRef - fTargetZ);
292 ty = rayY / (zRef - fTargetZ);
293 t = sqrt(1. + tx * tx + ty * ty);
294 }
295
296 // ray position at zMin
297 double z = zMin;
298 double x = rayX + tx * (z - zRef);
299 double y = rayY + ty * (z - zRef);
300 LOG_IF(info, fVerbose > 2) << "ray at " << x << " " << y << " " << z << " zMin " << zMin << " zMax "
301 << zMax;
302
303 TGeoNode* node = navi->InitTrack(x, y, z, tx / t, ty / t, 1. / t);
304
305 bool doContinue = 1;
306 for (int iStep = 0; doContinue; iStep++) {
307
308 if (!node) {
309 // may happen when tracing outside of the CBM volume -> produce a warning
310 LOG(warning) << "kf::tools::MaterialMapFactory: TGeoNavigator can not find the geo node";
311 break;
312 }
313
314 TGeoMedium* medium = node->GetMedium();
315 if (!medium) {
316 LOG(fatal) << "kf::tools::MaterialMapFactory: TGeoNavigator can not find the geo medium";
317 }
318
319 TGeoMaterial* material = medium->GetMaterial();
320 if (!material) {
321 LOG(fatal) << "kf::tools::MaterialMapFactory: TGeoNavigator can not find the geo material";
322 }
323
324 double radLen = material->GetRadLen();
325
326 if (radLen < kMinRadLength) { // 0.5612 is rad. length of Lead at normal density
327 LOG(fatal) << "kf::tools::MaterialMapFactory: failed assertion! "
328 << " TGeoNavigator returns a suspicious material with an unrealistic "
329 << "radiation length of " << radLen << " cm. "
330 << " The allowed minimum is 0.56 cm (Lead has 0.5612 cm). Check your material! "
331 "Modify this assertion if necessary."
332 << " TGeoNode \"" << node->GetName() << "\", TGeoMedium \"" << medium->GetName()
333 << "\", TGeoMaterial \"" << material->GetName() << "\"";
334 }
335
336 // move navi to the next material border
337 node = navi->FindNextBoundaryAndStep(); //5., kTRUE);
338 nThreadCrosses[iThread]++;
339
340 LOG_IF(info, fVerbose > 2) << " RL " << radLen << " next pt " << navi->GetCurrentPoint()[0] << " "
341 << navi->GetCurrentPoint()[1] << " " << navi->GetCurrentPoint()[2];
342
343 double zNew = navi->GetCurrentPoint()[2];
344 if (zNew >= zMax) {
345 zNew = zMax;
346 doContinue = 0;
347 }
348
349 //if (zNew < z) {
350 //LOG(info) << " z curr " << z << " z new " << zNew << " dz " << zNew - z ; }
351 double dz = zNew - z;
352 if (dz < 0.) {
353 if (iStep == 0 && dz > -1.e-8) {
354 // when the ray starts exactly at the volume border, first dz might become negative,
355 // probably due to some roundoff errors. So we let it be a bit negative for the first intersection.
356 dz = 0.;
357 }
358 else {
359 LOG(fatal)
360 << "kf::tools::MaterialMapFactory: TGeoNavigator propagates the ray upstream. Something is wrong."
361 << " z old " << z << " z new " << zNew << ", dz " << dz;
362 }
363 }
364 radThick += dz / radLen;
365 z = zNew;
366 }
367
368 nRays++;
369 nThreadRays[iThread]++;
370 if (fVerbose > 2 && nThreadRays[iThread] % 1000000 == 0) {
371 LOG(info) << "kf::tools::MaterialMapFactory: report from thread " << iThread << ": material map is "
372 << 100. * nThreadRays[iThread] / nThreadRaysExpected << " \% done";
373 }
374 }
375 }
376 radThick /= nRays;
377 LOG_IF(info, fVerbose > 2) << " radThick " << radThick;
378 //doPrint = (radThick > 0.01);
379 matBudget.SetRadThickBin(iBinX, iBinY, radThick);
380 } // iBinX
381 } // iBinY
382 };
383
384 std::vector<std::thread> threads(fNthreads);
385
386 // run n threads
387 for (int i = 0; i < fNthreads; i++) {
388 threads[i] = std::thread(naviThread, i);
389 }
390
391 // wait for the threads to finish
392 for (auto& th : threads) {
393 th.join();
394 }
395
397 long nRays = 0;
398 long nCrosses = 0;
399 for (int i = 0; i < fNthreads; i++) {
400 nRays += nThreadRays[i];
401 nCrosses += nThreadCrosses[i];
402 }
403
404 LOG_IF(info, fVerbose > 0) << "kf::tools::MaterialMapFactory: shooted " << nRays << " rays. Each ray crossed "
405 << nCrosses * 1. / nRays << " material boundaries in average";
406
407 return matBudget;
408}
Utility to generate material budget map from the TGeoNavigator representation of the Setup (implement...
friend fvec sqrt(const fvec &a)
A map of station thickness in units of radiation length (X0) to the specific point in XY plane.
void SetRadThickBin(int iBinX, int iBinY, float thickness)
Sets value of material thickness in units of X0 for a given cell of the material table.
An utility class to create a material budget map from the TGeo.
MaterialMapFactory(int verbose=0)
Constructor from parameters.
int fNthreadsOld
number of threads in TGeoManager before the helper was created
std::vector< TGeoNavigator * > fNavigators
list of created navigators
bool fDoRadialProjection
if project rays horizontally along the Z axis (special mode)
TGeoNavigator * GetCurrentNavigator(int iThread)
Gets navigator for current thread, creates it if it does not exist.
void CleanUpThreads()
Cleans up the TGeoManager: threadIds, create a default navigator.
double fTargetZ
z of the target for the radial projection
static constexpr double kMinRadLength
Minimal radiational length allowed [cm].
void InitThreads()
Initializes the necessary amount of threads in TGeoManager.
int fNraysBinPerDim
shoot fNraysBinPerDim * fNraysBinPerDim rays in each map bin