Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2024-06-29 07:05:54

0001 // SPDX-License-Identifier: LGPL-3.0-or-later
0002 // Copyright (C) 2022 Chao Peng, Wouter Deconinck, Sylvester Joosten, Barak Schmookler, David Lawrence
0003 
0004 // A general digitization for CalorimeterHit from simulation
0005 // 1. Smear energy deposit with a/sqrt(E/GeV) + b + c/E or a/sqrt(E/GeV) (relative value)
0006 // 2. Digitize the energy with dynamic ADC range and add pedestal (mean +- sigma)
0007 // 3. Time conversion with smearing resolution (absolute value)
0008 // 4. Signal is summed if the SumFields are provided
0009 //
0010 // Author: Chao Peng
0011 // Date: 06/02/2021
0012 
0013 
0014 #include "CalorimeterHitDigi.h"
0015 
0016 #include <DD4hep/Detector.h>
0017 #include <DD4hep/IDDescriptor.h>
0018 #include <DD4hep/Readout.h>
0019 #include <DD4hep/config.h>
0020 #include <DDSegmentation/BitFieldCoder.h>
0021 #include <Evaluator/DD4hepUnits.h>
0022 #include <algorithms/service.h>
0023 #include <edm4hep/CaloHitContributionCollection.h>
0024 #include <fmt/core.h>
0025 #include <podio/RelationRange.h>
0026 #include <cmath>
0027 #include <cstddef>
0028 #include <gsl/pointers>
0029 #include <limits>
0030 #include <stdexcept>
0031 #include <string>
0032 #include <unordered_map>
0033 #include <utility>
0034 #include <vector>
0035 
0036 #include "algorithms/calorimetry/CalorimeterHitDigiConfig.h"
0037 #include "services/evaluator/EvaluatorSvc.h"
0038 
0039 using namespace dd4hep;
0040 
0041 namespace eicrecon {
0042 
0043 //
0044 // TODO:
0045 // - Array type configuration parameters are not yet supported in JANA (needs to be added)
0046 // - Random number service needs to bew resolved (on global scale)
0047 // - It is possible standard running of this with Gaudi relied on a number of parameters
0048 //   being set in the config. If that is the case, they should be moved into the default
0049 //   values here. This needs to be confirmed.
0050 
0051 
0052 void CalorimeterHitDigi::init() {
0053 
0054     // Gaudi implements a random number generator service. It is not clear to me how this
0055     // can work. There are multiple race conditions that occur in parallel event processing:
0056     // 1. The exact same events processed by a given thread in one invocation will not
0057     //    necessarily be the combination of events any thread sees in a subsequent
0058     //    invocation. Thus, you can't rely on thread_local storage.
0059     // 2. Its possible for the factory execution order to be modified by the presence of
0060     //    a processor (e.g. monitoring plugin). This is not as serious since changing the
0061     //    command line should cause one not to expect reproducibility. Still, one may
0062     //    expect the inclusion of an "observer" plugin not to have such side affects.
0063     //
0064     // More information will be needed. In the meantime, we implement a local random number
0065     // generator. Ideally, this would be seeded with the run number+event number, but for
0066     // now, just use default values defined in header file.
0067 
0068     // set energy resolution numbers
0069     if (m_cfg.eRes.empty()) {
0070       m_cfg.eRes.resize(3);
0071     } else if (m_cfg.eRes.size() != 3) {
0072       error("Invalid m_cfg.eRes.size()");
0073       throw std::runtime_error("Invalid m_cfg.eRes.size()");
0074     }
0075 
0076     // using juggler internal units (GeV, mm, radian, ns)
0077     tRes       = m_cfg.tRes / dd4hep::ns;
0078     stepTDC    = dd4hep::ns / m_cfg.resolutionTDC;
0079 
0080     // sanity checks
0081     if (m_cfg.readout.empty()) {
0082         error("readoutClass is not provided, it is needed to know the fields in readout ids");
0083         throw std::runtime_error("readoutClass is not provided");
0084     }
0085 
0086     // get decoders
0087     try {
0088         id_spec = m_geo.detector()->readout(m_cfg.readout).idSpec();
0089     } catch (...) {
0090         // Can not be more verbose. In JANA2, this will be attempted at each event, which
0091         // pollutes output for geometries that are less than complete.
0092         // We could save an exception and throw it from process.
0093         debug("Failed to load ID decoder for {}", m_cfg.readout);
0094         throw std::runtime_error(fmt::format("Failed to load ID decoder for {}", m_cfg.readout));
0095     }
0096 
0097     decltype(id_mask) id_inverse_mask = 0;
0098     // all these are for signal sum at digitization level
0099     if (!m_cfg.fields.empty()) {
0100         for (auto & field : m_cfg.fields) {
0101             id_inverse_mask |= id_spec.field(field)->mask();
0102         }
0103         debug("ID mask in {:s}: {:#064b}", m_cfg.readout, id_mask);
0104     }
0105     id_mask = ~id_inverse_mask;
0106 
0107     std::function hit_to_map = [this](const edm4hep::SimCalorimeterHit &h) {
0108       std::unordered_map<std::string, double> params;
0109       for(const auto &p : id_spec.fields()) {
0110         const std::string &name = p.first;
0111         const dd4hep::IDDescriptor::Field* field = p.second;
0112         params.emplace(name, field->value(h.getCellID()));
0113         trace("{} = {}", name, field->value(h.getCellID()));
0114       }
0115       return params;
0116     };
0117 
0118     auto& serviceSvc = algorithms::ServiceSvc::instance();
0119     corrMeanScale = serviceSvc.service<EvaluatorSvc>("EvaluatorSvc")->compile(m_cfg.corrMeanScale, hit_to_map);
0120 }
0121 
0122 
0123 void CalorimeterHitDigi::process(
0124       const CalorimeterHitDigi::Input& input,
0125       const CalorimeterHitDigi::Output& output) const {
0126 
0127     const auto [simhits] = input;
0128     auto [rawhits] = output;
0129 
0130     // find the hits that belong to the same group (for merging)
0131     std::unordered_map<uint64_t, std::vector<std::size_t>> merge_map;
0132     std::size_t ix = 0;
0133     for (const auto &ahit : *simhits) {
0134         uint64_t hid = ahit.getCellID() & id_mask;
0135 
0136         trace("org cell ID in {:s}: {:#064b}", m_cfg.readout, ahit.getCellID());
0137         trace("new cell ID in {:s}: {:#064b}", m_cfg.readout, hid);
0138 
0139         merge_map[hid].push_back(ix);
0140 
0141         ix++;
0142     }
0143 
0144     // signal sum
0145     // NOTE: we take the cellID of the most energetic hit in this group so it is a real cellID from an MC hit
0146     for (const auto &[id, ixs] : merge_map) {
0147         double edep     = 0;
0148         double time     = std::numeric_limits<double>::max();
0149         double max_edep = 0;
0150         auto   leading_hit = (*simhits)[ixs[0]];
0151         // sum energy, take time from the most energetic hit
0152         for (size_t i = 0; i < ixs.size(); ++i) {
0153             auto hit = (*simhits)[ixs[i]];
0154 
0155             double timeC = std::numeric_limits<double>::max();
0156             for (const auto& c : hit.getContributions()) {
0157                 if (c.getTime() <= timeC) {
0158                     timeC = c.getTime();
0159                 }
0160             }
0161             if (timeC > m_cfg.capTime) continue;
0162             edep += hit.getEnergy();
0163             trace("adding {} \t total: {}", hit.getEnergy(), edep);
0164 
0165             // change maximum hit energy & time if necessary
0166             if (hit.getEnergy() > max_edep) {
0167                 max_edep = hit.getEnergy();
0168                 leading_hit = hit;
0169                 if (timeC <= time) {
0170                     time = timeC;
0171                 }
0172             }
0173         }
0174         if (time > m_cfg.capTime) continue;
0175 
0176         // safety check
0177         const double eResRel = (edep > m_cfg.threshold)
0178                 ? m_gaussian(m_generator) * std::sqrt(
0179                      std::pow(m_cfg.eRes[0] / std::sqrt(edep), 2) +
0180                      std::pow(m_cfg.eRes[1], 2) +
0181                      std::pow(m_cfg.eRes[2] / (edep), 2)
0182                   )
0183                 : 0;
0184         double    corrMeanScale_value = corrMeanScale(leading_hit);
0185         double    ped     = m_cfg.pedMeanADC + m_gaussian(m_generator) * m_cfg.pedSigmaADC;
0186         unsigned long long adc     = std::llround(ped + edep * corrMeanScale_value * ( 1.0 + eResRel) / m_cfg.dyRangeADC * m_cfg.capADC);
0187         unsigned long long tdc     = std::llround((time + m_gaussian(m_generator) * tRes) * stepTDC);
0188 
0189         if (edep> 1.e-3) trace("E sim {} \t adc: {} \t time: {}\t maxtime: {} \t tdc: {} \t corrMeanScale: {}", edep, adc, time, m_cfg.capTime, tdc, corrMeanScale_value);
0190         rawhits->create(
0191                 leading_hit.getCellID(),
0192                 (adc > m_cfg.capADC ? m_cfg.capADC : adc),
0193                 tdc
0194         );
0195     }
0196 }
0197 
0198 } // namespace eicrecon