Skip to content

Commit 931eda3

Browse files
sawenzelclaude
andcommitted
Draw transport random numbers from a per-thread generator
This adds o2::base::simRandom(), the generator used for seeding and drawing during transport, and moves the simulation to it. - simRandom() returns a generator installed for the calling thread, and gRandom otherwise, so the sequential simulation is unchanged. - Sub-event, per-track and stack seeding and the stepping draws in CPV, FDD, FT0, HMPID, PHOS, TPC, TRD and ZDC use it. - VMCSeederService seeds Geant4 from it directly, with the recipe of TG4RunManager::SetRandomSeed, which reads gRandom. - A unit test checks the default and that threads keep separate sequences. This prepares the simulation for multithreaded Geant4, where gRandom would be shared by all workers. https://its.cern.ch/jira/browse/O2-7305 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 42dae4a commit 931eda3

18 files changed

Lines changed: 185 additions & 35 deletions

File tree

‎Detectors/Base/CMakeLists.txt‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ o2_add_library(DetectorsBase
3434
src/GRPGeomHelper.cxx
3535
src/Stack.cxx
3636
src/VMCSeederService.cxx
37+
src/SimRandom.cxx
3738
src/GlobalParams.cxx
3839
src/O2Tessellated.cxx
3940
src/TGeoGeometryUtils.cxx
@@ -89,6 +90,13 @@ o2_add_test(
8990
PUBLIC_LINK_LIBRARIES O2::DetectorsBase
9091
LABELS detectorsbase)
9192

93+
o2_add_test(
94+
SimRandom
95+
SOURCES test/testSimRandom.cxx
96+
COMPONENT_NAME DetectorsBase
97+
PUBLIC_LINK_LIBRARIES O2::DetectorsBase
98+
LABELS detectorsbase)
99+
92100
o2_add_test(
93101
O2Tessellated
94102
SOURCES test/testO2Tessellated.cxx
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Copyright 2019-2026 CERN and copyright holders of ALICE O2.
2+
// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
3+
// All rights not expressly granted are reserved.
4+
//
5+
// This software is distributed under the terms of the GNU General Public
6+
// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
7+
//
8+
// In applying this license CERN does not waive the privileges and immunities
9+
// granted to it by virtue of its status as an Intergovernmental Organization
10+
// or submit itself to any jurisdiction.
11+
12+
#ifndef ALICEO2_BASE_SIMRANDOM_H
13+
#define ALICEO2_BASE_SIMRANDOM_H
14+
15+
#include <TRandom.h>
16+
17+
namespace o2::base
18+
{
19+
20+
/// The random number generator used during particle transport, for seeding and for drawing.
21+
/// Each thread can install its own generator; a thread that has none uses gRandom.
22+
TRandom* simRandom();
23+
24+
/// Install the transport generator of the calling thread (not owned); nullptr reverts to gRandom.
25+
void setSimRandom(TRandom* random);
26+
27+
} // namespace o2::base
28+
29+
#endif

‎Detectors/Base/include/DetectorsBase/VMCSeederService.h‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@
1313
#define ALICEO2_VMC_SEEDERSERVICE_H
1414

1515
class TVirtualMC;
16+
class TRandom;
1617

18+
#include <atomic>
1719
#include <functional>
1820

1921
namespace o2
@@ -33,19 +35,19 @@ class VMCSeederService
3335
return inst;
3436
}
3537

36-
void setSeed() const; // will propagate seed to the VMC engines
38+
void setSeed() const; // will propagate the seed of o2::base::simRandom() to the VMC engine of this thread
3739

3840
/// how often a seed was propagated; lets callers detect a silent no-op
3941
unsigned long long getSeedCount() const { return mSeedCount; }
4042

41-
typedef std::function<void()> SeederFcn;
43+
typedef std::function<void(TRandom*)> SeederFcn;
4244

4345
private:
4446
VMCSeederService();
4547
void initSeederFunction(TVirtualMC const*);
4648

47-
SeederFcn mSeederFcn; // the just-in-time compiled function talking to the VMC engines
48-
mutable unsigned long long mSeedCount{0}; // number of setSeed() calls
49+
SeederFcn mSeederFcn; // the just-in-time compiled function talking to the VMC engines
50+
mutable std::atomic<unsigned long long> mSeedCount{0}; // number of setSeed() calls
4951
};
5052

5153
} // namespace base

‎Detectors/Base/src/SimRandom.cxx‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright 2019-2026 CERN and copyright holders of ALICE O2.
2+
// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
3+
// All rights not expressly granted are reserved.
4+
//
5+
// This software is distributed under the terms of the GNU General Public
6+
// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
7+
//
8+
// In applying this license CERN does not waive the privileges and immunities
9+
// granted to it by virtue of its status as an Intergovernmental Organization
10+
// or submit itself to any jurisdiction.
11+
12+
#include "DetectorsBase/SimRandom.h"
13+
14+
namespace
15+
{
16+
thread_local TRandom* sThreadSimRandom = nullptr;
17+
}
18+
19+
namespace o2::base
20+
{
21+
22+
TRandom* simRandom() { return sThreadSimRandom ? sThreadSimRandom : gRandom; }
23+
24+
void setSimRandom(TRandom* random) { sThreadSimRandom = random; }
25+
26+
} // namespace o2::base

‎Detectors/Base/src/Stack.cxx‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
#include <cassert>
3737
#include <cstddef> // for NULL
3838
#include <cmath>
39+
#include "DetectorsBase/SimRandom.h"
3940

4041
using std::cout;
4142
using std::endl;
@@ -361,7 +362,7 @@ TParticle* Stack::PopNextTrack(Int_t& iTrack)
361362
auto hash = getHash(mCurrentParticle);
362363
// LOG(info) << "SEEDING NEW TRACK USING HASH" << hash;
363364
// init seed per track
364-
gRandom->SetSeed(hash);
365+
o2::base::simRandom()->SetSeed(hash);
365366
o2::base::VMCSeederService::instance().setSeed();
366367
// NOTE: THE BETTER PLACE WOULD BE IN PRETRACK HOOK BUT THIS DOES NOT SEEM TO WORK
367368
// WORKS ONLY WITH G3 SINCE G4 DOES NOT CALL THIS FUNCTION

‎Detectors/Base/src/VMCSeederService.cxx‎

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,24 +10,31 @@
1010
// or submit itself to any jurisdiction.
1111

1212
#include "DetectorsBase/VMCSeederService.h"
13+
#include "DetectorsBase/SimRandom.h"
1314
#include "TVirtualMC.h"
1415
#include <fairlogger/Logger.h> // for FairLogger
1516
#include <CommonUtils/ConfigurationMacroHelper.h> // for ROOT JIT helpers
17+
#include <TInterpreter.h>
18+
#include <TRandom.h>
1619

1720
using namespace o2::base;
1821

1922
void VMCSeederService::initSeederFunction(TVirtualMC const* vmc)
2023
{
2124
if (strcmp(vmc->GetName(), "TGeant3TGeo") == 0) {
2225
// Geant3 doesn't need anything special in our context
23-
mSeederFcn = []() {};
26+
mSeederFcn = [](TRandom*) {};
2427
} else if (strcmp(vmc->GetName(), "TGeant4") == 0) {
25-
// dynamically get access to the Geant4_VMC seeding function (without this function linking against Geant4)
26-
std::string G4func("std::function<void()> G4func() { gSystem->Load(\"libgeant4vmc\"); return [](){ ((TGeant4*)TVirtualMC::GetMC())->SetRandomSeed(); };}");
27-
mSeederFcn = o2::conf::JITAndEvalFunction<SeederFcn>(G4func, "G4func()", "std::function<void()>", "VMCSEEDERFUNC123");
28+
// Seed the Geant4 engine of the calling thread from the given generator, with the same recipe as
29+
// TG4RunManager::SetRandomSeed (which always reads gRandom). JIT-compiled to avoid linking against Geant4.
30+
if (!gInterpreter->Declare("#include <CLHEP/Random/Random.h>")) {
31+
LOG(fatal) << "CLHEP/Random/Random.h not found on ROOT_INCLUDE_PATH; cannot seed Geant4";
32+
}
33+
std::string G4func("std::function<void(TRandom*)> G4func() { gSystem->Load(\"libgeant4vmc\"); return [](TRandom* r){ long seeds[10]; seeds[0] = r->GetSeed(); seeds[1] = r->GetSeed(); seeds[2] = 0; CLHEP::HepRandom::setTheSeeds(seeds); };}");
34+
mSeederFcn = o2::conf::JITAndEvalFunction<SeederFcn>(G4func, "G4func()", "std::function<void(TRandom*)>", "VMCSEEDERFUNC123");
2835
} else {
2936
LOG(warn) << "Unknown VMC engine or unimplemented VMC seeding function";
30-
mSeederFcn = []() {};
37+
mSeederFcn = [](TRandom*) {};
3138
}
3239
}
3340

@@ -45,10 +52,11 @@ VMCSeederService::VMCSeederService()
4552

4653
void VMCSeederService::setSeed() const
4754
{
48-
// Make sure gRandom is sufficiently well initialized
55+
// Make sure the generator is sufficiently well initialized
4956
// by calling Rndm() once.
50-
// This is ok since in any case gRandom->SetSeed(seed); gRandom->GetSeed() != seed;
51-
gRandom->Rndm();
52-
mSeederFcn();
57+
// This is ok since in any case r->SetSeed(seed); r->GetSeed() != seed;
58+
auto r = simRandom();
59+
r->Rndm();
60+
mSeederFcn(r);
5361
++mSeedCount;
5462
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Copyright 2019-2026 CERN and copyright holders of ALICE O2.
2+
// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
3+
// All rights not expressly granted are reserved.
4+
//
5+
// This software is distributed under the terms of the GNU General Public
6+
// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
7+
//
8+
// In applying this license CERN does not waive the privileges and immunities
9+
// granted to it by virtue of its status as an Intergovernmental Organization
10+
// or submit itself to any jurisdiction.
11+
12+
/// \file testSimRandom.cxx
13+
/// \author Sandro Wenzel (CERN)
14+
/// \brief Checks that o2::base::simRandom() defaults to gRandom and is per thread
15+
16+
#define BOOST_TEST_MODULE Test SimRandom
17+
#define BOOST_TEST_MAIN
18+
#define BOOST_TEST_DYN_LINK
19+
#include <boost/test/unit_test.hpp>
20+
21+
#include "DetectorsBase/SimRandom.h"
22+
#include <TRandom.h>
23+
#include <TRandom3.h>
24+
#include <thread>
25+
#include <vector>
26+
27+
BOOST_AUTO_TEST_CASE(defaults_to_gRandom)
28+
{
29+
BOOST_CHECK(o2::base::simRandom() == gRandom);
30+
TRandom3 own(1);
31+
o2::base::setSimRandom(&own);
32+
BOOST_CHECK(o2::base::simRandom() == &own);
33+
o2::base::setSimRandom(nullptr);
34+
BOOST_CHECK(o2::base::simRandom() == gRandom);
35+
}
36+
37+
BOOST_AUTO_TEST_CASE(per_thread_generators)
38+
{
39+
// each thread draws from its own generator; equal seeds give equal sequences, whatever the other threads do
40+
constexpr int nthreads = 8;
41+
constexpr int ndraws = 1000;
42+
std::vector<std::vector<double>> draws(nthreads);
43+
std::vector<std::thread> threads;
44+
for (int t = 0; t < nthreads; ++t) {
45+
threads.emplace_back([t, &draws]() {
46+
TRandom3 own(t % 2 + 1);
47+
o2::base::setSimRandom(&own);
48+
for (int i = 0; i < ndraws; ++i) {
49+
draws[t].push_back(o2::base::simRandom()->Rndm());
50+
}
51+
o2::base::setSimRandom(nullptr);
52+
});
53+
}
54+
for (auto& th : threads) {
55+
th.join();
56+
}
57+
for (int t = 2; t < nthreads; ++t) {
58+
BOOST_CHECK(draws[t] == draws[t % 2]);
59+
}
60+
BOOST_CHECK(draws[0] != draws[1]);
61+
// the main thread was never changed
62+
BOOST_CHECK(o2::base::simRandom() == gRandom);
63+
}

‎Detectors/CPV/simulation/src/Detector.cxx‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232

3333
#include <boost/algorithm/string/predicate.hpp>
3434
#include <boost/range/irange.hpp>
35+
#include "DetectorsBase/SimRandom.h"
3536

3637
using namespace o2::cpv;
3738

@@ -171,7 +172,7 @@ Bool_t Detector::ProcessHits(FairVolume* v)
171172
float dZY = pZ / pNorm * cpvparam.mCPVGasThickness;
172173
float dXY = pX / pNorm * cpvparam.mCPVGasThickness;
173174
float rnor1 = 0., rnor2 = 0.;
174-
gRandom->Rannor(rnor1, rnor2);
175+
o2::base::simRandom()->Rannor(rnor1, rnor2);
175176
float eloss = cpvparam.mdEdx * (1 + cpvparam.mDetR * rnor1) *
176177
TMath::Sqrt((1. + (pow(dZY, 2) + pow(dXY, 2)) / pow(cpvparam.mCPVGasThickness, 2)));
177178
float zhit1 = hitZ + cpvparam.mPadSizeZ * cpvparam.mnCellZ / 2 - dZY / 2;

‎Detectors/FIT/FDD/simulation/src/Detector.cxx‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
#include "FairRootManager.h"
3434
#include "FairVolume.h"
3535
#include "FairRootManager.h"
36+
#include "DetectorsBase/SimRandom.h"
3637

3738
using namespace o2::fdd;
3839
using o2::fdd::Geometry;
@@ -149,7 +150,7 @@ Bool_t Detector::ProcessHits(FairVolume* vol)
149150

150151
Float_t destep_ad = fMC->Edep();
151152
Int_t nPhotonsInStep_ad = Int_t(fFDDLightYield * destep_ad);
152-
nPhotonsInStep_ad = gRandom->Poisson(nPhotonsInStep_ad);
153+
nPhotonsInStep_ad = o2::base::simRandom()->Poisson(nPhotonsInStep_ad);
153154

154155
mElossAD += destep_ad;
155156

‎Detectors/FIT/FT0/simulation/src/Detector.cxx‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
#include "FT0Base/Geometry.h"
3838
#include "FT0Simulation/Detector.h"
3939
#include "DetectorsBase/Stack.h"
40+
#include "DetectorsBase/SimRandom.h"
4041

4142
using namespace o2::ft0;
4243
using o2::ft0::Geometry;
@@ -1373,7 +1374,7 @@ void Detector::FillOtherOptProperties()
13731374
Bool_t Detector::RegisterPhotoE(float energy)
13741375
{
13751376
float eff = mPMTeff->Eval(energy);
1376-
float p = gRandom->Rndm();
1377+
float p = o2::base::simRandom()->Rndm();
13771378
if (p > eff) {
13781379
return kFALSE;
13791380
}

0 commit comments

Comments
 (0)