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