Skip to content

Commit 5ae2dd2

Browse files
sawenzelclaude
andcommitted
Add optional per-sector seeding to the TPC digitizer
This adds an option that makes the TPC digits independent of the lane assignment, as a preparation for MT digitization. - The random rings of ElectronTransport, GEMAmplification and SAMPAProcessing kept their positions from one sector to the next, so the digits depended on the number of TPC lanes. - With --tpc-sector-seeding, each Digitizer owns these three objects, fills their rings once from DigiParams.seed, and moves the rings to positions derived from the seed, the sector and the timeframe. - RandomRing can now be filled from a given generator and moved to a random position. - Without the option, the output is unchanged. https://its.cern.ch/jira/browse/O2-7309 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 32fea31 commit 5ae2dd2

10 files changed

Lines changed: 166 additions & 33 deletions

File tree

‎Common/MathUtils/include/MathUtils/RandomRing.h‎

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -54,19 +54,22 @@ class RandomRing
5454

5555
/// constructor
5656
/// @param [in] randomType type of the random generator
57-
RandomRing(const RandomType randomType = RandomType::Gaus);
57+
/// @param [in] rng random generator to draw from, gRandom if null
58+
RandomRing(const RandomType randomType = RandomType::Gaus, TRandom* rng = nullptr);
5859

5960
/// constructor accepting TF1
6061
/// @param [in] function TF1 function
6162
RandomRing(TF1& function);
6263

6364
/// initialisation of the random ring
6465
/// @param [in] randomType type of the random generator
65-
void initialize(const RandomType randomType = RandomType::Gaus);
66+
/// @param [in] rng random generator to draw from, gRandom if null
67+
void initialize(const RandomType randomType = RandomType::Gaus, TRandom* rng = nullptr);
6668

6769
/// initialisation of the random ring
68-
/// @param [in] randomType type of the random generator
69-
void initialize(TF1& function);
70+
/// @param [in] function TF1 function
71+
/// @param [in] rng random generator to draw from, gRandom if null
72+
void initialize(TF1& function, TRandom* rng = nullptr);
7073

7174
/// initialisation of the random ring
7275
/// @param [in] randomType type of the random generator
@@ -110,6 +113,10 @@ class RandomRing
110113
/// @return position in the ring buffer
111114
unsigned int getRingPosition() const { return mRingPosition; }
112115

116+
/// move to a random position in the ring buffer; not for rings read with getNextValueVc()
117+
/// @param [in] rng random generator to draw the position from
118+
void setRandomPosition(TRandom& rng) { mRingPosition = rng.Integer(N); }
119+
113120
private:
114121
// =========================================================================
115122
// ===| members |===========================================================
@@ -123,12 +130,12 @@ class RandomRing
123130

124131
//______________________________________________________________________________
125132
template <size_t N>
126-
inline RandomRing<N>::RandomRing(const RandomType randomType)
133+
inline RandomRing<N>::RandomRing(const RandomType randomType, TRandom* rng)
127134
: mRandomType(randomType),
128135
mRandomNumbers()
129136

130137
{
131-
initialize(randomType);
138+
initialize(randomType, rng);
132139
}
133140

134141
//______________________________________________________________________________
@@ -142,18 +149,18 @@ inline RandomRing<N>::RandomRing(TF1& function)
142149

143150
//______________________________________________________________________________
144151
template <size_t N>
145-
inline void RandomRing<N>::initialize(const RandomType randomType)
152+
inline void RandomRing<N>::initialize(const RandomType randomType, TRandom* rng)
146153
{
147-
154+
TRandom* random = rng ? rng : gRandom;
148155
for (auto& v : mRandomNumbers) {
149156
// TODO: configurable mean and sigma
150157
switch (randomType) {
151158
case RandomType::Gaus: {
152-
v = gRandom->Gaus(0, 1);
159+
v = random->Gaus(0, 1);
153160
break;
154161
}
155162
case RandomType::Flat: {
156-
v = gRandom->Rndm();
163+
v = random->Rndm();
157164
break;
158165
}
159166
default: {
@@ -166,11 +173,11 @@ inline void RandomRing<N>::initialize(const RandomType randomType)
166173

167174
//______________________________________________________________________________
168175
template <size_t N>
169-
inline void RandomRing<N>::initialize(TF1& function)
176+
inline void RandomRing<N>::initialize(TF1& function, TRandom* rng)
170177
{
171178
mRandomType = RandomType::CustomTF1;
172179
for (auto& v : mRandomNumbers) {
173-
v = function.GetRandom();
180+
v = function.GetRandom(rng);
174181
}
175182
}
176183

‎Detectors/TPC/simulation/include/TPCSimulation/Digitizer.h‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ namespace tpc
3131
{
3232

3333
class DigitContainer;
34+
class ElectronTransport;
35+
class GEMAmplification;
3436

3537
template <class T>
3638
class SpaceCharge;
@@ -141,7 +143,25 @@ class Digitizer
141143
/// in case of scaled distortions, the distortions can be recalculated to ensure consistent distortions and corrections
142144
void recalculateDistortions();
143145

146+
/// Use digitizer-owned random rings filled from seed and positioned per sector and timeframe in init(),
147+
/// so that the digits of a sector do not depend on which sectors were processed before
148+
/// \param seed base seed, typically DigiParams.seed
149+
void setUseSectorSeeding(ULong64_t seed);
150+
151+
/// Set the timeframe identifier that enters the sector seeding
152+
void setSeedingTimeframe(ULong64_t timeframe) { mSeedingTimeframe = timeframe; }
153+
144154
private:
155+
ElectronTransport& electronTransport();
156+
GEMAmplification& gemAmplification();
157+
SAMPAProcessing& sampaProcessing();
158+
159+
std::unique_ptr<ElectronTransport> mElectronTransport; //!< Digitizer-owned instance with sector seeding, else the singleton is used
160+
std::unique_ptr<GEMAmplification> mGEMAmplification; //!< Digitizer-owned instance with sector seeding, else the singleton is used
161+
std::unique_ptr<SAMPAProcessing> mSAMPAProcessing; //!< Digitizer-owned instance with sector seeding, else the singleton is used
162+
ULong64_t mSeed = 0; //!< Base seed for sector seeding
163+
ULong64_t mSeedingTimeframe = 0; //!< Timeframe identifier for sector seeding
164+
bool mUseSectorSeeding = false; //!< Whether sector seeding is used
145165
DigitContainer mDigitContainer; ///< Container for the Digits
146166
std::vector<float> mSignalArray; //!< Workspace for the shaped signal of one electron
147167
std::unique_ptr<SC> mSpaceCharge; ///< Handler of full distortions (static + IR dependant)

‎Detectors/TPC/simulation/include/TPCSimulation/ElectronTransport.h‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,16 @@ class ElectronTransport
4141
return electronTransport;
4242
}
4343

44+
/// Constructor
45+
/// \param rng random generator to fill the random rings from, gRandom if null
46+
explicit ElectronTransport(TRandom* rng = nullptr);
47+
4448
/// Destructor
4549
~ElectronTransport() = default;
4650

51+
/// Move the random rings to new positions drawn from rng
52+
void setRandomRingPositions(TRandom& rng);
53+
4754
/// Update the OCDB parameters cached in the class. To be called once per event
4855
void updateParameters(float vdrift = 0);
4956

@@ -72,8 +79,6 @@ class ElectronTransport
7279
float getDriftTime(float zPos, float signChange = 1.f) const;
7380

7481
private:
75-
ElectronTransport();
76-
7782
/// Circular random buffer containing random values of the Gauss distribution to take into account diffusion of the
7883
/// electrons
7984
math_utils::RandomRing<> mRandomGaus;

‎Detectors/TPC/simulation/include/TPCSimulation/GEMAmplification.h‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,16 @@ class GEMAmplification
4343
return gemAmplification;
4444
}
4545

46+
/// Constructor
47+
/// \param rng random generator to fill the random rings from, gRandom if null
48+
explicit GEMAmplification(TRandom* rng = nullptr);
49+
4650
/// Destructor
4751
~GEMAmplification() = default;
4852

53+
/// Move the random rings to new positions drawn from rng
54+
void setRandomRingPositions(TRandom& rng);
55+
4956
/// Update the OCDB parameters cached in the class. To be called once per event
5057
void updateParameters();
5158

@@ -90,8 +97,6 @@ class GEMAmplification
9097
int getGEMMultiplication(int nElectrons, int GEM);
9198

9299
private:
93-
GEMAmplification();
94-
95100
/// Circular random buffer containing random Gaus values for gain fluctuation if the number of electrons is larger
96101
/// (central limit theorem)
97102
math_utils::RandomRing<> mRandomGaus;

‎Detectors/TPC/simulation/include/TPCSimulation/SAMPAProcessing.h‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,16 @@ class SAMPAProcessing
4545
static SAMPAProcessing sampaProcessing;
4646
return sampaProcessing;
4747
}
48+
/// Constructor
49+
/// \param rng random generator to fill the random ring from, gRandom if null
50+
explicit SAMPAProcessing(TRandom* rng = nullptr);
51+
4852
/// Destructor
4953
~SAMPAProcessing() = default;
5054

55+
/// Move the random ring to a new position drawn from rng
56+
void setRandomRingPositions(TRandom& rng) { mRandomNoiseRing.setRandomPosition(rng); }
57+
5158
/// Update the OCDB parameters cached in the class. To be called once per event
5259
void updateParameters(float vdrift = 0);
5360

@@ -134,7 +141,6 @@ class SAMPAProcessing
134141
float getPedestalCRU(const int sector, const int globalPadInSector) const;
135142

136143
private:
137-
SAMPAProcessing();
138144
const ParameterGas* mGasParam; ///< Caching of the parameter class to avoid multiple CDB calls
139145
const ParameterDetector* mDetParam; ///< Caching of the parameter class to avoid multiple CDB calls
140146
const ParameterElectronics* mEleParam; ///< Caching of the parameter class to avoid multiple CDB calls

‎Detectors/TPC/simulation/src/Digitizer.cxx‎

Lines changed: 64 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include "TPCCalibration/CorrMapParam.h"
3131

3232
#include <fairlogger/Logger.h>
33+
#include <TRandom3.h>
3334

3435
ClassImp(o2::tpc::Digitizer);
3536

@@ -39,13 +40,68 @@ Digitizer::~Digitizer() = default;
3940

4041
Digitizer::Digitizer() = default;
4142

43+
namespace
44+
{
45+
// splitmix64 finaliser, to derive well separated seeds from the base seed
46+
ULong64_t mixSeed(ULong64_t x)
47+
{
48+
x += 0x9e3779b97f4a7c15ULL;
49+
x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9ULL;
50+
x = (x ^ (x >> 27)) * 0x94d049bb133111ebULL;
51+
return x ^ (x >> 31);
52+
}
53+
54+
// TRandom3 takes 32 bits of the seed and treats 0 as a request for a random seed
55+
UInt_t toTRandom3Seed(ULong64_t x)
56+
{
57+
const UInt_t seed = static_cast<UInt_t>(x ^ (x >> 32));
58+
return seed ? seed : 1;
59+
}
60+
} // namespace
61+
62+
ElectronTransport& Digitizer::electronTransport()
63+
{
64+
return mElectronTransport ? *mElectronTransport : ElectronTransport::instance();
65+
}
66+
67+
GEMAmplification& Digitizer::gemAmplification()
68+
{
69+
return mGEMAmplification ? *mGEMAmplification : GEMAmplification::instance();
70+
}
71+
72+
SAMPAProcessing& Digitizer::sampaProcessing()
73+
{
74+
return mSAMPAProcessing ? *mSAMPAProcessing : SAMPAProcessing::instance();
75+
}
76+
77+
void Digitizer::setUseSectorSeeding(ULong64_t seed)
78+
{
79+
mUseSectorSeeding = true;
80+
mSeed = seed;
81+
}
82+
4283
void Digitizer::init()
4384
{
44-
auto& gemAmplification = GEMAmplification::instance();
85+
if (mUseSectorSeeding) {
86+
if (!mGEMAmplification) {
87+
// the rings are filled once, in the construction order of the singletons
88+
TRandom3 rng(toTRandom3Seed(mixSeed(mSeed)));
89+
mGEMAmplification = std::make_unique<GEMAmplification>(&rng);
90+
mElectronTransport = std::make_unique<ElectronTransport>(&rng);
91+
mSAMPAProcessing = std::make_unique<SAMPAProcessing>(&rng);
92+
mDigitContainer.setSAMPAProcessing(mSAMPAProcessing.get());
93+
}
94+
const ULong64_t sectorSeed = mixSeed(mSeed ^ mixSeed(static_cast<ULong64_t>(mSector) + 1));
95+
TRandom3 rng(toTRandom3Seed(mixSeed(sectorSeed ^ mixSeed(mSeedingTimeframe))));
96+
mGEMAmplification->setRandomRingPositions(rng);
97+
mElectronTransport->setRandomRingPositions(rng);
98+
mSAMPAProcessing->setRandomRingPositions(rng);
99+
}
100+
auto& gemAmplification = this->gemAmplification();
45101
gemAmplification.updateParameters();
46-
auto& electronTransport = ElectronTransport::instance();
102+
auto& electronTransport = this->electronTransport();
47103
electronTransport.updateParameters(mVDrift);
48-
auto& sampaProcessing = SAMPAProcessing::instance();
104+
auto& sampaProcessing = this->sampaProcessing();
49105
// the calibration update may reload FEE parameters from file, so SAMPAProcessing picks up its pointers afterwards
50106
mDigitContainer.updateCalibrations();
51107
sampaProcessing.updateParameters(mVDrift);
@@ -59,9 +115,9 @@ void Digitizer::process(const std::vector<o2::tpc::HitGroup>& hits,
59115
auto& eleParam = ParameterElectronics::Instance();
60116
auto& gemParam = ParameterGEM::Instance();
61117

62-
auto& gemAmplification = GEMAmplification::instance();
63-
auto& electronTransport = ElectronTransport::instance();
64-
auto& sampaProcessing = SAMPAProcessing::instance();
118+
auto& gemAmplification = this->gemAmplification();
119+
auto& electronTransport = this->electronTransport();
120+
auto& sampaProcessing = this->sampaProcessing();
65121

66122
const int nShapedPoints = eleParam.NShapedPoints;
67123
const auto amplificationMode = gemParam.AmplMode;
@@ -171,7 +227,7 @@ void Digitizer::flush(std::vector<o2::tpc::Digit>& digits,
171227
std::vector<o2::tpc::CommonMode>& commonModeOutput,
172228
bool finalFlush)
173229
{
174-
SAMPAProcessing& sampaProcessing = SAMPAProcessing::instance();
230+
SAMPAProcessing& sampaProcessing = this->sampaProcessing();
175231
mDigitContainer.fillOutputContainer(digits, labels, commonModeOutput, mSector, sampaProcessing.getTimeBinFromTime(mEventTime - mOutputDigitTimeOffset), mIsContinuous, finalFlush);
176232
// flushing debug output to file
177233
if (((finalFlush && mIsContinuous) || (!mIsContinuous)) && mSpaceCharge) {
@@ -227,7 +283,7 @@ void Digitizer::setStartTime(double time)
227283
// this is setting the first timebin index for the digit container
228284
// note that negative times w.r.t start of timeframe/data-taking == mOutputDigitTimeOffset
229285
// will yield the 0-th bin (due to casting logic in sampaProcessing)
230-
SAMPAProcessing& sampaProcessing = SAMPAProcessing::instance();
286+
SAMPAProcessing& sampaProcessing = this->sampaProcessing();
231287
sampaProcessing.updateParameters(mVDrift);
232288
const auto timediff = time - mOutputDigitTimeOffset;
233289
const auto starttimebin = sampaProcessing.getTimeBinFromTime(timediff);

‎Detectors/TPC/simulation/src/ElectronTransport.cxx‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,17 @@
2121
using namespace o2::tpc;
2222
using namespace o2::math_utils;
2323

24-
ElectronTransport::ElectronTransport() : mRandomGaus(), mRandomFlat(RandomRing<>::RandomType::Flat)
24+
ElectronTransport::ElectronTransport(TRandom* rng) : mRandomGaus(RandomRing<>::RandomType::Gaus, rng), mRandomFlat(RandomRing<>::RandomType::Flat, rng)
2525
{
2626
updateParameters();
2727
}
2828

29+
void ElectronTransport::setRandomRingPositions(TRandom& rng)
30+
{
31+
mRandomGaus.setRandomPosition(rng);
32+
mRandomFlat.setRandomPosition(rng);
33+
}
34+
2935
void ElectronTransport::updateParameters(float vdrift)
3036
{
3137
mGasParam = &(ParameterGas::Instance());

‎Detectors/TPC/simulation/src/GEMAmplification.cxx‎

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,15 @@ using namespace o2::tpc;
2626
using namespace o2::math_utils;
2727
using boost::format;
2828

29-
GEMAmplification::GEMAmplification()
30-
: mRandomGaus(),
31-
mRandomFlat(RandomRing<>::RandomType::Flat),
32-
mGain()
29+
GEMAmplification::GEMAmplification(TRandom* rng)
30+
: mRandomGaus(RandomRing<>::RandomType::Gaus, rng),
31+
mRandomFlat(RandomRing<>::RandomType::Flat, rng),
32+
// the gain rings are refilled from the Polya distributions below; with an explicit generator they start empty
33+
mGain{RandomRing<>(rng ? RandomRing<>::RandomType::CustomTF1 : RandomRing<>::RandomType::Gaus),
34+
RandomRing<>(rng ? RandomRing<>::RandomType::CustomTF1 : RandomRing<>::RandomType::Gaus),
35+
RandomRing<>(rng ? RandomRing<>::RandomType::CustomTF1 : RandomRing<>::RandomType::Gaus),
36+
RandomRing<>(rng ? RandomRing<>::RandomType::CustomTF1 : RandomRing<>::RandomType::Gaus)},
37+
mGainFullStack(rng ? RandomRing<>::RandomType::CustomTF1 : RandomRing<>::RandomType::Gaus)
3338
{
3439
updateParameters();
3540

@@ -65,7 +70,7 @@ GEMAmplification::GEMAmplification()
6570
polyaDistribution = (o2::math_utils::CachingTF1*)outfile->Get(TString::Format("func%d", i).Data());
6671
// FIXME: verify that distribution corresponds to the parameters used here
6772
}
68-
mGain[i].initialize(*polyaDistribution);
73+
mGain[i].initialize(*polyaDistribution, rng);
6974

7075
if (!cacheexists) {
7176
outfile->WriteTObject(polyaDistribution, TString::Format("func%d", i).Data());
@@ -88,7 +93,7 @@ GEMAmplification::GEMAmplification()
8893
} else {
8994
polyaDistribution = (o2::math_utils::CachingTF1*)outfile->Get("polyaStack");
9095
}
91-
mGainFullStack.initialize(*polyaDistribution);
96+
mGainFullStack.initialize(*polyaDistribution, rng);
9297

9398
if (!cacheexists) {
9499
outfile->WriteTObject(polyaDistribution, "polyaStack");
@@ -102,6 +107,16 @@ GEMAmplification::GEMAmplification()
102107
LOG(info) << "TPC: GEM setup (polya) took " << watch.CpuTime();
103108
}
104109

110+
void GEMAmplification::setRandomRingPositions(TRandom& rng)
111+
{
112+
mRandomGaus.setRandomPosition(rng);
113+
mRandomFlat.setRandomPosition(rng);
114+
for (auto& gain : mGain) {
115+
gain.setRandomPosition(rng);
116+
}
117+
mGainFullStack.setRandomPosition(rng);
118+
}
119+
105120
void GEMAmplification::updateParameters()
106121
{
107122
auto& cdb = CDBInterface::instance();

‎Detectors/TPC/simulation/src/SAMPAProcessing.cxx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424

2525
using namespace o2::tpc;
2626

27-
SAMPAProcessing::SAMPAProcessing() : mRandomNoiseRing()
27+
SAMPAProcessing::SAMPAProcessing(TRandom* rng) : mRandomNoiseRing(o2::math_utils::RandomRing<>::RandomType::Gaus, rng)
2828
{
2929
updateParameters();
3030
}

0 commit comments

Comments
 (0)