LDMX Software
ParticleFlow.cxx
2
3#include <cmath>
4#include <vector>
5
7
8namespace recon {
9
11 // I/O
12 input_ecal_coll_name_ = ps.get<std::string>("input_ecal_coll_name");
13 input_hcal_coll_name_ = ps.get<std::string>("input_hcal_coll_name");
14 input_track_coll_name_ = ps.get<std::string>("input_track_coll_name");
15 output_coll_name_ = ps.get<std::string>("output_coll_name");
16 input_ecal_passname_ = ps.get<std::string>("input_ecal_passname");
17 input_hcal_passname_ = ps.get<std::string>("input_hcal_passname");
18 input_tracks_passname_ = ps.get<std::string>("input_tracks_passname");
19
20 // Algorithm configuration
21 single_particle_ = ps.get<bool>("single_particle");
22 use_existing_ecal_clusters_ = ps.get<bool>("use_existing_ecal_clusters");
23
24 // Calibration factors, from jason, temperary
25 std::vector<float> em1{250.0, 750.0, 1250.0, 1750.0, 2250.0, 2750.0,
26 3250.0, 3750.0, 4250.0, 4750.0, 5250.0, 5750.};
27 std::vector<float> em2{1.175, 1.02, 0.99, 0.985, 0.975, 0.975,
28 0.96, 0.94, 0.87, 0.8, 0.73, 0.665};
29 std::vector<float> h1{25.0, 75.0, 125.0, 175.0, 225.0,
30 275.0, 325.0, 375.0, 425.0};
31 std::vector<float> h2{8.44, 7.38, 7.76, 8.535, 9.47,
32 10.45, 10.47, 9.71, 8.87};
33 e_corr_ = new TGraph(em1.size(), em1.data(), em2.data());
34 h_corr_ = new TGraph(h1.size(), h1.data(), h2.data());
35}
36
37// produce candidate track info
38void ParticleFlow::fillCandTrack(ldmx::PFCandidate& cand,
39 const ldmx::SimTrackerHit& tk) {
40 // TODO: smear
41 std::vector<float> xyz = tk.getPosition();
42 std::vector<double> pxyz = tk.getMomentum();
43 float ecal_z = 248;
44 float ecal_x =
45 xyz[0] + pxyz[0] / pxyz[2] * (ecal_z - xyz[2]); // project onto ecal face
46 float ecal_y = xyz[1] + pxyz[1] / pxyz[2] * (ecal_z - xyz[2]);
47 cand.setEcalPositionXYZ(ecal_x, ecal_y, ecal_z);
48 cand.setTrackPxPyPz(pxyz[0], pxyz[1], pxyz[2]);
49 // also use this object to set truth info
50 cand.setTruthEcalXYZ(ecal_x, ecal_y, ecal_z);
51 cand.setTruthPxPyPz(pxyz[0], pxyz[1], pxyz[2]);
52 float m2 = pow(tk.getEnergy(), 2) - pow(pxyz[0], 2) - pow(pxyz[1], 2) -
53 pow(pxyz[2], 2);
54 if (m2 < 0) m2 = 0;
55 cand.setTruthMass(sqrt(m2));
56 cand.setTruthEnergy(tk.getEnergy());
57 cand.setTruthPdgId(tk.getPdgID());
58 cand.setPID(cand.getPID() | 1); // OR with 001
59}
60// produce candidate ECal info
61void ParticleFlow::fillCandEMCalo(ldmx::PFCandidate& cand,
62 const ldmx::CaloCluster& em) {
63 float corr = 1.;
64 float energy = em.getEnergy();
65 // update energy: use min or max factor if outside calibration range
66 if (energy < e_corr_->GetX()[0]) {
67 corr = e_corr_->GetY()[0];
68 } else if (energy > e_corr_->GetX()[e_corr_->GetN() - 1]) {
69 corr = e_corr_->GetY()[e_corr_->GetN() - 1];
70 } else { // else look up calibration factor
71 corr = e_corr_->Eval(energy);
72 }
73 cand.setEcalEnergy(energy * corr);
74 cand.setEcalRawEnergy(energy);
75 cand.setEcalClusterXYZ(em.getCentroidX(), em.getCentroidY(),
76 em.getCentroidZ());
77 cand.setEcalClusterEXYZ(em.getRMSX(), em.getRMSY(), em.getRMSZ());
78 cand.setEcalClusterDXDZ(em.getDXDZ());
79 cand.setEcalClusterDYDZ(em.getDYDZ());
80 cand.setEcalClusterEDXDZ(em.getEDXDZ());
81 cand.setEcalClusterEDYDZ(em.getEDYDZ());
82 cand.setPID(cand.getPID() | 2); // OR with 010
83}
84// produce candidate HCal info
85void ParticleFlow::fillCandHadCalo(ldmx::PFCandidate& cand,
86 const ldmx::CaloCluster& had) {
87 float corr = 1.;
88 float energy = had.getEnergy();
89 if (energy < h_corr_->GetX()[0]) {
90 corr = h_corr_->GetY()[0];
91 } else if (energy > h_corr_->GetX()[h_corr_->GetN() - 1]) {
92 corr = h_corr_->GetY()[h_corr_->GetN() - 1];
93 } else {
94 corr = h_corr_->Eval(energy);
95 }
96 cand.setHcalEnergy(energy * corr);
97 cand.setHcalRawEnergy(energy);
98 cand.setHcalClusterXYZ(had.getCentroidX(), had.getCentroidY(),
99 had.getCentroidZ());
100 cand.setHcalClusterEXYZ(had.getRMSX(), had.getRMSY(), had.getRMSZ());
101 cand.setHcalClusterDXDZ(had.getDXDZ());
102 cand.setHcalClusterDYDZ(had.getDYDZ());
103 cand.setHcalClusterEDXDZ(had.getEDXDZ());
104 cand.setHcalClusterEDYDZ(had.getEDYDZ());
105 cand.setPID(cand.getPID() | 4); // OR with 100
106}
107
108// produce candidate calorimeter info (any type)
109void ParticleFlow::fillCandCalo(ldmx::PFCandidate& cand,
110 const ldmx::CaloCluster& cl, TGraph gResponse,
111 int PIDnb) {
112 float corr = 1.;
113 float energy = cl.getEnergy();
114 // update energy: use min or max factor if outside calibration range
115 if (energy < gResponse.GetX()[0]) {
116 corr = gResponse.GetY()[0];
117 } else if (energy > gResponse.GetX()[gResponse.GetN() - 1]) {
118 corr = gResponse.GetY()[gResponse.GetN() - 1];
119 } else { // else look up calibration factor
120 corr = gResponse.Eval(energy);
121 }
122 cand.setEcalEnergy(energy * corr);
123 cand.setEcalRawEnergy(energy);
124 cand.setEcalClusterXYZ(cl.getCentroidX(), cl.getCentroidY(),
125 cl.getCentroidZ());
126 cand.setEcalClusterEXYZ(cl.getRMSX(), cl.getRMSY(), cl.getRMSZ());
127 cand.setEcalClusterDXDZ(cl.getDXDZ());
128 cand.setEcalClusterDYDZ(cl.getDYDZ());
129 cand.setEcalClusterEDXDZ(cl.getEDXDZ());
130 cand.setEcalClusterEDYDZ(cl.getEDYDZ());
131 cand.setPID(cand.getPID() | PIDnb); // set calo PID number bit
132}
133
134// produce track, ecal, and hcal linking
136 if (!event.exists(input_track_coll_name_, input_tracks_passname_)) {
137 ldmx_log(error) << "Unable to find (one) collection named "
138 << input_track_coll_name_ << "_" << input_tracks_passname_;
139 return;
140 }
141 if (!event.exists(input_ecal_coll_name_, input_ecal_passname_)) {
142 ldmx_log(error) << "Unable to find (one) collection named "
143 << input_ecal_coll_name_ << "_" << input_ecal_passname_;
144 return;
145 }
146 if (!event.exists(input_hcal_coll_name_, input_hcal_passname_)) {
147 ldmx_log(error) << "Unable to find (one) collection named "
148 << input_hcal_coll_name_ << "_" << input_hcal_passname_;
149 return;
150 }
151 // get the track and clustering info
152 const auto hcal_clusters = event.getCollection<ldmx::CaloCluster>(
153 input_hcal_coll_name_, input_hcal_passname_);
154 const auto tracks = event.getCollection<ldmx::SimTrackerHit>(
155 input_track_coll_name_, input_tracks_passname_);
156 // here allow for using existing clusters of different type (EcalCluster)
157 const auto ecal_clusters =
158 use_existing_ecal_clusters_
159 ? getEcalClusters(event, input_ecal_coll_name_, input_ecal_passname_)
160 : event.getCollection<ldmx::CaloCluster>(input_ecal_coll_name_,
161 input_ecal_passname_);
162
163 std::vector<ldmx::PFCandidate> pf_cands;
164 // multi-particle case
165 if (!single_particle_) {
166 /*
167 1. Build links maps at the Tk/Ecal and Hcal/Hcal interfaces
168 2. Categorize tracks as: Ecal-matched, (Side) Hcal-matched, unmatched
169 3. Categorize Ecal clusters as: Hcal-matched, unmatched
170 4a. (Upstream?) Categorize tracks with dedx?
171 4b. (Upstream?) Categorize ecal clusters as: EM/Had-like
172 4c. (Upstream?) Categorize hcal clusters as: EM/Had-like
173 5. Build candidates by category, moving from Tk-Ecal-Hcal
174 */
175
176 //
177 // track-calo linking
178 //
179 std::map<int, std::vector<int> > tk_calo_map;
180 std::map<int, std::vector<int> > calo_tk_map;
181 std::map<std::pair<int, int>, float> tk_em_dist;
182 // std::vector<int> unmatchedTracks;
183 for (int i = 0; i < tracks.size(); i++) {
184 const auto& tk = tracks[i];
185 const std::vector<float> xyz = tk.getPosition();
186 const std::vector<double> pxyz = tk.getMomentum();
187 const float p = sqrt(pow(pxyz[0], 2) + pow(pxyz[1], 2) + pow(pxyz[2], 2));
188 // float bestMatchVal = 9e9;
189 for (int j = 0; j < ecal_clusters.size(); j++) {
190 const auto& ecal = ecal_clusters[j];
191 // Matching logic
192 const float ecal_clus_z = ecal.getCentroidZ();
193 const float tk_x_at_clus =
194 xyz[0] +
195 pxyz[0] / pxyz[2] * (ecal_clus_z - xyz[2]); // extrapolation
196 const float tk_y_at_clus =
197 xyz[1] + pxyz[1] / pxyz[2] * (ecal_clus_z - xyz[2]);
198 float dist = hypot((tk_x_at_clus - ecal.getCentroidX()) /
199 std::max(1.0, ecal.getRMSX()),
200 (tk_y_at_clus - ecal.getCentroidY()) /
201 std::max(1.0, ecal.getRMSY()));
202 tk_em_dist[{i, j}] = dist;
203 bool is_match =
204 (dist < 2) && (ecal.getEnergy() > 0.3 * p &&
205 ecal.getEnergy() < 2 * p); // matching criteria *
206 // if (isMatch && dist < bestMatchVal) bestMatchVal = dist;
207 if (is_match) {
208 if (tk_calo_map.count(i))
209 tk_calo_map[i].push_back(j);
210 else
211 tk_calo_map[i] = {j};
212 if (calo_tk_map.count(j))
213 calo_tk_map[j].push_back(i);
214 else
215 calo_tk_map[j] = {i};
216 }
217 }
218 }
219
220 // em-hadcalo linking
221 std::map<int, std::vector<int> > em_had_calo_map;
222 std::map<std::pair<int, int>, float> em_had_dist;
223 for (int i = 0; i < ecal_clusters.size(); i++) {
224 const auto& ecal = ecal_clusters[i];
225 for (int j = 0; j < hcal_clusters.size(); j++) {
226 const auto& hcal = hcal_clusters[j];
227 // TODO: matching logic
228 const float x_at_h_clus =
229 ecal.getCentroidX() +
230 ecal.getDXDZ() * (hcal.getCentroidZ() -
231 ecal.getCentroidZ()); // extrapolated position
232 const float y_at_h_clus =
233 ecal.getCentroidY() +
234 ecal.getDYDZ() * (hcal.getCentroidZ() - ecal.getCentroidZ());
235 float dist = sqrt(
236 pow(x_at_h_clus - hcal.getCentroidX(), 2) /
237 std::max(1.0, pow(hcal.getRMSX(), 2) + pow(ecal.getRMSX(), 2)) +
238 pow(y_at_h_clus - hcal.getCentroidY(), 2) /
239 std::max(1.0, pow(hcal.getRMSY(), 2) + pow(ecal.getRMSY(), 2)));
240 em_had_dist[{i, j}] = dist;
241 bool is_match = (dist < 5); // matching criteria, was 2
242 if (is_match) {
243 if (em_had_calo_map.count(i))
244 em_had_calo_map[i].push_back(j);
245 else
246 em_had_calo_map[i] = {j};
247 }
248 }
249 }
250
251 // NOT YET IMPLEMENTED...
252 // tk-hadcalo linking (Side HCal)
253 std::map<int, std::vector<int> > tk_had_calo_map;
254 // for(int i=0; i<tracks.size(); i++){
255 // const auto& tk = tracks[i];
256 // for(int j=0; j<hcalClusters.size(); j++){
257 // const auto& hcal = hcalClusters[j];
258 // // TODO: add the matching logic here...
259 // bool isMatch = true;
260 // if(isMatch){
261 // if (tkHadCaloMap.count(i)) tkHadCaloMap[i].push_back(j);
262 // else tkHadCaloMap[i] = {j};
263 // }
264 // }
265 // }
266
267 //
268 // track / ecal cluster arbitration
269 //
270 std::vector<bool> tk_is_em_linked(tracks.size(), false);
271 std::vector<bool> em_is_tk_linked(ecal_clusters.size(), false);
272 std::map<int, int> tk_em_pairs{};
273 for (int i = 0; i < tracks.size(); i++) {
274 if (tk_calo_map.count(i)) {
275 // pick first (highest-energy) unused matching cluster
276 for (int em_idx : tk_calo_map[i]) {
277 if (!em_is_tk_linked[em_idx]) {
278 em_is_tk_linked[em_idx] = true;
279 tk_is_em_linked[i] = true;
280 tk_em_pairs[i] = em_idx;
281 break;
282 }
283 }
284 }
285 }
286
287 // track / hcal cluster arbitration
288 std::vector<bool> em_is_had_linked(ecal_clusters.size(), false);
289 std::vector<bool> had_is_em_linked(hcal_clusters.size(), false);
290 std::map<int, int> em_had_pairs{};
291 for (int i = 0; i < ecal_clusters.size(); i++) {
292 if (em_had_calo_map.count(i)) {
293 // pick first (highest-energy) unused matching cluster
294 for (int had_idx : em_had_calo_map[i]) {
295 if (!had_is_em_linked[had_idx]) {
296 had_is_em_linked[had_idx] = true;
297 em_is_had_linked[i] = true;
298 em_had_pairs[i] = had_idx;
299 break;
300 }
301 }
302 }
303 }
304
305 // can consider combining satellite clusters here...
306 // define some "primary cluster" ID criterion
307 // and can add fails to the primaries
308
309 //
310 // Begin building pf candidates from tracks
311 //
312
313 // std::vector<ldmx::PFCandidate> chargedMatch;
314 // std::vector<ldmx::PFCandidate> chargedUnmatch;
315 for (int i = 0; i < tracks.size(); i++) {
317 fillCandTrack(cand, tracks[i]); // append track info to candidate
318
319 cand.setTrackIndex(i);
320 if (!tk_is_em_linked[i]) {
321 // chargedUnmatch.push_back(cand);
322 } else { // if track is linked with ECal cluster
323 fillCandEMCalo(cand, ecal_clusters[tk_em_pairs[i]]);
324 cand.setEcalIndex(tk_em_pairs[i]);
325 if (em_is_had_linked[tk_em_pairs[i]]) { // if ECal is linked with HCal
326 // cluster
327 fillCandHadCalo(cand, hcal_clusters[em_had_pairs[tk_em_pairs[i]]]);
328 }
329 // chargedMatch.push_back(cand);
330 }
331 pf_cands.push_back(cand);
332 }
333
334 // std::vector<ldmx::PFCandidate> emMatch;
335 // std::vector<ldmx::PFCandidate> emUnmatch;
336 for (int i = 0; i < ecal_clusters.size(); i++) {
337 // already linked with ECal in the previous step
338 if (em_is_tk_linked[i]) continue;
340 fillCandEMCalo(cand, ecal_clusters[i]);
341 cand.setEcalIndex(i);
342 if (em_is_had_linked[tk_em_pairs[i]]) {
343 fillCandHadCalo(cand, hcal_clusters[em_had_pairs[i]]);
344 // emMatch.push_back(cand);
345 } else {
346 // emUnmatch.push_back(cand);
347 }
348 pf_cands.push_back(cand);
349 }
350 std::vector<ldmx::PFCandidate> had_only;
351 for (int i = 0; i < hcal_clusters.size(); i++) {
352 if (had_is_em_linked[i]) continue;
354 fillCandHadCalo(cand, hcal_clusters[i]);
355 cand.setHcalIndex(i);
356 // hadOnly.push_back(cand);
357 pf_cands.push_back(cand);
358 }
359
360 // // track / ecal cluster arbitration
361 // std::vector<ldmx::PFCandidate> caloMatchedTks;
362 // std::vector<ldmx::PFCandidate> unmatchedTks;
363 // std::vector<bool> emUsed (ecalClusters.size(), false);
364 // for(int i=0; i<tracks.size(); i++){
365 // ldmx::PFCandidate cand;
366 // fillCandTrack(cand, tracks[i]);
367 // if( tkCaloMap.count(i)==0 ){
368 // unmatchedTks.push_back(cand);
369 // } else {
370 // // look for the first (highest-energy) unused matching cluster
371 // bool linked=false;
372 // for(int em_idx : tkCaloMap[i]){
373 // if(!emUsed[em_idx]){
374 // fillCandEMCalo(cand, tkCaloMap[i][0]);
375 // caloMatchedTks.push_back(cand);
376 // emUsed[ em_idx ] = true;
377 // linked = true;
378 // break;
379 // }
380 // }
381 // if (!linked) unmatchedTks.push_back(cand);
382 // }
383 // }
384
385 // // ecal / hcal cluster arbitration
386 // std::vector<bool> hadUsed (hcalClusters.size(), false);
387 // for(int i=0; i<ecalClusters.size(); i++){
388 // if( emHadCaloMap.count(i)==0 ){
389 // unmatchedTks.push_back(cand);
390 // } else {
391 // // look for the first (highest-energy) unused matching cluster
392 // bool linked=false;
393 // for(int em_idx : tkCaloMap[i]){
394 // if(!emUsed[em_idx]){
395 // fillCandEMCalo(cand, tkCaloMap[i][0]);
396 // caloMatchedTks.push_back(cand);
397 // emUsed[ em_idx ] = true;
398 // linked = true;
399 // break;
400 // }
401 // }
402 // if (!linked) unmatchedTks.push_back(cand);
403 // }
404 // }
405
406 // std::vector<ldmx::PFCandidate> unmatchedEMs;
407 // for(int i=0; i<ecalClusters.size(); i++){
408 // if(emUsed[i]) continue;
409 // ldmx::PFCandidate cand;
410 // fillCandEMCalo(cand, ecalClusters[i]);
411 // }
412
413 // }
414 // if( tkCaloMap[i].size()==1 ){
415 // if(!emUsed[ tkCaloMap[i][0] ]){
416 // fillCandEMCalo(cand, tkCaloMap[i][0]);
417 // caloMatchedTks.push_back(cand);
418 // emUsed[ tkCaloMap[i][0] ] = true;
419 // }
420 // }
421 // } else if( tkCaloMap[i].size()==1 ){
422 // if(!emUsed[ tkCaloMap[i][0] ]){
423 // fillCandEMCalo(cand, tkCaloMap[i][0]);
424 // caloMatchedTks.push_back(cand);
425 // emUsed[ tkCaloMap[i][0] ] = true;
426 // }
427 // }
428 // }
429
430 } else {
431 // Single-particle builder
433 int pid = 0; // initialize pid to add
434 if (tracks.size()) {
435 fillCandTrack(pf, tracks[0]);
436 pid += 1;
437 }
438 if (ecal_clusters.size()) {
439 fillCandEMCalo(pf, ecal_clusters[0]);
440 pid += 2;
441 }
442 if (hcal_clusters.size()) {
443 fillCandHadCalo(pf, hcal_clusters[0]);
444 pid += 4;
445 }
446 pf.setPID(pid);
447 pf.setEnergy(pf.getEcalEnergy() + pf.getHcalEnergy());
448 pf_cands.push_back(pf);
449 }
450
451 event.add(output_coll_name_, pf_cands);
452}
453// stupid function to type cast from ecal to calo cluster
454const std::vector<ldmx::CaloCluster> ParticleFlow::getEcalClusters(
455 framework::Event& event, std::string inputClusterCollName,
456 std::string inputClusterPassName) {
457 const auto tmp_clusters = event.getCollection<ldmx::EcalCluster>(
458 inputClusterCollName, inputClusterPassName);
459 std::string new_name = inputClusterCollName + "Cast";
460 std::vector<ldmx::CaloCluster> new_clusters;
461 for (auto cl : tmp_clusters) {
462 new_clusters.emplace_back(cl);
463 }
464 event.add(new_name, new_clusters);
465 const auto calo_clusters =
466 event.getCollection<ldmx::CaloCluster>(new_name, "");
467 return calo_clusters;
468}
469
471 ldmx_log(debug) << "Process ends!";
472 delete e_corr_;
473 delete h_corr_;
474
475 return;
476}
477
478} // namespace recon
479
Class that stores cluster information from the ECal.
#define DECLARE_PRODUCER(CLASS)
Macro which allows the framework to construct a producer given its name during configuration.
Simple PFlow algorithm.
Implements an event buffer system for storing event data.
Definition Event.h:40
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:107
Class encapsulating parameters for configuring a processor.
Definition Parameters.h:26
const T & get(const std::string &name) const
Retrieve the parameter of the given name.
Definition Parameters.h:75
Stores cluster information from the ECal.
Definition CaloCluster.h:25
double getRMSX() const
rms in x
double getDYDZ() const
Delta in y-z plane.
double getCentroidZ() const
centroid z-location
double getCentroidX() const
centroid x-location
double getRMSZ() const
rms in z
double getRMSY() const
rms in y
double getCentroidY() const
centroid y-location
double getEDXDZ() const
Delta unc on unc in x-z plane.
double getDXDZ() const
Delta in x-z plane.
double getEDYDZ() const
Delta unc on unc in y-z plane.
Stores cluster information from the ECal.
Definition EcalCluster.h:20
Represents a reconstructed particle.
Definition PFCandidate.h:19
Represents a simulated tracker hit in the simulation.
int getPdgID() const
Get the Sim particle track ID of the hit.
std::vector< float > getPosition() const
Get the XYZ position of the hit [mm].
float getEnergy() const
Get the energy.
std::vector< double > getMomentum() const
Get the XYZ momentum of the particle at the position at which the hit took place [MeV].
virtual void produce(framework::Event &event)
Process the event and put new data products into it.
virtual void onProcessEnd()
Callback for the EventProcessor to take any necessary action when the processing of events finishes,...
virtual void configure(framework::config::Parameters &ps)
Callback for the EventProcessor to configure itself from the given set of parameters.