ATLAS Offline Software
Loading...
Searching...
No Matches
TRTProcessingOfStraw.cxx
Go to the documentation of this file.
1/*
2 Copyright (C) 2002-2026 CERN for the benefit of the ATLAS collaboration
3*/
4
6
7#include "TRTDigit.h"
8#include "TRTTimeCorrection.h"
10#include "TRTNoise.h"
11#include "TRTDigCondBase.h"
12
15#include "TRTDigSettings.h"
16#include "TRTDigiHelper.h"
17
18//TRT detector information:
22
23//helpers for identifiers and hitids (only for debug methods):
26
28
29// Units & Constants
30#include "CLHEP/Units/SystemOfUnits.h"
31#include "CLHEP/Units/PhysicalConstants.h"
32
36
37// For the Athena-based random numbers.
38#include "CLHEP/Random/RandPoisson.h" //randpoissonq? (fixme)
39#include "CLHEP/Random/RandFlat.h"
40#include "CLHEP/Random/RandBinomial.h"
41#include "CLHEP/Random/RandExpZiggurat.h"
42#include "CLHEP/Random/RandGaussZiggurat.h"
43#include <cmath>
44#include <cstdlib> //Always include this when including cmath!
45
46
47//________________________________________________________________________________
49 const InDetDD::TRT_DetectorManager* detmgr,
50 ITRT_PAITool* paitoolXe,
51 ITRT_SimDriftTimeTool* simdrifttool,
53 TRTNoise * noise,
54 TRTDigCondBase* digcond,
55 const TRT_ID* trt_id,
56 ITRT_PAITool* paitoolAr,
57 ITRT_PAITool* paitoolKr,
58 const ITRT_CalDbTool* calDbTool)
59
60: AthMessaging("TRTProcessingOfStraw"),
61 m_settings(digset),
62 m_detmgr(detmgr),
63 m_pPAItoolXe(paitoolXe),
64 m_pPAItoolAr(paitoolAr),
65 m_pPAItoolKr(paitoolKr),
66 m_pSimDriftTimeTool(simdrifttool),
67// m_time_y_eq_zero(0.0),
68// m_ComTime(NULL),
69 m_pTimeCorrection(nullptr),
71 m_pNoise(noise),
72 m_pDigConditions(digcond),
73 m_genData(std::make_unique<GenData>()),
75 m_id_helper(trt_id)
76
77{
78 Initialize(calDbTool);
79}
80
81//________________________________________________________________________________
86
87//________________________________________________________________________________
89{
90
91 m_useMagneticFieldMap = m_settings->useMagneticFieldMap();
92 m_signalPropagationSpeed = m_settings->signalPropagationSpeed() ;
93 m_attenuationLength = m_settings->attenuationLength();
94 m_useAttenuation = m_settings->useAttenuation();
95 m_innerRadiusOfStraw = m_settings->innerRadiusOfStraw();
96 m_outerRadiusOfWire = m_settings->outerRadiusOfWire();
97 m_timeCorrection = m_settings->timeCorrection(); // false for beamType='cosmics'
98 m_solenoidFieldStrength = m_settings->solenoidFieldStrength();
99
100 m_maxelectrons = 100; // 100 gives good Gaussian approximation
101
102 if (m_pPAItoolXe==nullptr) {
103 ATH_MSG_FATAL ( "TRT_PAITool for Xenon not defined! no point in continuing!" );
104 }
105 if (m_pPAItoolKr==nullptr) {
106 ATH_MSG_ERROR ( "TRT_PAITool for Krypton is not defined!!! Xenon TRT_PAITool will be used for Krypton straws!" );
108 }
109 if (m_pPAItoolAr==nullptr) {
110 ATH_MSG_ERROR ( "TRT_PAITool for Argon is not defined!!! Xenon TRT_PAITool will be used for Argon straws!" );
112 }
113
114 ATH_MSG_INFO ( "Xe barrel drift-time at r = 2 mm is " << m_pSimDriftTimeTool->getAverageDriftTime(2.0, 0.002*0.002, 0) << " ns." );
115 ATH_MSG_INFO ( "Kr barrel drift-time at r = 2 mm is " << m_pSimDriftTimeTool->getAverageDriftTime(2.0, 0.002*0.002, 1) << " ns." );
116 ATH_MSG_INFO ( "Ar barrel drift-time at r = 2 mm is " << m_pSimDriftTimeTool->getAverageDriftTime(2.0, 0.002*0.002, 2) << " ns." );
117
119
120 const double intervalBetweenCrossings(m_settings->timeInterval() / 3.);
121 m_minCrossingTime = - (intervalBetweenCrossings * 2. + 1.*CLHEP::ns);
122 m_maxCrossingTime = intervalBetweenCrossings * 3. + 1.*CLHEP::ns;
123 m_shiftOfZeroPoint = static_cast<double>( m_settings->numberOfCrossingsBeforeMain() ) * intervalBetweenCrossings;
124
125 // Tabulate exp(-dist/m_attenuationLength) as a function of dist = time*m_signalPropagationSpeed [0.0 mm, 1500 mm)
126 // otherwise we are doing an exp() for every cluster! > 99.9% of output digits are the same, saves 13% CPU time.
127 m_expattenuation.reserve(150);
128 for (unsigned int k=0; k<150; k++) {
129 double dist = 10.0*(k+0.5); // [5mm, 1415mm] max 5 mm error (sigma = 3 mm)
130 m_expattenuation.push_back(exp(-dist/m_attenuationLength));
131 }
132
133 //For the field effect on drifttimes in this class we will assume that the local x,y coordinates of endcap straws are such that the
134 //local y-direction is parallel to the global z-direction. As a sanity check against future code developments we will test this
135 //assumption on one straw from each endcap layer.
136
138 {
139 const InDetDD::TRT_Numerology * num = m_detmgr->getNumerology();
140 for (unsigned int iwheel = 0; iwheel < num->getNEndcapWheels(); ++iwheel)
141 {
142 for (unsigned int iside = 0; iside < 2; ++iside)
143 { //positive and negative endcap
144 for (unsigned int ilayer = 0; ilayer < num->getNEndcapLayers(iwheel); ++ilayer)
145 {
146 const InDetDD::TRT_EndcapElement * ec_element
147 = m_detmgr->getEndcapElement(iside,//positive or negative endcap
148 iwheel,//wheelIndex,
149 ilayer,//strawLayerIndex,
150 0);//phiIndex
151 //Local straw center and local straw point one unit along x:
152 if (!ec_element)
153 {
154 ATH_MSG_VERBOSE ( "Failed to retrieve endcap element for (iside,iwheel,ilayer)=("
155 << iside<<", "<<iwheel<<", "<<ilayer<<")." );
156 continue;
157 }
158 Amg::Vector3D strawcenter(0.0,0.0,0.0);
159 Amg::Vector3D strawx(1.0,0.0,0.0);
160 //Transform to global coordinate (use first straw in element):
161 strawcenter = ec_element->strawTransform(0) * strawcenter;
162 strawx = ec_element->strawTransform(0) * strawx;
163 const Amg::Vector3D v(strawx-strawcenter);
164 const double zcoordfrac((v.z()>0?v.z():-v.z())/v.mag());
165 if (zcoordfrac<0.98 || zcoordfrac > 1.02)
166 {
167 ATH_MSG_WARNING ( "Found endcap straw where the assumption that local x-direction"
168 <<" is parallel to global z-direction is NOT valid."
169 <<" Drift times will be somewhat off." );
170 }
171 }
172 }
173 }
174
175 //check local barrel coordinates at four positions.
176 for (unsigned int phi_it = 0; phi_it < 32; phi_it++)
177 {
178 const InDetDD::TRT_BarrelElement * bar_element = m_detmgr->getBarrelElement(1,1,phi_it,1);
179
180 Amg::Vector3D strawcenter(0.0,0.0,0.0);
181 Amg::Vector3D straw(cos((double)phi_it*2*M_PI/32.),sin((double)phi_it*2*M_PI/32.),0.0);
182 strawcenter = bar_element->strawTransform(0) * strawcenter;
183 straw = bar_element->strawTransform(0) * straw;
184 const Amg::Vector3D v(straw-strawcenter);
185 const double coordfrac(atan2(v.x(),v.y()));
186 if (coordfrac>0.2 || coordfrac < -0.2)
187 {
188 ATH_MSG_WARNING ( "Found barrel straw where the assumption that local y-direction"
189 <<" is along the straw is NOT valid."
190 <<" Drift times will be somewhat off." );
191 }
192 }
193 }
194
195 m_randBinomialXe = std::make_unique<CLHEP::RandBinomialFixedP>(nullptr, 1, m_settings->smearingFactor(0), m_maxelectrons);
196 m_randBinomialKr = std::make_unique<CLHEP::RandBinomialFixedP>(nullptr, 1, m_settings->smearingFactor(1), m_maxelectrons);
197 m_randBinomialAr = std::make_unique<CLHEP::RandBinomialFixedP>(nullptr, 1, m_settings->smearingFactor(2), m_maxelectrons);
198
199 ATH_MSG_VERBOSE ( "Initialization done" );
200}
201
202//________________________________________________________________________________
203void TRTProcessingOfStraw::addClustersFromStep ( double scaledKineticEnergy, double particleCharge,
204 double timeOfHit,
205 double prex, double prey, double prez,
206 double postx, double posty, double postz,
207 std::vector<cluster>& clusterlist, int strawGasType,
208 CLHEP::HepRandomEngine* rndmEngine,
209 CLHEP::HepRandomEngine* paiRndmEngine)
210{
211
212 // Choose the appropriate ITRT_PAITool for this straw
213 ITRT_PAITool* activePAITool;
214 if (strawGasType==0) { activePAITool = m_pPAItoolXe; }
215 else if (strawGasType==1) { activePAITool = m_pPAItoolKr; }
216 else if (strawGasType==2) { activePAITool = m_pPAItoolAr; }
217 else { activePAITool = m_pPAItoolXe; } // should never happen
218
219 //Differences and steplength:
220 const double deltaX(postx - prex);
221 const double deltaY(posty - prey);
222 const double deltaZ(postz - prez);
223 const double stepLength(sqrt(deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ));
224
225 const double meanFreePath(activePAITool->GetMeanFreePath( scaledKineticEnergy, particleCharge*particleCharge ));
226
227 //How many clusters did we actually create:
228 const unsigned int numberOfClusters(CLHEP::RandPoisson::shoot(rndmEngine,stepLength / meanFreePath));
229 //fixme: use RandPoissionQ?
230
231 //Position each of those randomly along the step, and use PAI to get their energies:
232 for (unsigned int iclus(0); iclus<numberOfClusters; ++iclus)
233 {
234 //How far along the step did the cluster get produced:
235 const double lambda(CLHEP::RandFlat::shoot(rndmEngine));
236
237 //Append cluster (the energy is given by the PAI model):
238 double clusE(activePAITool->GetEnergyTransfer(scaledKineticEnergy, paiRndmEngine));
239 clusterlist.emplace_back(clusE, timeOfHit,
240 prex + lambda * deltaX,
241 prey + lambda * deltaY,
242 prez + lambda * deltaZ);
243 }
244
245
246}
247
248//________________________________________________________________________________
250 const InDetDD::TRT_DetElementContainer* detElements,
253 TRTDigit& outdigit,
254 bool & alreadyPrintedPDGcodeWarning,
255 double cosmicEventPhase, // const ComTime* m_ComTime,
256 int strawGasType,
257 bool emulationArflag,
258 bool emulationKrflag,
259 CLHEP::HepRandomEngine* rndmEngine,
260 CLHEP::HepRandomEngine* elecProcRndmEngine,
261 CLHEP::HepRandomEngine* elecNoiseRndmEngine,
262 CLHEP::HepRandomEngine* paiRndmEngine)
263{
264
266 // We need the straw id several times in the following //
268 const int hitID((*i)->GetHitID());
269 unsigned int region(TRTDigiHelper::getRegion(hitID));
270 const bool isBarrel(region<3 );
271 //const bool isEC (!isBarrel);
272 //const bool isShort (region==1);
273 //const bool isLong (region==2);
274 const bool isECA (region==3);
275 const bool isECB (region==4);
276
278 //======================================================//
280 // First step is to loop over the simhits in this straw //
281 // and produce a list of primary ionisation clusters. //
282 // Each cluster needs the following information: //
283 // //
284 // * The total energy of the cluster. //
285 // * Its location in local straw (x,y,z) coordinates. //
286 // * The time the cluster was created. //
287 // //
289 //======================================================//
291 // TimeShift is the same for all the simhits in the straw.
292 const double timeShift(m_pTimeCorrection->TimeShift(hitID, detElements)); // rename hitID to strawID
293
294 m_clusterlist.clear();
295
296 //For magnetic field evaluation
297 Amg::Vector3D TRThitGlobalPos(0.0,0.0,0.0);
298
299 //Now we loop over all the simhits
300 for (hitCollConstIter hit_iter= i; hit_iter != e; ++hit_iter)
301 {
302 //Get the hit:
303 const TimedHitPtr<TRTUncompressedHit> *theHit = &(*hit_iter);
304
305 TRThitGlobalPos = getGlobalPosition(hitID, theHit, detElements);
306
307 //Figure out the global time of the hit (all clusters from the hit
308 //will get same timing).
309
310 double timeOfHit(0.0); // remains zero if timeCorrection is false (beamType='cosmics')
311 if (m_timeCorrection) {
312 const double globalHitTime(hitTime(*theHit));
313 const double globalTime = static_cast<double>((*theHit)->GetGlobalTime());
314 const double bunchCrossingTime(globalHitTime - globalTime);
315 if ( (bunchCrossingTime < m_minCrossingTime) || (bunchCrossingTime > m_maxCrossingTime) ) continue;
316 timeOfHit = m_shiftOfZeroPoint + globalHitTime - timeShift; //is this shiftofzeropoint correct pileup-wise?? [fixme]
317 }
318
319 //if (timeOfHit>125.0) continue; // Will give 3% speed up (but changes seeds!). Needs careful thought though.
320
321 //What kind of particle are we dealing with?
322 const int particleEncoding((*theHit)->GetParticleEncoding());
323
324 //Safeguard against pdgcode 0.
325 if (particleEncoding == 0)
326 {
327 //According to Andrea this is usually nuclear fragments from
328 //hadronic interactions. They therefore ought to be allowed to
329 //contribute, but we ignore them for now - until it can be studied
330 //that it is indeed safe to stop doing so
333 ATH_MSG_WARNING ( "Ignoring sim. particle with pdgcode 0. This warning is only shown once per job" );
334 }
335 continue;
336 }
337
338 // If it is a photon we assume it was absorbed entirely at the point of interaction.
339 // we simply deposit the entire energy into the last point of the sim. step.
340 if ( MC::isPhoton(particleEncoding) ) {
341
342 const double energyDeposit = (*theHit)->GetEnergyDeposit(); // keV (see comment below)
343 // Apply radiator efficiency "fudge factor" to ignore some TR photons (assuming they are over produced in the sim. step.
344 // The fraction removed is based on tuning pHT for electrons to data, after tuning pHT for muons (which do not produce TR).
345 // The efficiency is different for Xe, Kr and Ar. Avoid fudging non-TR photons (TR is < 30 keV).
346 // Also: for |eta|<0.5 apply parabolic scale; see "Parabolic Fudge" https://indico.cern.ch/event/304066/
347
348 if ( energyDeposit<30.0 ) {
349
350 // Improved Argon Emulation tuning October 2018 by Hassane Hamdaoui https://cds.cern.ch/record/2643784
351 // Previously 0.15, 0.28, 0.28
352 double ArEmulationScaling_BA = 0.05;
353 double ArEmulationScaling_ECA = 0.20;
354 double ArEmulationScaling_ECB = 0.20;
355
356 // ROUGH GUESSES RIGHT NOW
357 double KrEmulationScaling_BA = 0.20;
358 double KrEmulationScaling_ECA = 0.39;
359 double KrEmulationScaling_ECB = 0.39;
360
361 if (isBarrel) { // Barrel
362 double trEfficiencyBarrel = m_settings->trEfficiencyBarrel(strawGasType);
363 double hitx = TRThitGlobalPos[0];
364 double hity = TRThitGlobalPos[1];
365 double hitz = TRThitGlobalPos[2];
366 double hitEta = std::abs(log(tan(0.5*atan2(sqrt(hitx*hitx+hity*hity),hitz))));
367 if ( hitEta < 0.5 ) { trEfficiencyBarrel *= ( 0.833333+0.6666667*hitEta*hitEta ); }
368 // scale down the TR efficiency if we are emulating
369 if ( strawGasType == 0 && emulationArflag ) { trEfficiencyBarrel *= ArEmulationScaling_BA; }
370 if ( strawGasType == 0 && emulationKrflag ) { trEfficiencyBarrel *= KrEmulationScaling_BA; }
371 if ( CLHEP::RandFlat::shoot(rndmEngine) > trEfficiencyBarrel ) continue; // Skip this photon
372 } // close if barrel
373 else { // Endcap - no eta dependence here.
374 if (isECA) {
375 double trEfficiencyEndCapA = m_settings->trEfficiencyEndCapA(strawGasType);
376 // scale down the TR efficiency if we are emulating
377 if ( strawGasType == 0 && emulationArflag ) { trEfficiencyEndCapA *= ArEmulationScaling_ECA; }
378 if ( strawGasType == 0 && emulationKrflag ) { trEfficiencyEndCapA *= KrEmulationScaling_ECA; }
379 if ( CLHEP::RandFlat::shoot(rndmEngine) > trEfficiencyEndCapA ) continue; // Skip this photon
380 }
381 if (isECB) {
382 double trEfficiencyEndCapB = m_settings->trEfficiencyEndCapB(strawGasType);
383 // scale down the TR efficiency if we are emulating
384 if ( strawGasType == 0 && emulationArflag ) { trEfficiencyEndCapB *= ArEmulationScaling_ECB; }
385 if ( strawGasType == 0 && emulationKrflag ) { trEfficiencyEndCapB *= KrEmulationScaling_ECB; }
386 if ( CLHEP::RandFlat::shoot(rndmEngine) > trEfficiencyEndCapB ) continue; // Skip this photon
387 }
388 } // close else (end caps)
389 } // energyDeposit < 30.0
390
391 // Append this (usually highly energetic) cluster to the list:
392 m_clusterlist.emplace_back( energyDeposit*CLHEP::keV, timeOfHit, (*theHit)->GetPostStepX(), (*theHit)->GetPostStepY(), (*theHit)->GetPostStepZ() );
393
394 // Regarding the CLHEP::keV above: In TRT_G4_SD we converting the hits to keV,
395 // so here we convert them back to CLHEP units by multiplying by CLHEP::keV.
396 }
397 else if ( MC::isMonopole(particleEncoding)
398 || ( MC::isGenericMultichargedParticle(particleEncoding) && MC::charge(particleEncoding) > 10. )
399 ) {
400 //Special treatment of magnetic monopoles && highly charged Qballs (charge > 10)
401 m_clusterlist.emplace_back( (*theHit)->GetEnergyDeposit()*CLHEP::keV, timeOfHit, (*theHit)->GetPostStepX(), (*theHit)->GetPostStepY(), (*theHit)->GetPostStepZ() );
402 }
403 else { // It's not a photon, monopole or Qball with charge > 10, so we proceed with regular ionization using the PAI model
404
405 double particleCharge = MC::charge(particleEncoding);
406 double particleMass(0.);
407
408 const auto particleMassFromTable = m_genData->particleMass(abs(particleEncoding));
409 if (particleMassFromTable) {
410 particleMass = particleMassFromTable.value();
411 }
412 else {
413 // TODO Should we handle charged Geantinos gracefully here?
414 if (!MC::isNucleus(particleEncoding)) {
415 ATH_MSG_WARNING ( "Data for sim. particle with pdgcode "<<particleEncoding <<" is not a nucleus and could not be retrieved from GenData. Assuming mass of pion. Please investigate." );
417 }
418 else {
419 const int A(static_cast<int>(MC::baryonNumber(particleEncoding)));
420 const int Z(static_cast<int>(std::abs(MC::numberOfProtons(particleEncoding))));
421 static constexpr double Mp(ParticleConstants::protonMassInMeV);
422 static constexpr double Mn(ParticleConstants::neutronMassInMeV);
423 particleMass = std::abs( Z*Mp+(A-Z)*Mn );
424
425 if (!alreadyPrintedPDGcodeWarning) {
426 ATH_MSG_WARNING ( "Data for sim. particle with pdgcode "<<particleEncoding
427 <<" could not be retrieved from GenData (unexpected ion)."
428 <<" Please Investigate the PDGTABLE.MeV file."
429 <<" Calculating mass and charge from pdg code."
430 <<" The result is: Charge = "<<particleCharge<<" Mass = "<<particleMass<<"MeV" );
431 alreadyPrintedPDGcodeWarning = true;
432 }
433 }
434 }
435 // Abort if uncharged particle.
436 if (!particleCharge) { continue; }
437 // Abort if weird massless charged particle.
438 if (!particleMass) {
439 ATH_MSG_WARNING ( "Ignoring ionization from particle with pdg code "<<particleEncoding
440 <<" since it appears to be a massless charged particle. Please investigate." );
441 continue;
442 }
443
444 //We are now in the most likely case: A normal ionizing
445 //particle. Using the PAI model we are going to distribute
446 //ionization clusters along the step taken by the sim particle.
447
448 const double scaledKineticEnergy( static_cast<double>((*theHit)->GetKineticEnergy()) * ( CLHEP::proton_mass_c2 / particleMass ));
449
450 addClustersFromStep ( scaledKineticEnergy, particleCharge, timeOfHit,
451 (*theHit)->GetPreStepX(),(*theHit)->GetPreStepY(),(*theHit)->GetPreStepZ(),
452 (*theHit)->GetPostStepX(),(*theHit)->GetPostStepY(),(*theHit)->GetPostStepZ(),
453 m_clusterlist, strawGasType, rndmEngine, paiRndmEngine);
454
455 }
456 }//end of hit loop
457
459 //======================================================//
461 // Second step is, using the cluster list along with //
462 // gas and wire properties, to create a list of energy //
463 // deposits (i.e. potential fluctuations) reaching the //
464 // frontend electronics. Each deposit needs: //
465 // //
466 // * The energy of the deposit. //
467 // * The time it arrives at the FE electronics //
468 // //
470 //======================================================//
472
473 m_depositList.clear();
474
475 ClustersToDeposits(fieldCache, hitID, m_clusterlist, m_depositList, TRThitGlobalPos, cosmicEventPhase, strawGasType, rndmEngine );
476
477
479 //======================================================//
481 // The third and final step is to simulate how the FE //
482 // turns the results into an output digit. This //
483 // includes the shaping/amplification and subsequent //
484 // discrimination as well as addition of noise. //
485 // //
487 //======================================================//
489
490 //If no deposits, and no electronics noise we might as well stop here:
491 if ( m_depositList.empty() && !m_pNoise )
492 {
493 outdigit = TRTDigit(hitID, 0);
494 return;
495 }
496
497 //Get straw conditions data:
498 double lowthreshold, noiseamplitude;
499 if (m_settings->noiseInSimhits()) {
500 m_pDigConditions->getStrawData( hitID, lowthreshold, noiseamplitude );
501 } else {
502 lowthreshold = isBarrel ? m_settings->lowThresholdBar(strawGasType) : m_settings->lowThresholdEC(strawGasType);
503 noiseamplitude = 0.0;
504 }
505
506 //Electronics processing:
507 m_pElectronicsProcessing->ProcessDeposits( m_depositList, hitID, outdigit, lowthreshold, noiseamplitude, strawGasType, elecProcRndmEngine, elecNoiseRndmEngine );
508}
509
510//________________________________________________________________________________
512 const std::vector<cluster>& clusters,
513 std::vector<TRTElectronicsProcessing::Deposit>& deposits,
514 const Amg::Vector3D& TRThitGlobalPos,
515 double cosmicEventPhase, // was const ComTime* m_ComTime,
516 int strawGasType,
517 CLHEP::HepRandomEngine* rndmEngine)
518{
519
520 //
521 // Some initial work before looping over the cluster
522 //
523
524 deposits.clear();
525
526 unsigned int region(TRTDigiHelper::getRegion(hitID));
527 const bool isBarrel(region<3 );
528 const bool isEC (!isBarrel);
529 const bool isShort (region==1);
530 const bool isLong (region==2);
531 //const bool isECA (region==3);
532 //const bool isECB (region==4);
533
534 CLHEP::RandBinomialFixedP *randBinomial{};
535 if (strawGasType == 0) { randBinomial = m_randBinomialXe.get(); }
536 else if (strawGasType == 1) { randBinomial = m_randBinomialKr.get(); }
537 else if (strawGasType == 2) { randBinomial = m_randBinomialAr.get(); }
538 else { randBinomial = m_randBinomialXe.get(); } // should never happen
539
540 double ionisationPotential = m_settings->ionisationPotential(strawGasType);
541 double smearingFactor = m_settings->smearingFactor(strawGasType);
542
543 std::vector<cluster>::const_iterator currentClusterIter(clusters.begin());
544 const std::vector<cluster>::const_iterator endOfClusterList(clusters.end());
545
546 // if (m_settings->doCosmicTimingPit()) {
547 // if (m_ComTime) { m_time_y_eq_zero = m_ComTime->getTime(); }
548 // else { ATH_MSG_WARNING("Configured to use ComTime tool, but did not find tool. All hits will get the same t0"); }
549 // }
550
551 // The average drift time is affected by the magnetic field which is not uniform so we need to use the
552 // detailed magnetic field map to obtain an effective field for this straw (used in SimDriftTimeTool).
553 // This systematically affects O(ns) drift times in the end cap, but has a very small effect in the barrel.
554 Amg::Vector3D globalPosition;
555 Amg::Vector3D mField;
556 double map_x2(0.),map_y2(0.),map_z2(0.);
557 double effectiveField2(0.); // effective field squared.
558
559 // Magnetic field is the same for all clusters in the straw so this can be called before the cluster loop.
561 {
562 globalPosition[0]=TRThitGlobalPos[0]*CLHEP::mm;
563 globalPosition[1]=TRThitGlobalPos[1]*CLHEP::mm;
564 globalPosition[2]=TRThitGlobalPos[2]*CLHEP::mm;
565
566 // MT Field cache is stored in cache
567 fieldCache.getField (globalPosition.data(), mField.data());
568
569 map_x2 = mField.x()*mField.x(); // would be zero for a uniform field
570 map_y2 = mField.y()*mField.y(); // would be zero for a uniform field
571 map_z2 = mField.z()*mField.z(); // would be m_solenoidfieldstrength^2 for uniform field
572 }
573
574 // Now we are ready to loop over clusters and form timed energy deposits at the wire:
575 //
576 // 1. First determine the number of surviving drift electrons and the energy of their deposits.
577 // 2. Then determine the timing of these deposits.
578
579 // Notes
580 //
581 // 1) It turns out that the total energy deposited (Ed) is, on average, equal to the cluster energy (Ec):
582 // <Ed> = m_ionisationPotential/m_smearingFactor * Ec/m_ionisationPotential * m_smearingFactor = Ec.
583 // Actually it exceeds this *slightly* due to the +1 electron in the calculation of nprimaryelectrons below.
584 // The energy processing below therefore exists only to model the energy *fluctuations*.
585 //
586 // 2) Gaussian approximation (for photons, HIPs etc):
587 // For large Ec (actually nprimaryelectrons > 99) a much faster Gaussian shoot replaces
588 // the Binomial and many exponential shoots. The Gaussian pdf has a mean of Ec and variance
589 // given by sigma^2 = Ec*ionisationPotential*(2-smearingFactor)/smearingFactor. That expression
590 // comes from the sum of the variances from the Binomial and Exponential. In this scheme it is
591 // sufficient (for diffusion) to divided the energy equally amongst 100 (maxelectrons) electrons.
592
593 // straw radius
594 const double wire_r2 = m_outerRadiusOfWire*m_outerRadiusOfWire;
595 const double straw_r2 = m_innerRadiusOfStraw*m_innerRadiusOfStraw;
596
597 // Cluster loop
598 for (;currentClusterIter!=endOfClusterList;++currentClusterIter)
599 {
600 // Get the cluster radius and energy.
601 const double cluster_x(currentClusterIter->xpos);
602 const double cluster_y(currentClusterIter->ypos);
603 const double cluster_z(this->setClusterZ(currentClusterIter->zpos, isLong, isShort, isEC));
604 const double cluster_x2(cluster_x*cluster_x);
605 const double cluster_y2(cluster_y*cluster_y);
606 double cluster_r2(cluster_x2+cluster_y2);
607
608 // These may never occur, but could be very problematic for getAverageDriftTime(), so check and correct this now.
609 if (cluster_r2<wire_r2) cluster_r2=wire_r2; // Compression may (v. rarely) cause r to be smaller than the wire radius. If r=0 then NaN's later!
610 if (cluster_r2>straw_r2) cluster_r2=straw_r2; // Should never occur
611
612 const double cluster_r(std::sqrt(cluster_r2)); // cluster radius
613 const double cluster_E(currentClusterIter->energy); // cluster energy
614 const unsigned int nprimaryelectrons( static_cast<unsigned int>( cluster_E / ionisationPotential + 1.0 ) );
615
616 // Determine the number of surviving electrons and their energy sum.
617 // If nprimaryelectrons > 100 then a Gaussian approximation is good (and much quicker)
618
619 double depositEnergy(0.); // This will be the energy deposited at the wire.
620
621 if (nprimaryelectrons<m_maxelectrons) // Use the detailed Binomial and Exponential treatment at this low energy.
622 {
623 unsigned int nsurvivingprimaryelectrons = static_cast<unsigned int>(randBinomial->fire(rndmEngine,nprimaryelectrons) + 0.5);
624 if (nsurvivingprimaryelectrons==0) continue; // no electrons survived; move on to the next cluster.
625 const double meanElectronEnergy(ionisationPotential / smearingFactor);
626 for (unsigned int ielec(0); ielec<nsurvivingprimaryelectrons; ++ielec) {
627 depositEnergy += CLHEP::RandExpZiggurat::shoot(rndmEngine, meanElectronEnergy);
628 }
629 }
630 else // Use a Gaussian approximation
631 {
632 const double fluctSigma(sqrt(cluster_E * ionisationPotential * (2 - smearingFactor) / smearingFactor));
633 do {
634 depositEnergy = CLHEP::RandGaussZiggurat::shoot(rndmEngine, cluster_E, fluctSigma);
635 } while(depositEnergy<0.0); // very rare.
636 }
637
638 // Now we have the depositEnergy, we need to work out the timing and attenuation
639
640 // First calculate the "effective field" (it's never negative):
641 // Electron drift time is prolonged by the magnetic field according to an "effective field".
642 // For the endcap, the effective field is dependent on the relative (x,y) position of the cluster.
643 // It is assumed here (checked in initialize) that the local straw x-direction is parallel with
644 // the solenoid z-direction. After Garfield simulations and long thought it was found that only
645 // field perpendicular to the electron drift is of importance.
646
647 if (!isBarrel) // Endcap
648 {
649 if (m_useMagneticFieldMap) { // Using magnetic field map
650 effectiveField2 = map_z2*cluster_y2/cluster_r2 + map_x2 + map_y2;
651 }
652 else { // Not using magnetic field map (you really should not do this!):
653 effectiveField2 = m_solenoidFieldStrength*m_solenoidFieldStrength * cluster_y2 / cluster_r2;
654 }
655 }
656 else // Barrel
657 {
658 if (m_useMagneticFieldMap) { // Using magnetic field map (here bug #91830 is corrected)
659 effectiveField2 = map_z2 + (map_x2+map_y2)*cluster_y2/cluster_r2;
660 }
661 else { // Without the mag field map (very small change in digi output)
663 }
664 }
665
666 // If there is no field we might need to reset effectiveField2 to zero.
667 if (m_solenoidFieldStrength == 0. ) effectiveField2=0.;
668
669 // Now we need the deposit time which is the sum of three components:
670 // 1. Time of the hit(cluster): clusterTime.
671 // 2. Drift times: either commondrifttime, or diffused m_drifttimes
672 // 3. Wire propagation times: timedirect and timereflect.
673
674 // get the time of the hit(cluster)
675 double clusterTime(currentClusterIter->time);
676
677 if ( m_settings->doCosmicTimingPit() )
678 { // make (x,y) dependent? i.e: + f(x,y).
679 // clusterTime = clusterTime - m_time_y_eq_zero + m_settings->jitterTimeOffset()*( CLHEP::RandFlat::shoot(rndmEngine) );
680 clusterTime = clusterTime + cosmicEventPhase + m_settings->jitterTimeOffset()*( CLHEP::RandFlat::shoot(rndmEngine) );
681 // yes it is a '+' now. Ask Alex Alonso.
682 }
683
684 // get the wire propagation times (for direct and reflected signals)
685 double timedirect(0.), timereflect(0.);
686 m_pTimeCorrection->PropagationTime( hitID, cluster_z, timedirect, timereflect );
687
688 // While we have the propagation times, we can calculate the exponential attenuation factor
689 double expdirect(1.0), expreflect(1.0); // Initially set to "no attenuation".
691 {
692 //expdirect = exp( -timedirect *m_signalPropagationSpeed / m_attenuationLength);
693 //expreflect = exp( -timereflect*m_signalPropagationSpeed / m_attenuationLength);
694 // Tabulating exp(-dist/m_attenuationLength) with only 150 elements: index [0,149].
695 // > 99.9% of output digits are the same, saves 13% CPU time.
696 // Distances the signal propagate along the wire:
697 // * distdirect is rarely negative (<0.2%) by ~ mm. In such cases there is
698 // no attenuation, which is equivalent to distdirect=0 and so is good.
699 // * distreflect is always +ve and less than 1500, and so is good.
700 // The code is protected against out of bounds in any case.
701 // But need to explicitly make sure that the argument of the cast is
702 // positive; otherwise, we'll see FPEs on arm.
703 const double distdirect = timedirect *m_signalPropagationSpeed;
704 const double distreflect = timereflect*m_signalPropagationSpeed;
705 const unsigned int kdirect = static_cast<unsigned int>(std::max(distdirect,0.0)/10);
706 const unsigned int kreflect = static_cast<unsigned int>(distreflect/10);
707 if (kdirect<150) expdirect = m_expattenuation[kdirect]; // otherwise there
708 if (kreflect<150) expreflect = m_expattenuation[kreflect]; // is no attenuation.
709 }
710
711 // Finally, deposit the energy on the wire using the drift-time tool (diffusion is no longer available).
712 double commondrifttime = m_pSimDriftTimeTool->getAverageDriftTime(cluster_r,effectiveField2,strawGasType);
713 double dt = clusterTime + commondrifttime;
714 deposits.emplace_back(0.5*depositEnergy*expdirect, timedirect+dt);
715 deposits.emplace_back(0.5*depositEnergy*expreflect, timereflect+dt);
716
717 } // end of cluster loop
718
719 }
720
721//________________________________________________________________________________
723 , const TimedHitPtr<TRTUncompressedHit>* theHit
724 , const InDetDD::TRT_DetElementContainer* detElements)
725{
726
727 const int mask(0x0000001F);
728 int word_shift(5);
729 int trtID, ringID, moduleID, layerID, strawID;
730 int wheelID, planeID, sectorID;
731
732 const InDetDD::TRT_BarrelElement *barrelElement;
733 const InDetDD::TRT_EndcapElement *endcapElement;
734
735 if ( !(hitID & 0x00200000) ) { // barrel
736
737 strawID = hitID & mask;
738 hitID >>= word_shift;
739 layerID = hitID & mask;
740 hitID >>= word_shift;
741 moduleID = hitID & mask;
742 hitID >>= word_shift;
743 ringID = hitID & mask;
744 trtID = hitID >> word_shift;
745
746 barrelElement = detElements->getBarrelDetElement(trtID, ringID, moduleID, layerID);
747
748 if (barrelElement) {
749 const Amg::Vector3D v( (*theHit)->GetPreStepX(),(*theHit)->GetPreStepY(),(*theHit)->GetPreStepZ());
750 return barrelElement->strawTransform(strawID)*v;
751 }
752
753 } else { // endcap
754
755 strawID = hitID & mask;
756 hitID >>= word_shift;
757 planeID = hitID & mask;
758 hitID >>= word_shift;
759 sectorID = hitID & mask;
760 hitID >>= word_shift;
761 wheelID = hitID & mask;
762 trtID = hitID >> word_shift;
763
764 // change trtID (which is 2/3 for endcaps) to use 0/1 in getEndcapElement
765 if (trtID == 3) trtID = 0;
766 else trtID = 1;
767
768 endcapElement = detElements->getEndcapDetElement(trtID, wheelID, planeID, sectorID);
769
770 if ( endcapElement ) {
771 const Amg::Vector3D v( (*theHit)->GetPreStepX(),(*theHit)->GetPreStepY(),(*theHit)->GetPreStepZ());
772 return endcapElement->strawTransform(strawID)*v;
773 }
774
775 }
776
777 ATH_MSG_WARNING ( "Could not find global coordinate of a straw - drifttime calculation will be inaccurate" );
778 return {0.0,0.0,0.0};
779
780}
781
782
783double TRTProcessingOfStraw::setClusterZ(double cluster_z_in, bool isLong, bool isShort, bool isEC) const {
784 double cluster_z(cluster_z_in);
785
786 // The active gas volume along the straw z-axis is: Barrel long +-349.315 mm; Barrel short +-153.375 mm; End caps +-177.150 mm.
787 // Here we give a warning for clusters that are outside of the straw gas volume in in z. Since T/P version 3 cluster z values
788 // can go several mm outside these ranges; 30 mm is plenty allowance in the checks below.
789 const double longBarrelStrawHalfLength(349.315*CLHEP::mm);
790 const double shortBarrelStrawHalfLength(153.375*CLHEP::mm);
791 const double EndcapStrawHalfLength(177.150*CLHEP::mm);
792 if ( isLong && std::abs(cluster_z)>longBarrelStrawHalfLength+30 ) {
793 double d = cluster_z<0 ? cluster_z+longBarrelStrawHalfLength : cluster_z-longBarrelStrawHalfLength;
794 ATH_MSG_WARNING ("Long barrel straw cluster is outside the active gas volume z = +- 349.315 mm by " << d << " mm.");
795 ATH_MSG_WARNING ("Setting cluster_z = 0.0");
796 cluster_z = 0.0;
797 }
798 if ( isShort && std::abs(cluster_z)>shortBarrelStrawHalfLength+30 ) {
799 double d = cluster_z<0 ? cluster_z+shortBarrelStrawHalfLength : cluster_z-shortBarrelStrawHalfLength;
800 ATH_MSG_WARNING ("Short barrel straw cluster is outside the active gas volume z = +- 153.375 mm by " << d << " mm.");
801 ATH_MSG_WARNING ("Setting cluster_z = 0.0");
802 cluster_z = 0.0;
803 }
804 if ( isEC && std::abs(cluster_z)>EndcapStrawHalfLength+30 ) {
805 double d = cluster_z<0 ? cluster_z+EndcapStrawHalfLength : cluster_z-EndcapStrawHalfLength;
806 ATH_MSG_WARNING ("End cap straw cluster is outside the active gas volume z = +- 177.150 mm by " << d << " mm.");
807 ATH_MSG_WARNING ("Setting cluster_z = 0.0");
808 cluster_z = 0.0;
809 }
810 return cluster_z;
811}
float hitTime(const AFP_SIDSimHit &hit)
#define M_PI
#define ATH_MSG_ERROR(x)
#define ATH_MSG_FATAL(x)
#define ATH_MSG_INFO(x)
#define ATH_MSG_VERBOSE(x)
#define ATH_MSG_WARNING(x)
ATLAS-specific HepMC functions.
A number of constexpr particle constants to avoid hardcoding them directly in various places.
This is an Identifier helper class for the TRT subdetector.
AthMessaging(IMessageSvc *msgSvc, const std::string &name)
Constructor.
GenData is a class for particle data access.
Definition GenData.h:30
abstract interface to TRT calibration constants
Give and AlgTool interface to the PAI model.
virtual double GetMeanFreePath(double scaledKineticEnergy, double squaredCharge) const =0
GetMeanFreePath.
virtual double GetEnergyTransfer(double scaledKineticEnergy, CLHEP::HepRandomEngine *rndmEngine) const =0
GetEnergyTransfer.
Extended TRT_BaseElement to describe a TRT readout element, this is a planar layer with n ( order of ...
const Amg::Transform3D & strawTransform(unsigned int straw) const
Straw transform - fast access in array, in Tracking frame: Amg.
Class to hold different TRT detector elements structures.
const TRT_EndcapElement * getEndcapDetElement(unsigned int positive, unsigned int wheelIndex, unsigned int strawLayerIndex, unsigned int phiIndex) const
const TRT_BarrelElement * getBarrelDetElement(unsigned int positive, unsigned int moduleIndex, unsigned int phiIndex, unsigned int strawLayerIndex) const
The Detector Manager for all TRT Detector elements, it acts as the interface to the detector elements...
Extended class of a TRT_BaseElement to describe a readout elment in the endcap.
Helper class to organize the straw elements on TRT readout elements.
Local cache for magnetic field (based on MagFieldServices/AtlasFieldSvcTLS.h).
void getField(const double *ATH_RESTRICT xyz, double *ATH_RESTRICT bxyz, double *ATH_RESTRICT deriv=nullptr)
get B field value at given position xyz[3] is in mm, bxyz[3] is in kT if deriv[9] is given,...
Communication with CondDB.
Class containing parameters and settings used by TRT digitization.
Class for TRT digits.
Definition TRTDigit.h:11
Simulation of noise hits in the TRT.
Definition TRTNoise.h:39
Amg::Vector3D getGlobalPosition(int hitID, const TimedHitPtr< TRTUncompressedHit > *theHit, const InDetDD::TRT_DetElementContainer *detElements)
std::unique_ptr< CLHEP::RandBinomialFixedP > m_randBinomialXe
TRTDigCondBase * m_pDigConditions
std::unique_ptr< CLHEP::RandBinomialFixedP > m_randBinomialKr
std::vector< cluster > m_clusterlist
std::unique_ptr< CLHEP::RandBinomialFixedP > m_randBinomialAr
void ClustersToDeposits(MagField::AtlasFieldCache &fieldCache, int hitID, const std::vector< cluster > &clusters, std::vector< TRTElectronicsProcessing::Deposit > &deposits, const Amg::Vector3D &TRThitGlobalPos, double m_cosmicEventPhase, int strawGasType, CLHEP::HepRandomEngine *rndmEngine)
Transform the ioniation clusters along the particle trajectory inside a straw to energy deposits (i....
TRTElectronicsProcessing * m_pElectronicsProcessing
void Initialize(const ITRT_CalDbTool *)
Initialize.
std::vector< TRTElectronicsProcessing::Deposit > m_depositList
std::vector< double > m_expattenuation
bool m_timeCorrection
Time to be corrected for flight and wire propagation delays false when beamType='cosmics'.
void addClustersFromStep(double scaledKineticEnergy, double particleCharge, double timeOfHit, double prex, double prey, double prez, double postx, double posty, double postz, std::vector< cluster > &clusterlist, int strawGasType, CLHEP::HepRandomEngine *rndmEngine, CLHEP::HepRandomEngine *paiRndmEngine)
This is the main function for re-simulation of the ionisation in the active gas via the PAI model.
TimedHitCollection< TRTUncompressedHit >::const_iterator hitCollConstIter
void ProcessStraw(MagField::AtlasFieldCache &fieldCache, const InDetDD::TRT_DetElementContainer *detElements, hitCollConstIter i, hitCollConstIter e, TRTDigit &outdigit, bool &m_alreadyPrintedPDGcodeWarning, double m_cosmicEventPhase, int strawGasType, bool emulationArflag, bool emulationKrflag, CLHEP::HepRandomEngine *rndmEngine, CLHEP::HepRandomEngine *elecProcRndmEngine, CLHEP::HepRandomEngine *elecNoiseRndmEngine, CLHEP::HepRandomEngine *paiRndmEngine)
Process this straw all the way from Geant4 hit to output digit.
std::unique_ptr< GenData > m_genData
TRTTimeCorrection * m_pTimeCorrection
const TRTDigSettings * m_settings
TRTProcessingOfStraw(const TRTDigSettings *, const InDetDD::TRT_DetectorManager *, ITRT_PAITool *, ITRT_SimDriftTimeTool *, TRTElectronicsProcessing *ep, TRTNoise *noise, TRTDigCondBase *digcond, const TRT_ID *, ITRT_PAITool *=nullptr, ITRT_PAITool *=nullptr, const ITRT_CalDbTool *=nullptr)
Constructor: Calls Initialize method.
ITRT_SimDriftTimeTool * m_pSimDriftTimeTool
const InDetDD::TRT_DetectorManager * m_detmgr
double setClusterZ(double cluster_z_in, bool isLong, bool isShort, bool isEC) const
Time correction.
This is an Identifier helper class for the TRT subdetector.
Definition TRT_ID.h:84
a smart pointer to a hit that also provides access to the extended timing info of the host event.
Definition TimedHitPtr.h:18
Eigen::Matrix< double, 3, 1 > Vector3D
int numberOfProtons(const T &p)
bool isGenericMultichargedParticle(const T &p)
In addition, there is a need to identify ”Q-ball” and similar very exotic (multi-charged) particles w...
bool isPhoton(const T &p)
bool isMonopole(const T &p)
PDG rule 11i Magnetic monopoles and dyons are assumed to have one unit of Dirac monopole charge and a...
double charge(const T &p)
bool isNucleus(const T &p)
PDG rule 16 Nuclear codes are given as 10-digit numbers ±10LZZZAAAI.
double baryonNumber(const T &p)
constexpr double protonMassInMeV
the mass of the proton (in MeV)
constexpr double chargedPionMassInMeV
the mass of the charged pion (in MeV)
constexpr double neutronMassInMeV
the mass of the neutron (in MeV)
unsigned int getRegion(int hitID)
STL namespace.
hold the test vectors and ease the comparison