LDMX Software
TrigMipReco.cxx
2
3namespace trigger {
4
6 profiling_map_["processing_time_"] = 0;
7}
8
10 hit_coll_name_ = ps.get<std::string>("hit_coll_name");
11 pass_coll_name_ = ps.get<std::string>("pass_coll_name");
12 hit_coll_passname_ = ps.get<std::string>("hit_coll_passname");
13 calorimeter_type_is_hcal_ = ps.get<bool>("calorimeter_type_is_hcal");
14
15 max_layer_ = ps.get<int>("max_layer", 32);
16 // mm
17 search_radius_ = ps.get<float>("search_radius", 50.0f);
18 min_track_length_ = ps.get<int>("min_track_length", 5);
19 // MeV; Change as needed
20 isolation_e_cut_ = ps.get<float>("isolation_e_cut", 180.0f);
21 hole_fraction_max_ = ps.get<float>("hole_fraction_max", 0.2f);
22
23 if (calorimeter_type_is_hcal_) {
24 // MIP peak is 10-11 MeV
25 hcal_min_energy_ = ps.get<float>("hcal_min_energy", 8.0f);
26 } else { // ECAL
27 // MIP peak around 17 MeV
28 ecal_min_energy_ = ps.get<float>("ecal_min_energy", 3.0f);
29 ecal_max_energy_ = ps.get<float>("ecal_max_energy", 26.0f);
30 }
31}
32
34 auto start = std::chrono::high_resolution_clock::now();
35 nevents_++;
36
37 if (!event.exists(hit_coll_name_, hit_coll_passname_)) return;
38
39 const auto calo_hits = event.getObject<std::vector<TrigCaloHit>>(
40 hit_coll_name_, hit_coll_passname_);
41
42 if (calorimeter_type_is_hcal_) { // HCAL MIP Reconstruction
43 std::vector<TrigCaloHit> sorted_hits;
44 int even_matrix[24][5] = {};
45 int even_start[5] = {99, 99, 99, 99, 99};
46 int even_end[5] = {};
47 int even_counts[5] = {};
48 int odd_matrix[24][5] = {};
49 int odd_start[5] = {99, 99, 99, 99, 99};
50 int odd_end[5] = {};
51 int odd_counts[5] = {};
52 // Start in first 5 layers
53 constexpr int layer_start = 80;
54
55 for (const auto& tp : calo_hits) {
56 if (tp.section() > 0 || tp.energy() < hcal_min_energy_ ||
57 tp.layer() > 47) {
58 continue;
59 }
60
61 sorted_hits.push_back(tp);
62 const int layer_index = tp.layer() / 2;
63 const int strip = tp.strip();
64 if (tp.layer() % 2) {
65 odd_matrix[layer_index][strip] = 1;
66 odd_start[strip] = std::min(odd_start[strip], tp.layer());
67 odd_end[strip] = std::max(odd_end[strip], tp.layer());
68 } else {
69 even_matrix[layer_index][strip] = 1;
70 even_start[strip] = std::min(even_start[strip], tp.layer());
71 even_end[strip] = std::max(even_end[strip], tp.layer());
72 }
73 }
74
75 for (int i = 0; i < 24; i++) {
76 for (int j = 0; j < 5; j++) {
77 if (even_matrix[i][j]) {
78 even_counts[j]++;
79 }
80 if (odd_matrix[i][j]) {
81 odd_counts[j]++;
82 }
83 }
84 }
85
86 // straight MIP reco
87 std::vector<TrigMip> mips;
88 // 5 elements in the even/odd_start matrices
89 for (int i = 0; i < 5; i++) {
90 if (odd_start[i] < layer_start) {
91 TrigMip m;
92 m.setStartLayer(odd_start[i]);
93 m.setEndLayer(odd_end[i]);
94 m.setNHits(odd_counts[i]);
95 m.setLength(odd_end[i] - odd_start[i] + 1);
96 int holes = m.length() / 2 - m.nHits();
97 if (holes < 0) {
98 holes = 0;
99 }
100 m.setNHoles(holes);
101 mips.push_back(m);
102 }
103 if (even_start[i] < layer_start) {
104 TrigMip m;
105 m.setStartLayer(even_start[i]);
106 m.setEndLayer(even_end[i]);
107 m.setNHits(even_counts[i]);
108 m.setLength(even_end[i] - even_start[i] + 1);
109 int holes = m.length() / 2 - m.nHits();
110 if (holes < 0) {
111 holes = 0;
112 }
113 m.setNHoles(holes);
114 mips.push_back(m);
115 }
116 }
117
118 std::sort(mips.begin(), mips.end());
119 event.add(pass_coll_name_, mips);
120
121 auto end = std::chrono::high_resolution_clock::now();
122 auto time_diff = end - start;
123 processing_time_ +=
124 std::chrono::duration<double, std::milli>(time_diff).count();
125
126 return;
127 // ECAL MIP Reconstruction
128 } else {
129 const float radius_cut_2 = search_radius_ * search_radius_;
130 std::map<int, std::vector<TrigCaloHit>> layer_hits;
131 std::set<const TrigCaloHit*> used_hits;
132 std::vector<std::vector<const TrigCaloHit*>> candidate_tracks;
133 std::map<const TrigCaloHit*, size_t> hit_to_best_track;
134
135 // Filter for section = 0 and hits < 33 layers
136 for (const auto& hit : calo_hits) {
137 if (hit.section() > 0 || hit.layer() > max_layer_) continue;
138 layer_hits[hit.layer()].push_back(hit);
139 }
140
141 // Find mip seeds
142 for (const auto& [seed_layer, seeds] : layer_hits) {
143 for (const auto& seed : seeds) {
144 // Skip if hit already used or outside mip energy range
145 if (used_hits.count(&seed) || seed.energy() < ecal_min_energy_ ||
146 seed.energy() > ecal_max_energy_) {
147 continue;
148 }
149
150 std::vector<const TrigCaloHit*> track{&seed};
151 // Most recent hit in track
152 const TrigCaloHit* last = &seed;
153 int holes = 0;
154 float growth_factor = 1.0f;
155
156 // Look layer by layer for next hit within dR
157 for (int l = seed.layer() + 1; l <= max_layer_; ++l) {
158 const TrigCaloHit* best_hit = nullptr;
159 // Grow search window if there is a hole
160 float best_d_r_2 = radius_cut_2 * growth_factor * growth_factor;
161
162 for (const auto& cand : layer_hits[l]) {
163 if (used_hits.count(&cand) || cand.energy() < ecal_min_energy_ ||
164 cand.energy() > ecal_max_energy_) {
165 continue;
166 }
167
168 // Corrects for layer shift in x-direction, calculated as 4.82 mm
169 const float layer_shift_last =
170 (last->layer() % 2 == 0) ? 0.0f : 4.82f;
171 const float layer_shift_cand =
172 (cand.layer() % 2 == 0) ? 0.0f : 4.82f;
173 const float dx = (cand.positionX() - layer_shift_cand) -
174 (last->positionX() - layer_shift_last);
175 const float dy = cand.positionY() - last->positionY();
176 const float d_r_2 = dx * dx + dy * dy;
177
178 if (d_r_2 < best_d_r_2) {
179 best_d_r_2 = d_r_2;
180 // Closest unused hit in next layer
181 best_hit = &cand;
182 }
183 }
184
185 if (best_hit) {
186 // Builds track from best hits
187 track.push_back(best_hit);
188 last = best_hit;
189 holes = 0;
190 // Reset search window
191 growth_factor = 1.0f;
192 } else {
193 holes++;
194 // Keep expanding
195 growth_factor = static_cast<float>(holes + 1);
196 }
197 }
198
199 bool is_isolated = true;
200 if (track.size() >= min_track_length_) {
201 // Isolation area energy check
202 for (const auto* hit : track) {
203 const int layer = hit->layer();
204 const float hit_x = hit->positionX();
205 const float hit_y = hit->positionY();
206 float sum_e = 0.0f;
207
208 for (const auto& cand : layer_hits[layer]) {
209 // Skips self
210 if (&cand == hit) continue;
211
212 const float dx = cand.positionX() - hit_x;
213 const float dy = cand.positionY() - hit_y;
214 const float d_r_2 = dx * dx + dy * dy;
215
216 if (d_r_2 < radius_cut_2) {
217 sum_e += cand.energy();
218 }
219 }
220
221 if (sum_e >= isolation_e_cut_) {
222 is_isolated = false;
223 // Adds used hits to vector so they cannot be used again
224 used_hits.insert(hit);
225 break;
226 }
227 }
228
229 if (!is_isolated) {
230 // Reject track if any hit is not isolated
231 continue;
232 }
233
234 const size_t i = candidate_tracks.size();
235 candidate_tracks.push_back(track);
236
237 for (const auto* hit : track) {
238 if (!hit_to_best_track.count(hit) ||
239 candidate_tracks[i].size() >
240 candidate_tracks[hit_to_best_track[hit]].size()) {
241 hit_to_best_track[hit] = i;
242 // Adds used hits to vector so they cannot be used again
243 used_hits.insert(hit);
244 }
245 }
246 }
247 }
248 }
249
250 std::set<size_t> valid_track_i_ds;
251 for (const auto& [hit, idx] : hit_to_best_track) {
252 valid_track_i_ds.insert(idx);
253 }
254
255 std::vector<TrigMip> mips;
256 for (const size_t idx : valid_track_i_ds) {
257 const auto& track = candidate_tracks[idx];
258 TrigMip mip;
259 mip.setStartLayer(track.front()->layer());
260 mip.setEndLayer(track.back()->layer());
261 mip.setNHits(track.size());
262 mip.setLength(track.back()->layer() - track.front()->layer());
263 int holes = mip.length() - mip.nHits();
264 if (holes < 0) {
265 holes = 0;
266 }
267 mip.setNHoles(holes);
268
269 const float hole_fraction =
270 static_cast<float>(mip.nHoles()) / mip.length();
271 // Remove mip tracks with hole fraction > 0.2
272 if (hole_fraction >= hole_fraction_max_) continue;
273
274 float total_isolation_e_sum = 0.0f;
275 for (const auto* hit : track) {
276 const int layer = hit->layer();
277 const float hit_x = hit->positionX();
278 const float hit_y = hit->positionY();
279 for (const auto& cand : layer_hits[layer]) {
280 if (&cand == hit) continue;
281 const float dx = cand.positionX() - hit_x;
282 const float dy = cand.positionY() - hit_y;
283 const float d_r_2 = dx * dx + dy * dy;
284 if (d_r_2 < radius_cut_2) {
285 total_isolation_e_sum += cand.energy();
286 }
287 }
288 }
289 mip.setSumEinIsolationRegion(total_isolation_e_sum);
290 mips.push_back(mip);
291 }
292
293 std::sort(mips.begin(), mips.end());
294 event.add(pass_coll_name_, mips);
295 }
296
297 auto end = std::chrono::high_resolution_clock::now();
298 auto time_diff = end - start;
299 processing_time_ +=
300 std::chrono::duration<double, std::milli>(time_diff).count();
301}
302
304 ldmx_log(info) << "AVG Time/Event: " << std::fixed << std::setprecision(3)
305 << processing_time_ / nevents_ << " ms";
306}
307
308} // namespace trigger
309
#define DECLARE_PRODUCER(CLASS)
Macro which allows the framework to construct a producer given its name during configuration.
Trigger Calo MIP finding algorithm.
Implements an event buffer system for storing event data.
Definition Event.h:42
bool exists(const std::string &name, const std::string &passName, bool unique=true) const
Check for the existence of an object or collection with the given name and pass name in the event.
Definition Event.cxx:92
Class encapsulating parameters for configuring a processor.
Definition Parameters.h:29
const T & get(const std::string &name) const
Retrieve the parameter of the given name.
Definition Parameters.h:78
Run-specific configuration and data stored in its own output TTree alongside the event TTree in the o...
Definition RunHeader.h:57
Class for calo hits used in trigger computations.
Definition TrigCaloHit.h:13
void configure(framework::config::Parameters &ps) override
Callback for the EventProcessor to configure itself from the given set of parameters.
void onNewRun(const ldmx::RunHeader &rh) override
onNewRun is the first function called for each processor after the conditions are fully configured an...
void produce(framework::Event &event) override
Process the event and put new data products into it.
void onProcessEnd() override
Callback for the EventProcessor to take any necessary action when the processing of events finishes,...
Class for clusters built from trigger calo hits.
Definition TrigMip.h:12