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