ATLAS Offline Software
Loading...
Searching...
No Matches
MuonPhaseII/MuonDigitization/sTgcDigitizationR4/src/sTgcDigitMaker.cxx
Go to the documentation of this file.
1/*
2 Copyright (C) 2002-2025 CERN for the benefit of the ATLAS collaboration
3*/
4
7#include "CLHEP/Units/SystemOfUnits.h"
8#include "CLHEP/Random/RandomEngine.h"
9#include "CLHEP/Random/RandFlat.h"
10#include "CLHEP/Random/RandGaussZiggurat.h"
11#include "CLHEP/Random/RandGamma.h"
13
14#include "Acts/Utilities/detail/Polynomials.hpp"
15
16#include "TF1.h"
17#include <cmath>
18#include <iostream>
19#include <fstream>
20
21namespace MuonR4{
22//---------------------------------------------------
23// Constructor and Destructor
24//---------------------------------------------------
26
28 digitMode mode,
29 double meanGasGain,
30 bool doPadChargeSharing)
31 : AthMessaging("sTgcDigitMaker"),
32 m_detMgr{detMgr},
33 m_digitMode(mode),
34 m_meanGasGain{meanGasGain},
35 m_doPadSharing{doPadChargeSharing}{}
36
38
39//------------------------------------------------------
40// Initialize digitization parameters
41//------------------------------------------------------
43 // Read arrival time data
45
46 // Read strip time correction if enabled
49 }
50
51 return StatusCode::SUCCESS;
52}
53
54//------------------------------------------------------
55// Compute ionization point for a hit
56//------------------------------------------------------
58 const DigiConditions& condContainers,
59 Ionization& ionization) const {
60 const MuonGMR4::sTgcReadoutElement* reEle = m_detMgr->getsTgcReadoutElement(hit->identify());
61
62 // Projecting the hit position on wire surface
63 const Amg::Vector3D locHitDir = xAOD::toEigen(hit->localDirection());
64 const Amg::Vector3D locHitPos = xAOD::toEigen(hit->localPosition());
65 ATH_MSG_VERBOSE("sTgc hit: time " << hit->globalTime()
66 << " position " << Amg::toString(locHitPos, 2) << " direction" << Amg::toString(locHitDir, 2)
67 << " mclink " << hit->genParticleLink() << " PDG ID " << hit->pdgId() );
68
69 Amg::Vector3D hitOnWireSurf = locHitPos;
70 const double scale = Amg::intersect<3>(locHitPos, locHitDir, Amg::Vector3D::UnitZ(), 0.).value_or(0);
71 const double halfStep = 0.5 * hit->stepLength();
72
73 // If Geant step length is smaller than the scale to wire plane, use the steplength
74 // to find the closest point to the wire plane.
75 if (std::abs(scale) <= halfStep) {
76 hitOnWireSurf = locHitPos + scale * locHitDir;
77 }
78 else {
79 hitOnWireSurf = locHitPos + Acts::copySign(halfStep, scale) * locHitDir;
80 }
81
82 const Identifier hitId = hit->identify();
83 const IdentifierHash wireLayHash = reEle->createHash(m_idHelper.gasGap(hitId),
84 ReadoutChannelType::Wire, 1);
85 const MuonGMR4::WireGroupDesign& wireDesign{reEle->wireDesign(wireLayHash)};
86 const MuonGMR4::StripLayer& stripLayer = reEle->stripLayer(wireLayHash);
87 const Amg::Vector2D hitOnWire2D = stripLayer.to2D(hitOnWireSurf, true);
88
89 if(!wireDesign.insideTrapezoid(hitOnWire2D)) {
90 return false;
91 }
92
93 // Check to see if a valid wire exists for the hit on wire surface, if not
94 // revert back to hit position, prestep and poststep, and check if there is a
95 // wire close to it, else, skip the hit.
96 std::pair<int, int> wireGrpWireNum = wireDesign.wireNumber(hitOnWire2D);
97
98 // Check if hit position gives valid wire group number
99 if (wireGrpWireNum.first < 0) {
100 const Amg::Vector2D locHitPos2D = stripLayer.to2D(locHitPos, true);
101 wireGrpWireNum = wireDesign.wireNumber(locHitPos2D);
102
103 // Check if prestep position gives valid wire group number
104 if (wireGrpWireNum.first < 0) {
105 const Amg::Vector3D preStepPos = locHitPos - halfStep * locHitDir;
106 const Amg::Vector2D preStepPos2D = stripLayer.to2D(preStepPos, true);
107 wireGrpWireNum = wireDesign.wireNumber(preStepPos2D);
108
109 // Check if poststep position gives valid wire group number
110 if (wireGrpWireNum.first < 0) {
111 const Amg::Vector3D postStepPos = locHitPos + halfStep * locHitDir;
112 const Amg::Vector2D postStepPos2D = stripLayer.to2D(postStepPos, true);
113 wireGrpWireNum = wireDesign.wireNumber(postStepPos2D);
114
115 // else return empty digit
116 if (wireGrpWireNum.first < 0) {
117 ATH_MSG_WARNING(__func__<<"() "<<__LINE__<<" - Unable to retrieve the wire number, skipping the hit: "
118 << m_idHelperSvc->toString(hitId)<<" @"<<Amg::toString(locHitPos2D));
119 return false;
120 }
121 }
122 }
123 }
124 int wireNumber = wireDesign.numPitchesToGroup(wireGrpWireNum.first) + wireGrpWireNum.second;
125 const int numWires = wireDesign.nAllWires();
126
127 if((wireNumber < 1) || (wireNumber > numWires)) {
128 ATH_MSG_WARNING(__func__<<"() "<<__LINE__<<" - Unable to retrieve the wire number, skipping the hit: "
129 << m_idHelperSvc->toString(hitId)<<" @"<<Amg::toString(hitOnWire2D));
130 return false;
131 }
132
133 // Compute the position of the ionization and its distance to the closest wire
134 ionization = pointClosestApproach(stripLayer, wireNumber, locHitPos, locHitDir, hit->stepLength());
135 double distToWire = ionization.distance;
136
137 if(distToWire > 0.) {
138 // Determine on which side of the wire does the particle cross
139 int adjacent = Acts::copySign(1, ionization.posOnSegment.y() - ionization.posOnWire.y());
140
141 Ionization ionizationAdj = pointClosestApproach(stripLayer, wireNumber+adjacent, locHitPos, locHitDir, hit->stepLength());
142 double distToWireAdj = ionizationAdj.distance;
143
144 if ((distToWireAdj > 0.) && (distToWireAdj < distToWire)) {
145 distToWire = distToWireAdj;
146 wireNumber += adjacent;
147 ionization = std::move(ionizationAdj);
148 }
149 } else {
150 ATH_MSG_DEBUG("Failed to get the distance between the wire " << wireNumber << " and hit " << Amg::toString(hitOnWire2D, 2));
151 return false;
152 }
153
154 // Do not digitize hits that are too far from the nearest wire
155 if (distToWire > wireDesign.stripPitch()) {
156 ATH_MSG_DEBUG("Distance to nearest wire: " << distToWire << " greater than wirePitch.");
157 return false;
158 }
159
160 // Get the gamma pdf parameters and calculate digit time
161 const GammaParameter gamParam = getGammaParameter(distToWire);
162 const double most_prob_time = getMostProbableArrivalTime(distToWire);
163 const double gamma_mpv = std::max((gamParam.kParameter - 1) * gamParam.thetaParameter, 0.);
164 const double t0_par = most_prob_time - gamma_mpv;
165 const double inv_theta = 1./gamParam.thetaParameter;
166
167 double digitTime = t0_par + CLHEP::RandGamma::shoot(condContainers.rndEngine, gamParam.kParameter, inv_theta);
168
169 constexpr unsigned shoot_limit = 4;
170 unsigned shoot_counter = 0;
171 while (digitTime < 0. && ++shoot_counter <= shoot_limit) {
172 digitTime = t0_par + CLHEP::RandGamma::shoot(condContainers.rndEngine, gamParam.kParameter,inv_theta);
173 }
174
175 ionization.time = std::max(0., digitTime);
176 return true;
177}
178
179//------------------------------------------------------
180// Calculate total charge from energy deposit
181//------------------------------------------------------
182double sTgcDigitMaker::calculateTotalCharge(double energyDeposit, CLHEP::HepRandomEngine* rndEngine) const {
183 // Ionized charge in pC per keV deposited
184 const double ionized_charge = (5.65E-6) * energyDeposit / CLHEP::keV;
185
186 // Calculate avalanche gain using gamma distribution (Polya function approximation)
187 const double gain = CLHEP::RandGamma::shoot(rndEngine, 1. + m_theta, (1. + m_theta) / m_meanGasGain);
188
189 return gain * ionized_charge;
190}
191
192//------------------------------------------------------
193// Execute digitization for a given hit
194//------------------------------------------------------
196 const TimedHit& hit) const {
197 sTgcDigitVec allDigits{};
198 // Extract energy deposit from the hit
199 const double energyDeposit = hit->energyDeposit();
200 if (energyDeposit < std::numeric_limits<float>::epsilon()) {
201 return allDigits; // Ignore hits with no energy deposit
202 }
203
204 // Retrieve the detector element for the given hit
205 const Identifier hitId = hit->identify();
206
207 // HV efficiency correction
208 if (condContainers.efficiencies) {
209 const double efficiency = condContainers.efficiencies->getEfficiency(hitId);
210 if (CLHEP::RandFlat::shoot(condContainers.rndEngine,0.0,1.0) > efficiency) {
211 return allDigits;
212 }
213 }
214
215
216 ATH_MSG_DEBUG("Retrieving detector element for: "<< m_idHelperSvc->toStringDetEl(hitId)
217 << " energyDeposit "<< energyDeposit );
218
219
220 // Get ionization point and time
221 Ionization ionization{};
222 if (!getIonizationPoint(hit, condContainers, ionization)) {
223 ATH_MSG_DEBUG("Failed to get ionization point for hit "<< m_idHelperSvc->toStringDetEl(hitId));
224 return allDigits;
225 }
226
227 DigiInput digiInput{};
228 digiInput.hitId = hitId;
229 digiInput.totalCharge = calculateTotalCharge(energyDeposit, condContainers.rndEngine);
230 digiInput.time = ionization.time;
231 digiInput.posOnSurf = ionization.posOnWire;
232 digiInput.hitDir = xAOD::toEigen(hit->localDirection());
233 digiInput.reEle = m_detMgr->getsTgcReadoutElement(hitId);
234
235 ATH_MSG_DEBUG("Ionization_info: distance: " << ionization.distance
236 << " locHitPos: " <<Amg::toString(xAOD::toEigen(hit->localPosition()), 3)
237 << " locHitDir: " <<Amg::toString(xAOD::toEigen(hit->localDirection()), 3)
238 << " posOnTrack: " <<Amg::toString(ionization.posOnSegment, 3)
239 << " posOnWire: " << Amg::toString(ionization.posOnWire, 3)
240 << " total charge: " << calculateTotalCharge(energyDeposit, condContainers.rndEngine)
241 << " EDep: " << energyDeposit
242 <<" "<<m_idHelperSvc->toStringGasGap(hitId));
243
244 //##################################################################################
245 //######################################### strip readout ##########################
246 //##################################################################################
247 sTgcDigitVec stripDigits = processStripDigitization(condContainers, digiInput);
248
250 ATH_MSG_WARNING("Only digitize strip response !");
251 return stripDigits;
252 }
253 allDigits.insert(allDigits.end(),
254 std::make_move_iterator(stripDigits.begin()),
255 std::make_move_iterator(stripDigits.end()));
256
257 //##################################################################################
258 //######################################### pad readout ############################
259 //##################################################################################
260 sTgcDigitVec padDigits = processPadDigitization(digiInput);
261
262 allDigits.insert(allDigits.end(),
263 std::make_move_iterator(padDigits.begin()),
264 std::make_move_iterator(padDigits.end()));
265
267 ATH_MSG_WARNING("Only digitize strip/pad response !");
268 return allDigits;
269 }
270
271 //##################################################################################
272 //######################################### wire readout ###########################
273 //##################################################################################
274 sTgcDigitVec wireDigits = processWireDigitization(digiInput);
275 allDigits.insert(allDigits.end(),
276 std::make_move_iterator(wireDigits.begin()),
277 std::make_move_iterator(wireDigits.end()));
278
279 return allDigits;
280}
281
282//------------------------------------------------------
283// Process strip digitization
284//------------------------------------------------------
286 const DigiInput& digiInput) const {
287 ATH_MSG_DEBUG("sTgcDigitMaker::strip response ");
288
289 const int gasGap = m_idHelper.gasGap(digiInput.hitId);
290 const IdentifierHash stripLayHash = digiInput.reEle->createHash(gasGap,
291 ReadoutChannelType::Strip, 1);
292 const Identifier stripLayId = digiInput.reEle->measurementId(stripLayHash);
293 const MuonGMR4::StripDesign& stripDesign{digiInput.reEle->stripDesign(stripLayHash)};
294 const Amg::Vector2D hitOnStripSurf = digiInput.reEle->stripLayer(stripLayHash).to2D(digiInput.posOnSurf, false);
295
296 if(!stripDesign.insideTrapezoid(hitOnStripSurf)) {
297 ATH_MSG_DEBUG("Outside of the strip surface boundary : "
298 << m_idHelperSvc->toString(stripLayId)
299 << "; local position " <<Amg::toString(hitOnStripSurf, 2));
300 return {};
301 }
302
303 constexpr double tolerance_length = 0.01*Gaudi::Units::mm;
304 int stripNumber = stripDesign.stripNumber(hitOnStripSurf);
305 if( stripNumber < 0){
306 const double newPosX = hitOnStripSurf.x() - std::copysign(tolerance_length, hitOnStripSurf.x());
307 const Amg::Vector2D newPos(newPosX, hitOnStripSurf.y());
308 stripNumber = stripDesign.stripNumber(newPos);
309 if (stripNumber < 0) {
310 ATH_MSG_WARNING("Failed to obtain strip number " << m_idHelperSvc->toString(stripLayId) );
311 return {};
312 }
313 }
314
315
316 const double tan_theta = digiInput.hitDir.perp() / digiInput.hitDir.z();
317 const double angle_dependency = std::hypot(m_posResIncident, m_posResAngular * tan_theta);
318
319 const double peak_position = CLHEP::RandGaussZiggurat::shoot(condContainers.rndEngine,
320 hitOnStripSurf.x(), m_StripResolution*angle_dependency);
321 ATH_MSG_VERBOSE(__func__<<"() - "<<__LINE__<<" Smeared hit from "<<hitOnStripSurf.x()<<" -> "<<
322 peak_position<<". Assign strip charges");
323 return processStripChargeSharing(digiInput,
324 peak_position,
325 stripNumber);
326}
327//------------------------------------------------------
328// Process strip charge sharing across neighboring strips
329//------------------------------------------------------
331 const double peak_position,
332 const int stripNumber) const {
333
334 const double norm = 0.5 * digiInput.totalCharge;
335
336 sTgcDigitVec digits{};
337 const double tan_theta = digiInput.hitDir.perp() / digiInput.hitDir.z();
338
339 constexpr double tolerance_charge = 0.0005;
340 constexpr int max_neighbor = 10;
341
342 const int gasGap = m_idHelper.gasGap(digiInput.hitId);
343 const auto& design = digiInput.reEle->stripDesign(digiInput.reEle->measurementHash(digiInput.hitId));
344 // Upper half of the strip cluster
345 for (int sign : {-1, 1}) {
346 for (int iStrip = 0; iStrip <= max_neighbor; ++iStrip) {
347
348 if (iStrip == 0 && sign == +1) continue; // center only once
349 int currentStrip = stripNumber + sign*iStrip;
350 if (currentStrip > design.numStrips() || currentStrip < 1) {
351 ATH_MSG_VERBOSE(__func__<<"() - "<<__LINE__<<" Breaking the upper half strip loop stripNumber: "
352 << currentStrip << " > " << design.numStrips());
353 break;
354 }
355 bool isValid = false;
356 const Identifier currentStripId = m_idHelper.channelID(digiInput.hitId,
357 digiInput.reEle->multilayer(),
358 gasGap, ReadoutChannelType::Strip,
359 currentStrip, isValid);
360 if (!isValid) {
361 continue;
362 }
363 const Amg::Vector2D currentStripPos = digiInput.reEle->localChannelPosition(digiInput.reEle->measurementHash(currentStripId));
364 const double x_relative = (currentStripPos.x() - peak_position);
365 const double normX = x_relative / design.stripPitch();
366 const double charge = std::hypot(1., m_chargeAngularFactor * tan_theta) * norm *
367 chargeIntegral(normX - 0.5, normX + 0.5);
368
369 if (charge < tolerance_charge) {
370 ATH_MSG_VERBOSE(__func__<<"() - "<<__LINE__<<" Breaking the upper half strip loop stripCharge: "
371 << charge << " < " << tolerance_charge);
372 break;
373 }
374
375 double strip_time = digiInput.time;
377 const bool shift = ((iStrip > 0) && ((x_relative + (0.5*0.75 - iStrip) * design.stripPitch()) < 0));
378 strip_time += getTimeOffsetStrip(iStrip - shift);
379 }
380
381 addDigit(digits, currentStripId, strip_time, charge);
382 ATH_MSG_VERBOSE(__func__<<"() - "<<__LINE__<<" Created a strip digit: strip number = "
383 << currentStrip << ", charge = " << charge);
384 }
385 }
386
387 return digits;
388}
389
390
391//------------------------------------------------------
392// Process pad digitization
393//------------------------------------------------------
395 ATH_MSG_DEBUG("sTgcDigitMaker::pad response ");
396
397 const int gasGap = m_idHelper.gasGap(digiInput.hitId);
398 const IdentifierHash padLayHash = digiInput.reEle->createHash(gasGap,ReadoutChannelType::Pad, 1);
399 const Amg::Vector2D hitOnPadSurf = digiInput.reEle->stripLayer(padLayHash).to2D(digiInput.posOnSurf, true);
400 const MuonGMR4::PadDesign& padDesign{digiInput.reEle->padDesign(padLayHash)};
401 if(!padDesign.insideTrapezoid(hitOnPadSurf)) {
402 ATH_MSG_DEBUG(__func__<<"() -"<<__LINE__<<" Outside of the pad surface boundary :"
403 << m_idHelperSvc->toString(digiInput.hitId)
404 << " local position " <<Amg::toString(hitOnPadSurf, 2));
405 return {};
406 }
407
408 constexpr double tolerance_length = 0.01*Gaudi::Units::mm;
409 auto [padEta, padPhi] = padDesign.channelNumber(hitOnPadSurf);
410
411 if( padEta < 1 || padPhi < 1){
412 const double newPosX = hitOnPadSurf.x() - std::copysign(tolerance_length, hitOnPadSurf.x());
413 const double newPosY = hitOnPadSurf.y() - std::copysign(tolerance_length, hitOnPadSurf.y());
414 const Amg::Vector2D newPosition(newPosX, newPosY);
415 auto [newPadEta, newPadPhi] = padDesign.channelNumber(newPosition);
416 padEta = newPadEta;
417 padPhi = newPadPhi;
418 if( padEta < 1 || padPhi < 1) {
419 ATH_MSG_WARNING("Failed to obtain pad number for " << m_idHelperSvc->toString(digiInput.hitId) );
420 return {};
421 }
422 }
423
424 return processPadChargeSharing(digiInput, padEta, padPhi);
425}
426
427//------------------------------------------------------
428// Process pad charge sharing
429//------------------------------------------------------
431 const int padEta,
432 const int padPhi) const {
433
434 bool isValid = false;
435 const int gasGap = m_idHelper.gasGap(digiInput.hitId);
436 const Identifier padHitId = m_idHelper.padID(digiInput.hitId,
437 digiInput.reEle->multilayer(),
438 gasGap, ReadoutChannelType::Pad, padEta, padPhi, isValid);
439 if(!isValid) {
440 return {};
441 }
442 sTgcDigitVec digits{};
443
444 const IdentifierHash padHitHash = digiInput.reEle->measurementHash(padHitId);
445 const MuonGMR4::PadDesign& padDesign{digiInput.reEle->padDesign(padHitHash)};
446
447 const Amg::Vector2D padPos = digiInput.reEle->localChannelPosition(padHitHash);
448 const Amg::Vector2D hitOnPadSurf = digiInput.reEle->stripLayer(padHitHash).to2D(digiInput.posOnSurf, true);
449
450 const Amg::Vector2D diff = hitOnPadSurf - padPos;
451 const double halfPadHeight = 0.5 * digiInput.reEle->padHeight(padHitHash);
452
453 const std::array<Amg::Vector2D, 4> padHitCorners = padDesign.padCorners(std::make_pair(padEta, padPhi));
454 const double padBottomBase = (padHitCorners[0] - padHitCorners[1]).norm();
455 const double padTopBase = (padHitCorners[2] - padHitCorners[3]).norm();
456 const double halfPadWidth = 0.5 * padBottomBase + 0.25 * (1 + diff.y()/halfPadHeight) * (padTopBase - padBottomBase);
457
458 double deltaX = halfPadWidth - std::abs(diff.x());
459 double deltaY = halfPadHeight - std::abs(diff.y());
460 const bool isNeighX = deltaX < 2.5*m_clusterParams[1];
461 const bool isNeighY = deltaY < 2.5*m_clusterParams[1];
462
463 if (deltaX < 0.) {
464 deltaX = 0.1;
465 }
466 if (deltaY < 0.) {
467 deltaY = 0.1;
468 }
469
470 if (m_doPadSharing && (isNeighX || isNeighY)) {
471 unsigned newPhi = padPhi - Acts::copySign(1, diff.x());
472 unsigned newEta = padEta + Acts::copySign(1, diff.y());
473 bool validEta = newEta > 0 && newEta <= digiInput.reEle->numPadEta(padHitHash);
474 bool validPhi = newPhi > 0 && newPhi <= digiInput.reEle->numPadPhi(padHitHash);
475
476 if (isNeighX && isNeighY && validEta && validPhi) {
477 const Identifier neigh_ID_X = m_idHelper.padID(padHitId, digiInput.reEle->multilayer(),
478 gasGap, ReadoutChannelType::Pad,
479 padEta, newPhi);
480 const Identifier neigh_ID_Y = m_idHelper.padID(padHitId, digiInput.reEle->multilayer(), gasGap,
481 ReadoutChannelType::Pad, newEta, padPhi);
482 const Identifier neigh_ID_XY = m_idHelper.padID(padHitId, digiInput.reEle->multilayer(), gasGap,
483 ReadoutChannelType::Pad, newEta, newPhi);
484 double xQfraction = getPadChargeFraction(deltaX);
485 double yQfraction = getPadChargeFraction(deltaY);
486
487 addDigit(digits, neigh_ID_X, digiInput.time, xQfraction*(1.-yQfraction)*0.5*digiInput.totalCharge);
488 addDigit(digits, neigh_ID_Y, digiInput.time, yQfraction*(1.-xQfraction)*0.5*digiInput.totalCharge);
489 addDigit(digits, neigh_ID_XY, digiInput.time, xQfraction*yQfraction*0.5*digiInput.totalCharge);
490 addDigit(digits, padHitId, digiInput.time, (1.-xQfraction-yQfraction+xQfraction*yQfraction)*0.5*digiInput.totalCharge);
491 } else if (isNeighX && validPhi){
492 const Identifier neigh_ID = m_idHelper.padID(padHitId, digiInput.reEle->multilayer(), gasGap,
493 ReadoutChannelType::Pad, padEta, newPhi);
494 double xQfraction = getPadChargeFraction(deltaX);
495 addDigit(digits, padHitId, digiInput.time, (1.-xQfraction)*0.5*digiInput.totalCharge);
496 addDigit(digits, neigh_ID, digiInput.time, xQfraction*0.5*digiInput.totalCharge);
497 }
498 else if (isNeighY && validEta){
499 const Identifier neigh_ID = m_idHelper.padID(padHitId, digiInput.reEle->multilayer(), gasGap,
500 ReadoutChannelType::Pad, newEta, padPhi);
501 double yQfraction = getPadChargeFraction(deltaY);
502 addDigit(digits, padHitId, digiInput.time, (1.-yQfraction)*0.5*digiInput.totalCharge);
503 addDigit(digits, neigh_ID, digiInput.time, yQfraction*0.5*digiInput.totalCharge);
504 }
505 } else{
506 addDigit(digits, padHitId, digiInput.time, 0.5*digiInput.totalCharge);
507 }
508 return digits;
509}
510
511
512//------------------------------------------------------
513// Process wire digitization
514//------------------------------------------------------
516 ATH_MSG_DEBUG("sTgcDigitMaker::wire response ");
517
518 const int gasGap = m_idHelper.gasGap(digiInput.hitId);
519 const IdentifierHash wireLayHash = digiInput.reEle->createHash(gasGap,
520 ReadoutChannelType::Wire, 1);
521 const MuonGMR4::WireGroupDesign& wireDesign{digiInput.reEle->wireDesign(wireLayHash)};
522
523 const Amg::Vector2D hitOnWireSurf = digiInput.reEle->stripLayer(wireLayHash).to2D(digiInput.posOnSurf, true);
524 if(!wireDesign.insideTrapezoid(hitOnWireSurf)) {
525 ATH_MSG_DEBUG("Outside of the wire surface boundary :" << m_idHelperSvc->toString(digiInput.hitId)
526 << " local position " <<Amg::toString(hitOnWireSurf, 2));
527 return {};
528 }
529
530 if (digiInput.reEle->isEtaZero(digiInput.reEle->measurementHash(digiInput.hitId), digiInput.posOnSurf.block<2,1>(0,0))) {
531 ATH_MSG_DEBUG("Hit inside wireCutout :" << m_idHelperSvc->toString(digiInput.hitId)
532 << " local position " <<Amg::toString(hitOnWireSurf, 2));
533 return {};
534 }
535
536 constexpr double tolerance_length = 0.01*Gaudi::Units::mm;
537 int wiregroupNumber = (wireDesign.wireNumber(hitOnWireSurf)).first;
538 if( wiregroupNumber < 1) {
539 const double newPosX = hitOnWireSurf.x() - std::copysign(tolerance_length, hitOnWireSurf.x());
540 const Amg::Vector2D newPos{newPosX, hitOnWireSurf.y()};
541 wiregroupNumber = (wireDesign.wireNumber(newPos)).first;
542 if (wiregroupNumber < 1) {
543 ATH_MSG_WARNING("Failed to obtain wire number " << m_idHelperSvc->toString(digiInput.hitId) );
544 return {};
545 }
546 }
547
548 bool isValid = false;
549 const Identifier wireGroupHitId = m_idHelper.channelID(digiInput.hitId, digiInput.reEle->multilayer(), gasGap,
550 ReadoutChannelType::Wire, wiregroupNumber, isValid);
551
552 if(!isValid) {
553 return {};
554 }
555 sTgcDigitVec digits{};
556 addDigit(digits, wireGroupHitId, digiInput.time, digiInput.totalCharge);
557 return digits;
558}
559
560//------------------------------------------------------
561// Compute closest approach between hit trajectory and wire
562//------------------------------------------------------
564 int wireNumber,
565 const Amg::Vector3D& locHitPos,
566 const Amg::Vector3D& locHitDir,
567 const double stepLength) const {
568
569 constexpr double angular_tolerance = 1e-3;
570 // Position of the ionization
571 Ionization ionization;
572 // Finding smallest distance and the points at the smallest distance.
573 // The smallest distance between two lines is perpendicular to both lines.
574 // Previous logic was to find the perpendicular distance between two lines using projection geometry
575 // We can construct two lines in the wire surface local coordinate frame:
576 // - one for the hit segment with equation h0 + t * v_h, where h0 is a point
577 // and v_h is the unit vector of the hit segment
578 // - another for the wire with similar equation w0 + s * v_w, where w0 is a
579 // point and v_w is the unit vector of the wire line
580 // Then it is possible to determine the closest points on each line
581 // by requiring that the vector between them is perpendicular to both:
582 // 1. (h0 + t*v_h - w0 - s*v_w) · v_h = 0
583 // 2. (h0 + t*v_h - w0 - s*v_w) · v_w = 0
584
585 // We have replaced this logic with using Amg::intersect<3> method
586 const MuonGMR4::WireGroupDesign& wireDesign = static_cast<const MuonGMR4::WireGroupDesign&>(stripLayer.design(true));
587 // Geometry setup defined in the eta surface local coordinate frame
588 const double wirePitch = wireDesign.stripPitch();
589 const double wirePosX = wireDesign.firstStripPos().x() + (wireNumber - 1) * wirePitch;
590 const Amg::Vector3D wireDir{stripLayer.to3D(wireDesign.stripDir(),true)};
591 const Amg::Vector3D wirePos(locHitPos.x(), -wirePosX, 0.);
592
593 // Use Amg::intersect to find closest point on hit segment to wire plane
594 std::optional<double> scaleHit = Amg::intersect<3>(locHitPos, locHitDir, Amg::Vector3D::UnitZ(), 0);
595 if (!scaleHit || std::abs(std::abs(wireDir.dot(locHitDir)) - 1.0) < angular_tolerance) {
596 ATH_MSG_DEBUG("The track segment is parallel to the wire, position of digit is undefined");
597 ionization.posOnSegment = locHitPos;
598 ionization.posOnWire = wirePos;
599 ionization.distance = std::hypot(locHitPos.y() + wirePosX, locHitPos.z());
600 if (ionization.distance > wirePitch) {
601 ATH_MSG_DEBUG(" localHitPos: " << Amg::toString(locHitPos, 2) << " localHitDir: " << Amg::toString(locHitDir, 2) << " scaleHit: " << scaleHit.value()
602 << " wirePos: " << Amg::toString(wirePos, 2) << " IonizationDistance: " << ionization.distance);
603 }
604 return ionization;
605 }
606 // Position on hit segment
607 Amg::Vector3D ionizationPos = locHitPos + scaleHit.value() * locHitDir;
608
609 if (scaleHit.value() > stepLength) {
610 ionization.posOnSegment = locHitPos;
611 const Amg::Vector3D closestPointToWirePlane = locHitPos + stepLength * locHitDir;
612 ionization.posOnWire = wirePos;
613 ionization.distance = std::abs(closestPointToWirePlane.z());
614 return ionization;
615 }
616
617 // Project ionization position onto the wire line
618 const double scaleWire = (ionizationPos - wirePos).dot(wireDir);
619 const Amg::Vector3D closestPointOnWire = wirePos + scaleWire * wireDir;
620
621 // Fill ionization result
622 ionization.posOnSegment = ionizationPos;
623 ionization.posOnWire = closestPointOnWire;
624 ionization.distance = (ionizationPos - closestPointOnWire).mag();
625
626 return ionization;
627}
628
629//------------------------------------------------------
630// Adds a digit to the appropriate cache
631//------------------------------------------------------
633 const Identifier& id,
634 const double digittime,
635 const double charge) {
636
637 constexpr double tolerance = 0.1;
638 if (!std::ranges::any_of(digits, [&](std::unique_ptr<sTgcDigit>& known) {
639 return known->identify() == id && std::abs(digittime - known->time()) < tolerance;
640 })) {
641 digits.push_back(std::make_unique<sTgcDigit>(id, 0, digittime, charge, 0, 0));
642 }
643}
644
645//------------------------------------------------------
646// Reads time arrival data file
647//------------------------------------------------------
649 const std::string file_name = "sTGC_Digitization_timeArrival.dat";
650 std::string file_path = PathResolver::find_file(file_name, "DATAPATH");
651 if(file_path.empty()) {
652 ATH_MSG_FATAL("readFileOfTimeWindowOffset(): Could not find file " << file_name );
653 return StatusCode::FAILURE;
654 }
655
656 std::ifstream ifs{file_path, std::ios::in};
657 if(ifs.bad()) {
658 ATH_MSG_FATAL("sTgcDigitMaker: Failed to open time of arrival file " << file_name );
659 return StatusCode::FAILURE;
660 }
661
662 // Read the sTGC_Digitization_timeWindowOffset.dat file
663 std::string line;
664 GammaParameter param{};
665 while (std::getline(ifs, line)) {
666 std::string key;
667 std::istringstream iss(line);
668 iss >> key;
669 if (key == "bin") {
670 iss >> param.lowEdge >> param.kParameter >> param.thetaParameter;
671 m_gammaParameter.push_back(param);
672 } else if (key == "mpv") {
673 double mpt{};
674 int idx{0};
675 while (iss >> mpt) {m_mostProbableArrivalTime[idx++] = mpt;}
676 }
677 }
678 ifs.close();
679 return StatusCode::SUCCESS;
680}
681
682//------------------------------------------------------
683// Retrieves gamma distribution parameters based on distance
684//------------------------------------------------------
686 const double d = std::abs(distance);
687 // Find the parameters assuming the container is sorted in ascending order of 'lowEdge'
688 if (d < m_gammaParameter.front().lowEdge) {
689 return m_gammaParameter.front();
690 }
691 int index{-1};
692 for (const auto& par: m_gammaParameter) {
693 if (d < par.lowEdge) {
694 break;
695 }
696 ++index;
697 }
698 return m_gammaParameter.at(index);
699}
700
701//------------------------------------------------------
702// Computes the most probable arrival time based on the distance of closest approach
703//------------------------------------------------------
704double sTgcDigitMaker::getMostProbableArrivalTime(double distance) const {
705 return Acts::detail::polynomialSum(std::abs(distance), m_mostProbableArrivalTime);
706}
707//------------------------------------------------------
708// Computes the charge on each strip
709//------------------------------------------------------
710
711double sTgcDigitMaker::chargeIntegral(double N, double M) const {
712
713 double term1 = 0.25 * std::erf( M / (std::sqrt(2) * m_clusterParams[0]));
714 double term2 = 0.25 * std::erf( N / (std::sqrt(2) * m_clusterParams[0]));
715 double term3 = 0.25 * std::erf( M / (std::sqrt(2) * m_clusterParams[1]));
716 double term4 = 0.25 * std::erf( N / (std::sqrt(2) * m_clusterParams[1]));
717
718 return (term1 - term2 + term3 - term4);
719}
720//------------------------------------------------------
721// Reads strip time offset data file
722//------------------------------------------------------
724 const std::string file_name = "sTGC_Digitization_timeOffsetStrip.dat";
725 std::string file_path = PathResolver::find_file(file_name, "DATAPATH");
726 if(file_path.empty()) {
727 ATH_MSG_FATAL("readFileOfTimeWindowOffset(): Could not find file " << file_name );
728 return StatusCode::FAILURE;
729 }
730
731 // Open the sTGC_Digitization_timeOffsetStrip.dat file
732 std::ifstream ifs{file_path, std::ios::in};
733 if(ifs.bad()) {
734 ATH_MSG_FATAL("Failed to open time of arrival file " << file_name );
735 return StatusCode::FAILURE;
736 }
737
738 // Initialize the container to store the time offset.
739 // The number of parameters, 6, corresponds to the number of lines to be read
740 // from sTGC_Digitization_timeOffsetStrip.dat.
741 // Setting the default offset to 0 ns.
742 std::string line;
743 size_t index{0};
744 double value{0.0};
745 while (std::getline(ifs, line)) {
746 std::string key;
747 std::istringstream iss(line);
748 iss >> key;
749 if (key == "strip") {
750 iss >> index >> value;
751 if (index >= m_timeOffsetStrip.size()) continue;
752 m_timeOffsetStrip.at(index) = value;
753 }
754 }
755 return StatusCode::SUCCESS;
756}
757
758//------------------------------------------------------
759// Retrieves time offset for strip clusters
760//------------------------------------------------------
761double sTgcDigitMaker::getTimeOffsetStrip(size_t neighbor_index) const {
762 return m_timeOffsetStrip.at(std::min(neighbor_index, m_timeOffsetStrip.size() -1));
763}
764
765//------------------------------------------------------
766// Computes pad charge fraction
767//------------------------------------------------------
769 // The charge fraction that is found past a distance x away from the
770 // centre of a 2D gaussian distribution of width of cluster profile is
771 // described by a modified error function.
772
773 // The modified error function perfectly describes
774 // the pad charge sharing distribution figure 16 of the sTGC
775 // testbeam paper https://arxiv.org/pdf/1509.06329.pdf
776 return 0.5 * (1.0 - std::erf( distance / (std::sqrt(2) * m_clusterParams[1])));
777}
778}
Scalar mag() const
mag method
#define ATH_CHECK
Evaluate an expression and check for errors.
#define ATH_MSG_FATAL(x)
#define ATH_MSG_VERBOSE(x)
#define ATH_MSG_WARNING(x)
#define ATH_MSG_DEBUG(x)
double charge(const T &p)
Definition AtlasPID.h:997
bool isValid(const T &p)
Av: we implement here an ATLAS-sepcific convention: all particles which are 99xxxxx are fine.
Definition AtlasPID.h:878
void diff(const Jet &rJet1, const Jet &rJet2, std::map< std::string, double > varDiff)
Difference between jets - Non-Class function required by trigger.
Definition Jet.cxx:631
int sign(int a)
AthMessaging(IMessageSvc *msgSvc, const std::string &name)
Constructor.
This is a "hash" representation of an Identifier.
std::pair< int, int > channelNumber(const Amg::Vector2D &hitPos) const
Function to retrieve the pad eta and phi given a local position coordinate.
const Amg::Vector2D & firstStripPos() const
Vector indicating the first strip position.
double stripPitch() const
Distance between two adjacent strips.
bool insideTrapezoid(const Amg::Vector2D &extPos) const
Checks whether an external point is inside the trapezoidal area.
virtual int stripNumber(const Amg::Vector2D &pos) const
Calculates the number of the strip whose center is closest to the given point.
const Amg::Vector2D & stripDir() const
Vector pointing along the strip.
The StripLayer interfaces the 2D description of the strip plane layout with the 3D description of the...
Definition StripLayer.h:19
const StripDesign & design(bool phiView=false) const
Returns the underlying strip design.
Amg::Vector2D to2D(const Amg::Vector3D &vec, const bool phiView) const
Transforms a 3D vector from the strip design into a 2D vector.
Amg::Vector3D to3D(CheckVector2D &&vec, const bool phiView) const
Transforms the 2D vector from the strip design into a 3D vector If phi view is switched on,...
unsigned int numPitchesToGroup(unsigned int groupNum) const
Returns the number of wire pitches to reach the given group.
std::pair< int, int > wireNumber(const Amg::Vector2D &extPos) const
Returns a pair where the first component indicate the wire group number and the second one returns th...
unsigned int nAllWires() const
Returns the number of all wires.
IdentifierHash measurementHash(const Identifier &measId) const override final
Constructs the identifier hash from the full measurement Identifier.
const PadDesign & padDesign(const IdentifierHash &measHash) const
Retrieves the readoutElement Layer given the Identifier/Hash.
double padHeight(const IdentifierHash &measHash) const
Returns the height of all the pads that are not adjacent to the bottom edge of the trapezoid active a...
int multilayer() const
Returns the multilayer of the sTgcReadoutElement.
Identifier measurementId(const IdentifierHash &measHash) const override final
Converts the measurement hash back to the full Identifier.
const StripDesign & stripDesign(const IdentifierHash &measHash) const
Retrieves the readoutElement Layer given the Identifier/Hash.
bool isEtaZero(const IdentifierHash &measurementHash, const Amg::Vector2D &localPosition) const
unsigned numPadEta(const IdentifierHash &measHash) const
Returns the number of pads in the eta direction in the given layer.
unsigned numPadPhi(const IdentifierHash &measHash) const
Returns the number of pads in the Phi direction in the given gasGap layer.
Amg::Vector2D localChannelPosition(const IdentifierHash &measHash) const
Returns the local position of the measurement in the repsective frame.
const WireGroupDesign & wireDesign(const IdentifierHash &measHash) const
Retrieves the readoutElement Layer given the Identifier/Hash.
static IdentifierHash createHash(const unsigned gasGap, const unsigned channelType, const unsigned channel, const unsigned wireInGrp=0)
Create a measurement hash from the Identifier fields.
const StripLayer & stripLayer(const IdentifierHash &measId) const
std::vector< std::unique_ptr< sTgcDigit > > sTgcDigitVec
Digitize a given hit.
sTgcDigitVec processWireDigitization(const DigiInput &digiInput) const
Processes wire digitization for a given hit.
StatusCode readFileOfTimeOffsetStrip()
Reads strip time offset data file.
sTgcDigitVec processPadDigitization(const DigiInput &digiInput) const
Processes pad digitization for a given hit.
static double getPadChargeFraction(double distance)
Computes charge fraction shared among pads.
double getTimeOffsetStrip(size_t neighbor_index) const
Gets the time offset for a strip cluster.
bool getIonizationPoint(const TimedHit &hit, const DigiConditions &condContainers, Ionization &ionization) const
Computes the ionization point for a hit.
digitMode m_digitMode
define offsets and widths of time windows for signals from wiregroups and strips.
GammaParameter getGammaParameter(double distance) const
Retrieves gamma distribution parameters based on distance.
static void addDigit(sTgcDigitVec &digits, const Identifier &id, double digittime, double charge)
Adds a digit to the appropriate cache.
sTgcDigitVec processStripChargeSharing(const DigiInput &digiInput, const double peak_position, const int stripNumber) const
Handles charge sharing for strip clusters.
StatusCode initialize()
Initialize digitization parameters, including reading necessary data files.
sTgcDigitMaker(const MuonGMR4::MuonDetectorManager *detMgr, digitMode mode, double meanGasGain, bool doPadChargeSharing)
sTgcDigitVec processStripDigitization(const DigiConditions &condContainers, const DigiInput &digiInput) const
Processes strip digitization for a given hit.
double getMostProbableArrivalTime(double distance) const
Computes the most probable arrival time based on the distance of closest approach.
double calculateTotalCharge(double energyDeposit, CLHEP::HepRandomEngine *rndEngine) const
Calculates total charge from energy deposit, including gas gain.
Ionization pointClosestApproach(const MuonGMR4::StripLayer &stripLayer, int wireNumber, const Amg::Vector3D &locHitPos, const Amg::Vector3D &locHitDir, const double stepLength) const
Computes the closest approach between a trajectory and a wire segment.
sTgcDigitVec processPadChargeSharing(const DigiInput &digiInput, const int padEta, const int padPhi) const
Handles charge sharing for pad clusters.
sTgcDigitVec executeDigi(const DigiConditions &condContainers, const TimedHit &hit) const
virtual ~sTgcDigitMaker()
Destructor.
double getEfficiency(const Identifier &channelId, bool isInnerQ1=false) const
Returns the signal generation efficiency of the sTgc channel.
static std::string find_file(const std::string &logical_file_name, const std::string &search_path)
ConstVectorMap< 3 > localDirection() const
Returns the local direction of the traversing particle.
int pdgId() const
Returns the pdgID of the traversing particle.
float stepLength() const
Returns the path length of the G4 step.
Identifier identify() const
Returns the global ATLAS identifier of the SimHit.
float energyDeposit() const
Returns the energy deposited by the traversing particle inside the gas volume.
ConstVectorMap< 3 > localPosition() const
Returns the local postion of the traversing particle.
const HepMcParticleLink & genParticleLink() const
Returns the link to the HepMC particle producing this hit.
float globalTime() const
Returns the time ellapsed since the collision of the traversing particle.
void efficiency(std::vector< double > &bins, std::vector< double > &values, const std::vector< std::string > &files, const std::string &histname, const std::string &tplotname, const std::string &label="")
std::optional< double > intersect(const AmgVector(N)&posA, const AmgVector(N)&dirA, const AmgVector(N)&posB, const AmgVector(N)&dirB)
Calculates the point B' along the line B that's closest to a second line A.
std::string toString(const Translation3D &translation, int precision=4)
GeoPrimitvesToStringConverter.
Eigen::Matrix< double, 2, 1 > Vector2D
Eigen::Matrix< double, 3, 1 > Vector3D
This header ties the generic definitions in this package.
Definition dot.py:1
Definition index.py:1