ATLAS Offline Software
Loading...
Searching...
No Matches
MuonPhaseII/MuonDigitization/sTgcDigitizationR4/src/sTgcDigitizationTool.cxx
Go to the documentation of this file.
1/*
2 Copyright (C) 2002-2025 CERN for the benefit of the ATLAS collaboration
3*/
4
8#include "CLHEP/Random/RandGaussZiggurat.h"
9
10namespace MuonR4 {
12 ATH_MSG_DEBUG("sTgcDigitizationTool::initialize()");
13 ATH_MSG_DEBUG ( "Configuration sTgcDigitizationTool" );
14 ATH_MSG_DEBUG ( "doSmearing "<< m_doSmearing);
15 ATH_MSG_DEBUG("OutputObjectName " << m_writeKey.key());
16 ATH_MSG_DEBUG ( "HV " << m_runVoltage);
17 ATH_MSG_DEBUG ( "threshold " << m_chargeThreshold);
18 ATH_MSG_DEBUG ( "useCondThresholds " << m_useCondThresholds);
19
21 ATH_CHECK(m_writeKey.initialize());
22 ATH_CHECK(m_effiDataKey.initialize(!m_effiDataKey.empty()));
24 ATH_CHECK(m_smearingTool.retrieve());
25 ATH_CHECK(m_calibrationTool.retrieve());
26
27 if (m_doSmearing) {
28 ATH_MSG_INFO("Running in smeared mode!");
29 }
30
36 ATH_MSG_WARNING("STGC run voltage must be within fit domain of 2.3 kV to 3.2 kV");
37 return StatusCode::FAILURE;
38 }
39 double meanGasGain = 2.15 * 1E-4 * std::exp(6.88*m_runVoltage);
41 m_digitizer = std::make_unique<sTgcDigitMaker>(m_idHelperSvc.get(), mode, meanGasGain, m_doPadSharing);
42 ATH_CHECK(m_digitizer->initialize());
43
44 return StatusCode::SUCCESS;
45 }
46
47 uint16_t sTgcDigitizationTool::bcTagging(const double digitTime) const {
48 uint16_t bctag = 0;
49 int bunchInteger = (digitTime > 0) ? static_cast<int>(std::abs(digitTime / 25.0))
50 : static_cast<int>(std::abs(digitTime / 25.0)) + 1;
51 bctag = (bctag | bunchInteger);
52 if (digitTime < 0) bctag = ~bctag;
53 return bctag;
54 }
55
56 double sTgcDigitizationTool::getChannelThreshold(const EventContext& ctx,
57 const Identifier& channelID,
58 const NswCalibDbThresholdData& thresholdData) const {
60 float elecThreshold = 0.0;
61 if (!thresholdData.getThreshold(channelID, elecThreshold)) {
62 THROW_EXCEPTION("Cannot retrieve VMM threshold from conditions database!");
63 }
64
65 if (!m_calibrationTool->pdoToCharge(ctx, true, elecThreshold, channelID, threshold)) {
66 THROW_EXCEPTION("Cannot convert VMM charge threshold via conditions data!");
67 }
68
69 return threshold;
70 }
71
72 StatusCode sTgcDigitizationTool::digitize(const EventContext& ctx,
73 const TimedHits& hitsToDigit,
74 xAOD::MuonSimHitContainer* sdoContainer) const {
75 const sTgcIdHelper& idHelper{m_idHelperSvc->stgcIdHelper()};
76 CLHEP::HepRandomEngine* rndEngine = getRandomEngine(ctx);
77 const ActsTrk::GeometryContext &gctx{getGeoCtx(ctx)};
78
79 const Muon::DigitEffiData* efficiencyMap{nullptr};
80 ATH_CHECK(SG::get(efficiencyMap, m_effiDataKey, ctx));
81 const NswCalibDbThresholdData* thresholdData{nullptr};
82 ATH_CHECK(SG::get(thresholdData, m_condThrshldsKey, ctx));
83
84 DigiConditions digiCond{m_detMgr, efficiencyMap, thresholdData, rndEngine};
85 DigiCache digitCache{};
86
87 double earliestEventTime = std::numeric_limits<double>::max();
88 xAOD::ChamberViewer viewer{hitsToDigit, m_idHelperSvc.get()};
89 const double angular_tolerance = 1e-3; // ~0.057 degrees
90
91 do {
92 if (viewer.size() == 0) {
93 ATH_MSG_VERBOSE("No hits to digitize — skipping sTGC digitization.");
94 continue;
95 }
96 std::array<sTgcSimDigitVec , 3> simDigitsByChType{};
97 for (const TimedHit& hit : viewer) {
98
99 if (m_digitizeMuonOnly && !MC::isMuon(hit)) {
100 ATH_MSG_VERBOSE("Hit is not from a muon - skipping ");
101 continue;
102 }
103 ATH_MSG_VERBOSE("Hit Particle ID : " << hit->pdgId() );
104 double eventTime = hit.eventTime();
105 earliestEventTime = std::min(earliestEventTime, eventTime);
106 if (hit->energyDeposit() < m_energyDepositThreshold){
107 ATH_MSG_VERBOSE("Hit with Energy Deposit of " << hit->energyDeposit()
108 << " less than " << m_energyDepositThreshold << ". Skip this hit." );
109 continue;
110 }
111
113 const Amg::Vector3D locHitPos = xAOD::toEigen(hit->localPosition());
114 const Amg::Vector3D locHitDir = xAOD::toEigen(hit->localDirection());
115
116 const double hitKineticEnergy = hit->kineticEnergy();
117 if (hitKineticEnergy < m_limitElectronKineticEnergy && MC::isElectron(hit)) {
118 ATH_MSG_DEBUG("Skip electron hit with kinetic energy " << hitKineticEnergy
119 << ", which is less than the lower limit of " << m_limitElectronKineticEnergy);
120 continue;
121 }
122
123 // No support for particles with direction perpendicular to the beam line, since such particles
124 // can deposit energy on a lot of strips and pads of the gas gap.
125 const double theta = std::acos(locHitDir.z()); // polar angle from Z axis
126 const double ninetyDegrees = std::numbers::pi / 2;
127 // Reject hits that are too close to 90 degrees (i.e., perpendicular to Z)
128 if (std::abs(theta - ninetyDegrees) < angular_tolerance) {
129 ATH_MSG_VERBOSE("Skipping hit nearly perpendicular to Z-axis (angle: " << theta << ")");
130 continue;
131 }
132 if (std::abs(locHitDir.z()) < 0.00001) {
133 ATH_MSG_VERBOSE("Skipping hit nearly perpendicular to Z-axis.");
134 continue;
135 }
136 if(eventTime != 0){
137 ATH_MSG_DEBUG("Updated hit global time to include off set of " << eventTime << " ns from OOT bunch.");
138 }
139 else {
140 ATH_MSG_DEBUG("This hit came from the in time bunch.");
141 }
142
143 const Identifier hitId = hit->identify();
146 if (m_doSmearing) {
147 bool acceptHit = true;
148 ATH_CHECK(m_smearingTool->isAccepted(hitId, acceptHit, rndEngine));
149 if ( !acceptHit ) {
150 ATH_MSG_DEBUG("Dropping the hit - smearing tool");
151 continue;
152 }
153 }
154 const MuonGMR4::sTgcReadoutElement* readoutElement = m_detMgr->getsTgcReadoutElement(hitId);
155 const Amg::Vector3D globalHitPos = readoutElement->localToGlobalTrans(gctx, hitId) * locHitPos;
156 double globalHitTime = hit->globalTime() + eventTime;
157 double tofCorrection = globalHitPos.mag() / CLHEP::c_light;
158 double bunchTime = globalHitTime - tofCorrection;
159
160 const HepMcParticleLink particleLink = hit->genParticleLink();
161 // Print some information about the sTGC hit
162 ATH_MSG_VERBOSE("hitID " << m_idHelperSvc->toString(hitId) << " Hit bunch time " << bunchTime << " tof/G4 hit time " << globalHitTime
163 << " globalHitPosition " << Amg::toString(globalHitPos) << " hit: r " << globalHitPos.perp() << " z " << globalHitPos.z()
164 << " mclink " << particleLink << "Kinetic energy " << hitKineticEnergy);
165 ATH_MSG_VERBOSE("Total hits passed to digitize: " << hitsToDigit.size());
166 sTgcDigitVec digitizedHits = m_digitizer->executeDigi(digiCond, hit);
167 if(digitizedHits.empty()) {
168 continue;
169 }
170 ATH_MSG_VERBOSE("Hit produced " << digitizedHits.size() << " digits." );
171
172 for (std::unique_ptr<sTgcDigit>& digit : digitizedHits) {
173 /*
174 NOTE:
175 -----
176 Since not every hit might end up resulting in a
177 digit, this construction might take place after the hit loop
178 in a loop of its own!
179 */
180 // make new sTgcDigit
181 const Identifier digitId = digit->identify();
182 double digitTime = digit->time();
183 int digitChType = idHelper.channelType(digitId);
184
185 if(digitChType == ReadoutChannelType::Strip) {
186 digitTime += CLHEP::RandGaussZiggurat::shoot(rndEngine, 0, m_timeJitterElectronicsStrip);
187 }
188 else {
189 digitTime += CLHEP::RandGaussZiggurat::shoot(rndEngine, 0, m_timeJitterElectronicsPad);
190 }
191
192 uint16_t digitBCTag = bcTagging(digitTime + bunchTime);
193 digitTime += m_doToFCorrection ? bunchTime : globalHitTime;
194
195 double digitCharge = digit->charge();
196 // Create a new digit with updated time and BCTag
197 int eventId = hit.eventId();
198 bool isDead{false}, isPileup{eventId != 0};
199 ATH_MSG_VERBOSE("Hit is from the main signal subevent if eventId is zero, eventId = "
200 << eventId << " newDigit time: " << digitTime);
201 auto newDigitPtr = std::make_unique<sTgcDigit>(digitId, digitBCTag, digitTime, digitCharge, isDead, isPileup);
202 if (digitChType == ReadoutChannelType::Strip) {
203 ATH_MSG_VERBOSE("Finalizing Digit "<<m_idHelperSvc->toString(digitId)
204 <<" BC tag = " << newDigitPtr->bcTag()
205 <<" digitTime = " << newDigitPtr->time()
206 <<" charge = " << newDigitPtr->charge());
207 }
208 simDigitsByChType[digitChType].emplace_back(std::move(hit), std::move(newDigitPtr));
209 }
210 }
211 if (!simDigitsByChType[ReadoutChannelType::Strip].empty()) {
212 ATH_CHECK(processDigitsWithVMM(ctx, digiCond, simDigitsByChType[ReadoutChannelType::Strip], m_deadtimeStrip, m_doNeighborOn, digitCache, *sdoContainer));
213 }
214 if (!simDigitsByChType[ReadoutChannelType::Pad].empty()) {
215 ATH_CHECK(processDigitsWithVMM(ctx, digiCond, simDigitsByChType[ReadoutChannelType::Pad], m_deadtimePad, false, digitCache, *sdoContainer));
216 }
217 if (!simDigitsByChType[ReadoutChannelType::Wire].empty()) {
218 ATH_CHECK(processDigitsWithVMM(ctx, digiCond, simDigitsByChType[ReadoutChannelType::Wire], m_deadtimeWire, false, digitCache, *sdoContainer));
219 }
220 } while (viewer.next());
222 ATH_CHECK(writeDigitContainer(ctx, m_writeKey, std::move(digitCache), idHelper.module_hash_max()));
223
224 return StatusCode::SUCCESS;
225 }
226
227 StatusCode sTgcDigitizationTool::processDigitsWithVMM(const EventContext& ctx,
228 const DigiConditions& digiCond,
229 sTgcSimDigitVec& digitsInChamber,
230 const double vmmDeadTime,
231 const bool isNeighbourOn,
232 DigiCache& cache,
233 xAOD::MuonSimHitContainer& outSdoContainer) const {
234
235 const sTgcIdHelper& idHelper{m_idHelperSvc->stgcIdHelper()};
236 if (digitsInChamber.empty()) {
237 ATH_MSG_WARNING("Failed to obtain the digitized hits for VMM Simulation" );
238 return StatusCode::SUCCESS;
239 }
241 sTgcSimDigitVec mergedDigits = processDigitsWithVMM(ctx, digiCond, vmmDeadTime,
242 digitsInChamber, isNeighbourOn);
244 if (mergedDigits.empty()) {
245 return StatusCode::SUCCESS;
246 }
247
248 for (sTgcSimDigitHit& merged : mergedDigits) {
249
251 bool acceptDigit{true};
252 float chargeAfterSmearing = merged.getDigit().charge();
253 if (m_doSmearing) {
254 ATH_CHECK(m_smearingTool->smearCharge(merged.identify(), chargeAfterSmearing, acceptDigit,
255 digiCond.rndEngine));
256 }
257 if (!acceptDigit) {
258 continue;
259 }
262 if (idHelper.channelType(merged.identify()) == ReadoutChannelType::Strip &&
263 chargeAfterSmearing < 0.001) {
264 continue;
265 }
266 std::unique_ptr<sTgcDigit> finalDigit = std::make_unique<sTgcDigit>(std::move(merged.getDigit()));
267 if (m_doSmearing) {
268 finalDigit->set_charge(chargeAfterSmearing);
269 }
270 ATH_MSG_VERBOSE("Final Digit "<<m_idHelperSvc->toString(finalDigit->identify())<<
271 " BC tag = " << finalDigit->bcTag()<<
272 " digitTime = " << finalDigit->time() <<
273 " charge = " << finalDigit->charge());
274
276 xAOD::MuonSimHit* sdoHit = addSDO(merged.getSimHit(), &outSdoContainer);
277 if(sdoHit) {
279 double globalHitTime = sdoHit->globalTime() + merged.getSimHit().eventTime();
280 sdoHit->setGlobalTime(globalHitTime);
281 }
283 sTgcDigitCollection* outColl = fetchCollection(finalDigit->identify(), cache);
284 outColl->push_back(std::move(finalDigit));
285 }
286 return StatusCode::SUCCESS;
287 }
288
290 const DigiConditions& digiCond,
291 const double vmmDeadTime,
292 sTgcSimDigitVec& unmergedDigits,
293 const bool isNeighbourOn) const {
294
295 const MuonGMR4::MuonDetectorManager* detMgr{digiCond.detMgr};
296 const sTgcIdHelper& idHelper{m_idHelperSvc->stgcIdHelper()};
298 std::stable_sort(unmergedDigits.begin(), unmergedDigits.end(),
299 [&idHelper](const sTgcSimDigitHit& a, const sTgcSimDigitHit& b) {
300 const int layA = idHelper.gasGap(a.identify());
301 const int layB = idHelper.gasGap(b.identify());
302 if (layA != layB) return layA < layB;
303 const int chA = idHelper.channel(a.identify());
304 const int chB = idHelper.channel(b.identify());
305 if (chA != chB) return chA < chB;
306 return a.time() < b.time();
307 }
308 );
309 sTgcSimDigitVec savedDigits{}, premerged{};
310
311 premerged.reserve(unmergedDigits.size());
312 savedDigits.reserve(premerged.capacity());
313
314 auto passNeigbourLogic = [&](const sTgcSimDigitHit& candidate) {
315 if (!isNeighbourOn || savedDigits.empty()) return false;
316 if (savedDigits.back().identify() == candidate.identify() &&
317 std::abs(savedDigits.back().time() - candidate.time()) < vmmDeadTime) {
318 ATH_MSG_VERBOSE("Digits are too close in time ");
319 return false;
320 }
321 const Identifier digitId = candidate.identify();
322 const int channel = idHelper.channel(digitId);
323 const int maxChannel = detMgr->getsTgcReadoutElement(digitId)->numChannels(digitId);
324 for (int neighbour : {std::max(1, channel -1), std::min(maxChannel, channel+1)}) {
326 if (neighbour == channel) continue;
327 const Identifier neighbourId = idHelper.channelID(digitId,
328 idHelper.multilayer(digitId),
329 idHelper.gasGap(digitId),
330 idHelper.channelType(digitId), neighbour);
331 const double threshold = m_useCondThresholds ? getChannelThreshold(ctx, neighbourId, *digiCond.thresholdData)
332 : m_chargeThreshold.value();
333 if (std::find_if(savedDigits.begin(), savedDigits.end(), [&](const sTgcSimDigitHit& known){
334 return known.identify() == neighbourId &&
335 known.getDigit().charge() > threshold &&
336 std::abs(known.time() - candidate.time()) < m_hitTimeMergeThreshold;
337 }) != savedDigits.end()) return true;
338
339 }
340 return false;
341 };
342 // Sort digits on every channel by earliest to latest time
343 // Also do hit merging to help with neighborOn logic
345 for (sTgcSimDigitVec::iterator merge_me = unmergedDigits.begin(); merge_me!= unmergedDigits.end(); ++merge_me) {
347 threshold = getChannelThreshold(ctx, (*merge_me).identify(), *digiCond.thresholdData);
348 }
351 sTgcDigit& digit1{(*merge_me).getDigit()};
352 double totalCharge = digit1.charge();
353 double weightedTime = digit1.time();
354
355 sTgcSimDigitVec::iterator merge_with = merge_me + 1;
356 for ( ; merge_with!= unmergedDigits.end(); ++merge_with) {
358 if ((*merge_with).identify() != (*merge_me).identify()) {
359 break;
360 }
361 const sTgcDigit& mergeDigit{(*merge_with).getDigit()};
362 // If future digits are within window, digit1 absorbs its charge
363 if (mergeDigit.time() - digit1.time() > m_hitTimeMergeThreshold) break;
364 // If digit1 is not above threshold prior to merging, the new time is
365 // a weighted average. Do it for every merging pair.
366 if (totalCharge < threshold) {
367 weightedTime = (weightedTime * totalCharge + mergeDigit.time() * mergeDigit.charge())
368 / (totalCharge + mergeDigit.charge());
369 }
370 totalCharge += mergeDigit.charge();
371 }
372 digit1.set_charge(totalCharge);
373 digit1.set_time(weightedTime);
374 sTgcSimDigitHit& mergedHit{*merge_me};
375 if (!savedDigits.empty() &&
376 savedDigits.back().identify() == digit1.identify() &&
377 std::abs(savedDigits.back().time() - digit1.time()) <= vmmDeadTime) continue;
378 if (digit1.charge() > threshold || passNeigbourLogic(mergedHit)){
379 savedDigits.emplace_back(std::move(mergedHit));
380 } else if (isNeighbourOn) {
381 premerged.emplace_back(std::move(mergedHit));
382 }
383 } // end of time-ordering and hit merging loop
384 std::copy_if(std::make_move_iterator(premerged.begin()),
385 std::make_move_iterator(premerged.end()),
386 std::back_inserter(savedDigits), passNeigbourLogic);
387 if(savedDigits.empty() && !unmergedDigits.empty()) {
388 }
389 return savedDigits;
390 }
391}
Scalar theta() const
theta method
#define ATH_CHECK
Evaluate an expression and check for errors.
#define ATH_MSG_INFO(x)
#define ATH_MSG_VERBOSE(x)
#define ATH_MSG_WARNING(x)
#define ATH_MSG_DEBUG(x)
ATLAS-specific HepMC functions.
value_type push_back(value_type pElem)
Add an element to the end of the collection.
Identifier identify() const
Definition MuonDigit.h:30
const Amg::Transform3D & localToGlobalTrans(const ActsTrk::GeometryContext &ctx) const
Returns the local to global transformation into the ATLAS coordinate system.
size_type module_hash_max() const
the maximum hash value
xAOD::MuonSimHit * addSDO(const TimedHit &hit, xAOD::MuonSimHitContainer *sdoContainer) const
Adds the timed simHit to the output SDO container.
ServiceHandle< Muon::IMuonIdHelperSvc > m_idHelperSvc
std::vector< TimedHitPtr< xAOD::MuonSimHit > > TimedHits
CLHEP::HepRandomEngine * getRandomEngine(const EventContext &ctx) const
TimedHitPtr< xAOD::MuonSimHit > TimedHit
DigitColl * fetchCollection(const Identifier &hitId, OutDigitCache_t< DigitColl > &digitCache) const
Helper function that provides fetches the proper DigitCollection from the DigitCache for a given hit ...
const MuonGMR4::MuonDetectorManager * m_detMgr
StatusCode writeDigitContainer(const EventContext &ctx, const SG::WriteHandleKey< DigitCont > &key, OutDigitCache_t< DigitColl > &&digitCache, unsigned int hashMax) const
Helper function to move the collected digits into the final DigitContainer.
const ActsTrk::GeometryContext & getGeoCtx(const EventContext &ctx) const
Returns the reference to the ActsTrk::GeometryContext needed to fetch global positions from the Reado...
StatusCode processDigitsWithVMM(const EventContext &ctx, const DigiConditions &digiCond, sTgcSimDigitVec &digitsInChamber, const double vmmDeadTime, const bool isNeighbourOn, DigiCache &cache, xAOD::MuonSimHitContainer &outSdoContainer) const
double getChannelThreshold(const EventContext &ctx, const Identifier &channelID, const NswCalibDbThresholdData &thresholdData) const
StatusCode digitize(const EventContext &ctx, const TimedHits &hitsToDigit, xAOD::MuonSimHitContainer *sdoContainer) const override final
Digitize the time ordered hits and write them to the digit format specific for the detector technolog...
bool getThreshold(const Identifier &, float &) const
void set_time(float newTime)
Definition sTgcDigit.cxx:76
float time() const
Definition sTgcDigit.cxx:61
float charge() const
Definition sTgcDigit.cxx:46
void set_charge(float newCharge)
Definition sTgcDigit.cxx:71
int multilayer(const Identifier &id) const
int channelType(const Identifier &id) const
int channel(const Identifier &id) const override
int gasGap(const Identifier &id) const override
get the hashes
Identifier channelID(int stationName, int stationEta, int stationPhi, int multilayer, int gasGap, int channelType, int channel) const
bool next() noexcept
Loads the hits from the next chamber.
std::size_t size() const noexcept
Returns how many hits are in the current chamber.
void setGlobalTime(const float time)
Sets the time of the traversing particle.
float globalTime() const
Returns the time ellapsed since the collision of the traversing particle.
std::string toString(const Translation3D &translation, int precision=4)
GeoPrimitvesToStringConverter.
Eigen::Matrix< double, 3, 1 > Vector3D
bool isElectron(const T &p)
bool isMuon(const T &p)
This header ties the generic definitions in this package.
const T * get(const ReadCondHandleKey< T > &key, const EventContext &ctx)
Convenience function to retrieve an object given a ReadCondHandleKey.
void stable_sort(DataModel_detail::iterator< DVL > beg, DataModel_detail::iterator< DVL > end)
Specialization of stable_sort for DataVector/List.
MuonSimHit_v1 MuonSimHit
Defined the version of the MuonSimHit.
Definition MuonSimHit.h:12
MuonSimHitContainer_v1 MuonSimHitContainer
Define the version of the pixel cluster container.
#define THROW_EXCEPTION(MESSAGE)
Definition throwExcept.h:10