Geant4 11.4.0
Toolkit for the simulation of the passage of particles through matter
Loading...
Searching...
No Matches
G4VoxelisationHelper.cc
Go to the documentation of this file.
1//
2// ********************************************************************
3// * License and Disclaimer *
4// * *
5// * The Geant4 software is copyright of the Copyright Holders of *
6// * the Geant4 Collaboration. It is provided under the terms and *
7// * conditions of the Geant4 Software License, included in the file *
8// * LICENSE and available at http://cern.ch/geant4/license . These *
9// * include a list of copyright holders. *
10// * *
11// * Neither the authors of this software system, nor their employing *
12// * institutes,nor the agencies providing financial support for this *
13// * work make any representation or warranty, express or implied, *
14// * regarding this software system or assume any liability for its *
15// * use. Please see the license in the file LICENSE and URL above *
16// * for the full disclaimer and the limitation of liability. *
17// * *
18// * This code implementation is the result of the scientific and *
19// * technical work of the GEANT4 collaboration. *
20// * By using, copying, modifying or distributing the software (or *
21// * any work based on the software) you agree to acknowledge its *
22// * use in resulting scientific publications, and indicate your *
23// * acceptance of all terms of the Geant4 Software license. *
24// ********************************************************************
25//
26// G4VoxelisationHelper implementation
27//
28// Author: John Apostolakis (CERN), 10.02.2025
29// --------------------------------------------------------------------
31
32#include "voxeldefs.hh"
33
34#include "G4LogicalVolume.hh"
36
37#include "G4SmartVoxelHeader.hh"
38
39#include "G4Threading.hh"
40#include "G4Timer.hh"
41
43{
44 fWallClockTimer = new G4Timer;
45
46 ResetListOfVolumesToOptimise();
47}
48
50{
51 delete fWallClockTimer;
52}
53
54// ***************************************************************************
55// Setup up state to enable parallel optimisation by workers.
56// ***************************************************************************
57//
58// Method's Old Name: ConfigureParallelOptimisation
59//
60void G4VoxelisationHelper::ReSetParallelOptimisation(G4bool verbose)
61{
62 if(verbose)
63 {
64 G4cout << "** G4VoxelisationHelper::ReSetParallelOptimisation() called. "
65 << " All the work (of voxel optimisation) WAS LEFT to the threads/tasks !"
66 << G4endl << " Clearing the state for this work." << G4endl;
67 }
68 // fParallelVoxelOptimisationRequested = true;
69 fParallelVoxelOptimisationUnderway = false;
70 fParallelVoxelOptimisationFinished = false;
71
72 // Keep values of options / verbosity for use in threads
73 fVerboseParallel = verbose;
74
75 // New effort -- reset all 'sums' & totals: total time, number of threads reporting
76 fSumVoxelTime = 0.0;
77 fNumberThreadsReporting = 0;
78 fTotalNumberVolumesOptimised = 0; // Number of volumes done
79
80 fWallClockStarted = false; // Will need to restart it!
81
82 fLogVolumeIterator = fVolumesToOptimise.cbegin();
83 // Reset the iterator -- else to be sure that work is done correctly
84}
85
86// ***************************************************************************
87// Creates a list of logical volumes which will be optimised
88// if allOpts=true it lists all voxels
89// otherwise it lists only the voxels of replicated volumes.
90// This list will be used subsequently to build their voxels.
91//
92// Note: this method is NOT thread safe!
93// It expects to be called only once in each (re)initalisation
94// i.e. either by master thread or a selected thread.
95// ***************************************************************************
96//
97void
98G4VoxelisationHelper::CreateListOfVolumesToOptimise(G4bool allOpts, G4bool verbose)
99{
100 // Prepare the work - must be called only in one thread !!
101
102 G4LogicalVolumeStore* Store = G4LogicalVolumeStore::GetInstance();
103
104 if( !fVolumesToOptimise.empty() )
105 {
106 ResetListOfVolumesToOptimise();
107 }
108
109 for (auto & n : *Store)
110 {
111 G4LogicalVolume* volume=n;
112
113 if ( ( (volume->IsToOptimise())
114 && (volume->GetNoDaughters()>=kMinVoxelVolumesLevel1&&allOpts) )
115 || ( (volume->GetNoDaughters()==1)
116 && (volume->GetDaughter(0)->IsReplicated())
117 && (volume->GetDaughter(0)->GetRegularStructureId()!=1) ) )
118 {
119 fVolumesToOptimise.push_back(volume);
120
121 // For safety, must check (later) if there are any existing voxels and
122 // delete before replacement:
123 // All 'clients' of this code must do the following:
124 // delete volume->GetVoxelHeader();
125 // volume->SetVoxelHeader(nullptr);
126
127#ifdef G4GEOMETRY_VOXELDEBUG
128 G4cout << "- Booking logical volume with " << volume->GetNoDaughters()
129 << " daughters and name = '" << volume->GetName() << "' "
130 << " -- for optimisation (ie voxels will be built for it). " << G4endl;
131#endif
132 }
133 else
134 {
135#ifdef G4GEOMETRY_VOXELDEBUG
136 G4cout << "- Skipping logical volume with " << volume->GetNoDaughters()
137 << " daughters and name = '" << volume->GetName() << "' " << G4endl;
138#endif
139 }
140 }
141
142 if(verbose)
143 G4cout << "** G4VoxelisationHelper::PrepareOptimisationWork: "
144 << " Number of volumes for voxelisation = "
145 << fVolumesToOptimise.size() << G4endl;
146
147 fLogVolumeIterator = fVolumesToOptimise.cbegin();
148}
149
150// ***************************************************************************
151// Build voxel optimisation in parallel -- prepare the work for threads/tasks
152// ***************************************************************************
153//
155{
156 if( verbose )
157 {
158 G4cout << "** G4VoxelisationHelper::PrepareParallelOptimisation() called."
159 << " LEAVING all the work (of voxel optimisation) to the threads/tasks !"
160 << G4endl;
161 }
162 CreateListOfVolumesToOptimise(allOpts, verbose);
163 ReSetParallelOptimisation(verbose);
164}
165
166
167
168// ***************************************************************************
169// Data structures / mutexes for parallel optimisation
170// ***************************************************************************
171//
172namespace
173{
174 // Mutex to obtain a volume to optimise
175 G4Mutex obtainVolumeMutex = G4MUTEX_INITIALIZER;
176
177 // Mutex to lock saving of voxel statistics
178 G4Mutex voxelStatsMutex = G4MUTEX_INITIALIZER;
179
180 // Mutex to provide Statistics Results
181 G4Mutex statResultsMutex = G4MUTEX_INITIALIZER;
182
183 // Mutex to start wall clock (global) timer
184 G4Mutex wallClockTimerMutex = G4MUTEX_INITIALIZER;
185
186 // Mutex to write debug output
187 G4Mutex outputDbgMutex = G4MUTEX_INITIALIZER;
188}
189
190
191// ***************************************************************************
192// Method for a thread/task to contribute dynamically to Optimisation
193// ***************************************************************************
194//
196{
197 G4bool verbose = fVerboseParallel;
198 G4LogicalVolume* logVolume = nullptr;
199
200 // Use a mutex to protect parallel writes, but may be better to use atomic,
201 // or do away with this entirely since it's not practically used?
202 {
203 G4AutoLock uo_lock(&statResultsMutex);
204 fParallelVoxelOptimisationUnderway = true;
205 }
206
207 // Start timer - if not already done
208 if( ( !fWallClockStarted ) && verbose )
209 {
210 G4AutoLock startTimeLock(wallClockTimerMutex);
211 if( !fWallClockStarted )
212 {
213 fWallClockTimer->Start();
214 fWallClockStarted= true;
215 }
216 }
217
218 G4Timer fetimer;
219 unsigned int numVolumesOptimised = 0;
220
221 while( (logVolume = ObtainVolumeToOptimise()) != nullptr )
222 {
223 if (verbose) fetimer.Start();
224
225 G4SmartVoxelHeader* head = logVolume->GetVoxelHeader();
226 delete head;
227 logVolume->SetVoxelHeader(nullptr);
228
229 head = new G4SmartVoxelHeader(logVolume);
230 // *********************************
231 logVolume->SetVoxelHeader(head);
232
233 if (head != nullptr)
234 {
235 ++numVolumesOptimised;
236 }
237 else
238 {
240 message << "VoxelHeader allocation error." << G4endl
241 << "Allocation of new VoxelHeader" << G4endl
242 << "for logical volume " << logVolume->GetName() << " failed.";
243 G4Exception("G4VoxelisationHelper::BuildOptimisationsParallel()",
244 "GeomMgt0003", FatalException, message);
245 }
246
247 if(verbose)
248 {
249 fetimer.Stop();
250 auto feRealElapsed = fetimer.GetRealElapsed();
251 // Must use 'real' elapsed time -- cannot trust user/system time
252 // (it accounts for all threads)
253
254 G4AutoLock lock(voxelStatsMutex);
255 fGlobVoxelStats.emplace_back( logVolume, head,
256 0.0, // Cannot estimate system time
257 feRealElapsed ); // Use real time instead of user time
258 fSumVoxelTime += feRealElapsed;
259 }
260 }
261
262 G4bool allDone = false;
263 G4int myCount= -1;
264
265 myCount = ReportWorkerIsDoneOptimising(numVolumesOptimised);
267
268 if( allDone && (myCount == G4Threading::GetNumberOfRunningWorkerThreads()) )
269 {
270 G4int badVolumes = CheckOptimisation(); // Check all voxels are created!
271 if( badVolumes > 0 )
272 {
274 errmsg <<" Expected that all voxelisation work is done, "
275 << "but found that voxels headers are missing in "
276 << badVolumes << " volumes.";
277 G4Exception("G4VoxelisationHelper::UndertakeOptimisation()",
278 "GeomMng002", FatalException, errmsg);
279 }
280
281 // Create report
282
283 if( verbose )
284 {
285 fWallClockTimer->Stop();
286
287 std::ostream& report_stream = std::cout; // G4cout; does not work!
288 report_stream << G4endl
289 << "G4VoxelisationHelper::UndertakeOptimisation"
290 << " -- Timing for Voxel Optimisation" << G4endl;
291 report_stream << " - Elapsed time (real) = " << std::setprecision(4)
292 << fWallClockTimer->GetRealElapsed() << "s (wall clock)"
293 << ", user " << fWallClockTimer->GetUserElapsed() << "s"
294 << ", system " << fWallClockTimer->GetSystemElapsed() << "s."
295 << G4endl;
296 report_stream << " - Sum voxel time (real) = " << fSumVoxelTime
297 << "s.";
298 report_stream << std::setprecision(6) << G4endl << G4endl;
299
300 ReportVoxelStats( fGlobVoxelStats, fSumVoxelTime, report_stream );
301 report_stream.flush();
302 }
303 }
304 else
305 {
306 WaitForVoxelisationFinish(false);
307 }
308}
309
310
311// ***************************************************************************
312// Obtain a logical volume from the list of volumes to optimise
313// Must be thread-safe: its role is to be called in parallel by threads/tasks!
314// Critical method for parallel optimisation - must be correct and fast.
315// ***************************************************************************
316//
317G4LogicalVolume* G4VoxelisationHelper::ObtainVolumeToOptimise()
318{
319 G4LogicalVolume* logVolume = nullptr;
320
321 G4AutoLock lock(obtainVolumeMutex);
322
323 if( fLogVolumeIterator != fVolumesToOptimise.cend() )
324 {
325 logVolume = *fLogVolumeIterator;
326 ++fLogVolumeIterator;
327 }
328 return logVolume;
329}
330
331// ***************************************************************************
332// Thread-safe method to clear the list of volumes to Optimise.
333// ***************************************************************************
334//
335void G4VoxelisationHelper::ResetListOfVolumesToOptimise()
336{
337 G4AutoLock lock(obtainVolumeMutex);
338
339 std::vector<G4LogicalVolume*>().swap(fVolumesToOptimise);
340 // Swapping with an empty vector in order to empty it
341 // without calling destructors of logical volumes.
342 // Must not call clear: i.e. fVolumesToOptimise.clear();
343
344 assert(fVolumesToOptimise.empty());
345 fLogVolumeIterator = fVolumesToOptimise.cbegin();
346
347 fGlobVoxelStats.clear();
348 // Reset also the statistics of volumes -- to avoid double recording.
349}
350
351// ***************************************************************************
352// Report that current thread/task is done optimising.
353// A thread call this method to reports that is is done (finished), and how
354// many volumes it optimised. The method:
355// - increments the count of workers that have finished, and return it;
356// - keeps count of number of volumes optimised;
357// - if all works is done (ie all workers have reported) it will result
358// in the 'Finished' state.
359// ***************************************************************************
360//
361G4int
362G4VoxelisationHelper::ReportWorkerIsDoneOptimising(unsigned int numVolumesOptimised)
363{
364 // Check that all are done and, if so, signal that optimisation is finished
365 G4int orderReporting;
366
367 G4AutoLock lock(statResultsMutex);
368 orderReporting = ++fNumberThreadsReporting;
369 fTotalNumberVolumesOptimised += numVolumesOptimised;
370
371 if( fVerboseParallel )
372 std::cout << "G4GeometryManager: the " << orderReporting << " worker has finished. "
373 << " Total volumes voxelised = " << fTotalNumberVolumesOptimised
374 << " out of " << fVolumesToOptimise.size() << G4endl;
375
376 if (fNumberThreadsReporting == G4Threading::GetNumberOfRunningWorkerThreads()
377 || fTotalNumberVolumesOptimised == fVolumesToOptimise.size()
378 )
379 {
380 const auto TotalThreads = G4Threading::GetNumberOfRunningWorkerThreads();
381 auto tid= G4Threading::G4GetThreadId();
382
383 // -- Some Checks
384 if( fTotalNumberVolumesOptimised != fVolumesToOptimise.size() )
385 {
387 errmsg << " [thread " << tid << " ] "
388 << " WARNING: Number of volumes 'voxelised' = " << fTotalNumberVolumesOptimised
389 << " is not equal to the total number requested " << fVolumesToOptimise.size() << " !! " << G4endl;
390 G4Exception("G4GeometryManager::ReportWorkerIsDoneOptimising", "G4GeomMgr0999",
391 FatalException, errmsg);
392 }
393
394 if( fNumberThreadsReporting > TotalThreads )
395 {
397 errmsg << " [thread " << tid << " ] "
398 << " WARNING: Number of threads 'reporting' = " << fNumberThreadsReporting
399 << " exceeds the total number of threads " << TotalThreads << " !! " << G4endl
400 << " *Missed* calling the method ConfigureParallelOptimisation() to reset. ";
401 G4Exception("G4GeometryManager::ReportWorkerIsDoneOptimising", "G4GeomMgr0999",
402 JustWarning, errmsg);
403 }
404 else
405 {
406 if( fTotalNumberVolumesOptimised == fVolumesToOptimise.size()
407 && ( fNumberThreadsReporting < TotalThreads ) )
408 {
410 errmsg << " [thread " << tid << " ] "
411 << " WARNING: All volumes optimised, yet only "
412 << fNumberThreadsReporting << " threads reported out of " << TotalThreads;
413 G4Exception("G4GeometryManager::ReportWorkerIsDoneOptimising", "G4GeomMgr099",
414 JustWarning, errmsg);
415 }
416 } // -- End of Checks
417
418 // Report the end
419 if( fNumberThreadsReporting <= G4Threading::GetNumberOfRunningWorkerThreads() )
420 {
421 // Close the work, and (if verbosity is on) report statistics
422 RecordOptimisationIsFinished(fVerboseParallel);
423 }
424 }
425
426 return orderReporting;
427}
428
429// ***************************************************************************
430// Inform that all work for parallel optimisation is finished.
431// ***************************************************************************
432//
433void G4VoxelisationHelper::RecordOptimisationIsFinished(G4bool verbose)
434{
435 if(verbose) // G4cout does not work!
436 {
437 std::cout << "** G4VoxelisationHelper: All voxel optimisation work is completed!"
438 << G4endl;
439 std::cout << " Total number of volumes optimised = "
440 << fTotalNumberVolumesOptimised
441 << " of " << fVolumesToOptimise.size() << " expected\n";
442 std::cout << " Number of workers reporting = "
443 << fNumberThreadsReporting
445 << " expected\n";
446 }
447 assert ( fTotalNumberVolumesOptimised == fVolumesToOptimise.size() );
448
449 fParallelVoxelOptimisationFinished = true;
450 // fParallelVoxelOptimisationRequested = false; // Maintain request for next one!
451 fParallelVoxelOptimisationUnderway = false; // It's no longer underway!
452}
453
454// ***************************************************************************
455// Ensure that all the work of voxelisation is done.
456// Can be called in GeometryManager methods or externally.
457// ***************************************************************************
458//
459void G4VoxelisationHelper::WaitForVoxelisationFinish(G4bool verbose)
460{
461 // Must wait until all workers are done ...
462 using namespace std::chrono_literals;
463 unsigned int trials = 0;
464 auto tid = G4Threading::G4GetThreadId();
465
466 std::ostream& out_stream = std::cout; // G4cout; does not work!
468 {
469 // Each thread must wait until all are done ...
470 std::this_thread::sleep_for(250ms);
471 ++trials;
472 }
473
474 if( verbose )
475 {
476 G4AutoLock lock(outputDbgMutex);
477 out_stream << G4endl
478 << "** UndertakeOptimisation done on tid= " << tid
479 << " after waiting for " << trials << " trials." << G4endl;
480 out_stream.flush();
481 }
482}
483
484
485// ***************************************************************************
486// Report about Voxel(isation) of a logical volume.
487// ***************************************************************************
488//
489void
491{
492 G4SmartVoxelHeader* head = logVolume->GetVoxelHeader();
493 if( head != nullptr )
494 {
495 os << "** Created optimisations for logical-volume '"
496 << std::setw(50) << logVolume->GetName() << "'" << G4endl
497 << "- Result VoxelInfo - START: " << " ptr= " << head << G4endl
498 << *head
499 << "- Result VoxelInfo - END. " << G4endl;
500 }
501 else
502 {
503 os << "** No optimisation for log-vol " << logVolume->GetName() << G4endl;
504 }
505 os << "*** Report Voxel Info: END " << G4endl;
506}
507
508// ***************************************************************************
509// Reports statistics on voxel optimisation when closing geometry.
510// ***************************************************************************
511//
512void
513G4VoxelisationHelper::ReportVoxelStats( std::vector<G4SmartVoxelStat> & stats,
514 G4double totalCpuTime,
515 std::ostream &os )
516{
517 os << "--------------------------------------------------------------------------------"
518 << G4endl;
519 os << "G4VoxelisationHelper::ReportVoxelStats -- Voxel Statistics"
520 << G4endl << G4endl;
521
522 //
523 // Get total memory use
524 //
525 G4int i, nStat = (G4int)stats.size();
526 G4long totalMemory = 0;
527
528 for( i=0; i<nStat; ++i ) { totalMemory += stats[i].GetMemoryUse(); }
529
530 os << " Total memory consumed for geometry optimisation: "
531 << totalMemory/1024 << " kByte" << G4endl;
532 os << " Total CPU time elapsed for geometry optimisation: "
533 << std::setprecision(4) << totalCpuTime << " seconds"
534 << std::setprecision(6) << G4endl;
535
536 //
537 // First list: sort by total CPU time
538 //
539 std::sort( stats.begin(), stats.end(),
540 [](const G4SmartVoxelStat& a, const G4SmartVoxelStat& b)
541 {
542 return a.GetTotalTime() > b.GetTotalTime();
543 } );
544
545 const G4int maxPrint = 20;
546 G4int nPrint = std::min ( nStat, maxPrint );
547
548 if (nPrint != 0)
549 {
550 os << "\n Voxelisation: top CPU users:" << G4endl;
551 os << " Percent Total CPU System CPU Memory Volume\n"
552 << " ------- ---------- ---------- -------- ----------"
553 << G4endl;
554 }
555
556 for(i=0; i<nPrint; ++i)
557 {
558 G4double total = stats[i].GetTotalTime();
559 G4double system = stats[i].GetSysTime();
560 G4double perc = 0.0;
561
562 if (system < 0) { system = 0.0; }
563 if ((total < 0) || (totalCpuTime < CLHEP::perMillion))
564 { total = 0; }
565 else
566 { perc = total*100/totalCpuTime; }
567
568 os << std::setprecision(2)
569 << std::setiosflags(std::ios::fixed|std::ios::right)
570 << std::setw(11) << perc
571 << std::setw(13) << total
572 << std::setw(13) << system
573 << std::setw(13) << (stats[i].GetMemoryUse()+512)/1024
574 << "k " << std::setiosflags(std::ios::left)
575 << stats[i].GetVolume()->GetName()
576 << std::resetiosflags(std::ios::floatfield|std::ios::adjustfield)
577 << std::setprecision(6)
578 << G4endl;
579 }
580
581 //
582 // Second list: sort by memory use
583 //
584 std::sort( stats.begin(), stats.end(),
585 [](const G4SmartVoxelStat& a, const G4SmartVoxelStat& b)
586 {
587 return a.GetMemoryUse() > b.GetMemoryUse();
588 } );
589
590 if (nPrint != 0)
591 {
592 os << "\n Voxelisation: top memory users:" << G4endl;
593 os << " Percent Memory Heads Nodes Pointers Total CPU Volume\n"
594 << " ------- -------- ------ ------ -------- ---------- ----------"
595 << G4endl;
596 }
597
598 for(i=0; i<nPrint; ++i)
599 {
600 G4long memory = stats[i].GetMemoryUse();
601 G4double totTime = stats[i].GetTotalTime();
602 if (totTime < 0) { totTime = 0.0; }
603
604 os << std::setprecision(2)
605 << std::setiosflags(std::ios::fixed|std::ios::right)
606 << std::setw(11) << G4double(memory*100)/G4double(totalMemory)
607 << std::setw(11) << memory/1024 << "k "
608 << std::setw( 9) << stats[i].GetNumberHeads()
609 << std::setw( 9) << stats[i].GetNumberNodes()
610 << std::setw(11) << stats[i].GetNumberPointers()
611 << std::setw(13) << totTime << " "
612 << std::setiosflags(std::ios::left)
613 << stats[i].GetVolume()->GetName()
614 << std::resetiosflags(std::ios::floatfield|std::ios::adjustfield)
615 << std::setprecision(6)
616 << G4endl;
617 }
618 os << "--------------------------------------------------------------------------------"
619 << G4endl << G4endl;
620}
621
623{
624 G4AutoLock lock(&statResultsMutex);
625 return fParallelVoxelOptimisationFinished;
626}
627
628// ***************************************************************************
629// Ensure that all logical volumes in list have a voxel-header.
630// ***************************************************************************
631//
633{
634 unsigned int numErrors = 0;
635 for ( const auto& logical : fVolumesToOptimise )
636 {
637 if( logical->GetVoxelHeader() == nullptr ) { ++numErrors; }
638 }
639 return numErrors;
640}
G4TemplateAutoLock< G4Mutex > G4AutoLock
@ JustWarning
@ FatalException
void G4Exception(const char *originOfException, const char *exceptionCode, G4ExceptionSeverity severity, const char *description)
std::ostringstream G4ExceptionDescription
#define G4MUTEX_INITIALIZER
std::mutex G4Mutex
double G4double
Definition G4Types.hh:83
long G4long
Definition G4Types.hh:87
bool G4bool
Definition G4Types.hh:86
int G4int
Definition G4Types.hh:85
#define G4endl
Definition G4ios.hh:67
G4GLOB_DLL std::ostream G4cout
static G4LogicalVolumeStore * GetInstance()
G4LogicalVolume represents a leaf node or unpositioned subtree in the geometry hierarchy....
void SetVoxelHeader(G4SmartVoxelHeader *pVoxel)
std::size_t GetNoDaughters() const
G4VPhysicalVolume * GetDaughter(const std::size_t i) const
G4bool IsToOptimise() const
const G4String & GetName() const
G4SmartVoxelHeader * GetVoxelHeader() const
G4SmartVoxelHeader represents a set of voxels, created by a single axis of virtual division....
G4SmartVoxelStat stores the information on the performance of the smart voxel optimisation algorithm ...
void Stop()
void Start()
G4double GetRealElapsed() const
Definition G4Timer.cc:113
virtual G4bool IsReplicated() const =0
virtual G4int GetRegularStructureId() const =0
void ReportVoxelInfo(G4LogicalVolume *logVolume, std::ostream &os)
static void ReportVoxelStats(std::vector< G4SmartVoxelStat > &stats, G4double totalCpuTime, std::ostream &os=G4cout)
void PrepareParallelOptimisation(G4bool allOpts, G4bool verbose)
G4int G4GetThreadId()
G4int GetNumberOfRunningWorkerThreads()
const G4int kMinVoxelVolumesLevel1
Definition voxeldefs.hh:39