ATLAS Offline Software
Loading...
Searching...
No Matches
MM_StripsResponseSimulation.cxx
Go to the documentation of this file.
1/*
2 Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
3*/
4
7
8#include <TGraph.h>
9
10#include <algorithm>
11#include <mutex>
12#include <thread>
13#include <map>
14
15#include "CLHEP/Random/RandFlat.h"
16#include "CLHEP/Random/RandGamma.h"
17#include "CLHEP/Random/RandGaussZiggurat.h"
18#include "CLHEP/Random/RandGeneral.h"
19#include "CLHEP/Random/RandomEngine.h"
20#include "GaudiKernel/SystemOfUnits.h"
24
25/*
26
27To Do List:
28
29Check Lorentz Angle is in correct direction...
30
31*/
32
33namespace {
34 constexpr float electronsToFC = 1. / 6241.;
35 constexpr float polyaTheta = 1.765;
36 static std::mutex fillMutex{};
37} // namespace
38
39/*******************************************************************************/
41 AthMessaging("MMStripResponseSimulation"),
42 m_cfg{std::move(mod)} {
43
44 initHistos();
45 // initialization for the random number generator for the number of secondary electrons
46 // values are from G. Iakovidis
47 std::vector<double> NelectronProb{65.6, 15., 6.4, 3.5, 2.25, 1.55, 1.05, 0.81, 0.61, 0.49, 0.39, 0.3,
48 0.25, 0.2, 0.16, 0.12, 0.095, 0.075, 0.063, 0.054, 0.049, 0.045, 0.044};
49 NelectronProb.reserve(s_NelectronPropBins);
50 for (unsigned int Nelectron = NelectronProb.size(); Nelectron < s_NelectronPropBins; ++Nelectron)
51 NelectronProb.push_back(21.6 / ((Nelectron) * (Nelectron)));
52
53 m_randNelectrons = std::make_unique<CLHEP::RandGeneral>(NelectronProb.data(), NelectronProb.size(), 1); // 1 means non-continious random numbers
54
55 ATH_MSG_DEBUG("MM_StripsResponseSimulation::initializationFrom set values");
56 }
57
58/*******************************************************************************/
60 if (m_cfg.writeOutputFile) {
61 m_outputFile = std::make_unique<TFile>("MM_StripsResponse_Plots.root", "RECREATE");
62 if (m_outputFile) m_outputFile->cd();
63
64 m_mapOfHistograms["nInteractions"] = std::make_unique<TH1F>("nInteractions", "nInteractions", 200, 0, 200);
65 m_mapOfHistograms["nElectrons"] = std::make_unique<TH1F>("nElectrons", "nElectrons", 200, 0, 200);
66 m_mapOfHistograms["lorentzAngle"] = std::make_unique<TH1F>("lorentzAngle", "lorentzAngle", 100, 0, 3);
67 m_mapOfHistograms["effectiveCharge"] = std::make_unique<TH1F>("effectiveCharge", "effectiveCharge", 100, 0, 1e5);
68 m_mapOfHistograms["effectiveNElectrons"] = std::make_unique<TH1F>("effectiveNElectrons", "effectiveNElectrons", 200, 0, 200);
69 m_mapOfHistograms["totalEffectiveCharge"] = std::make_unique<TH1F>("totalEffectiveCharge", "totalEffectiveCharge", 400, 0, 200);
70 m_mapOf2DHistograms["totalEffectiveChargeVsTheta"] = std::make_unique<TH2F>("totalEffectiveChargeVsTheta", "totalEffectiveChargeVsTheta", 17, 0, 35, 300, 0, 300);
71
72 m_mapOf2DHistograms["pathLengthVsTheta"] = std::make_unique<TH2F>("pathLengthVsTheta", "pathLengthVsTheta", 100, 0, 10, 100, -3, 3);
73 m_mapOf2DHistograms["pathLengthVsAlpha"] = std::make_unique<TH2F>("pathLengthVsAlpha", "pathLengthVsAlpha", 100, 0, 10, 100, -3, 3);
74
75 m_mapOf2DHistograms["lorentzAngleVsTheta"] = std::make_unique<TH2F>("lorentzAngleVsTheta", "lorentzAngleVsTheta", 100, -3, 3, 100, -3, 3);
76 m_mapOf2DHistograms["lorentzAngleVsBy"] = std::make_unique<TH2F>("lorentzAngleVsBy", "lorentzAngleVsBy", 100, -3, 3, 100, -2., 2.);
77
78 m_mapOf2DHistograms["deltaLorentzAngleVsByForPosTheta"] =
79 std::make_unique<TH2F>("deltaLorentzAngleVsByForPosTheta", "deltaLorentzAngleVsByForPosTheta", 100, -3, 3, 100, -2., 2.);
80 m_mapOf2DHistograms["deltaLorentzAngleVsByForNegTheta"] =
81 std::make_unique<TH2F>("deltaLorentzAngleVsByForNegTheta", "deltaLorentzAngleVsByForNegTheta", 100, -3, 3, 100, -2., 2.);
82
83 m_mapOf2DHistograms["driftDistanceVsDriftTime"] =
84 std::make_unique<TH2F>("driftDistanceVsDriftTime", "driftDistanceVsDriftTime", 100, 0, 10, 100, 0, 200);
85 }
86}
87/*******************************************************************************/
89 if (!m_outputFile) {
90 return;
91 }
92 for (auto& [histo_name, histo] : m_mapOfHistograms) {
93 m_outputFile->WriteObject(histo.get(), histo_name.c_str());
94 }
95 for (auto& [histo_name, histo] : m_mapOf2DHistograms) {
96 m_outputFile->WriteObject(histo.get(), histo_name.c_str());
97 }
98 m_mapOf2DHistograms.clear();
99 m_mapOfHistograms.clear();
100}
101/*******************************************************************************/
103 double gainFraction, double stripPitch,
104 CLHEP::HepRandomEngine* rndmEngine) const {
105 ATH_MSG_DEBUG("Starting to get response from strips");
106 DataCache cache{};
107 whichStrips(cache, digiInput, gainFraction, stripPitch, rndmEngine);
108
109 ATH_MSG_DEBUG("Creating MmDigitToolOutput object");
110
111 MM_StripToolOutput tmpStripToolOutput(std::move(cache.finalNumberofStrip),
112 std::move(cache.finalqStrip),
113 std::move(cache.finaltStrip));
114
115 return tmpStripToolOutput;
116}
117
118//__________________________________________________________________________________________________
119//==================================================================================================
120// calculate the strips that got fired, charge and time. Assume any angle thetaDegrees....
121//==================================================================================================
123 const MM_DigitToolInput& digiInput,
124 double gainFraction, double stripPitch,
125 CLHEP::HepRandomEngine* rndmEngine) const {
126 ATH_MSG_DEBUG("Starting to calculate strips that got fired");
127 const float hitx = digiInput.positionWithinStrip();
128 const int stripID = digiInput.stripIDLocal();
129 const float incidentAngleXZ = digiInput.incomingAngleXZ(); // degrees
130 const float incidentAngleYZ = digiInput.incomingAngleYZ(); // degrees
131 const int stripMinID = digiInput.stripMinID();
132 const int stripMaxID = digiInput.stripMaxID();
133
134 CLHEP::RandGeneral* randDist ATLAS_THREAD_SAFE = m_randNelectrons.get();
135 const float eventTime = digiInput.eventTime();
136 const float theta = incidentAngleXZ * Gaudi::Units::degree; // Important for path length and strip distribution
137 const float alpha = incidentAngleYZ * Gaudi::Units::degree; // Important for path length
138
139 int nPrimaryIons = 0;
140
141 Amg::Vector3D b = digiInput.magneticField() * 1000.;
142
143 // Still need to understand which sign is which... But I think this is correct...
144
145 float lorentzAngle = (b.y() > 0. ? 1. : -1.) * m_cfg.lorentzAngleFunction(std::abs(b.y())) * Gaudi::Units::deg; // in radians
146 if (m_cfg.writeOutputFile) {
147 std::lock_guard guard{fillMutex};
148 m_mapOf2DHistograms.at("lorentzAngleVsTheta")->Fill(lorentzAngle, theta);
149 m_mapOf2DHistograms.at("lorentzAngleVsBy")->Fill(lorentzAngle, b.y());
150 }
151
152 ATH_MSG_DEBUG("LorentzAngle vs theta: " << lorentzAngle << " " << theta);
153
154 float pathLengthTraveled = getPathLengthTraveled(rndmEngine);
155
156 float pathLength = m_cfg.driftGapWidth / std::cos(theta); // Increasing path length based on XZ angle
157 pathLength /= std::cos(alpha); // Further increasing path length for YZ angle
158
159 if (m_cfg.writeOutputFile) {
160 std::lock_guard guard{fillMutex};
161 m_mapOf2DHistograms.at("pathLengthVsTheta")->Fill(pathLength, theta);
162 m_mapOf2DHistograms.at("pathLengthVsAlpha")->Fill(pathLength, alpha);
163
164 if (theta > 0)
165 m_mapOf2DHistograms.at("deltaLorentzAngleVsByForPosTheta")->Fill(lorentzAngle - theta, b.y());
166 else
167 m_mapOf2DHistograms.at("deltaLorentzAngleVsByForNegTheta")->Fill(lorentzAngle - theta, b.y());
168 }
169 while (pathLengthTraveled < pathLength) {
170 // N.B. Needs correction from alpha angle still...
171 std::unique_ptr<MM_IonizationCluster> IonizationCluster =
172 std::make_unique<MM_IonizationCluster>(hitx, pathLengthTraveled * std::sin(theta), pathLengthTraveled * std::cos(theta));
173
174 // +1 since first entry in electron probability vector corresponds to 1 electron and not to 0 electrons
175 int nElectrons = s_NelectronPropBins * randDist->shoot(rndmEngine) + 1;
176 IonizationCluster->createElectrons(nElectrons);
177
178 const Amg::Vector2D& initialPosition = IonizationCluster->getIonizationStart();
179
180 ATH_MSG_DEBUG("New interaction starting at x,y, pathLengthTraveled: " << initialPosition.x() << " " << initialPosition.y() << " "
181 << pathLengthTraveled);
182
183 for (auto& Electron : IonizationCluster->getElectrons()) {
184 Electron->setOffsetPosition(getTransverseDiffusion(initialPosition.y(), rndmEngine),
185 getLongitudinalDiffusion(initialPosition.y(), rndmEngine));
186 }
187
188 IonizationCluster->propagateElectrons(lorentzAngle, m_cfg.driftVelocity);
189
190 int tmpEffectiveNElectrons = 0;
191
192 for (auto& Electron : IonizationCluster->getElectrons()) {
193 float effectiveCharge = getEffectiveCharge(rndmEngine) * gainFraction;
194
195 Electron->setCharge(effectiveCharge);
196
197 ATH_MSG_DEBUG("Electron's effective charge is " << effectiveCharge);
198
199 if (m_cfg.writeOutputFile) {
200 std::lock_guard guard{fillMutex};
201 m_mapOfHistograms.at("effectiveCharge")->Fill(effectiveCharge);
202 m_mapOf2DHistograms.at("driftDistanceVsDriftTime")->Fill(Electron->getOffsetPosition().mag(), Electron->getTime());
203 }
204 // Add eventTime in Electron time
205 Electron->setTime(Electron->getTime() + eventTime);
206
207 tmpEffectiveNElectrons += effectiveCharge;
208 }
209
210 if (m_cfg.writeOutputFile) {
211 std::lock_guard guard{fillMutex};
212 m_mapOfHistograms.at("effectiveNElectrons")->Fill(tmpEffectiveNElectrons / m_cfg.avalancheGain);
213 }
214 //---
215 cache.IonizationClusters.push_back(std::move(IonizationCluster));
216
217 pathLengthTraveled += getPathLengthTraveled(rndmEngine);
218
219 ATH_MSG_DEBUG("Path length traveled: " << pathLengthTraveled);
220
221 ++nPrimaryIons;
222 if (nPrimaryIons >= m_cfg.maxPrimaryIons) break; // don't create more than "MaxPrimaryIons" along a track....
223
224 } // end of clusters loop
225
226 float timeresolution = 0.01; // ns
227
228 MM_StripResponse stripResponseObject(cache.IonizationClusters, timeresolution, stripPitch, stripID, stripMinID, stripMaxID);
229 stripResponseObject.timeOrderElectrons();
230 stripResponseObject.calculateTimeSeries(incidentAngleXZ, digiInput.gasgap());
231 stripResponseObject.simulateCrossTalk(m_cfg.crossTalk1, m_cfg.crossTalk2);
232 stripResponseObject.calculateSummaries(m_cfg.qThreshold);
233
234 // Connect the output with the rest of the existing code
235 //
236 cache.finalNumberofStrip = stripResponseObject.getStripVec();
237 cache.finalqStrip = stripResponseObject.getTotalChargeVec();
238 cache.finaltStrip = stripResponseObject.getTimeThresholdVec();
239 cache.tStripElectronicsAbThr = stripResponseObject.getTimeMaxChargeVec();
240 cache.qStripElectronics = stripResponseObject.getMaxChargeVec();
241
242 // Output diagnostic hists and graphs
243 //
244 if (m_cfg.writeOutputFile) {
245 std::lock_guard guard{fillMutex};
246 m_mapOfHistograms.at("nInteractions")->Fill(nPrimaryIons);
247 m_mapOfHistograms.at("nElectrons")->Fill(stripResponseObject.getNElectrons());
248 m_mapOfHistograms.at("totalEffectiveCharge")->Fill(stripResponseObject.totalCharge() * electronsToFC);
249 m_mapOf2DHistograms.at("totalEffectiveChargeVsTheta")->Fill(std::abs(incidentAngleXZ),
250 stripResponseObject.totalCharge() * electronsToFC);
251 ATH_MSG_DEBUG("incidentAngleXZ" << incidentAngleXZ << "charge [fC]" << stripResponseObject.totalCharge() / 6241.);
252 m_mapOfHistograms.at("lorentzAngle")->Fill(lorentzAngle);
253 }
254
255 if (m_cfg.writeEventDisplays && m_outputFile) {
256 std::lock_guard guard{fillMutex};
257 static std::atomic<unsigned> written{0};
258 TGraph grIonizationXZ(cache.IonizationClusters.size());
259 for (size_t iIonization = 0; iIonization < cache.IonizationClusters.size(); ++iIonization) {
260 Amg::Vector2D ionizationPosition(cache.IonizationClusters.at(iIonization)->getIonizationStart());
261 grIonizationXZ.SetPoint(iIonization, ionizationPosition.x(), ionizationPosition.y());
262 }
263 m_outputFile->WriteObject(&grIonizationXZ, Form("ionizationXZ_%u", ++written));
264
265 TGraph grElectronsXZ(stripResponseObject.getNElectrons());
266 int iElectron = 0;
267 for (const auto& electron : stripResponseObject.getElectrons()) {
268 grElectronsXZ.SetPoint(iElectron, electron->getX(), electron->getY());
269 iElectron++;
270 }
271 m_outputFile->WriteObject(&grElectronsXZ, Form("electronsXZ_%u", ++written));
272 }
273
274} // end of whichStrips()
275
276float MM_StripsResponseSimulation::generateTransverseDiffusion(float posY, CLHEP::HepRandomEngine* rndmEngine) const {
277 // this is a helper function used in getTransverseDiffusion, generating double gaussian distributions
278
279 // avoid division by zero in calculation of scale if ypos is 0
280 // also protect against negative values of ypos which should never happen
281 if (posY <= 0.) posY = 0.001;
282
283 // need to scale weigths since initial distributions were not normalized
284 const double scale = 0.001 / (posY * m_cfg.transverseDiffusionSigma);
285
286 const double uni = CLHEP::RandFlat::shoot(rndmEngine, 0, 1.0 + scale);
287 if (uni < scale) return CLHEP::RandGaussZiggurat::shoot(rndmEngine, 0.0, 1.0);
288 return CLHEP::RandGaussZiggurat::shoot(rndmEngine, 0., m_cfg.transverseDiffusionSigma * posY);
289}
290
291float MM_StripsResponseSimulation::getTransverseDiffusion(float posY, CLHEP::HepRandomEngine* rndmEngine) const {
292 // the random numbers are generate from the following function:
293 // "1.*TMath::Exp(-TMath::Power(x,2.)/(2.*[0]*[0])) + 0.001*TMath::Exp(-TMath::Power(x,2)/(2.*[1]*[1]))"
294 // in the range from -1 to 1
295 // Since sampling from a TF1 where its parameters are changed for every random number is slow,
296 // the following approach is used to generate random numbers from the mixture of two Gaussian:
297 // https://stats.stackexchange.com/questions/226834/sampling-from-a-mixture-of-two-gamma-distributions/226837#226837
298 // this approach seems to be around 20000 times faster
299
300 // if one of the diffusions is off, the tail is not present
301 float diffusion{0.};
302 if (m_cfg.longitudinalDiffusionSigma == 0 || m_cfg.transverseDiffusionSigma == 0) {
303 // limit random number to be -1 < x < 1
304 do {
305 diffusion = CLHEP::RandGaussZiggurat::shoot(rndmEngine, 0.0, posY * m_cfg.transverseDiffusionSigma);
306 ATH_MSG_VERBOSE("getTransverseDiffusion() -- posY x transverseSigma: "<<(posY* m_cfg.transverseDiffusionSigma)<<" engine seed: "<<rndmEngine->getSeed()<<" "<<rndmEngine->flat()<<" diffusion "<<diffusion);
307 } while (std::abs(diffusion) > 1.);
308 return diffusion;
309 }
310 do {
311 diffusion = generateTransverseDiffusion(posY, rndmEngine);
312 ATH_MSG_VERBOSE("getTransverseDiffusion() -- posY: "<<posY<<" engine seed: "<<rndmEngine->getSeed()<<" "<<rndmEngine->flat()<<" diffusion "<<diffusion);
313 } while (std::abs(diffusion)> 1.);
314 return diffusion;
315}
316
317float MM_StripsResponseSimulation::getLongitudinalDiffusion(float posY, CLHEP::HepRandomEngine* rndmEngine) const {
318 float diffusion{0.};
319 // We only want random numbers between -5 and 5
320 do {
321 diffusion= CLHEP::RandGaussZiggurat::shoot(rndmEngine, 0.0, posY * m_cfg.longitudinalDiffusionSigma);
322 ATH_MSG_VERBOSE("getLongitudinalDiffusion() -- posY: "<<posY<<" engine seed: "<<rndmEngine->getSeed()<<" "<<rndmEngine->flat()<<" diffusion "<<diffusion);
323 } while (std::abs(diffusion) > 5);
324 return diffusion;
325}
326
327float MM_StripsResponseSimulation::getEffectiveCharge(CLHEP::HepRandomEngine* rndmEngine) const {
328 // charge fluctuatation is described by Polya function
329 // To calculate the gain from polya distibution we replace "alpha = 1+theta and beta = 1+theta/mean" in gamma PDF. With this gamma PDF
330 // gives us same sampling values as we get from polya PDF. Idea taken from MR 40547
331 return CLHEP::RandGamma::shoot(rndmEngine, 1. + polyaTheta, (1. + polyaTheta) / m_cfg.avalancheGain);
332}
333
334float MM_StripsResponseSimulation::getPathLengthTraveled(CLHEP::HepRandomEngine* rndmEngine) const {
335 // Probability of having an interaction (per unit length traversed) is sampled from a gaussian provided by G. Iakovidis
336 float rndGaus{0.};
337
338 // gaussian random number should be in the range from 0 to 10
339 do {
340 rndGaus = CLHEP::RandGaussZiggurat::shoot(rndmEngine, m_cfg.interactionDensityMean, m_cfg.interactionDensitySigma);
341 ATH_MSG_VERBOSE("getPathLengthTraveled() -- engine seed: "<<rndmEngine->getSeed()<<" "<<rndmEngine->flat()<<" rndGaus: "<<rndGaus);
342 } while (rndGaus < 0. || rndGaus > 10.) ;
343
344 return (1. / rndGaus) * -1. * std::log(CLHEP::RandFlat::shoot(rndmEngine));
345}
346
347/*******************************************************************************/
351/*******************************************************************************/
Scalar theta() const
theta method
#define ATH_MSG_VERBOSE(x)
#define ATH_MSG_DEBUG(x)
std::atomic_flag m_initialized ATLAS_THREAD_SAFE
Messaging initialized (initMessaging)
AthMessaging(IMessageSvc *msgSvc, const std::string &name)
Constructor.
double incomingAngleXZ() const
float eventTime() const
double positionWithinStrip() const
const Amg::Vector3D & magneticField() const
double incomingAngleYZ() const
std::vector< std::unique_ptr< MM_Electron > > & getElectrons()
const std::vector< std::vector< float > > & getTimeThresholdVec() const
void calculateSummaries(float chargeThreshold)
const std::vector< float > & getMaxChargeVec() const
int getNElectrons() const
const std::vector< float > & getTimeMaxChargeVec() const
void calculateTimeSeries(float thetaD, int gasgap)
void simulateCrossTalk(float crossTalk1, float crossTalk2)
const std::vector< std::vector< float > > & getTotalChargeVec() const
float totalCharge() const
const std::vector< int > & getStripVec() const
void whichStrips(DataCache &cache, const MM_DigitToolInput &digiInput, double gainFraction, double stripPitch, CLHEP::HepRandomEngine *rndmEngine) const
float getTransverseDiffusion(float posY, CLHEP::HepRandomEngine *rndmEngine) const
MM_StripToolOutput GetResponseFrom(const MM_DigitToolInput &digiInput, double gainFraction, double stripPitch, CLHEP::HepRandomEngine *rndmEngine) const
std::map< std::string, std::unique_ptr< TH2F > > m_mapOf2DHistograms
std::map< std::string, std::unique_ptr< TH1F > > m_mapOfHistograms
float getLongitudinalDiffusion(float posY, CLHEP::HepRandomEngine *rndmEngine) const
float getEffectiveCharge(CLHEP::HepRandomEngine *rndmEngine) const
float getPathLengthTraveled(CLHEP::HepRandomEngine *rndmEngine) const
float generateTransverseDiffusion(float posY, CLHEP::HepRandomEngine *rndmEngine) const
std::unique_ptr< CLHEP::RandGeneral > m_randNelectrons
static constexpr unsigned int s_NelectronPropBins
Class describing an electron.
Eigen::Matrix< double, 2, 1 > Vector2D
Eigen::Matrix< double, 3, 1 > Vector3D
STL namespace.
std::vector< std::vector< float > > finalqStrip
std::vector< std::vector< float > > finaltStrip
std::vector< std::unique_ptr< MM_IonizationCluster > > IonizationClusters