1#include "DQM/ParticleFlowDQM.h"
9#include "SimCore/Event/SimParticle.h"
17enum Category { kE, kMu, kPi, kK, kP, kGamma, kNHad, kOther };
20enum Region { kRegionEcal, kRegionSideHcal, kRegionBackHcal, kNoInteraction };
23enum PFType { kPFE, kPFMu, kPFH, kPFGamma, kPFH0, kPFOther, kPFNotFound };
25int category(
int pdg) {
26 switch (std::abs(pdg)) {
51 switch (std::abs(pdg)) {
68int truthPFType(
int pdg) {
69 switch (category(pdg)) {
89int neutralRow(
int pdg,
int region) {
90 switch (std::abs(pdg)) {
102int massColumn(
float mass) {
103 if (mass > 700)
return 2;
104 if (mass > 300)
return 1;
109struct TruthParticle {
114 double x_, y_, z_, px_, py_, pz_;
115 bool matched_{
false};
119std::pair<double, double> project(
const TruthParticle& t,
double z) {
120 if (t.pz_ <= 0)
return {t.x_, t.y_};
121 return {t.x_ + t.px_ / t.pz_ * (z - t.z_), t.y_ + t.py_ / t.pz_ * (z - t.z_)};
126 auto xyz = (c.getPID() & 2) ? c.getEcalClusterXYZ() : c.getHcalClusterXYZ();
127 double n = std::hypot(xyz[0], xyz[1], xyz[2]);
128 if (n <= 0)
return {0, 0, 1};
129 return {xyz[0] / n, xyz[1] / n, xyz[2] / n};
136bool ParticleFlowDQM::reconstructable(
int pdg,
double px,
double py,
double pz,
138 double p = std::hypot(px, py, pz);
139 if (p <= 0 || std::acos(pz / p) > reco_max_theta_ * M_PI / 180.)
return false;
140 switch (category(pdg)) {
142 return p > reco_min_p_pion_;
145 return p > reco_min_p_heavy_;
147 return ke > reco_min_ke_neutral_;
155int ParticleFlowDQM::interactionRegion(
const std::vector<double>& end)
const {
156 double x = std::abs(end[0]), y = std::abs(end[1]), z = end[2];
157 if (x > hcal_max_xy_ || y > hcal_max_xy_ || z > hcal_max_z_ || z < 0)
158 return kNoInteraction;
159 if (z < ecal_back_z_) {
160 return (x < ecal_half_xy_ && y < ecal_half_xy_) ? kRegionEcal
163 return kRegionBackHcal;
167 pf_cand_coll_name_ = ps.
get<std::string>(
"pf_cand_coll_name");
168 pf_cand_pass_name_ = ps.
get<std::string>(
"pf_cand_pass_name");
169 ecal_sp_coll_name_ = ps.
get<std::string>(
"ecal_sp_coll_name");
170 ecal_sp_pass_name_ = ps.
get<std::string>(
"ecal_sp_pass_name");
171 sim_particles_coll_name_ = ps.
get<std::string>(
"sim_particles_coll_name");
172 sim_particles_pass_name_ = ps.
get<std::string>(
"sim_particles_pass_name");
173 truth_min_energy_ = ps.
get<
double>(
"truth_min_energy");
174 neutral_match_dist_ = ps.
get<
double>(
"neutral_match_dist");
175 neutral_match_max_ratio_ = ps.
get<
double>(
"neutral_match_max_ratio");
176 neutral_hcal_match_angle_ = ps.
get<
double>(
"neutral_hcal_match_angle");
177 reco_max_theta_ = ps.
get<
double>(
"reco_max_theta");
178 reco_min_p_pion_ = ps.
get<
double>(
"reco_min_p_pion");
179 reco_min_p_heavy_ = ps.
get<
double>(
"reco_min_p_heavy");
180 reco_min_ke_neutral_ = ps.
get<
double>(
"reco_min_ke_neutral");
181 reco_min_ke_pi0_ = ps.
get<
double>(
"reco_min_ke_pi0");
182 ecal_back_z_ = ps.
get<
double>(
"ecal_back_z");
183 ecal_half_xy_ = ps.
get<
double>(
"ecal_half_xy");
184 hcal_max_z_ = ps.
get<
double>(
"hcal_max_z");
185 hcal_max_xy_ = ps.
get<
double>(
"hcal_max_xy");
189 if (!event.
exists(pf_cand_coll_name_, pf_cand_pass_name_)) {
190 ldmx_log(debug) <<
"No collection " << pf_cand_coll_name_;
197 int n_charged = 0, n_neutral = 0;
198 double charged_p = 0, neutral_e = 0, pf_e = 0;
199 for (
auto& cand : cands) {
200 int pid = cand.getPID();
203 if (pfType(cand.getPdgId()) == kPFH) {
206 pf_e += cand.getEnergy();
207 double ecal_e = cand.getEcalEnergy();
208 double hcal_e = cand.getHcalEnergy();
212 auto p = cand.getTrackPxPyPz();
213 double trk_p = std::hypot(p[0], p[1], p[2]);
217 if (cand.getDeDxNhits() > 0) {
219 cand.getDeDxMassSquared());
221 if ((pid & 2) && trk_p > 0) {
224 cand.getEcalRawEnergy() / trk_p);
226 auto trk_pos = cand.getEcalPositionXYZ();
227 auto cl_pos = cand.getEcalClusterXYZ();
231 if (cand.getTruthPdgId() != 0) {
233 auto tp = cand.getTruthPxPyPz();
234 double true_p = std::hypot(tp[0], tp[1], tp[2]);
239 auto t_pos = cand.getTruthEcalXYZ();
240 auto r_pos = cand.getEcalPositionXYZ();
243 int t_cat = category(cand.getTruthPdgId());
244 if ((pid & 2) && trk_p > 0) {
247 if (cand.getDeDxNhits() > 0) {
249 cand.getDeDxMassSquared());
254 neutral_e += cand.getEnergy();
270 std::vector<int> photons;
271 for (
int i = 0; i < static_cast<int>(cands.size()); i++) {
272 if (cands[i].getPdgId() == 22) photons.push_back(i);
274 for (
size_t a = 0; a < photons.size(); a++) {
275 for (
size_t b = a + 1; b < photons.size(); b++) {
276 const auto& c1 = cands[photons[a]];
277 const auto& c2 = cands[photons[b]];
278 auto d1 = direction(c1);
279 auto d2 = direction(c2);
280 double cos12 = d1[0] * d2[0] + d1[1] * d2[1] + d1[2] * d2[2];
281 double m2 = 2 * c1.getEnergy() * c2.getEnergy() * (1 - cos12);
286 analyzeTruth(event, cands);
290 std::vector<ldmx::PFCandidate>& cands) {
291 if (!event.
exists(ecal_sp_coll_name_, ecal_sp_pass_name_))
return;
293 ecal_sp_coll_name_, ecal_sp_pass_name_);
296 std::map<int, TruthParticle> by_id;
297 for (
const auto& sp : sp_hits) {
298 auto pos = sp.getPosition();
299 auto mom = sp.getMomentum();
300 if (std::abs(240 - pos[2]) > 0.1 || mom[2] <= 0)
continue;
301 double e = sp.getEnergy();
302 int apdg = std::abs(sp.getPdgID());
303 double vis = (apdg == 11 || apdg == 22)
305 : e - std::sqrt(std::max(0., e * e - mom[0] * mom[0] -
308 if (vis < truth_min_energy_)
continue;
309 auto it = by_id.find(sp.getTrackID());
310 if (it != by_id.end() && it->second.visible_e_ >= vis)
continue;
311 by_id[sp.getTrackID()] = {sp.getTrackID(), sp.getPdgID(), vis, e,
312 pos[0], pos[1], pos[2], mom[0],
315 std::vector<TruthParticle> truth;
318 for (
auto& [
id, t] : by_id) {
320 int cat = category(t.pdg_);
321 truth_e += (cat <= kP) ? t.total_e_ : t.visible_e_;
325 for (
const auto& c : cands) pf_e += c.getEnergy();
326 if (truth_e > 0)
histograms_.
fill(
"sum_pf_e_over_truth", pf_e / truth_e);
329 std::vector<int> cand_truth(cands.size(), -1);
330 for (
size_t i = 0; i < cands.size(); i++) {
331 if (!(cands[i].getPID() & 1))
continue;
332 for (
size_t j = 0; j < truth.size(); j++) {
333 if (!truth[j].matched_ &&
334 truth[j].track_id_ == cands[i].getTruthTrackId()) {
335 truth[j].matched_ =
true;
343 std::vector<size_t> neutral;
344 for (
size_t i = 0; i < cands.size(); i++) {
345 if (!(cands[i].getPID() & 1)) neutral.push_back(i);
347 std::sort(neutral.begin(), neutral.end(), [&cands](
size_t a,
size_t b) {
348 return cands[a].getEnergy() > cands[b].getEnergy();
350 for (
size_t i : neutral) {
351 bool has_ecal = cands[i].getPID() & 2;
353 has_ecal ? cands[i].getEcalClusterXYZ() : cands[i].getHcalClusterXYZ();
356 auto cand_dir = direction(cands[i]);
358 double best_d = has_ecal ? neutral_match_dist_
359 : neutral_hcal_match_angle_ * M_PI / 180.;
360 for (
size_t j = 0; j < truth.size(); j++) {
361 if (truth[j].matched_)
continue;
363 double ratio = cands[i].getEnergy() / truth[j].visible_e_;
364 if (ratio < 1. / neutral_match_max_ratio_ ||
365 ratio > neutral_match_max_ratio_)
369 auto [tx, ty] = project(truth[j], xyz[2]);
370 d = std::hypot(tx - xyz[0], ty - xyz[1]);
372 const auto& t = truth[j];
373 double pt = std::hypot(t.px_, t.py_, t.pz_);
375 (t.px_ * cand_dir[0] + t.py_ * cand_dir[1] + t.pz_ * cand_dir[2]) /
377 d = std::acos(std::clamp(c, -1., 1.));
385 truth[best].matched_ =
true;
386 cand_truth[i] = best;
391 std::vector<int> truth_cand(truth.size(), -1);
392 for (
size_t i = 0; i < cands.size(); i++) {
393 int r_type = pfType(cands[i].getPdgId());
394 if (cand_truth[i] < 0) {
397 if (r_type == kPFH) {
399 massColumn(cands[i].getMass()));
401 if (!(cands[i].getPID() & 1)) {
406 truth_cand[cand_truth[i]] = i;
407 const auto& t = truth[cand_truth[i]];
409 double ref = (cands[i].getPID() & 1) ? t.total_e_ : t.visible_e_;
411 histograms_.
fill(
"response_vs_type", r_type, cands[i].getEnergy() / ref);
417 event.exists(sim_particles_coll_name_, sim_particles_pass_name_);
418 std::map<int, ldmx::SimParticle> particles;
421 sim_particles_pass_name_);
426 std::vector<bool> seen(truth.size(),
false);
427 for (
size_t j = 0; j < truth.size(); j++) {
428 if (truth_cand[j] >= 0)
continue;
429 for (
const auto& cand : cands) {
430 int pid = cand.getPID();
431 if (pid & 4 && !(pid & 2)) {
432 auto d = direction(cand);
433 const auto& t = truth[j];
434 double pt = std::hypot(t.px_, t.py_, t.pz_);
435 double cs = (t.px_ * d[0] + t.py_ * d[1] + t.pz_ * d[2]) / pt;
436 if (cs > std::cos(neutral_hcal_match_angle_ * M_PI / 180.))
440 (pid & 2) ? cand.getEcalClusterXYZ() : cand.getEcalPositionXYZ();
441 auto [tx, ty] = project(truth[j], xyz[2]);
442 if (std::hypot(tx - xyz[0], ty - xyz[1]) < neutral_match_dist_)
451 for (
size_t j = 0; j < truth.size(); j++) {
452 const auto& t = truth[j];
453 int c = truth_cand[j];
454 int r_type = c >= 0 ? pfType(cands[c].getPdgId())
455 : (seen[j] ? kPFOther : kPFNotFound);
456 bool reco = reconstructable(t.pdg_, t.px_, t.py_, t.pz_, t.visible_e_);
462 int sp = category(t.pdg_);
463 if (sp == kPi || sp == kK || sp == kP) {
465 int col = r_type == kPFH ? massColumn(cands[c].getMass())
466 : r_type == kPFNotFound ? 4
476 int col = r_type == kPFNotFound ? 4 : 3;
477 if (r_type == kPFH0) col = 0;
478 if (r_type == kPFGamma) col = 1;
479 int region = kNoInteraction;
480 if (
auto it = particles.find(t.track_id_); it != particles.end()) {
481 region = interactionRegion(it->second.getEndPoint());
483 int row = neutralRow(t.pdg_, region);
490 for (
const auto& t : truth) {
492 if (t.matched_)
histograms_.
fill(
"truth_type_matched", category(t.pdg_));
495 if (t.matched_)
histograms_.
fill(
"truth_gamma_e_matched", t.visible_e_);
500 if (!has_particles)
return;
501 auto pi0_parent = [&particles](
int track_id) {
502 auto it = particles.find(track_id);
503 if (it == particles.end() || it->second.getParents().empty())
return -1;
504 int parent = it->second.getParents()[0];
505 auto pit = particles.find(parent);
506 if (pit == particles.end() || pit->second.getPdgID() != 111)
return -1;
509 for (
size_t a = 0; a < cands.size(); a++) {
510 if (cands[a].getPdgId() != 22 || cand_truth[a] < 0)
continue;
511 int pa = pi0_parent(truth[cand_truth[a]].track_id_);
512 if (pa < 0)
continue;
513 for (
size_t b = a + 1; b < cands.size(); b++) {
514 if (cands[b].getPdgId() != 22 || cand_truth[b] < 0)
continue;
515 if (pi0_parent(truth[cand_truth[b]].track_id_) != pa)
continue;
516 auto d1 = direction(cands[a]);
517 auto d2 = direction(cands[b]);
518 double cos12 = d1[0] * d2[0] + d1[1] * d2[1] + d1[2] * d2[2];
519 double m2 = 2 * cands[a].getEnergy() * cands[b].getEnergy() * (1 - cos12);
524 std::map<int, std::vector<int>> pi0_photons;
525 for (
size_t j = 0; j < truth.size(); j++) {
526 if (truth[j].pdg_ != 22)
continue;
527 int pa = pi0_parent(truth[j].track_id_);
528 if (pa >= 0) pi0_photons[pa].push_back(j);
530 for (
const auto& [pa, photons] : pi0_photons) {
532 if (photons.size() < 2)
continue;
534 int n_gamma = 0, n_h0 = 0, n_seen = 0;
535 for (
int j : photons) {
536 int c = truth_cand[j];
537 n_seen += c >= 0 || seen[j];
539 int r_type = pfType(cands[c].getPdgId());
540 n_gamma += r_type == kPFGamma;
541 n_h0 += r_type == kPFH0;
543 int col = n_seen > 0 ? 3 : 4;
548 else if (n_gamma == 1)
551 const auto& pi0 = particles.at(pa);
552 double pi0_ke = pi0.getEnergy() - pi0.getMass();
553 double pi0_theta = std::acos(
554 pi0.getMomentum()[2] /
555 std::max(1e-9, std::hypot(pi0.getMomentum()[0], pi0.getMomentum()[1],
556 pi0.getMomentum()[2])));
557 if (pi0_ke > reco_min_ke_pi0_ &&
558 pi0_theta < reco_max_theta_ * M_PI / 180.) {
#define DECLARE_ANALYZER(CLASS)
Macro which allows the framework to construct an analyzer given its name during configuration.
Class that represents a reconstructed particle candidate.
Class which encapsulates information from a hit in a simulated tracking detector.
Monitor PF candidates, their ID, and the match to truth.
void configure(framework::config::Parameters &ps) override
Callback for the EventProcessor to configure itself from the given set of parameters.
void analyze(const framework::Event &event) override
Process the event and make histograms or summaries.
HistogramPool histograms_
helper object for making and filling histograms
Implements an event buffer system for storing event data.
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.
void fill(const std::string &name, const T &val)
Fill a 1D histogram.
Class encapsulating parameters for configuring a processor.
const T & get(const std::string &name) const
Retrieve the parameter of the given name.
Represents a reconstructed particle.
Class representing a simulated particle.
Represents a simulated tracker hit in the simulation.