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 if (!tk_is_em_linked[i]) {
317 // chargedUnmatch.push_back(cand);
318 } else { // if track is linked with ECal cluster
319 fillCandEMCalo(cand, ecal_clusters[tk_em_pairs[i]]);
320 if (em_is_had_linked[tk_em_pairs[i]]) { // if ECal is linked with HCal
321 // cluster
322 fillCandHadCalo(cand, hcal_clusters[em_had_pairs[tk_em_pairs[i]]]);
323 }
324 // chargedMatch.push_back(cand);
325 }
326 pf_cands.push_back(cand);
327 }
328
329 // std::vector<ldmx::PFCandidate> emMatch;
330 // std::vector<ldmx::PFCandidate> emUnmatch;
331 for (int i = 0; i < ecal_clusters.size(); i++) {
332 // already linked with ECal in the previous step
333 if (em_is_tk_linked[i]) continue;
334
336 fillCandEMCalo(cand, ecal_clusters[i]);
337 if (em_is_had_linked[tk_em_pairs[i]]) {
338 fillCandHadCalo(cand, hcal_clusters[em_had_pairs[i]]);
339 // emMatch.push_back(cand);
340 } else {
341 // emUnmatch.push_back(cand);
342 }
343 pf_cands.push_back(cand);
344 }
345 std::vector<ldmx::PFCandidate> had_only;
346 for (int i = 0; i < hcal_clusters.size(); i++) {
347 if (had_is_em_linked[i]) continue;
349 fillCandHadCalo(cand, hcal_clusters[i]);
350 // hadOnly.push_back(cand);
351 pf_cands.push_back(cand);
352 }
353
354 // // track / ecal cluster arbitration
355 // std::vector<ldmx::PFCandidate> caloMatchedTks;
356 // std::vector<ldmx::PFCandidate> unmatchedTks;
357 // std::vector<bool> emUsed (ecalClusters.size(), false);
358 // for(int i=0; i<tracks.size(); i++){
359 // ldmx::PFCandidate cand;
360 // fillCandTrack(cand, tracks[i]);
361 // if( tkCaloMap.count(i)==0 ){
362 // unmatchedTks.push_back(cand);
363 // } else {
364 // // look for the first (highest-energy) unused matching cluster
365 // bool linked=false;
366 // for(int em_idx : tkCaloMap[i]){
367 // if(!emUsed[em_idx]){
368 // fillCandEMCalo(cand, tkCaloMap[i][0]);
369 // caloMatchedTks.push_back(cand);
370 // emUsed[ em_idx ] = true;
371 // linked = true;
372 // break;
373 // }
374 // }
375 // if (!linked) unmatchedTks.push_back(cand);
376 // }
377 // }
378
379 // // ecal / hcal cluster arbitration
380 // std::vector<bool> hadUsed (hcalClusters.size(), false);
381 // for(int i=0; i<ecalClusters.size(); i++){
382 // if( emHadCaloMap.count(i)==0 ){
383 // unmatchedTks.push_back(cand);
384 // } else {
385 // // look for the first (highest-energy) unused matching cluster
386 // bool linked=false;
387 // for(int em_idx : tkCaloMap[i]){
388 // if(!emUsed[em_idx]){
389 // fillCandEMCalo(cand, tkCaloMap[i][0]);
390 // caloMatchedTks.push_back(cand);
391 // emUsed[ em_idx ] = true;
392 // linked = true;
393 // break;
394 // }
395 // }
396 // if (!linked) unmatchedTks.push_back(cand);
397 // }
398 // }
399
400 // std::vector<ldmx::PFCandidate> unmatchedEMs;
401 // for(int i=0; i<ecalClusters.size(); i++){
402 // if(emUsed[i]) continue;
403 // ldmx::PFCandidate cand;
404 // fillCandEMCalo(cand, ecalClusters[i]);
405 // }
406
407 // }
408 // if( tkCaloMap[i].size()==1 ){
409 // if(!emUsed[ tkCaloMap[i][0] ]){
410 // fillCandEMCalo(cand, tkCaloMap[i][0]);
411 // caloMatchedTks.push_back(cand);
412 // emUsed[ tkCaloMap[i][0] ] = true;
413 // }
414 // }
415 // } else if( tkCaloMap[i].size()==1 ){
416 // if(!emUsed[ tkCaloMap[i][0] ]){
417 // fillCandEMCalo(cand, tkCaloMap[i][0]);
418 // caloMatchedTks.push_back(cand);
419 // emUsed[ tkCaloMap[i][0] ] = true;
420 // }
421 // }
422 // }
423
424 } else {
425 // Single-particle builder
427 int pid = 0; // initialize pid to add
428 if (tracks.size()) {
429 fillCandTrack(pf, tracks[0]);
430 pid += 1;
431 }
432 if (ecal_clusters.size()) {
433 fillCandEMCalo(pf, ecal_clusters[0]);
434 pid += 2;
435 }
436 if (hcal_clusters.size()) {
437 fillCandHadCalo(pf, hcal_clusters[0]);
438 pid += 4;
439 }
440 pf.setPID(pid);
441 pf.setEnergy(pf.getEcalEnergy() + pf.getHcalEnergy());
442 pf_cands.push_back(pf);
443 }
444
445 event.add(output_coll_name_, pf_cands);
446}
447// stupid function to type cast from ecal to calo cluster
448const std::vector<ldmx::CaloCluster> ParticleFlow::getEcalClusters(
449 framework::Event& event, std::string inputClusterCollName,
450 std::string inputClusterPassName) {
451 const auto tmp_clusters = event.getCollection<ldmx::EcalCluster>(
452 inputClusterCollName, inputClusterPassName);
453 std::string new_name = inputClusterCollName + "Cast";
454 std::vector<ldmx::CaloCluster> new_clusters;
455 for (auto cl : tmp_clusters) {
456 new_clusters.emplace_back(cl);
457 }
458 event.add(new_name, new_clusters);
459 const auto calo_clusters =
460 event.getCollection<ldmx::CaloCluster>(new_name, "");
461 return calo_clusters;
462}
463
465 ldmx_log(debug) << "Process ends!";
466 delete e_corr_;
467 delete h_corr_;
468
469 return;
470}
471
472} // namespace recon
473
#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.