File indexing completed on 2025-07-05 08:15:14
0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
0013
0014
0015 #include "PhotoMultiplierHitDigi.h"
0016
0017 #include <Evaluator/DD4hepUnits.h>
0018 #include <algorithm>
0019 #include <algorithms/logger.h>
0020 #include <cmath>
0021 #include <edm4eic/EDM4eicVersion.h>
0022 #include <edm4hep/Vector3d.h>
0023 #include <fmt/core.h>
0024 #include <gsl/pointers>
0025 #include <iterator>
0026 #include <podio/ObjectID.h>
0027
0028 #include "algorithms/digi/PhotoMultiplierHitDigiConfig.h"
0029
0030 namespace eicrecon {
0031
0032
0033
0034
0035 void PhotoMultiplierHitDigi::init() {
0036
0037 debug() << m_cfg << endmsg;
0038
0039
0040
0041
0042
0043 if (m_cfg.seed == 0) {
0044 warning("using seed=0 may cause thread-unsafe behavior of TRandom (EICrecon issue 539)");
0045 }
0046
0047
0048 m_random.SetSeed(m_cfg.seed);
0049 m_rngNorm = [&]() { return m_random.Gaus(0., 1.0); };
0050 m_rngUni = [&]() { return m_random.Uniform(0., 1.0); };
0051
0052 auto sc1 = m_rngUni;
0053 auto sc2 = m_rngNorm;
0054
0055 if (!sc1 || !sc2) {
0056 throw std::runtime_error("Cannot initialize random generator!");
0057 }
0058
0059
0060 qe_init();
0061 }
0062
0063
0064
0065
0066 void PhotoMultiplierHitDigi::process(const PhotoMultiplierHitDigi::Input& input,
0067 const PhotoMultiplierHitDigi::Output& output) const {
0068 const auto [sim_hits] = input;
0069 auto [raw_hits, hit_assocs] = output;
0070
0071 trace("{:=^70}", " call PhotoMultiplierHitDigi::process ");
0072 std::unordered_map<CellIDType, std::vector<HitData>> hit_groups;
0073
0074
0075 trace("{:-<70}", "Loop over simulated hits ");
0076 for (std::size_t sim_hit_index = 0; sim_hit_index < sim_hits->size(); sim_hit_index++) {
0077 const auto& sim_hit = sim_hits->at(sim_hit_index);
0078 auto edep_eV = sim_hit.getEDep() *
0079 1e9;
0080 auto id = sim_hit.getCellID();
0081 trace("hit: pixel id={:#018X} edep = {} eV", id, edep_eV);
0082
0083
0084 if (m_rngUni() > m_cfg.safetyFactor) {
0085 continue;
0086 }
0087
0088
0089 if (m_cfg.enableQuantumEfficiency and !qe_pass(edep_eV, m_rngUni())) {
0090 continue;
0091 }
0092
0093
0094 if (m_cfg.enablePixelGaps) {
0095 auto pos = sim_hit.getPosition();
0096 if (!m_PixelGapMask(
0097 id, dd4hep::Position(pos.x * dd4hep::mm, pos.y * dd4hep::mm, pos.z * dd4hep::mm))) {
0098 continue;
0099 }
0100 }
0101
0102
0103 trace(" -> hit accepted");
0104 trace(" -> MC hit id={}", sim_hit.getObjectID().index);
0105 auto time = sim_hit.getTime();
0106 double amp = m_cfg.speMean + m_rngNorm() * m_cfg.speError;
0107
0108
0109 InsertHit(hit_groups, id, amp, time, sim_hit_index);
0110 }
0111
0112
0113 if (level() <= algorithms::LogLevel::kTrace) {
0114 trace("{:-<70}", "Accepted hit groups ");
0115 for (auto& [id, hitVec] : hit_groups) {
0116 for (auto& hit : hitVec) {
0117 trace("hit_group: pixel id={:#018X} -> npe={} signal={} time={}", id, hit.npe, hit.signal,
0118 hit.time);
0119 for (auto i : hit.sim_hit_indices) {
0120 trace(" - MC hit: EDep={}, id={}", sim_hits->at(i).getEDep(),
0121 sim_hits->at(i).getObjectID().index);
0122 }
0123 }
0124 }
0125 }
0126
0127
0128 if (m_cfg.enableNoise) {
0129 trace("{:=^70}", " BEGIN NOISE INJECTION ");
0130 float p = m_cfg.noiseRate * m_cfg.noiseTimeWindow;
0131 auto cellID_action = [this, &hit_groups](auto id) {
0132
0133 double amp = m_cfg.speMean + m_rngNorm() * m_cfg.speError;
0134 TimeType time = m_cfg.noiseTimeWindow * m_rngUni() / dd4hep::ns;
0135
0136
0137 this->InsertHit(hit_groups, id, amp, time,
0138 0,
0139 true);
0140 };
0141 m_VisitRngCellIDs(cellID_action, p);
0142 }
0143
0144
0145 trace("{:-<70}", "Digitized raw hits ");
0146 for (auto& it : hit_groups) {
0147 for (auto& data : it.second) {
0148
0149
0150 auto raw_hit = raw_hits->create();
0151 raw_hit.setCellID(it.first);
0152 raw_hit.setCharge(static_cast<decltype(edm4eic::RawTrackerHitData::charge)>(data.signal));
0153 raw_hit.setTimeStamp(static_cast<decltype(edm4eic::RawTrackerHitData::timeStamp)>(
0154 data.time / m_cfg.timeResolution));
0155 trace("raw_hit: cellID={:#018X} -> charge={} timeStamp={}", raw_hit.getCellID(),
0156 raw_hit.getCharge(), raw_hit.getTimeStamp());
0157
0158
0159 if (!data.sim_hit_indices.empty()) {
0160 for (auto i : data.sim_hit_indices) {
0161 auto hit_assoc = hit_assocs->create();
0162 hit_assoc.setWeight(1.0 / data.sim_hit_indices.size());
0163 hit_assoc.setRawHit(raw_hit);
0164 #if EDM4EIC_VERSION_MAJOR >= 6
0165 hit_assoc.setSimHit(sim_hits->at(i));
0166 #else
0167 hit_assoc.addToSimHits(sim_hits->at(i));
0168 #endif
0169 }
0170 }
0171 }
0172 }
0173 }
0174
0175 void PhotoMultiplierHitDigi::qe_init() {
0176
0177 qeff.clear();
0178 auto hc = dd4hep::h_Planck * dd4hep::c_light / (dd4hep::eV * dd4hep::nm);
0179 for (const auto& [wl, qe] : m_cfg.quantumEfficiency) {
0180 qeff.emplace_back(hc / wl, qe);
0181 }
0182
0183
0184 std::sort(qeff.begin(), qeff.end(),
0185 [](const std::pair<double, double>& v1, const std::pair<double, double>& v2) {
0186 return v1.first < v2.first;
0187 });
0188
0189
0190 debug("{:-^60}", " Quantum Efficiency vs. Energy ");
0191 for (auto& [en, qe] : qeff) {
0192 debug(" {:>10.4} {:<}", en, qe);
0193 }
0194 trace("{:=^60}", "");
0195
0196 if (m_cfg.enableQuantumEfficiency) {
0197
0198 if (qeff.empty()) {
0199 qeff = {{2.6, 0.3}, {7.0, 0.3}};
0200 warning("Invalid quantum efficiency data provided, using default values {} {:.2f} {} {:.2f} "
0201 "{} {:.2f} {} {:.2f} {}",
0202 "{{", qeff.front().first, ",", qeff.front().second, "},{", qeff.back().first, ",",
0203 qeff.back().second, "}}");
0204 }
0205 if (qeff.front().first > 3.0) {
0206 warning("Quantum efficiency data start from {:.2f} {}", qeff.front().first,
0207 " eV, maybe you are using wrong units?");
0208 }
0209 if (qeff.back().first < 3.0) {
0210 warning("Quantum efficiency data end at {:.2f} {}", qeff.back().first,
0211 " eV, maybe you are using wrong units?");
0212 }
0213 }
0214 }
0215
0216 template <class RndmIter, typename T, class Compare>
0217 RndmIter PhotoMultiplierHitDigi::interval_search(RndmIter beg, RndmIter end, const T& val,
0218 Compare comp) const {
0219
0220 auto dist = std::distance(beg, end);
0221 if ((dist < 2) || (comp(*beg, val) > 0) || (comp(*std::prev(end), val) < 0)) {
0222 return end;
0223 }
0224 auto mid = std::next(beg, dist / 2);
0225
0226 while (mid != end) {
0227 if (comp(*mid, val) == 0) {
0228 return mid;
0229 }
0230 if (comp(*mid, val) > 0) {
0231 end = mid;
0232 } else {
0233 beg = std::next(mid);
0234 }
0235 mid = std::next(beg, std::distance(beg, end) / 2);
0236 }
0237
0238 if (mid == end || comp(*mid, val) > 0) {
0239 return std::prev(mid);
0240 }
0241 return mid;
0242 }
0243
0244 bool PhotoMultiplierHitDigi::qe_pass(double ev, double rand) const {
0245 auto it = interval_search(
0246 qeff.begin(), qeff.end(), ev,
0247 [](const std::pair<double, double>& vals, double val) { return vals.first - val; });
0248
0249 if (it == qeff.end()) {
0250
0251 return false;
0252 }
0253
0254 double prob = it->second;
0255 auto itn = std::next(it);
0256 if (itn != qeff.end() && (itn->first - it->first != 0)) {
0257 prob = (it->second * (itn->first - ev) + itn->second * (ev - it->first)) /
0258 (itn->first - it->first);
0259 }
0260
0261
0262 return rand <= prob;
0263 }
0264
0265
0266
0267 void PhotoMultiplierHitDigi::InsertHit(
0268 std::unordered_map<CellIDType, std::vector<HitData>>& hit_groups, CellIDType id, double amp,
0269 TimeType time, std::size_t sim_hit_index,
0270 bool is_noise_hit) const
0271 {
0272 auto it = hit_groups.find(id);
0273 if (it != hit_groups.end()) {
0274 std::size_t i = 0;
0275 for (auto ghit = it->second.begin(); ghit != it->second.end(); ++ghit, ++i) {
0276 if (std::abs(time - ghit->time) <= (m_cfg.hitTimeWindow)) {
0277
0278 ghit->npe += 1;
0279 ghit->signal += amp;
0280 if (!is_noise_hit) {
0281 ghit->sim_hit_indices.push_back(sim_hit_index);
0282 }
0283 trace(" -> add to group @ {:#018X}: signal={}", id, ghit->signal);
0284 break;
0285 }
0286 }
0287
0288 if (i >= it->second.size()) {
0289 auto sig = amp + m_cfg.pedMean + m_cfg.pedError * m_rngNorm();
0290 decltype(HitData::sim_hit_indices) indices;
0291 if (!is_noise_hit) {
0292 indices.push_back(sim_hit_index);
0293 }
0294 hit_groups.insert({id, {HitData{1, sig, time, indices}}});
0295 trace(" -> no group found,");
0296 trace(" so new group @ {:#018X}: signal={}", id, sig);
0297 }
0298 } else {
0299 auto sig = amp + m_cfg.pedMean + m_cfg.pedError * m_rngNorm();
0300 decltype(HitData::sim_hit_indices) indices;
0301 if (!is_noise_hit) {
0302 indices.push_back(sim_hit_index);
0303 }
0304 hit_groups.insert({id, {HitData{1, sig, time, indices}}});
0305 trace(" -> new group @ {:#018X}: signal={}", id, sig);
0306 }
0307 }
0308
0309 }