From 931eda386602f508edf8176c326ad8618bd291d5 Mon Sep 17 00:00:00 2001 From: Sandro Wenzel Date: Thu, 5 Nov 2026 20:52:05 +0100 Subject: [PATCH 1/3] 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 --- Detectors/Base/CMakeLists.txt | 8 +++ .../Base/include/DetectorsBase/SimRandom.h | 29 +++++++++ .../include/DetectorsBase/VMCSeederService.h | 10 +-- Detectors/Base/src/SimRandom.cxx | 26 ++++++++ Detectors/Base/src/Stack.cxx | 3 +- Detectors/Base/src/VMCSeederService.cxx | 26 +++++--- Detectors/Base/test/testSimRandom.cxx | 63 +++++++++++++++++++ Detectors/CPV/simulation/src/Detector.cxx | 3 +- Detectors/FIT/FDD/simulation/src/Detector.cxx | 3 +- Detectors/FIT/FT0/simulation/src/Detector.cxx | 3 +- Detectors/HMPID/simulation/src/Detector.cxx | 11 ++-- Detectors/PHOS/simulation/src/Detector.cxx | 3 +- Detectors/TPC/simulation/src/Detector.cxx | 11 ++-- Detectors/TRD/simulation/src/Detector.cxx | 5 +- Detectors/TRD/simulation/src/TRsim.cxx | 5 +- Detectors/ZDC/simulation/src/Detector.cxx | 3 +- Steer/src/O2MCApplication.cxx | 5 +- run/O2SimDevice.cxx | 3 +- 18 files changed, 185 insertions(+), 35 deletions(-) create mode 100644 Detectors/Base/include/DetectorsBase/SimRandom.h create mode 100644 Detectors/Base/src/SimRandom.cxx create mode 100644 Detectors/Base/test/testSimRandom.cxx diff --git a/Detectors/Base/CMakeLists.txt b/Detectors/Base/CMakeLists.txt index d4fe4fd11a26c..2025c6855c4db 100644 --- a/Detectors/Base/CMakeLists.txt +++ b/Detectors/Base/CMakeLists.txt @@ -34,6 +34,7 @@ o2_add_library(DetectorsBase src/GRPGeomHelper.cxx src/Stack.cxx src/VMCSeederService.cxx + src/SimRandom.cxx src/GlobalParams.cxx src/O2Tessellated.cxx src/TGeoGeometryUtils.cxx @@ -89,6 +90,13 @@ o2_add_test( PUBLIC_LINK_LIBRARIES O2::DetectorsBase LABELS detectorsbase) +o2_add_test( + SimRandom + SOURCES test/testSimRandom.cxx + COMPONENT_NAME DetectorsBase + PUBLIC_LINK_LIBRARIES O2::DetectorsBase + LABELS detectorsbase) + o2_add_test( O2Tessellated SOURCES test/testO2Tessellated.cxx diff --git a/Detectors/Base/include/DetectorsBase/SimRandom.h b/Detectors/Base/include/DetectorsBase/SimRandom.h new file mode 100644 index 0000000000000..051d22d7cf591 --- /dev/null +++ b/Detectors/Base/include/DetectorsBase/SimRandom.h @@ -0,0 +1,29 @@ +// Copyright 2019-2026 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifndef ALICEO2_BASE_SIMRANDOM_H +#define ALICEO2_BASE_SIMRANDOM_H + +#include + +namespace o2::base +{ + +/// The random number generator used during particle transport, for seeding and for drawing. +/// Each thread can install its own generator; a thread that has none uses gRandom. +TRandom* simRandom(); + +/// Install the transport generator of the calling thread (not owned); nullptr reverts to gRandom. +void setSimRandom(TRandom* random); + +} // namespace o2::base + +#endif diff --git a/Detectors/Base/include/DetectorsBase/VMCSeederService.h b/Detectors/Base/include/DetectorsBase/VMCSeederService.h index 5f8f70f48840f..6ea0ed001c52a 100644 --- a/Detectors/Base/include/DetectorsBase/VMCSeederService.h +++ b/Detectors/Base/include/DetectorsBase/VMCSeederService.h @@ -13,7 +13,9 @@ #define ALICEO2_VMC_SEEDERSERVICE_H class TVirtualMC; +class TRandom; +#include #include namespace o2 @@ -33,19 +35,19 @@ class VMCSeederService return inst; } - void setSeed() const; // will propagate seed to the VMC engines + void setSeed() const; // will propagate the seed of o2::base::simRandom() to the VMC engine of this thread /// how often a seed was propagated; lets callers detect a silent no-op unsigned long long getSeedCount() const { return mSeedCount; } - typedef std::function SeederFcn; + typedef std::function SeederFcn; private: VMCSeederService(); void initSeederFunction(TVirtualMC const*); - SeederFcn mSeederFcn; // the just-in-time compiled function talking to the VMC engines - mutable unsigned long long mSeedCount{0}; // number of setSeed() calls + SeederFcn mSeederFcn; // the just-in-time compiled function talking to the VMC engines + mutable std::atomic mSeedCount{0}; // number of setSeed() calls }; } // namespace base diff --git a/Detectors/Base/src/SimRandom.cxx b/Detectors/Base/src/SimRandom.cxx new file mode 100644 index 0000000000000..f6c61ab79d895 --- /dev/null +++ b/Detectors/Base/src/SimRandom.cxx @@ -0,0 +1,26 @@ +// Copyright 2019-2026 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#include "DetectorsBase/SimRandom.h" + +namespace +{ +thread_local TRandom* sThreadSimRandom = nullptr; +} + +namespace o2::base +{ + +TRandom* simRandom() { return sThreadSimRandom ? sThreadSimRandom : gRandom; } + +void setSimRandom(TRandom* random) { sThreadSimRandom = random; } + +} // namespace o2::base diff --git a/Detectors/Base/src/Stack.cxx b/Detectors/Base/src/Stack.cxx index a215ae27facc7..e7caca2982318 100644 --- a/Detectors/Base/src/Stack.cxx +++ b/Detectors/Base/src/Stack.cxx @@ -36,6 +36,7 @@ #include #include // for NULL #include +#include "DetectorsBase/SimRandom.h" using std::cout; using std::endl; @@ -361,7 +362,7 @@ TParticle* Stack::PopNextTrack(Int_t& iTrack) auto hash = getHash(mCurrentParticle); // LOG(info) << "SEEDING NEW TRACK USING HASH" << hash; // init seed per track - gRandom->SetSeed(hash); + o2::base::simRandom()->SetSeed(hash); o2::base::VMCSeederService::instance().setSeed(); // NOTE: THE BETTER PLACE WOULD BE IN PRETRACK HOOK BUT THIS DOES NOT SEEM TO WORK // WORKS ONLY WITH G3 SINCE G4 DOES NOT CALL THIS FUNCTION diff --git a/Detectors/Base/src/VMCSeederService.cxx b/Detectors/Base/src/VMCSeederService.cxx index 8fc36d9074fab..2773a50515ffa 100644 --- a/Detectors/Base/src/VMCSeederService.cxx +++ b/Detectors/Base/src/VMCSeederService.cxx @@ -10,9 +10,12 @@ // or submit itself to any jurisdiction. #include "DetectorsBase/VMCSeederService.h" +#include "DetectorsBase/SimRandom.h" #include "TVirtualMC.h" #include // for FairLogger #include // for ROOT JIT helpers +#include +#include using namespace o2::base; @@ -20,14 +23,18 @@ void VMCSeederService::initSeederFunction(TVirtualMC const* vmc) { if (strcmp(vmc->GetName(), "TGeant3TGeo") == 0) { // Geant3 doesn't need anything special in our context - mSeederFcn = []() {}; + mSeederFcn = [](TRandom*) {}; } else if (strcmp(vmc->GetName(), "TGeant4") == 0) { - // dynamically get access to the Geant4_VMC seeding function (without this function linking against Geant4) - std::string G4func("std::function G4func() { gSystem->Load(\"libgeant4vmc\"); return [](){ ((TGeant4*)TVirtualMC::GetMC())->SetRandomSeed(); };}"); - mSeederFcn = o2::conf::JITAndEvalFunction(G4func, "G4func()", "std::function", "VMCSEEDERFUNC123"); + // Seed the Geant4 engine of the calling thread from the given generator, with the same recipe as + // TG4RunManager::SetRandomSeed (which always reads gRandom). JIT-compiled to avoid linking against Geant4. + if (!gInterpreter->Declare("#include ")) { + LOG(fatal) << "CLHEP/Random/Random.h not found on ROOT_INCLUDE_PATH; cannot seed Geant4"; + } + std::string G4func("std::function 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); };}"); + mSeederFcn = o2::conf::JITAndEvalFunction(G4func, "G4func()", "std::function", "VMCSEEDERFUNC123"); } else { LOG(warn) << "Unknown VMC engine or unimplemented VMC seeding function"; - mSeederFcn = []() {}; + mSeederFcn = [](TRandom*) {}; } } @@ -45,10 +52,11 @@ VMCSeederService::VMCSeederService() void VMCSeederService::setSeed() const { - // Make sure gRandom is sufficiently well initialized + // Make sure the generator is sufficiently well initialized // by calling Rndm() once. - // This is ok since in any case gRandom->SetSeed(seed); gRandom->GetSeed() != seed; - gRandom->Rndm(); - mSeederFcn(); + // This is ok since in any case r->SetSeed(seed); r->GetSeed() != seed; + auto r = simRandom(); + r->Rndm(); + mSeederFcn(r); ++mSeedCount; } diff --git a/Detectors/Base/test/testSimRandom.cxx b/Detectors/Base/test/testSimRandom.cxx new file mode 100644 index 0000000000000..1e11fc809b868 --- /dev/null +++ b/Detectors/Base/test/testSimRandom.cxx @@ -0,0 +1,63 @@ +// Copyright 2019-2026 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file testSimRandom.cxx +/// \author Sandro Wenzel (CERN) +/// \brief Checks that o2::base::simRandom() defaults to gRandom and is per thread + +#define BOOST_TEST_MODULE Test SimRandom +#define BOOST_TEST_MAIN +#define BOOST_TEST_DYN_LINK +#include + +#include "DetectorsBase/SimRandom.h" +#include +#include +#include +#include + +BOOST_AUTO_TEST_CASE(defaults_to_gRandom) +{ + BOOST_CHECK(o2::base::simRandom() == gRandom); + TRandom3 own(1); + o2::base::setSimRandom(&own); + BOOST_CHECK(o2::base::simRandom() == &own); + o2::base::setSimRandom(nullptr); + BOOST_CHECK(o2::base::simRandom() == gRandom); +} + +BOOST_AUTO_TEST_CASE(per_thread_generators) +{ + // each thread draws from its own generator; equal seeds give equal sequences, whatever the other threads do + constexpr int nthreads = 8; + constexpr int ndraws = 1000; + std::vector> draws(nthreads); + std::vector threads; + for (int t = 0; t < nthreads; ++t) { + threads.emplace_back([t, &draws]() { + TRandom3 own(t % 2 + 1); + o2::base::setSimRandom(&own); + for (int i = 0; i < ndraws; ++i) { + draws[t].push_back(o2::base::simRandom()->Rndm()); + } + o2::base::setSimRandom(nullptr); + }); + } + for (auto& th : threads) { + th.join(); + } + for (int t = 2; t < nthreads; ++t) { + BOOST_CHECK(draws[t] == draws[t % 2]); + } + BOOST_CHECK(draws[0] != draws[1]); + // the main thread was never changed + BOOST_CHECK(o2::base::simRandom() == gRandom); +} diff --git a/Detectors/CPV/simulation/src/Detector.cxx b/Detectors/CPV/simulation/src/Detector.cxx index 4191fbc574f95..f25a19e84d7aa 100644 --- a/Detectors/CPV/simulation/src/Detector.cxx +++ b/Detectors/CPV/simulation/src/Detector.cxx @@ -32,6 +32,7 @@ #include #include +#include "DetectorsBase/SimRandom.h" using namespace o2::cpv; @@ -171,7 +172,7 @@ Bool_t Detector::ProcessHits(FairVolume* v) float dZY = pZ / pNorm * cpvparam.mCPVGasThickness; float dXY = pX / pNorm * cpvparam.mCPVGasThickness; float rnor1 = 0., rnor2 = 0.; - gRandom->Rannor(rnor1, rnor2); + o2::base::simRandom()->Rannor(rnor1, rnor2); float eloss = cpvparam.mdEdx * (1 + cpvparam.mDetR * rnor1) * TMath::Sqrt((1. + (pow(dZY, 2) + pow(dXY, 2)) / pow(cpvparam.mCPVGasThickness, 2))); float zhit1 = hitZ + cpvparam.mPadSizeZ * cpvparam.mnCellZ / 2 - dZY / 2; diff --git a/Detectors/FIT/FDD/simulation/src/Detector.cxx b/Detectors/FIT/FDD/simulation/src/Detector.cxx index e97c3fb63dae9..0d54b0d9776c7 100644 --- a/Detectors/FIT/FDD/simulation/src/Detector.cxx +++ b/Detectors/FIT/FDD/simulation/src/Detector.cxx @@ -33,6 +33,7 @@ #include "FairRootManager.h" #include "FairVolume.h" #include "FairRootManager.h" +#include "DetectorsBase/SimRandom.h" using namespace o2::fdd; using o2::fdd::Geometry; @@ -149,7 +150,7 @@ Bool_t Detector::ProcessHits(FairVolume* vol) Float_t destep_ad = fMC->Edep(); Int_t nPhotonsInStep_ad = Int_t(fFDDLightYield * destep_ad); - nPhotonsInStep_ad = gRandom->Poisson(nPhotonsInStep_ad); + nPhotonsInStep_ad = o2::base::simRandom()->Poisson(nPhotonsInStep_ad); mElossAD += destep_ad; diff --git a/Detectors/FIT/FT0/simulation/src/Detector.cxx b/Detectors/FIT/FT0/simulation/src/Detector.cxx index 89bd72e5336b8..de7e72e5f1709 100644 --- a/Detectors/FIT/FT0/simulation/src/Detector.cxx +++ b/Detectors/FIT/FT0/simulation/src/Detector.cxx @@ -37,6 +37,7 @@ #include "FT0Base/Geometry.h" #include "FT0Simulation/Detector.h" #include "DetectorsBase/Stack.h" +#include "DetectorsBase/SimRandom.h" using namespace o2::ft0; using o2::ft0::Geometry; @@ -1373,7 +1374,7 @@ void Detector::FillOtherOptProperties() Bool_t Detector::RegisterPhotoE(float energy) { float eff = mPMTeff->Eval(energy); - float p = gRandom->Rndm(); + float p = o2::base::simRandom()->Rndm(); if (p > eff) { return kFALSE; } diff --git a/Detectors/HMPID/simulation/src/Detector.cxx b/Detectors/HMPID/simulation/src/Detector.cxx index 56cfcf934f7ee..8780a93afb56c 100644 --- a/Detectors/HMPID/simulation/src/Detector.cxx +++ b/Detectors/HMPID/simulation/src/Detector.cxx @@ -33,6 +33,7 @@ #include "DetectorsBase/MaterialManager.h" #include "Framework/Logger.h" +#include "DetectorsBase/SimRandom.h" namespace o2 { @@ -185,14 +186,14 @@ void Detector::GenFee(Float_t qtot) // eloss=0 means photon so only pulse height distribution is to be analysed. TLorentzVector x4; fMC->TrackPosition(x4); - Int_t iNphotons = fMC->GetRandom()->Poisson(0.02 * qtot); // # of feedback photons is proportional to the charge of hit + Int_t iNphotons = o2::base::simRandom()->Poisson(0.02 * qtot); // # of feedback photons is proportional to the charge of hit // AliDebug(1,Form("N photons=%i",iNphotons)); Int_t j; Float_t cthf, phif, enfp = 0, sthf, e1[3], e2[3], e3[3], vmod, uswop, dir[3], phi, pol[3], mom[4]; // Generate photons for (Int_t i = 0; i < iNphotons; i++) { // feedbacks loop Double_t ranf[2]; - fMC->GetRandom()->RndmArray(2, ranf); // Sample direction + o2::base::simRandom()->RndmArray(2, ranf); // Sample direction cthf = ranf[0] * 2 - 1.0; if (cthf < 0) { continue; @@ -200,7 +201,7 @@ void Detector::GenFee(Float_t qtot) sthf = TMath::Sqrt((1. - cthf) * (1. + cthf)); phif = ranf[1] * 2 * TMath::Pi(); - if (Double_t randomNumber = fMC->GetRandom()->Rndm() <= 0.57) { + if (Double_t randomNumber = o2::base::simRandom()->Rndm() <= 0.57) { enfp = 7.5e-9; } else if (randomNumber <= 0.7) { enfp = 6.4e-9; @@ -268,7 +269,7 @@ void Detector::GenFee(Float_t qtot) e2[j] *= vmod; } - phi = fMC->GetRandom()->Rndm() * 2 * TMath::Pi(); + phi = o2::base::simRandom()->Rndm() * 2 * TMath::Pi(); for (j = 0; j < 3; j++) { pol[j] = e1[j] * TMath::Sin(phi) + e2[j] * TMath::Cos(phi); } @@ -293,7 +294,7 @@ Bool_t Detector::IsLostByFresnel() Double_t localTc = localMom[0] * localMom[0] + localMom[2] * localMom[2]; Double_t localTheta = TMath::ATan2(TMath::Sqrt(localTc), localMom[1]); Double_t cotheta = TMath::Abs(TMath::Cos(localTheta)); - if (fMC->GetRandom()->Rndm() < Fresnel(p4.E() * 1e9, cotheta, 1)) { + if (o2::base::simRandom()->Rndm() < Fresnel(p4.E() * 1e9, cotheta, 1)) { // AliDebug(1,"Photon lost"); return kTRUE; } else { diff --git a/Detectors/PHOS/simulation/src/Detector.cxx b/Detectors/PHOS/simulation/src/Detector.cxx index 5b537db1f2c0e..92f42700fb60e 100644 --- a/Detectors/PHOS/simulation/src/Detector.cxx +++ b/Detectors/PHOS/simulation/src/Detector.cxx @@ -32,6 +32,7 @@ #include #include +#include "DetectorsBase/SimRandom.h" using namespace o2::phos; @@ -107,7 +108,7 @@ void Detector::FinishEvent() first = mHits->begin(); last = mHits->end(); while (first != last) { - float light = gRandom->Poisson(first->GetEnergyLoss() * o2::phos::PHOSSimParams::Instance().mLightYieldPerGeV); + float light = o2::base::simRandom()->Poisson(first->GetEnergyLoss() * o2::phos::PHOSSimParams::Instance().mLightYieldPerGeV); first->SetEnergyLoss(light / o2::phos::PHOSSimParams::Instance().mLightYieldPerGeV); first++; } diff --git a/Detectors/TPC/simulation/src/Detector.cxx b/Detectors/TPC/simulation/src/Detector.cxx index b69a19a86bb79..fcb546abb63b3 100644 --- a/Detectors/TPC/simulation/src/Detector.cxx +++ b/Detectors/TPC/simulation/src/Detector.cxx @@ -53,6 +53,7 @@ #include #include +#include "DetectorsBase/SimRandom.h" using std::cout; using std::endl; @@ -144,7 +145,7 @@ Bool_t Detector::ProcessHits(FairVolume* vol) // random shift to avoid binning effects), which was tuned for GEANT4, see // https://indico.cern.ch/event/316891/contributions/732168/ - const double rnd = fMC->GetRandom()->Rndm(); + const double rnd = o2::base::simRandom()->Rndm(); fMC->SetMaxStep(0.2 + (2. * rnd - 1.) * 0.05); // 2 mm +- rndm*0.5mm step // ===| check active sector |================================================= @@ -243,7 +244,7 @@ Bool_t Detector::ProcessHits(FairVolume* vol) // ---| mean number of collisions and random for this event |--- const double meanNcoll = stepSize * trackCharge * trackCharge * primaryElectronsPerCM; - const int nColl = static_cast(fMC->GetRandom()->Poisson(meanNcoll)); + const int nColl = static_cast(o2::base::simRandom()->Poisson(meanNcoll)); // Variables needed to generate random powerlaw distributed energy loss const double alpha_p1 = 1. - gasParam.Exp; // NA49/G3 value @@ -259,7 +260,7 @@ Bool_t Detector::ProcessHits(FairVolume* vol) // P(eDep) ~ k * edep^-gasParam.getExp() // eMin(~I) < eDep < eMax(300 electrons) // k fixed so that Int_Emin^EMax P(Edep) = 1. - const double rndm = fMC->GetRandom()->Rndm(); + const double rndm = o2::base::simRandom()->Rndm(); const double eDep = TMath::Power((kMax - kMin) * rndm + kMin, oneOverAlpha_p1); int nel_step = static_cast(((eDep - eMin) / wIon) + 1); nel_step = TMath::Min(nel_step, gasParam.MaxElePerStep); // 300 electrons corresponds to 10 keV @@ -3255,8 +3256,8 @@ Double_t Detector::Gamma(Double_t k) Double_t x; Double_t y = -1.; while (1) { - Double_t nu1 = gRandom->Rndm(); - Double_t nu2 = gRandom->Rndm(); + Double_t nu1 = o2::base::simRandom()->Rndm(); + Double_t nu2 = o2::base::simRandom()->Rndm(); Double_t w1 = c1 + TMath::Log(nu1); Double_t w2 = c2 + TMath::Log(nu2); y = n * (b1 * w2 - b2 * w1); diff --git a/Detectors/TRD/simulation/src/Detector.cxx b/Detectors/TRD/simulation/src/Detector.cxx index 71763f5083bc6..5c951e0abbc25 100644 --- a/Detectors/TRD/simulation/src/Detector.cxx +++ b/Detectors/TRD/simulation/src/Detector.cxx @@ -29,6 +29,7 @@ #include #include #include +#include "DetectorsBase/SimRandom.h" using namespace o2::trd; using namespace o2::trd::constants; @@ -277,7 +278,7 @@ void Detector::createTRhit(int det) double muMy = mTR->getMuMy(energyMeV); sigma = muMy * mFoilDensity; if (sigma > 0.0) { - absLength = gRandom->Exp(1.0 / sigma); + absLength = o2::base::simRandom()->Exp(1.0 / sigma); if (absLength < Geometry::myThick()) { continue; } @@ -300,7 +301,7 @@ void Detector::createTRhit(int det) // The distance after which the energy of the TR photon // is deposited. if (sigma > 0.0) { - absLength = gRandom->Exp(1.0 / sigma); + absLength = o2::base::simRandom()->Exp(1.0 / sigma); if (absLength > (Geometry::drThick() + Geometry::amThick())) { continue; } diff --git a/Detectors/TRD/simulation/src/TRsim.cxx b/Detectors/TRD/simulation/src/TRsim.cxx index a7d4ac290e74a..7e19262a89de6 100644 --- a/Detectors/TRD/simulation/src/TRsim.cxx +++ b/Detectors/TRD/simulation/src/TRsim.cxx @@ -49,6 +49,7 @@ #include "TRDSimulation/TRsim.h" #include "FairModule.h" +#include "DetectorsBase/SimRandom.h" using namespace o2::trd; @@ -228,7 +229,7 @@ int TRsim::calculatePhotons(float p, float mass, std::vector& ePhoton) // (binsize corr.) float nTr = mSpBinWidth * mSpectrum->Integral(); // Number of TR photons from Poisson distribution with mean - int nPhCand = gRandom->Poisson(nTr); + int nPhCand = o2::base::simRandom()->Poisson(nTr); // Link the MC stack and get info about parent electron TVirtualMCStack* stack = TVirtualMC::GetMC()->GetStack(); @@ -244,7 +245,7 @@ int TRsim::calculatePhotons(float p, float mass, std::vector& ePhoton) TVirtualMC::GetMC()->TrackPosition(x, y, z); double t = TVirtualMC::GetMC()->TrackTime(); for (int iPhoton = 0; iPhoton < nPhCand; ++iPhoton) { - double e = mSpectrum->GetRandom(); // Energy of the TR photon + double e = mSpectrum->GetRandom(o2::base::simRandom()); // Energy of the TR photon // Put TR photon on particle stack if (e > 15) { e *= 1e-6; // Convert it to GeV diff --git a/Detectors/ZDC/simulation/src/Detector.cxx b/Detectors/ZDC/simulation/src/Detector.cxx index 7685890b61426..198044b34118b 100644 --- a/Detectors/ZDC/simulation/src/Detector.cxx +++ b/Detectors/ZDC/simulation/src/Detector.cxx @@ -16,6 +16,7 @@ #include "DetectorsBase/MaterialManager.h" #include "DetectorsBase/Stack.h" #include "ZDCSimulation/Detector.h" +#include "DetectorsBase/SimRandom.h" #include "DataFormatsZDC/Hit.h" #include "TMath.h" @@ -460,7 +461,7 @@ Bool_t Detector::ProcessHits(FairVolume* v) //printf(" \t ZPtableEntry[%d %d %d] = %1.5f -> lightoutput %f\n", ibeta, iangle, iradius, mLightTableZP[ibeta][iangle][iradius], lightoutput); } if (lightoutput > 0) { - nphe = gRandom->Poisson(lightoutput); + nphe = o2::base::simRandom()->Poisson(lightoutput); //printf(" \t\t-> nphe %d \n", nphe); } } diff --git a/Steer/src/O2MCApplication.cxx b/Steer/src/O2MCApplication.cxx index 04da526e963de..fc45a870603b4 100644 --- a/Steer/src/O2MCApplication.cxx +++ b/Steer/src/O2MCApplication.cxx @@ -50,6 +50,7 @@ #include #include #include +#include "DetectorsBase/SimRandom.h" namespace o2 { @@ -194,7 +195,7 @@ void O2MCApplicationBase::PreTrack() // step, so a suspended track is not reseeded mid-flight. auto hash = hashCurrentTrack(fMC); // TRandom::SetSeed(0) means "seed from the clock" -- never let that happen. - gRandom->SetSeed(hash == 0 ? 1 : hash); + o2::base::simRandom()->SetSeed(hash == 0 ? 1 : hash); o2::base::VMCSeederService::instance().setSeed(); } @@ -271,6 +272,8 @@ void O2MCApplicationBase::initStepFilterHook() void O2MCApplicationBase::InitGeometry() { + // set up the engine seeding here, on the main thread, so that transport threads never JIT-compile it + o2::base::VMCSeederService::instance(); // load special cuts which might be given from the outside first. auto& matMgr = o2::base::MaterialManager::Instance(); matMgr.loadCutsAndProcessesFromJSON(o2::base::MaterialManager::ESpecial::kTRUE); diff --git a/run/O2SimDevice.cxx b/run/O2SimDevice.cxx index 5d3bc14bfe99b..f2d3f22d7e036 100644 --- a/run/O2SimDevice.cxx +++ b/run/O2SimDevice.cxx @@ -22,6 +22,7 @@ #include #include #include +#include "DetectorsBase/SimRandom.h" void doLogInfo(int workerID, std::string const& message) { @@ -192,7 +193,7 @@ bool O2SimDevice::Kernel(int workerID, fair::mq::Channel& requestchannel, fair:: if (reproducibleSim) { LOG(info) << workerStr() << " Setting seed for this sub-event to " << chunk->mSubEventInfo.seed; - gRandom->SetSeed(chunk->mSubEventInfo.seed); + o2::base::simRandom()->SetSeed(chunk->mSubEventInfo.seed); o2::base::VMCSeederService::instance().setSeed(); } From fa1cbc3ac10aa97f26089c548f6cc5bf64ce2ae7 Mon Sep 17 00:00:00 2001 From: Sandro Wenzel Date: Fri, 6 Nov 2026 07:22:17 +0100 Subject: [PATCH 2/3] Seed Geant4 through a function installed by the Geant4 configuration This replaces the just-in-time compiled Geant4 seeding with a function that the Geant4 configuration installs. - The JIT code needed CLHEP/Random/Random.h on ROOT_INCLUDE_PATH, which the alienv module environment does not provide. - g4Config.C links Geant4 and installs the same seeding recipe through VMCSeederService::setGeant4Seeder. https://its.cern.ch/jira/browse/O2-7305 Co-Authored-By: Claude Opus 5.5 --- .../Base/include/DetectorsBase/VMCSeederService.h | 10 +++++++++- Detectors/Base/src/VMCSeederService.cxx | 14 +++++--------- Detectors/gconfig/g4Config.C | 12 ++++++++++++ 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/Detectors/Base/include/DetectorsBase/VMCSeederService.h b/Detectors/Base/include/DetectorsBase/VMCSeederService.h index 6ea0ed001c52a..aba093195c26b 100644 --- a/Detectors/Base/include/DetectorsBase/VMCSeederService.h +++ b/Detectors/Base/include/DetectorsBase/VMCSeederService.h @@ -42,11 +42,19 @@ class VMCSeederService typedef std::function SeederFcn; + /// installs the function that seeds Geant4 from a generator; called by the Geant4 configuration, which links Geant4 + static void setGeant4Seeder(SeederFcn fcn) { geant4Seeder() = std::move(fcn); } + private: VMCSeederService(); + static SeederFcn& geant4Seeder() + { + static SeederFcn fcn; + return fcn; + } void initSeederFunction(TVirtualMC const*); - SeederFcn mSeederFcn; // the just-in-time compiled function talking to the VMC engines + SeederFcn mSeederFcn; // the function talking to the VMC engine mutable std::atomic mSeedCount{0}; // number of setSeed() calls }; diff --git a/Detectors/Base/src/VMCSeederService.cxx b/Detectors/Base/src/VMCSeederService.cxx index 2773a50515ffa..d429b5ab309d3 100644 --- a/Detectors/Base/src/VMCSeederService.cxx +++ b/Detectors/Base/src/VMCSeederService.cxx @@ -12,9 +12,7 @@ #include "DetectorsBase/VMCSeederService.h" #include "DetectorsBase/SimRandom.h" #include "TVirtualMC.h" -#include // for FairLogger -#include // for ROOT JIT helpers -#include +#include // for FairLogger #include using namespace o2::base; @@ -25,13 +23,11 @@ void VMCSeederService::initSeederFunction(TVirtualMC const* vmc) // Geant3 doesn't need anything special in our context mSeederFcn = [](TRandom*) {}; } else if (strcmp(vmc->GetName(), "TGeant4") == 0) { - // Seed the Geant4 engine of the calling thread from the given generator, with the same recipe as - // TG4RunManager::SetRandomSeed (which always reads gRandom). JIT-compiled to avoid linking against Geant4. - if (!gInterpreter->Declare("#include ")) { - LOG(fatal) << "CLHEP/Random/Random.h not found on ROOT_INCLUDE_PATH; cannot seed Geant4"; + // installed by the Geant4 configuration, so that this library does not link against Geant4 + mSeederFcn = geant4Seeder(); + if (!mSeederFcn) { + LOG(fatal) << "No Geant4 seeding function installed (VMCSeederService::setGeant4Seeder)"; } - std::string G4func("std::function 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); };}"); - mSeederFcn = o2::conf::JITAndEvalFunction(G4func, "G4func()", "std::function", "VMCSEEDERFUNC123"); } else { LOG(warn) << "Unknown VMC engine or unimplemented VMC seeding function"; mSeederFcn = [](TRandom*) {}; diff --git a/Detectors/gconfig/g4Config.C b/Detectors/gconfig/g4Config.C index 43d49a7a4796d..f57a364f908d8 100644 --- a/Detectors/gconfig/g4Config.C +++ b/Detectors/gconfig/g4Config.C @@ -68,6 +68,9 @@ R__LOAD_LIBRARY(libgeant4vmc) #include #include "SimSetup/G4RunConfiguration.h" #include "SimSetup/VecGeomNavigation.h" +#include "DetectorsBase/VMCSeederService.h" +#include "CLHEP/Random/Random.h" +#include "TRandom.h" #endif #include "commonConfig.C" @@ -153,6 +156,15 @@ void Config() TGeant4* geant4 = new TGeant4("TGeant4", "The Geant4 Monte Carlo", runConfiguration); std::cout << "Geant4 has been created." << std::endl; + // seeds the Geant4 engine of the calling thread from a ROOT generator, as TG4RunManager::SetRandomSeed does from gRandom + o2::base::VMCSeederService::setGeant4Seeder([](TRandom* r) { + long seeds[10]; + seeds[0] = r->GetSeed(); + seeds[1] = r->GetSeed(); + seeds[2] = 0; + CLHEP::HepRandom::setTheSeeds(seeds); + }); + if (g4Params.navmode == o2::conf::EG4Nav::kVecGeom) { o2::simsetup::installVecGeomNavigator(); } From 2d3e549de6d084f694582fcbcc0f3ca1936cc3c8 Mon Sep 17 00:00:00 2001 From: Sandro Wenzel Date: Fri, 6 Nov 2026 08:35:32 +0100 Subject: [PATCH 3/3] Seed the Cave chunk seeding through the transport generator Cave::BeginPrimary seeded gRandom directly; it now uses o2::base::simRandom() like the other transport-time seeding. Co-Authored-By: Claude Opus 5.5 --- Detectors/Passive/src/Cave.cxx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Detectors/Passive/src/Cave.cxx b/Detectors/Passive/src/Cave.cxx index 80f96141e72b4..38e102dac2414 100644 --- a/Detectors/Passive/src/Cave.cxx +++ b/Detectors/Passive/src/Cave.cxx @@ -33,6 +33,7 @@ #include "TGeoPgon.h" #include "TGeoTube.h" #include "TGeoCompositeShape.h" +#include "DetectorsBase/SimRandom.h" using namespace o2::passive; void Cave::createMaterials() @@ -153,7 +154,7 @@ void Cave::BeginPrimary() if (chunks != -1) { if (mPrimaryCounter % chunks == 0) { auto seed = mChunkSeedCounter + 10; - gRandom->SetSeed(seed); + o2::base::simRandom()->SetSeed(seed); mChunkSeedCounter++; } }