File indexing completed on 2026-03-31 07:48:20
0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
0013 #include "CalorimeterHitDigi.h"
0014
0015 #include <DD4hep/Detector.h>
0016 #include <DD4hep/IDDescriptor.h>
0017 #include <DD4hep/Readout.h>
0018 #include <DDSegmentation/BitFieldCoder.h>
0019 #include <Evaluator/DD4hepUnits.h>
0020 #include <algorithms/service.h>
0021 #include <edm4eic/EDM4eicVersion.h>
0022 #include <edm4eic/MCRecoCalorimeterHitAssociationCollection.h>
0023 #include <edm4hep/CaloHitContributionCollection.h>
0024 #include <fmt/format.h>
0025 #include <podio/RelationRange.h>
0026 #include <podio/detail/Link.h>
0027 #include <podio/detail/LinkCollectionImpl.h>
0028 #include <algorithm>
0029 #include <cmath>
0030 #include <cstddef>
0031 #include <gsl/pointers>
0032 #include <limits>
0033 #include <map>
0034 #include <memory>
0035 #include <random>
0036 #include <stdexcept>
0037 #include <string>
0038 #include <unordered_map>
0039 #include <utility>
0040 #include <vector>
0041
0042 #include "algorithms/calorimetry/CalorimeterHitDigiConfig.h"
0043 #include "services/evaluator/EvaluatorSvc.h"
0044
0045 using namespace dd4hep;
0046
0047 namespace eicrecon {
0048
0049
0050
0051
0052
0053
0054
0055
0056
0057 void CalorimeterHitDigi::init() {
0058
0059
0060
0061
0062
0063
0064
0065
0066
0067
0068
0069
0070
0071
0072
0073
0074 if (m_cfg.eRes.empty()) {
0075 m_cfg.eRes.resize(3);
0076 } else if (m_cfg.eRes.size() != 3) {
0077 error("Invalid m_cfg.eRes.size()");
0078 throw std::runtime_error("Invalid m_cfg.eRes.size()");
0079 }
0080
0081
0082 tRes = m_cfg.tRes / dd4hep::ns;
0083 stepTDC = dd4hep::ns / m_cfg.resolutionTDC;
0084
0085
0086 if (m_cfg.readout.empty()) {
0087 error("readoutClass is not provided, it is needed to know the fields in readout ids");
0088 throw std::runtime_error("readoutClass is not provided");
0089 }
0090
0091
0092 try {
0093 id_spec = m_geo.detector()->readout(m_cfg.readout).idSpec();
0094 } catch (...) {
0095
0096
0097
0098 debug("Failed to load ID decoder for {}", m_cfg.readout);
0099 throw std::runtime_error(fmt::format("Failed to load ID decoder for {}", m_cfg.readout));
0100 }
0101
0102 decltype(id_mask) id_inverse_mask = 0;
0103
0104 if (!m_cfg.fields.empty()) {
0105 for (auto& field : m_cfg.fields) {
0106 id_inverse_mask |= id_spec.field(field)->mask();
0107 }
0108 debug("ID mask in {:s}: {:#064b}", m_cfg.readout, id_mask);
0109 }
0110 id_mask = ~id_inverse_mask;
0111
0112 std::function hit_to_map = [this](const edm4hep::SimCalorimeterHit& h) {
0113 std::unordered_map<std::string, double> params;
0114 for (const auto& p : id_spec.fields()) {
0115 const std::string& name = p.first;
0116 const dd4hep::IDDescriptor::Field* field = p.second;
0117 params.emplace(name, field->value(h.getCellID()));
0118 trace("{} = {}", name, field->value(h.getCellID()));
0119 }
0120 return params;
0121 };
0122
0123 auto& serviceSvc = algorithms::ServiceSvc::instance();
0124 corrMeanScale =
0125 serviceSvc.service<EvaluatorSvc>("EvaluatorSvc")->compile(m_cfg.corrMeanScale, hit_to_map);
0126
0127 std::map<std::string, readout_enum> readoutTypes{{"simple", kSimpleReadout},
0128 {"poisson_photon", kPoissonPhotonReadout},
0129 {"sipm", kSipmReadout}};
0130 if (not readoutTypes.count(m_cfg.readoutType)) {
0131 error("Invalid readoutType \"{}\"", m_cfg.readoutType);
0132 throw std::runtime_error(fmt::format("Invalid readoutType \"{}\"", m_cfg.readoutType));
0133 }
0134 readoutType = readoutTypes.at(m_cfg.readoutType);
0135 }
0136
0137 void CalorimeterHitDigi::process(const CalorimeterHitDigi::Input& input,
0138 const CalorimeterHitDigi::Output& output) const {
0139
0140 const auto [headers, simhits] = input;
0141 #if EDM4EIC_BUILD_VERSION >= EDM4EIC_VERSION(8, 7, 0)
0142 auto [rawhits, links, rawassocs] = output;
0143 #else
0144 auto [rawhits, rawassocs] = output;
0145 #endif
0146
0147
0148 auto seed = m_uid.getUniqueID(*headers, name());
0149 std::default_random_engine generator(seed);
0150 std::normal_distribution<double> gaussian;
0151
0152
0153 std::unordered_map<uint64_t, std::vector<std::size_t>> merge_map;
0154 std::size_t ix = 0;
0155 for (const auto& ahit : *simhits) {
0156 uint64_t hid = ahit.getCellID() & id_mask;
0157
0158 trace("org cell ID in {:s}: {:#064b}", m_cfg.readout, ahit.getCellID());
0159 trace("new cell ID in {:s}: {:#064b}", m_cfg.readout, hid);
0160
0161 merge_map[hid].push_back(ix);
0162
0163 ix++;
0164 }
0165
0166
0167
0168 for (const auto& [id, ixs] : merge_map) {
0169
0170
0171 edm4hep::MutableRawCalorimeterHit rawhit;
0172 #if EDM4EIC_BUILD_VERSION >= EDM4EIC_VERSION(8, 7, 0)
0173 std::vector<edm4eic::MutableMCRecoCalorimeterHitLink> links_staging;
0174 #endif
0175 std::vector<edm4eic::MutableMCRecoCalorimeterHitAssociation> rawassocs_staging;
0176
0177 double edep = 0;
0178 double time = std::numeric_limits<double>::max();
0179 double max_edep = 0;
0180 auto leading_hit = (*simhits)[ixs[0]];
0181
0182 for (unsigned long i : ixs) {
0183 auto hit = (*simhits)[i];
0184
0185 double timeC = std::numeric_limits<double>::max();
0186 for (const auto& c : hit.getContributions()) {
0187 timeC = std::min<double>(c.getTime(), timeC);
0188 }
0189 if (timeC > m_cfg.capTime) {
0190 debug("retaining hit, even though time %f ns > %f ns", timeC / dd4hep::ns,
0191 m_cfg.capTime / dd4hep::ns);
0192 }
0193 edep += hit.getEnergy();
0194 trace("adding {} \t total: {}", hit.getEnergy(), edep);
0195
0196
0197 if (hit.getEnergy() > max_edep) {
0198 max_edep = hit.getEnergy();
0199 leading_hit = hit;
0200 time = std::min(timeC, time);
0201 }
0202
0203 edm4eic::MutableMCRecoCalorimeterHitAssociation assoc;
0204 assoc.setRawHit(rawhit);
0205 assoc.setSimHit(hit);
0206 assoc.setWeight(hit.getEnergy());
0207 rawassocs_staging.push_back(assoc);
0208 #if EDM4EIC_BUILD_VERSION >= EDM4EIC_VERSION(8, 7, 0)
0209 edm4eic::MutableMCRecoCalorimeterHitLink link;
0210 link.setFrom(rawhit);
0211 link.setTo(hit);
0212 link.setWeight(hit.getEnergy());
0213 links_staging.push_back(link);
0214 #endif
0215 }
0216 if (time > m_cfg.capTime) {
0217 debug("retaining hit, even though time %f ns > %f ns", time / dd4hep::ns,
0218 m_cfg.capTime / dd4hep::ns);
0219 }
0220
0221
0222 const double eResRel =
0223 (edep > m_cfg.threshold)
0224 ? gaussian(generator) *
0225 std::sqrt(std::pow(m_cfg.eRes[0] / std::sqrt(edep), 2) +
0226 std::pow(m_cfg.eRes[1], 2) + std::pow(m_cfg.eRes[2] / (edep), 2))
0227 : 0;
0228
0229 double corrMeanScale_value = corrMeanScale(leading_hit);
0230
0231 double ped = m_cfg.pedMeanADC + gaussian(generator) * m_cfg.pedSigmaADC;
0232
0233
0234 unsigned long long adc = 0;
0235 unsigned long long tdc = std::llround((time + gaussian(generator) * tRes) * stepTDC);
0236
0237
0238 edep *= std::max(0.0, 1.0 + eResRel);
0239
0240 if (readoutType == kSimpleReadout) {
0241 adc = std::max(
0242 std::llround(ped + edep * corrMeanScale_value / m_cfg.dyRangeADC * m_cfg.capADC), 0LL);
0243 } else if (readoutType == kPoissonPhotonReadout) {
0244 const long long int n_photons_mean =
0245 edep * m_cfg.lightYield * m_cfg.photonDetectionEfficiency;
0246 std::poisson_distribution<> n_photons_detected_dist(n_photons_mean);
0247 const long long int n_photons_detected = n_photons_detected_dist(generator);
0248 const long long int n_max_photons =
0249 m_cfg.dyRangeADC * m_cfg.lightYield * m_cfg.photonDetectionEfficiency;
0250 trace("n_photons_detected {}", n_photons_detected);
0251 adc = std::max(std::llround(ped + n_photons_detected * corrMeanScale_value / n_max_photons *
0252 m_cfg.capADC),
0253 0LL);
0254 } else if (readoutType == kSipmReadout) {
0255 const long long int n_photons = edep * m_cfg.lightYield;
0256 std::binomial_distribution<> n_photons_detected_dist(n_photons,
0257 m_cfg.photonDetectionEfficiency);
0258 const long long int n_photons_detected = n_photons_detected_dist(generator);
0259 const long long int n_pixels_fired =
0260 m_cfg.numEffectiveSipmPixels *
0261 (1 - exp(-n_photons_detected / (double)m_cfg.numEffectiveSipmPixels));
0262 const long long int n_max_photons =
0263 m_cfg.dyRangeADC * m_cfg.lightYield * m_cfg.photonDetectionEfficiency;
0264 trace("n_photons_detected {}, n_pixels_fired {}, n_max_photons {}", n_photons_detected,
0265 n_pixels_fired, n_max_photons);
0266 adc = std::max(
0267 std::llround(ped + n_pixels_fired * corrMeanScale_value / n_max_photons * m_cfg.capADC),
0268 0LL);
0269 }
0270
0271 if (edep > 1.e-3) {
0272 trace("E sim {} \t adc: {} \t time: {}\t maxtime: {} \t tdc: {} \t corrMeanScale: {}", edep,
0273 adc, time, m_cfg.capTime, tdc, corrMeanScale_value);
0274 }
0275
0276 rawhit.setCellID(leading_hit.getCellID());
0277 rawhit.setAmplitude(adc > m_cfg.capADC ? m_cfg.capADC : adc);
0278 rawhit.setTimeStamp(tdc);
0279 rawhits->push_back(rawhit);
0280
0281 #if EDM4EIC_BUILD_VERSION >= EDM4EIC_VERSION(8, 7, 0)
0282 for (auto& link : links_staging) {
0283 link.setWeight(link.getWeight() / edep);
0284 links->push_back(link);
0285 }
0286 #endif
0287 for (auto& assoc : rawassocs_staging) {
0288 assoc.setWeight(assoc.getWeight() / edep);
0289 rawassocs->push_back(assoc);
0290 }
0291 }
0292 }
0293
0294 }