File indexing completed on 2025-12-16 09:27:58
0001
0002
0003
0004
0005
0006
0007
0008 #include "PulseGeneration.h"
0009
0010 #include <RtypesCore.h>
0011 #include <TMath.h>
0012 #include <algorithms/service.h>
0013 #include <edm4hep/CaloHitContribution.h>
0014 #include <edm4hep/MCParticle.h>
0015 #include <edm4hep/Vector3f.h>
0016 #include <cstdlib>
0017 #include <algorithm>
0018 #include <cmath>
0019 #include <cstddef>
0020 #include <cstdint>
0021 #include <functional>
0022 #include <stdexcept>
0023 #include <string>
0024 #include <unordered_map>
0025 #include <vector>
0026
0027 #include "services/evaluator/EvaluatorSvc.h"
0028
0029 namespace eicrecon {
0030
0031 class SignalPulse {
0032
0033 public:
0034 virtual ~SignalPulse() = default;
0035
0036 virtual double operator()(double time, double charge) = 0;
0037
0038 virtual double getMaximumTime() const = 0;
0039 };
0040
0041
0042
0043
0044 class LandauPulse : public SignalPulse {
0045 public:
0046 LandauPulse(std::vector<double> params) {
0047
0048 if ((params.size() != 2) && (params.size() != 3)) {
0049 throw std::runtime_error(
0050 "LandauPulse requires 2 or 3 parameters, gain, sigma_analog, [hit_sigma_offset], got " +
0051 std::to_string(params.size()));
0052 }
0053
0054 m_gain = params[0];
0055 m_sigma_analog = params[1];
0056 if (params.size() == 3) {
0057 m_hit_sigma_offset = params[2];
0058 }
0059 };
0060
0061 double operator()(double time, double charge) override {
0062 return charge * m_gain *
0063 TMath::Landau(time, m_hit_sigma_offset * m_sigma_analog, m_sigma_analog, kTRUE);
0064 }
0065
0066 double getMaximumTime() const override { return m_hit_sigma_offset * m_sigma_analog; }
0067
0068 private:
0069 double m_gain = 1.0;
0070 double m_sigma_analog = 1.0;
0071 double m_hit_sigma_offset = 3.5;
0072 };
0073
0074
0075 class EvaluatorPulse : public SignalPulse {
0076 public:
0077 EvaluatorPulse(const std::string& expression, const std::vector<double>& params) {
0078
0079 std::vector<std::string> keys = {"time", "charge"};
0080 for (std::size_t i = 0; i < params.size(); i++) {
0081 std::string p = "param" + std::to_string(i);
0082
0083 if (expression.find(p) == std::string::npos) {
0084 throw std::runtime_error("Parameter " + p + " not found in expression");
0085 }
0086 keys.push_back(p);
0087 param_map[p] = params[i];
0088 }
0089
0090
0091 if (expression.find("time") == std::string::npos) {
0092 throw std::runtime_error("Parameter [time] not found in expression");
0093 }
0094 if (expression.find("charge") == std::string::npos) {
0095 throw std::runtime_error("Parameter [charge] not found in expression");
0096 }
0097
0098 auto& serviceSvc = algorithms::ServiceSvc::instance();
0099 m_evaluator = serviceSvc.service<EvaluatorSvc>("EvaluatorSvc")->_compile(expression, keys);
0100 };
0101
0102 double operator()(double time, double charge) override {
0103 param_map["time"] = time;
0104 param_map["charge"] = charge;
0105 return m_evaluator(param_map);
0106 }
0107
0108 double getMaximumTime() const override { return 0; }
0109
0110 private:
0111 std::unordered_map<std::string, double> param_map;
0112 std::function<double(const std::unordered_map<std::string, double>&)> m_evaluator;
0113 };
0114
0115 class PulseShapeFactory {
0116 public:
0117 static std::unique_ptr<SignalPulse> createPulseShape(const std::string& type,
0118 const std::vector<double>& params) {
0119 if (type == "LandauPulse") {
0120 return std::make_unique<LandauPulse>(params);
0121 }
0122
0123
0124
0125
0126 try {
0127 return std::make_unique<EvaluatorPulse>(type, params);
0128 } catch (...) {
0129 throw std::invalid_argument("Unable to make pulse shape type: " + type);
0130 }
0131 }
0132 };
0133
0134 std::tuple<double, double>
0135 HitAdapter<edm4hep::SimTrackerHit>::getPulseSources(const edm4hep::SimTrackerHit& hit) {
0136 return {hit.getTime(), hit.getEDep()};
0137 }
0138
0139 #if EDM4EIC_VERSION_MAJOR > 8 || (EDM4EIC_VERSION_MAJOR == 8 && EDM4EIC_VERSION_MINOR >= 1)
0140 void HitAdapter<edm4hep::SimTrackerHit>::addRelations(MutablePulseType& pulse,
0141 const edm4hep::SimTrackerHit& hit) {
0142 pulse.addToTrackerHits(hit);
0143 pulse.addToParticles(hit.getParticle());
0144 }
0145 #endif
0146
0147 std::tuple<double, double>
0148 HitAdapter<edm4hep::SimCalorimeterHit>::getPulseSources(const edm4hep::SimCalorimeterHit& hit) {
0149 const auto& contribs = hit.getContributions();
0150 auto earliest_contrib = std::ranges::min_element(
0151 contribs, [](const auto& a, const auto& b) { return a.getTime() < b.getTime(); });
0152 return {earliest_contrib->getTime(), hit.getEnergy()};
0153 }
0154
0155 #if EDM4EIC_VERSION_MAJOR > 8 || (EDM4EIC_VERSION_MAJOR == 8 && EDM4EIC_VERSION_MINOR >= 1)
0156 void HitAdapter<edm4hep::SimCalorimeterHit>::addRelations(MutablePulseType& pulse,
0157 const edm4hep::SimCalorimeterHit& hit) {
0158 pulse.addToCalorimeterHits(hit);
0159 pulse.addToParticles(hit.getContributions(0).getParticle());
0160 }
0161 #endif
0162
0163 template <typename HitT> void PulseGeneration<HitT>::init() {
0164 m_pulse =
0165 PulseShapeFactory::createPulseShape(m_cfg.pulse_shape_function, m_cfg.pulse_shape_params);
0166 m_min_sampling_time = m_cfg.min_sampling_time;
0167
0168 m_min_sampling_time = std::max<double>(m_pulse->getMaximumTime(), m_min_sampling_time);
0169 }
0170
0171 template <typename HitT>
0172 void PulseGeneration<HitT>::process(
0173 const typename PulseGenerationAlgorithm<HitT>::Input& input,
0174 const typename PulseGenerationAlgorithm<HitT>::Output& output) const {
0175 const auto [simhits] = input;
0176 auto [rawPulses] = output;
0177
0178 for (const auto& hit : *simhits) {
0179 const auto [time, charge] = HitAdapter<HitT>::getPulseSources(hit);
0180
0181 double signal_time = m_cfg.timestep * std::floor(time / m_cfg.timestep);
0182
0183 bool passed_threshold = false;
0184 std::uint32_t skip_bins = 0;
0185 float integral = 0;
0186 std::vector<float> pulse;
0187
0188 for (std::uint32_t i = 0; i < m_cfg.max_time_bins; i++) {
0189 double t = signal_time + i * m_cfg.timestep - time;
0190 auto signal = (*m_pulse)(t, charge);
0191 if (std::abs(signal) < m_cfg.ignore_thres) {
0192 if (!passed_threshold) {
0193 skip_bins = i;
0194 continue;
0195 }
0196 if (t > m_min_sampling_time) {
0197 break;
0198 }
0199 }
0200 passed_threshold = true;
0201 pulse.push_back(signal);
0202 integral += signal;
0203 }
0204
0205 if (!passed_threshold) {
0206 continue;
0207 }
0208
0209 auto time_series = rawPulses->create();
0210 time_series.setCellID(hit.getCellID());
0211 time_series.setInterval(m_cfg.timestep);
0212 time_series.setTime(signal_time + skip_bins * m_cfg.timestep);
0213
0214 for (const auto& value : pulse) {
0215 time_series.addToAmplitude(value);
0216 }
0217
0218 #if EDM4EIC_VERSION_MAJOR > 8 || (EDM4EIC_VERSION_MAJOR == 8 && EDM4EIC_VERSION_MINOR >= 1)
0219 time_series.setIntegral(integral);
0220 time_series.setPosition(
0221 edm4hep::Vector3f(hit.getPosition().x, hit.getPosition().y, hit.getPosition().z));
0222 HitAdapter<HitT>::addRelations(time_series, hit);
0223 #endif
0224 }
0225
0226 }
0227
0228 template class PulseGeneration<edm4hep::SimTrackerHit>;
0229 template class PulseGeneration<edm4hep::SimCalorimeterHit>;
0230
0231 }