Skip to content

Commit 6e3493b

Browse files
mhkim-anlpre-commit-ci[bot]veprblwdconincgithub-actions[bot]
authored
Pulse generation from time-clustered Simcalorimeterhits using a shared template (#2004)
### Briefly, what does this PR introduce? This PR is for pulse generation from time-clustered `SimCalorimeterHit`s. To generate a pulse from the contributions that are close in time, a `timeID` is assigned in the `SimCalorimeterHitProcessor`. Since both `SimCalorimeterHit` and `SimTrackerHit` share a common pulse generation mechanism, a shared template algorithm, `PulseGeneration`, has been implemented. ### What kind of change does this PR introduce? - [ ] Bug fix (issue #__) - [x] New feature (issue #2001) - [ ] Documentation update - [ ] Other: __ ### Please check if this PR fulfills the following: - [ ] Tests for the changes have been added - [ ] Documentation has been added / updated - [x] Changes have been communicated to collaborators ### Does this PR introduce breaking changes? What changes might users need to make to their code? ### Does this PR change default behavior? --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Dmitry Kalinkin <[email protected]> Co-authored-by: Wouter Deconinck <[email protected]> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 5e191f5 commit 6e3493b

File tree

14 files changed

+260
-148
lines changed

14 files changed

+260
-148
lines changed

src/algorithms/calorimetry/SimCalorimeterHitProcessor.cc

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
#include <edm4hep/Vector3f.h>
1414
#include <edm4hep/utils/vector_utils.h>
1515
#include <fmt/core.h>
16-
#include <podio/ObjectID.h>
1716
#include <podio/RelationRange.h>
1817
#include <podio/podioVersion.h>
1918
#include <cmath>
@@ -57,12 +56,13 @@ template <> struct hash<edm4hep::MCParticle> {
5756

5857
// Hash for tuple<edm4hep::MCParticle, uint64_t>
5958
// --> not yet supported by any compiler at the moment
60-
template <> struct hash<std::tuple<edm4hep::MCParticle, uint64_t>> {
61-
size_t operator()(const std::tuple<edm4hep::MCParticle, uint64_t>& key) const noexcept {
62-
const auto& [particle, cellID] = key;
63-
size_t h1 = hash<edm4hep::MCParticle>{}(particle);
64-
size_t h2 = hash<uint64_t>{}(cellID);
65-
return h1 ^ (h2 << 1);
59+
template <> struct hash<std::tuple<edm4hep::MCParticle, uint64_t, int>> {
60+
size_t operator()(const std::tuple<edm4hep::MCParticle, uint64_t, int>& key) const noexcept {
61+
const auto& [particle, cellID, timeID] = key;
62+
size_t h1 = hash<edm4hep::MCParticle>{}(particle);
63+
size_t h2 = hash<uint64_t>{}(cellID);
64+
size_t h3 = hash<int>{}(timeID);
65+
return ((h1 ^ (h2 << 1)) >> 1) ^ (h3 << 1);
6666
}
6767
};
6868

@@ -173,7 +173,7 @@ void SimCalorimeterHitProcessor::process(const SimCalorimeterHitProcessor::Input
173173
// (hence modify) contributions which is not supported for PodIO VectorMembers. Using
174174
// reasonable contribution merging, at least the intermediary structure should be
175175
// quite a bit smaller than the original hit collection.
176-
using HitIndex = std::tuple<edm4hep::MCParticle, uint64_t /* cellID */>;
176+
using HitIndex = std::tuple<edm4hep::MCParticle, uint64_t /* cellID */, int /* timeID */>;
177177
std::unordered_map<HitIndex,
178178
std::unordered_map<uint64_t /* cellID */, HitContributionAccumulator>>
179179
hit_map;
@@ -184,21 +184,22 @@ void SimCalorimeterHitProcessor::process(const SimCalorimeterHitProcessor::Input
184184
(ih.getCellID() & m_hit_id_mask.value() & m_contribution_id_mask.value());
185185
// the cell ID of this particular contribution (we are using contributions to store
186186
// the hits making up this "superhit" with more segmentation)
187-
const uint64_t newcontrib_cellID = (ih.getCellID() & m_hit_id_mask.value());
187+
const uint64_t newcontrib_cellID = (ih.getCellID() & m_contribution_id_mask.value());
188188
// Optional attenuation
189189
const double attFactor =
190190
m_attenuationReferencePosition ? get_attenuation(ih.getPosition().z) : 1.;
191191
// Use primary particle (traced back through parents) to group contributions
192192
for (const auto& contrib : ih.getContributions()) {
193193
edm4hep::MCParticle primary = lookup_primary(contrib);
194-
auto& hit_accum = hit_map[{primary, newhit_cellID}][newcontrib_cellID];
195194
const double propagationTime =
196195
m_attenuationReferencePosition
197196
? std::abs(m_attenuationReferencePosition.value() - ih.getPosition().z) *
198197
m_cfg.inversePropagationSpeed
199198
: 0.;
200-
hit_accum.add(contrib.getEnergy() * attFactor,
201-
contrib.getTime() + propagationTime + m_cfg.fixedTimeDelay, ih.getPosition());
199+
const double totalTime = contrib.getTime() + propagationTime + m_cfg.fixedTimeDelay;
200+
const int newhit_timeID = std::floor(totalTime / m_cfg.timeWindow);
201+
auto& hit_accum = hit_map[{primary, newhit_cellID, newhit_timeID}][newcontrib_cellID];
202+
hit_accum.add(contrib.getEnergy() * attFactor, totalTime, ih.getPosition());
202203
}
203204
}
204205

@@ -208,7 +209,7 @@ void SimCalorimeterHitProcessor::process(const SimCalorimeterHitProcessor::Input
208209

209210
auto out_hit = out_hits->create();
210211

211-
const auto& [particle, cellID] = hit_idx;
212+
const auto& [particle, cellID, timeID] = hit_idx;
212213
HitContributionAccumulator new_hit;
213214
for (const auto& [contrib_idx, contrib] : contribs) {
214215
// Aggregate contributions to for the global hit

src/algorithms/calorimetry/SimCalorimeterHitProcessorConfig.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
#include <string>
77
#include <vector>
8+
#include <edm4eic/unit_system.h>
89

910
namespace eicrecon {
1011

@@ -27,6 +28,8 @@ struct SimCalorimeterHitProcessorConfig {
2728
double inversePropagationSpeed{};
2829
// detector-related time delay (e.g., scintillation)
2930
double fixedTimeDelay{};
31+
// time window for grouping contributions
32+
double timeWindow{100 * edm4eic::unit::ns};
3033
};
3134

3235
} // namespace eicrecon

src/algorithms/digi/SiliconPulseGeneration.cc renamed to src/algorithms/digi/PulseGeneration.cc

Lines changed: 51 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,27 @@
11
// SPDX-License-Identifier: LGPL-3.0-or-later
2-
// Copyright (C) 2024-2025 Simon Gardner, Chun Yuen Tsang, Prithwish Tribedy
2+
// Copyright (C) 2024-2025 Simon Gardner, Chun Yuen Tsang, Prithwish Tribedy,
3+
// Minho Kim, Sylvester Joosten, Wouter Deconinck, Dmitry Kalinkin
34
//
45
// Convert energy deposition into ADC pulses
56
// ADC pulses are assumed to follow the shape of landau function
67

7-
#include "SiliconPulseGeneration.h"
8+
#include "PulseGeneration.h"
89

910
#include <RtypesCore.h>
1011
#include <TMath.h>
1112
#include <algorithms/service.h>
12-
#include <edm4hep/Vector3d.h>
13+
#include <edm4hep/CaloHitContribution.h>
14+
#include <edm4hep/MCParticle.h>
1315
#include <edm4hep/Vector3f.h>
16+
#include <podio/RelationRange.h>
17+
#include <stdlib.h>
18+
#include <algorithm>
1419
#include <cmath>
1520
#include <cstddef>
1621
#include <cstdint>
1722
#include <functional>
18-
#include <gsl/pointers>
1923
#include <stdexcept>
24+
#include <string>
2025
#include <unordered_map>
2126
#include <vector>
2227

@@ -127,7 +132,37 @@ class PulseShapeFactory {
127132
}
128133
};
129134

130-
void SiliconPulseGeneration::init() {
135+
std::tuple<double, double>
136+
HitAdapter<edm4hep::SimTrackerHit>::getPulseSources(const edm4hep::SimTrackerHit& hit) {
137+
return {hit.getTime(), hit.getEDep()};
138+
}
139+
140+
#if EDM4EIC_VERSION_MAJOR > 8 || (EDM4EIC_VERSION_MAJOR == 8 && EDM4EIC_VERSION_MINOR >= 1)
141+
void HitAdapter<edm4hep::SimTrackerHit>::addRelations(MutablePulseType& pulse,
142+
const edm4hep::SimTrackerHit& hit) {
143+
pulse.addToTrackerHits(hit);
144+
pulse.addToParticles(hit.getParticle());
145+
}
146+
#endif
147+
148+
std::tuple<double, double>
149+
HitAdapter<edm4hep::SimCalorimeterHit>::getPulseSources(const edm4hep::SimCalorimeterHit& hit) {
150+
const auto& contribs = hit.getContributions();
151+
auto earliest_contrib =
152+
std::min_element(contribs.begin(), contribs.end(),
153+
[](const auto& a, const auto& b) { return a.getTime() < b.getTime(); });
154+
return {earliest_contrib->getTime(), hit.getEnergy()};
155+
}
156+
157+
#if EDM4EIC_VERSION_MAJOR > 8 || (EDM4EIC_VERSION_MAJOR == 8 && EDM4EIC_VERSION_MINOR >= 1)
158+
void HitAdapter<edm4hep::SimCalorimeterHit>::addRelations(MutablePulseType& pulse,
159+
const edm4hep::SimCalorimeterHit& hit) {
160+
pulse.addToCalorimeterHits(hit);
161+
pulse.addToParticles(hit.getContributions(0).getParticle());
162+
}
163+
#endif
164+
165+
template <typename HitT> void PulseGeneration<HitT>::init() {
131166
m_pulse =
132167
PulseShapeFactory::createPulseShape(m_cfg.pulse_shape_function, m_cfg.pulse_shape_params);
133168
m_min_sampling_time = m_cfg.min_sampling_time;
@@ -137,17 +172,15 @@ void SiliconPulseGeneration::init() {
137172
}
138173
}
139174

140-
void SiliconPulseGeneration::process(const SiliconPulseGeneration::Input& input,
141-
const SiliconPulseGeneration::Output& output) const {
175+
template <typename HitT>
176+
void PulseGeneration<HitT>::process(
177+
const typename PulseGenerationAlgorithm<HitT>::Input& input,
178+
const typename PulseGenerationAlgorithm<HitT>::Output& output) const {
142179
const auto [simhits] = input;
143180
auto [rawPulses] = output;
144181

145182
for (const auto& hit : *simhits) {
146-
147-
auto cellID = hit.getCellID();
148-
double time = hit.getTime();
149-
double charge = hit.getEDep();
150-
183+
const auto [time, charge] = HitAdapter<HitT>::getPulseSources(hit);
151184
// Calculate nearest timestep to the hit time rounded down (assume clocks aligned with time 0)
152185
double signal_time = m_cfg.timestep * std::floor(time / m_cfg.timestep);
153186

@@ -178,7 +211,7 @@ void SiliconPulseGeneration::process(const SiliconPulseGeneration::Input& input,
178211
}
179212

180213
auto time_series = rawPulses->create();
181-
time_series.setCellID(cellID);
214+
time_series.setCellID(hit.getCellID());
182215
time_series.setInterval(m_cfg.timestep);
183216
time_series.setTime(signal_time + skip_bins * m_cfg.timestep);
184217

@@ -190,11 +223,13 @@ void SiliconPulseGeneration::process(const SiliconPulseGeneration::Input& input,
190223
time_series.setIntegral(integral);
191224
time_series.setPosition(
192225
edm4hep::Vector3f(hit.getPosition().x, hit.getPosition().y, hit.getPosition().z));
193-
time_series.addToTrackerHits(hit);
194-
time_series.addToParticles(hit.getParticle());
226+
HitAdapter<HitT>::addRelations(time_series, hit);
195227
#endif
196228
}
197229

198-
} // SiliconPulseGeneration:process
230+
} // PulseGeneration:process
231+
232+
template class PulseGeneration<edm4hep::SimTrackerHit>;
233+
template class PulseGeneration<edm4hep::SimCalorimeterHit>;
199234

200235
} // namespace eicrecon
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// SPDX-License-Identifier: LGPL-3.0-or-later
2+
// Copyright (C) 2024-2025 Simon Gardner, Chun Yuen Tsang, Prithwish Tribedy,
3+
// Minho Kim, Sylvester Joosten, Wouter Deconinck, Dmitry Kalinkin
4+
//
5+
// Convert energy deposition into analog pulses
6+
7+
#pragma once
8+
9+
#include <algorithms/algorithm.h>
10+
#include <edm4eic/EDM4eicVersion.h>
11+
#include <edm4eic/unit_system.h>
12+
#include <edm4hep/SimCalorimeterHitCollection.h>
13+
#include <edm4hep/SimTrackerHitCollection.h>
14+
#if EDM4EIC_VERSION_MAJOR > 8 || (EDM4EIC_VERSION_MAJOR == 8 && EDM4EIC_VERSION_MINOR >= 1)
15+
#include <edm4eic/SimPulseCollection.h>
16+
#else
17+
#include <edm4hep/TimeSeriesCollection.h>
18+
#endif
19+
#include <memory>
20+
#include <string_view>
21+
#include <tuple>
22+
#include <variant>
23+
24+
#include "algorithms/digi/PulseGenerationConfig.h"
25+
#include "algorithms/interfaces/WithPodConfig.h"
26+
27+
namespace eicrecon {
28+
29+
#if EDM4EIC_VERSION_MAJOR > 8 || (EDM4EIC_VERSION_MAJOR == 8 && EDM4EIC_VERSION_MINOR >= 1)
30+
using PulseType = edm4eic::SimPulse;
31+
using MutablePulseType = edm4eic::MutableSimPulse;
32+
#else
33+
using PulseType = edm4hep::TimeSeries;
34+
using MutablePulseType = edm4hep::MutableTimeSeries;
35+
#endif
36+
37+
template <typename HitT> struct HitAdapter;
38+
39+
template <> struct HitAdapter<edm4hep::SimTrackerHit> {
40+
static std::tuple<double, double> getPulseSources(const edm4hep::SimTrackerHit& hit);
41+
#if EDM4EIC_VERSION_MAJOR > 8 || (EDM4EIC_VERSION_MAJOR == 8 && EDM4EIC_VERSION_MINOR >= 1)
42+
static void addRelations(MutablePulseType& pulse, const edm4hep::SimTrackerHit& hit);
43+
#endif
44+
};
45+
46+
template <> struct HitAdapter<edm4hep::SimCalorimeterHit> {
47+
static std::tuple<double, double> getPulseSources(const edm4hep::SimCalorimeterHit& hit);
48+
#if EDM4EIC_VERSION_MAJOR > 8 || (EDM4EIC_VERSION_MAJOR == 8 && EDM4EIC_VERSION_MINOR >= 1)
49+
static void addRelations(MutablePulseType& pulse, const edm4hep::SimCalorimeterHit& hit);
50+
#endif
51+
};
52+
53+
template <typename HitT>
54+
using PulseGenerationAlgorithm =
55+
algorithms::Algorithm<algorithms::Input<typename HitT::collection_type>,
56+
algorithms::Output<PulseType::collection_type>>;
57+
58+
class SignalPulse;
59+
60+
template <typename HitT>
61+
class PulseGeneration : public PulseGenerationAlgorithm<HitT>,
62+
public WithPodConfig<PulseGenerationConfig> {
63+
64+
public:
65+
PulseGeneration(std::string_view name)
66+
: PulseGenerationAlgorithm<HitT>{name, {"RawHits"}, {"OutputPulses"}, {}} {}
67+
void init() final;
68+
void process(const typename PulseGenerationAlgorithm<HitT>::Input&,
69+
const typename PulseGenerationAlgorithm<HitT>::Output&) const final;
70+
71+
private:
72+
std::shared_ptr<SignalPulse> m_pulse;
73+
float m_min_sampling_time = 0 * edm4eic::unit::ns;
74+
};
75+
76+
} // namespace eicrecon

src/algorithms/digi/SiliconPulseGenerationConfig.h renamed to src/algorithms/digi/PulseGenerationConfig.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
namespace eicrecon {
99

10-
struct SiliconPulseGenerationConfig {
10+
struct PulseGenerationConfig {
1111
// Parameters of Silicon signal generation
1212
std::string pulse_shape_function = "LandauPulse"; // Pulse shape function
1313
std::vector<double> pulse_shape_params = {1.0, 0.1}; // Parameters of the pulse shape function

src/algorithms/digi/SiliconPulseGeneration.h

Lines changed: 0 additions & 52 deletions
This file was deleted.

0 commit comments

Comments
 (0)