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 const double scale = Amg::intersect<3>(locHitPos, locHitDir, Amg::Vector3D::UnitZ(), 0.).value_or(0);
70 Amg::Vector3D hitOnWireSurf = locHitPos + scale * locHitDir;
71
72 const Identifier hitId = hit->identify();
73 const IdentifierHash wireLayHash = reEle->createHash(m_idHelper.gasGap(hitId),
74 ReadoutChannelType::Wire, 1);
75 const MuonGMR4::WireGroupDesign& wireDesign{reEle->wireDesign(wireLayHash)};
76 const MuonGMR4::StripLayer& stripLayer = reEle->stripLayer(wireLayHash);
77 const Amg::Vector2D hitOnWire2D = stripLayer.to2D(hitOnWireSurf, true);
78
79 if(!wireDesign.insideTrapezoid(hitOnWire2D)) {
80 return false;
81 }
82
83 std::pair<int, int> wireGrpWireNum = wireDesign.wireNumber(hitOnWire2D);
84 if (wireGrpWireNum.first < 0) {
85 ATH_MSG_WARNING(__func__<<"() "<<__LINE__<<" - Unable to retrieve the wire number, skipping the hit: "
86 << m_idHelperSvc->toString(hitId)<<" @"<<Amg::toString(hitOnWire2D));
87 return false;
88 }
89 int wireNumber = wireDesign.numPitchesToGroup(wireGrpWireNum.first) + wireGrpWireNum.second;
90 const int numWires = wireDesign.nAllWires();
91
92 if((wireNumber < 1) || (wireNumber > numWires)) {
93 ATH_MSG_WARNING(__func__<<"() "<<__LINE__<<" - Unable to retrieve the wire number, skipping the hit: "
94 << m_idHelperSvc->toString(hitId)<<" @"<<Amg::toString(hitOnWire2D));
95 return false;
96 }
97
98 // Compute the position of the ionization and its distance to the closest wire
99 ionization = pointClosestApproach(stripLayer, wireNumber, locHitPos, locHitDir, hit->stepLength());
100 double distToWire = ionization.distance;
101
102 if(distToWire > 0.) {
103 // Determine on which side of the wire does the particle cross
104 int adjacent = Acts::copySign(1, ionization.posOnSegment.y() - ionization.posOnWire.y());
105
106 Ionization ionizationAdj = pointClosestApproach(stripLayer, wireNumber+adjacent, locHitPos, locHitDir, hit->stepLength());
107 double distToWireAdj = ionizationAdj.distance;
108
109 if ((distToWireAdj > 0.) && (distToWireAdj < distToWire)) {
110 distToWire = distToWireAdj;
111 wireNumber += adjacent;
112 ionization = std::move(ionizationAdj);
113 }
114 } else {
115 ATH_MSG_DEBUG("Failed to get the distance between the wire " << wireNumber << " and hit " << Amg::toString(hitOnWire2D, 2));
116 return false;
117 }
118
119 // Do not digitize hits that are too far from the nearest wire
120 if (distToWire > wireDesign.stripPitch()) {
121 ATH_MSG_DEBUG("Distance to nearest wire: " << distToWire << " greater than wirePitch.");
122 return false;
123 }
124
125 // Get the gamma pdf parameters and calculate digit time
126 const GammaParameter gamParam = getGammaParameter(distToWire);
127 const double most_prob_time = getMostProbableArrivalTime(distToWire);
128 const double gamma_mpv = std::max((gamParam.kParameter - 1) * gamParam.thetaParameter, 0.);
129 const double t0_par = most_prob_time - gamma_mpv;
130 const double inv_theta = 1./gamParam.thetaParameter;
131
132 double digitTime = t0_par + CLHEP::RandGamma::shoot(condContainers.rndEngine, gamParam.kParameter, inv_theta);
133
134 constexpr unsigned shoot_limit = 4;
135 unsigned shoot_counter = 0;
136 while (digitTime < 0. && ++shoot_counter <= shoot_limit) {
137 digitTime = t0_par + CLHEP::RandGamma::shoot(condContainers.rndEngine, gamParam.kParameter,inv_theta);
138 }
139
140 ionization.time = std::max(0., digitTime);
141 return true;
142}
143
144//------------------------------------------------------
145// Calculate total charge from energy deposit
146//------------------------------------------------------
147double sTgcDigitMaker::calculateTotalCharge(double energyDeposit, CLHEP::HepRandomEngine* rndEngine) const {
148 // Ionized charge in pC per keV deposited
149 const double ionized_charge = (5.65E-6) * energyDeposit / CLHEP::keV;
150
151 // Calculate avalanche gain using gamma distribution (Polya function approximation)
152 const double gain = CLHEP::RandGamma::shoot(rndEngine, 1. + m_theta, (1. + m_theta) / m_meanGasGain);
153
154 return gain * ionized_charge;
155}
156
157//------------------------------------------------------
158// Execute digitization for a given hit
159//------------------------------------------------------
161 const TimedHit& hit) const {
162 sTgcDigitVec allDigits{};
163 // Extract energy deposit from the hit
164 const double energyDeposit = hit->energyDeposit();
165 if (energyDeposit < std::numeric_limits<float>::epsilon()) {
166 return allDigits; // Ignore hits with no energy deposit
167 }
168
169 // Retrieve the detector element for the given hit
170 const Identifier hitId = hit->identify();
171
172 // HV efficiency correction
173 if (condContainers.efficiencies) {
174 const double efficiency = condContainers.efficiencies->getEfficiency(hitId);
175 if (CLHEP::RandFlat::shoot(condContainers.rndEngine,0.0,1.0) > efficiency) {
176 return allDigits;
177 }
178 }
179
180
181 ATH_MSG_DEBUG("Retrieving detector element for: "<< m_idHelperSvc->toStringDetEl(hitId)
182 << " energyDeposit "<< energyDeposit );
183
184
185 // Get ionization point and time
186 Ionization ionization{};
187 if (!getIonizationPoint(hit, condContainers, ionization)) {
188 ATH_MSG_DEBUG("Failed to get ionization point for hit "<< m_idHelperSvc->toStringDetEl(hitId));
189 return allDigits;
190 }
191
192 DigiInput digiInput{};
193 digiInput.hitId = hitId;
194 digiInput.totalCharge = calculateTotalCharge(energyDeposit, condContainers.rndEngine);
195 digiInput.time = ionization.time;
196 digiInput.posOnSurf = ionization.posOnWire;
197 digiInput.hitDir = xAOD::toEigen(hit->localDirection());
198 digiInput.reEle = m_detMgr->getsTgcReadoutElement(hitId);
199
200
201 //##################################################################################
202 //######################################### strip readout ##########################
203 //##################################################################################
204 sTgcDigitVec stripDigits = processStripDigitization(condContainers, digiInput);
205
207 ATH_MSG_WARNING("Only digitize strip response !");
208 return stripDigits;
209 }
210 allDigits.insert(allDigits.end(),
211 std::make_move_iterator(stripDigits.begin()),
212 std::make_move_iterator(stripDigits.end()));
213
214 //##################################################################################
215 //######################################### pad readout ############################
216 //##################################################################################
217 sTgcDigitVec padDigits = processPadDigitization(digiInput);
218
219 allDigits.insert(allDigits.end(),
220 std::make_move_iterator(padDigits.begin()),
221 std::make_move_iterator(padDigits.end()));
222
224 ATH_MSG_WARNING("Only digitize strip/pad response !");
225 return allDigits;
226 }
227
228 //##################################################################################
229 //######################################### wire readout ###########################
230 //##################################################################################
231 sTgcDigitVec wireDigits = processWireDigitization(digiInput);
232 allDigits.insert(allDigits.end(),
233 std::make_move_iterator(wireDigits.begin()),
234 std::make_move_iterator(wireDigits.end()));
235
236 return allDigits;
237}
238
239//------------------------------------------------------
240// Process strip digitization
241//------------------------------------------------------
243 const DigiInput& digiInput) const {
244 ATH_MSG_DEBUG("sTgcDigitMaker::strip response ");
245
246 const int gasGap = m_idHelper.gasGap(digiInput.hitId);
247 const IdentifierHash stripLayHash = digiInput.reEle->createHash(gasGap,
248 ReadoutChannelType::Strip, 1);
249 const Identifier stripLayId = digiInput.reEle->measurementId(stripLayHash);
250 const MuonGMR4::StripDesign& stripDesign{digiInput.reEle->stripDesign(stripLayHash)};
251 const Amg::Vector2D hitOnStripSurf = digiInput.reEle->stripLayer(stripLayHash).to2D(digiInput.posOnSurf, false);
252
253 if(!stripDesign.insideTrapezoid(hitOnStripSurf)) {
254 ATH_MSG_DEBUG("Outside of the strip surface boundary : "
255 << m_idHelperSvc->toString(stripLayId)
256 << "; local position " <<Amg::toString(hitOnStripSurf, 2));
257 return {};
258 }
259
260 constexpr double tolerance_length = 0.01*Gaudi::Units::mm;
261 int stripNumber = stripDesign.stripNumber(hitOnStripSurf);
262 if( stripNumber < 0){
263 const double newPosX = hitOnStripSurf.x() - std::copysign(tolerance_length, hitOnStripSurf.x());
264 const Amg::Vector2D newPos(newPosX, hitOnStripSurf.y());
265 stripNumber = stripDesign.stripNumber(newPos);
266 if (stripNumber < 0) {
267 ATH_MSG_WARNING("Failed to obtain strip number " << m_idHelperSvc->toString(stripLayId) );
268 return {};
269 }
270 }
271
272
273 const double tan_theta = digiInput.hitDir.perp() / digiInput.hitDir.z();
274 const double angle_dependency = std::hypot(m_posResIncident, m_posResAngular * tan_theta);
275
276 const double peak_position = CLHEP::RandGaussZiggurat::shoot(condContainers.rndEngine,
277 hitOnStripSurf.x(), m_StripResolution*angle_dependency);
278 ATH_MSG_VERBOSE(__func__<<"() - "<<__LINE__<<" Smeared hit from "<<hitOnStripSurf.x()<<" -> "<<
279 peak_position<<". Assign strip charges");
280 return processStripChargeSharing(digiInput,
281 peak_position,
282 stripNumber);
283}
284//------------------------------------------------------
285// Process strip charge sharing across neighboring strips
286//------------------------------------------------------
288 const double peak_position,
289 const int stripNumber) const {
290
291 const double norm = 0.5 * digiInput.totalCharge;
292
293 sTgcDigitVec digits{};
294 const double tan_theta = digiInput.hitDir.perp() / digiInput.hitDir.z();
295
296 constexpr double tolerance_charge = 0.0005;
297 constexpr int max_neighbor = 10;
298
299 const int gasGap = m_idHelper.gasGap(digiInput.hitId);
300 const auto& design = digiInput.reEle->stripDesign(digiInput.reEle->measurementHash(digiInput.hitId));
301 // Upper half of the strip cluster
302 for (int iStrip = 0; iStrip <= max_neighbor; ++iStrip) {
303 for (int sign : {-1 , 1}) {
304 int currentStrip = stripNumber + sign*iStrip;
305 if (currentStrip > design.numStrips() || currentStrip < 1) {
306 ATH_MSG_VERBOSE(__func__<<"() - "<<__LINE__<<" Breaking the upper half strip loop stripNumber: "
307 << currentStrip << " > " << design.numStrips());
308 break;
309 }
310 bool isValid = false;
311 const Identifier currentStripId = m_idHelper.channelID(digiInput.hitId,
312 digiInput.reEle->multilayer(),
313 gasGap, ReadoutChannelType::Strip,
314 currentStrip, isValid);
315 if (!isValid) {
316 continue;
317 }
318 const Amg::Vector2D currentStripPos = digiInput.reEle->localChannelPosition(digiInput.reEle->measurementHash(currentStripId));
319 const double x_relative = (currentStripPos.x() - peak_position);
320 const double normX = x_relative / design.stripPitch();
321 const double charge = std::hypot(1., m_chargeAngularFactor * tan_theta) * norm *
322 chargeIntegral(normX - 0.5, normX + 0.5);
323
324 if (charge < tolerance_charge) {
325 ATH_MSG_VERBOSE(__func__<<"() - "<<__LINE__<<" Breaking the upper half strip loop stripCharge: "
326 << charge << " < " << tolerance_charge);
327 break;
328 }
329
330 double strip_time = digiInput.time;
332 const bool shift = ((iStrip > 0) && ((x_relative + (0.5*0.75 - iStrip) * design.stripPitch()) < 0));
333 strip_time += getTimeOffsetStrip(iStrip - shift);
334 }
335
336 addDigit(digits, currentStripId, strip_time, charge);
337 ATH_MSG_VERBOSE(__func__<<"() - "<<__LINE__<<" Created a strip digit: strip number = "
338 << currentStrip << ", charge = " << charge);
339 }
340 }
341
342 return digits;
343}
344
345
346//------------------------------------------------------
347// Process pad digitization
348//------------------------------------------------------
350 ATH_MSG_DEBUG("sTgcDigitMaker::pad response ");
351
352 const int gasGap = m_idHelper.gasGap(digiInput.hitId);
353 const IdentifierHash padLayHash = digiInput.reEle->createHash(gasGap,ReadoutChannelType::Pad, 1);
354 const Amg::Vector2D hitOnPadSurf = digiInput.reEle->stripLayer(padLayHash).to2D(digiInput.posOnSurf, true);
355 const MuonGMR4::PadDesign& padDesign{digiInput.reEle->padDesign(padLayHash)};
356 if(!padDesign.insideTrapezoid(hitOnPadSurf)) {
357 ATH_MSG_DEBUG(__func__<<"() -"<<__LINE__<<" Outside of the pad surface boundary :"
358 << m_idHelperSvc->toString(digiInput.hitId)
359 << " local position " <<Amg::toString(hitOnPadSurf, 2));
360 return {};
361 }
362
363 constexpr double tolerance_length = 0.01*Gaudi::Units::mm;
364 auto [padEta, padPhi] = padDesign.channelNumber(hitOnPadSurf);
365
366 if( padEta < 1 || padPhi < 1){
367 const double newPosX = hitOnPadSurf.x() - std::copysign(tolerance_length, hitOnPadSurf.x());
368 const double newPosY = hitOnPadSurf.y() - std::copysign(tolerance_length, hitOnPadSurf.y());
369 const Amg::Vector2D newPosition(newPosX, newPosY);
370 auto [newPadEta, newPadPhi] = padDesign.channelNumber(newPosition);
371 padEta = newPadEta;
372 padPhi = newPadPhi;
373 if( padEta < 1 || padPhi < 1) {
374 ATH_MSG_WARNING("Failed to obtain pad number for " << m_idHelperSvc->toString(digiInput.hitId) );
375 return {};
376 }
377 }
378
379 return processPadChargeSharing(digiInput, padEta, padPhi);
380}
381
382//------------------------------------------------------
383// Process pad charge sharing
384//------------------------------------------------------
386 const int padEta,
387 const int padPhi) const {
388
389 bool isValid = false;
390 const int gasGap = m_idHelper.gasGap(digiInput.hitId);
391 const Identifier padHitId = m_idHelper.padID(digiInput.hitId,
392 digiInput.reEle->multilayer(),
393 gasGap, ReadoutChannelType::Pad, padEta, padPhi, isValid);
394 if(!isValid) {
395 return {};
396 }
397 sTgcDigitVec digits{};
398
399 const IdentifierHash padHitHash = digiInput.reEle->measurementHash(padHitId);
400 const MuonGMR4::PadDesign& padDesign{digiInput.reEle->padDesign(padHitHash)};
401
402 const Amg::Vector2D padPos = digiInput.reEle->localChannelPosition(padHitHash);
403 const Amg::Vector2D hitOnPadSurf = digiInput.reEle->stripLayer(padHitHash).to2D(digiInput.posOnSurf, true);
404
405 const Amg::Vector2D diff = hitOnPadSurf - padPos;
406 const double halfPadHeight = 0.5 * digiInput.reEle->padHeight(padHitHash);
407
408 const std::array<Amg::Vector2D, 4> padHitCorners = padDesign.padCorners(std::make_pair(padEta, padPhi));
409 const double padBottomBase = (padHitCorners[0] - padHitCorners[1]).norm();
410 const double padTopBase = (padHitCorners[2] - padHitCorners[3]).norm();
411 const double halfPadWidth = 0.5 * padBottomBase + 0.25 * (1 + diff.y()/halfPadHeight) * (padTopBase - padBottomBase);
412
413 double deltaX = halfPadWidth - std::abs(diff.x());
414 double deltaY = halfPadHeight - std::abs(diff.y());
415 const bool isNeighX = deltaX < 2.5*m_clusterParams[1];
416 const bool isNeighY = deltaY < 2.5*m_clusterParams[1];
417
418 if (deltaX < 0.) {
419 deltaX = 0.1;
420 }
421 if (deltaY < 0.) {
422 deltaY = 0.1;
423 }
424
425 if (m_doPadSharing && (isNeighX || isNeighY)) {
426 unsigned newPhi = padPhi - Acts::copySign(1, diff.x());
427 unsigned newEta = padEta + Acts::copySign(1, diff.y());
428 bool validEta = newEta > 0 && newEta <= digiInput.reEle->numPadEta(padHitHash);
429 bool validPhi = newPhi > 0 && newPhi <= digiInput.reEle->numPadPhi(padHitHash);
430
431 if (isNeighX && isNeighY && validEta && validPhi) {
432 const Identifier neigh_ID_X = m_idHelper.padID(padHitId, digiInput.reEle->multilayer(),
433 gasGap, ReadoutChannelType::Pad,
434 padEta, newPhi);
435 const Identifier neigh_ID_Y = m_idHelper.padID(padHitId, digiInput.reEle->multilayer(), gasGap,
436 ReadoutChannelType::Pad, newEta, padPhi);
437 const Identifier neigh_ID_XY = m_idHelper.padID(padHitId, digiInput.reEle->multilayer(), gasGap,
438 ReadoutChannelType::Pad, newEta, newPhi);
439 double xQfraction = getPadChargeFraction(deltaX);
440 double yQfraction = getPadChargeFraction(deltaY);
441
442 addDigit(digits, neigh_ID_X, digiInput.time, xQfraction*(1.-yQfraction)*0.5*digiInput.totalCharge);
443 addDigit(digits, neigh_ID_Y, digiInput.time, yQfraction*(1.-xQfraction)*0.5*digiInput.totalCharge);
444 addDigit(digits, neigh_ID_XY, digiInput.time, xQfraction*yQfraction*0.5*digiInput.totalCharge);
445 addDigit(digits, padHitId, digiInput.time, (1.-xQfraction-yQfraction+xQfraction*yQfraction)*0.5*digiInput.totalCharge);
446 } else if (isNeighX && validPhi){
447 const Identifier neigh_ID = m_idHelper.padID(padHitId, digiInput.reEle->multilayer(), gasGap,
448 ReadoutChannelType::Pad, padEta, newPhi);
449 double xQfraction = getPadChargeFraction(deltaX);
450 addDigit(digits, padHitId, digiInput.time, (1.-xQfraction)*0.5*digiInput.totalCharge);
451 addDigit(digits, neigh_ID, digiInput.time, xQfraction*0.5*digiInput.totalCharge);
452 }
453 else if (isNeighY && validEta){
454 const Identifier neigh_ID = m_idHelper.padID(padHitId, digiInput.reEle->multilayer(), gasGap,
455 ReadoutChannelType::Pad, newEta, padPhi);
456 double yQfraction = getPadChargeFraction(deltaY);
457 addDigit(digits, padHitId, digiInput.time, (1.-yQfraction)*0.5*digiInput.totalCharge);
458 addDigit(digits, neigh_ID, digiInput.time, yQfraction*0.5*digiInput.totalCharge);
459 }
460 } else{
461 addDigit(digits, padHitId, digiInput.time, 0.5*digiInput.totalCharge);
462 }
463 return digits;
464}
465
466
467//------------------------------------------------------
468// Process wire digitization
469//------------------------------------------------------
471 ATH_MSG_DEBUG("sTgcDigitMaker::wire response ");
472
473 const int gasGap = m_idHelper.gasGap(digiInput.hitId);
474 const IdentifierHash wireLayHash = digiInput.reEle->createHash(gasGap,
475 ReadoutChannelType::Wire, 1);
476 const MuonGMR4::WireGroupDesign& wireDesign{digiInput.reEle->wireDesign(wireLayHash)};
477
478 const Amg::Vector2D hitOnWireSurf = digiInput.reEle->stripLayer(wireLayHash).to2D(digiInput.posOnSurf, true);
479 if(!wireDesign.insideTrapezoid(hitOnWireSurf)) {
480 ATH_MSG_DEBUG("Outside of the wire surface boundary :" << m_idHelperSvc->toString(digiInput.hitId)
481 << " local position " <<Amg::toString(hitOnWireSurf, 2));
482 return {};
483 }
484
485 constexpr double tolerance_length = 0.01*Gaudi::Units::mm;
486 int wiregroupNumber = (wireDesign.wireNumber(hitOnWireSurf)).first;
487 if( wiregroupNumber < 1) {
488 const double newPosX = hitOnWireSurf.x() - std::copysign(tolerance_length, hitOnWireSurf.x());
489 const Amg::Vector2D newPos{newPosX, hitOnWireSurf.y()};
490 wiregroupNumber = (wireDesign.wireNumber(newPos)).first;
491 if (wiregroupNumber < 1) {
492 ATH_MSG_WARNING("Failed to obtain wire number " << m_idHelperSvc->toString(digiInput.hitId) );
493 return {};
494 }
495 }
496
497 bool isValid = false;
498 const Identifier wireGroupHitId = m_idHelper.channelID(digiInput.hitId, digiInput.reEle->multilayer(), gasGap,
499 ReadoutChannelType::Wire, wiregroupNumber, isValid);
500
501 if(!isValid) {
502 return {};
503 }
504 sTgcDigitVec digits{};
505 addDigit(digits, wireGroupHitId, digiInput.time, digiInput.totalCharge);
506 return digits;
507}
508
509//------------------------------------------------------
510// Compute closest approach between hit trajectory and wire
511//------------------------------------------------------
513 int wireNumber,
514 const Amg::Vector3D& locHitPos,
515 const Amg::Vector3D& locHitDir,
516 const double stepLength) const {
517
518 constexpr double angular_tolerance = 1e-3;
519 // Position of the ionization
520 Ionization ionization;
521 // Finding smallest distance and the points at the smallest distance.
522 // The smallest distance between two lines is perpendicular to both lines.
523 // Previous logic was to find the perpendicular distance between two lines using projection geometry
524 // We can construct two lines in the wire surface local coordinate frame:
525 // - one for the hit segment with equation h0 + t * v_h, where h0 is a point
526 // and v_h is the unit vector of the hit segment
527 // - another for the wire with similar equation w0 + s * v_w, where w0 is a
528 // point and v_w is the unit vector of the wire line
529 // Then it is possible to determine the closest points on each line
530 // by requiring that the vector between them is perpendicular to both:
531 // 1. (h0 + t*v_h - w0 - s*v_w) · v_h = 0
532 // 2. (h0 + t*v_h - w0 - s*v_w) · v_w = 0
533
534 // We have replaced this logic with using Amg::intersect<3> method
535 const MuonGMR4::WireGroupDesign& wireDesign = static_cast<const MuonGMR4::WireGroupDesign&>(stripLayer.design(true));
536 // Geometry setup defined in the eta surface local coordinate frame
537 const double wirePitch = wireDesign.stripPitch();
538 const double wirePosX = wireDesign.firstStripPos().x() + (wireNumber - 1) * wirePitch;
539 const Amg::Vector3D wireDir{stripLayer.to3D(wireDesign.stripDir(),true)};
540 const Amg::Vector3D wirePos(locHitPos.x(), -wirePosX, 0.);
541
542 // Use Amg::intersect to find closest point on hit segment to wire plane
543 std::optional<double> scaleHit = Amg::intersect<3>(locHitPos, locHitDir, Amg::Vector3D::UnitZ(), 0);
544 if (!scaleHit || std::abs(std::abs(wireDir.dot(locHitDir)) - 1.0) < angular_tolerance) {
545 ATH_MSG_DEBUG("The track segment is parallel to the wire, position of digit is undefined");
546 ionization.posOnSegment = locHitPos;
547 ionization.posOnWire = wirePos;
548 ionization.distance = std::hypot(locHitPos.y() + wirePosX, locHitPos.z());
549 if (ionization.distance > wirePitch) {
550 ATH_MSG_DEBUG(" localHitPos: " << Amg::toString(locHitPos, 2) << " localHitDir: " << Amg::toString(locHitDir, 2) << " scaleHit: " << scaleHit.value()
551 << " wirePos: " << Amg::toString(wirePos, 2) << " IonizationDistance: " << ionization.distance);
552 }
553 return ionization;
554 }
555 // Position on hit segment
556 Amg::Vector3D ionizationPos = locHitPos + scaleHit.value() * locHitDir;
557
558 if (scaleHit.value() > stepLength) {
559 ionization.posOnSegment = locHitPos;
560 const Amg::Vector3D closestPointToWirePlane = locHitPos + stepLength * locHitDir;
561 ionization.posOnWire = wirePos;
562 ionization.distance = std::abs(closestPointToWirePlane.z());
563 return ionization;
564 }
565
566 // Project ionization position onto the wire line
567 const double scaleWire = (ionizationPos - wirePos).dot(wireDir);
568 const Amg::Vector3D closestPointOnWire = wirePos + scaleWire * wireDir;
569
570 // Fill ionization result
571 ionization.posOnSegment = ionizationPos;
572 ionization.posOnWire = closestPointOnWire;
573 ionization.distance = (ionizationPos - closestPointOnWire).mag();
574
575 return ionization;
576}
577
578//------------------------------------------------------
579// Adds a digit to the appropriate cache
580//------------------------------------------------------
582 const Identifier& id,
583 const double digittime,
584 const double charge) {
585
586 constexpr double tolerance = 0.1;
587 if (!std::ranges::any_of(digits, [&](std::unique_ptr<sTgcDigit>& known) {
588 return known->identify() == id && std::abs(digittime - known->time()) < tolerance;
589 })) {
590 digits.push_back(std::make_unique<sTgcDigit>(id, 0, digittime, charge, 0, 0));
591 }
592}
593
594//------------------------------------------------------
595// Reads time arrival data file
596//------------------------------------------------------
598 const std::string file_name = "sTGC_Digitization_timeArrival.dat";
599 std::string file_path = PathResolver::find_file(file_name, "DATAPATH");
600 if(file_path.empty()) {
601 ATH_MSG_FATAL("readFileOfTimeWindowOffset(): Could not find file " << file_name );
602 return StatusCode::FAILURE;
603 }
604
605 std::ifstream ifs{file_path, std::ios::in};
606 if(ifs.bad()) {
607 ATH_MSG_FATAL("sTgcDigitMaker: Failed to open time of arrival file " << file_name );
608 return StatusCode::FAILURE;
609 }
610
611 // Read the sTGC_Digitization_timeWindowOffset.dat file
612 std::string line;
613 GammaParameter param{};
614 while (std::getline(ifs, line)) {
615 std::string key;
616 std::istringstream iss(line);
617 iss >> key;
618 if (key == "bin") {
619 iss >> param.lowEdge >> param.kParameter >> param.thetaParameter;
620 m_gammaParameter.push_back(param);
621 } else if (key == "mpv") {
622 double mpt{};
623 int idx{0};
624 while (iss >> mpt) {m_mostProbableArrivalTime[idx++] = mpt;}
625 }
626 }
627 ifs.close();
628 return StatusCode::SUCCESS;
629}
630
631//------------------------------------------------------
632// Retrieves gamma distribution parameters based on distance
633//------------------------------------------------------
635 const double d = std::abs(distance);
636 // Find the parameters assuming the container is sorted in ascending order of 'lowEdge'
637 if (d < m_gammaParameter.front().lowEdge) {
638 return m_gammaParameter.front();
639 }
640 int index{-1};
641 for (const auto& par: m_gammaParameter) {
642 if (d < par.lowEdge) {
643 break;
644 }
645 ++index;
646 }
647 return m_gammaParameter.at(index);
648}
649
650//------------------------------------------------------
651// Computes the most probable arrival time based on the distance of closest approach
652//------------------------------------------------------
653double sTgcDigitMaker::getMostProbableArrivalTime(double distance) const {
654 return Acts::detail::polynomialSum(std::abs(distance), m_mostProbableArrivalTime);
655}
656//------------------------------------------------------
657// Computes the charge on each strip
658//------------------------------------------------------
659
660double sTgcDigitMaker::chargeIntegral(double N, double M) const {
661
662 double term1 = 0.25 * std::erf( M / (std::sqrt(2) * m_clusterParams[0]));
663 double term2 = 0.25 * std::erf( N / (std::sqrt(2) * m_clusterParams[0]));
664 double term3 = 0.25 * std::erf( M / (std::sqrt(2) * m_clusterParams[1]));
665 double term4 = 0.25 * std::erf( N / (std::sqrt(2) * m_clusterParams[1]));
666
667 return (term1 - term2 + term3 - term4);
668}
669//------------------------------------------------------
670// Reads strip time offset data file
671//------------------------------------------------------
673 const std::string file_name = "sTGC_Digitization_timeOffsetStrip.dat";
674 std::string file_path = PathResolver::find_file(file_name, "DATAPATH");
675 if(file_path.empty()) {
676 ATH_MSG_FATAL("readFileOfTimeWindowOffset(): Could not find file " << file_name );
677 return StatusCode::FAILURE;
678 }
679
680 // Open the sTGC_Digitization_timeOffsetStrip.dat file
681 std::ifstream ifs{file_path, std::ios::in};
682 if(ifs.bad()) {
683 ATH_MSG_FATAL("Failed to open time of arrival file " << file_name );
684 return StatusCode::FAILURE;
685 }
686
687 // Initialize the container to store the time offset.
688 // The number of parameters, 6, corresponds to the number of lines to be read
689 // from sTGC_Digitization_timeOffsetStrip.dat.
690 // Setting the default offset to 0 ns.
691 std::string line;
692 size_t index{0};
693 double value{0.0};
694 while (std::getline(ifs, line)) {
695 std::string key;
696 std::istringstream iss(line);
697 iss >> key;
698 if (key == "strip") {
699 iss >> index >> value;
700 if (index >= m_timeOffsetStrip.size()) continue;
701 m_timeOffsetStrip.at(index) = value;
702 }
703 }
704 return StatusCode::SUCCESS;
705}
706
707//------------------------------------------------------
708// Retrieves time offset for strip clusters
709//------------------------------------------------------
710double sTgcDigitMaker::getTimeOffsetStrip(size_t neighbor_index) const {
711 return m_timeOffsetStrip.at(std::min(neighbor_index, m_timeOffsetStrip.size() -1));
712}
713
714//------------------------------------------------------
715// Computes pad charge fraction
716//------------------------------------------------------
718 // The charge fraction that is found past a distance x away from the
719 // centre of a 2D gaussian distribution of width of cluster profile is
720 // described by a modified error function.
721
722 // The modified error function perfectly describes
723 // the pad charge sharing distribution figure 16 of the sTGC
724 // testbeam paper https://arxiv.org/pdf/1509.06329.pdf
725 return 0.5 * (1.0 - std::erf( distance / (std::sqrt(2) * m_clusterParams[1])));
726}
727}
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.
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