LDMX Software
PhotoNuclearDQM.cxx
1
2#include "DQM/PhotoNuclearDQM.h"
3
4/*~~~~~~~~~~~~~~~~*/
5/* C++ StdLib */
6/*~~~~~~~~~~~~~~~~*/
7#include <algorithm>
8
9//----------//
10// ROOT //
11//----------//
12#include "TH1F.h"
13#include "TH2F.h"
14
15//----------//
16// LDMX //
17//----------//
18#include <TVector3.h>
19
20#include "Framework/Event.h"
21#include "Tools/AnalysisUtils.h"
22
23namespace dqm {
24
25PhotoNuclearDQM::PhotoNuclearDQM(const std::string &name,
26 framework::Process &process)
27 : framework::Analyzer(name, process) {}
28
30std::vector<const ldmx::SimParticle *> PhotoNuclearDQM::findDaughters(
31 const std::map<int, ldmx::SimParticle> &particleMap,
32 const ldmx::SimParticle *parent) const {
33 std::vector<const ldmx::SimParticle *> pnDaughters;
34 for (const auto &daughterTrackID : parent->getDaughters()) {
35 // skip daughters that weren't saved
36 if (particleMap.count(daughterTrackID) == 0) {
37 continue;
38 }
39
40 auto daughter{&(particleMap.at(daughterTrackID))};
41
42 // Get the PDG ID
43 auto pdgID{daughter->getPdgID()};
44
45 // Ignore photons and nuclei
46 if (pdgID == 22 ||
47 (pdgID > 10000 && (!count_light_ions_ || !isLightIon(pdgID)))) {
48 continue;
49 }
50 pnDaughters.push_back(daughter);
51 }
52
53 std::sort(pnDaughters.begin(), pnDaughters.end(),
54 [](const auto &lhs, const auto &rhs) {
55 double lhs_ke = lhs->getEnergy() - lhs->getMass();
56 double rhs_ke = rhs->getEnergy() - rhs->getMass();
57 return lhs_ke > rhs_ke;
58 });
59
60 return pnDaughters;
61}
63 histograms_.fill("recoil_vertex_x", recoil->getVertex()[0]);
64 histograms_.fill("recoil_vertex_y", recoil->getVertex()[1]);
65 histograms_.fill("recoil_vertex_z", recoil->getVertex()[2]);
66 histograms_.fill("recoil_vertex_x:recoil_vertex_y", recoil->getVertex()[0],
67 recoil->getVertex()[1]);
68}
70 const std::vector<const ldmx::SimParticle *> &pnDaughters) {
71 double hardest_ke{-1}, hardest_theta{-1};
72 double hardest_proton_ke{-1}, hardest_proton_theta{-1};
73 double hardest_neutron_ke{-1}, hardest_neutron_theta{-1};
74 double hardest_pion_ke{-1}, hardest_pion_theta{-1};
75 double total_ke{0};
76 double total_neutron_ke{0};
77 int neutron_multiplicity{0};
78 // Loop through all of the PN daughters and extract kinematic
79 // information.
80 for (const auto *daughter : pnDaughters) {
81 // skip daughters that weren't saved
82
83 // Get the PDG ID
84 auto pdgID{daughter->getPdgID()};
85
86 // Calculate the kinetic energy
87 double ke{daughter->getEnergy() - daughter->getMass()};
88 total_ke += ke;
89
90 std::vector<double> vec{daughter->getMomentum()};
91 TVector3 pvec(vec[0], vec[1], vec[2]);
92
93 // Calculate the polar angle
94 auto theta{pvec.Theta() * (180 / 3.14159)};
95
96 if (hardest_ke < ke) {
97 hardest_ke = ke;
98 hardest_theta = theta;
99 }
100
101 if (pdgID == 2112) {
102 total_neutron_ke += ke;
103 neutron_multiplicity++;
104 if (hardest_neutron_ke < ke) {
105 hardest_neutron_ke = ke;
106 hardest_neutron_theta = theta;
107 }
108 }
109
110 if ((pdgID == 2212) && (hardest_proton_ke < ke)) {
111 hardest_proton_ke = ke;
112 hardest_proton_theta = theta;
113 }
114
115 if (((std::abs(pdgID) == 211) || (pdgID == 111)) &&
116 (hardest_pion_ke < ke)) {
117 hardest_pion_ke = ke;
118 hardest_pion_theta = theta;
119 }
120 }
121 histograms_.fill("hardest_ke", hardest_ke);
122 histograms_.fill("hardest_theta", hardest_theta);
123 histograms_.fill("h_ke_h_theta", hardest_ke, hardest_theta);
124 histograms_.fill("hardest_p_ke", hardest_proton_ke);
125 histograms_.fill("hardest_p_theta", hardest_proton_theta);
126 histograms_.fill("hardest_n_ke", hardest_neutron_ke);
127 histograms_.fill("hardest_n_theta", hardest_neutron_theta);
128 histograms_.fill("hardest_pi_ke", hardest_pion_ke);
129 histograms_.fill("hardest_pi_theta", hardest_pion_theta);
130
131 histograms_.fill("pn_neutron_mult", neutron_multiplicity);
132 histograms_.fill("pn_total_ke", total_ke);
133 histograms_.fill("pn_total_neutron_ke", total_neutron_ke);
134}
135
137 const ldmx::SimParticle *pnGamma,
138 const std::vector<const ldmx::SimParticle *> &pnDaughters,
139 const PhotoNuclearDQM::EventType eventType) {
140 // Note: Assumes sorted by energy
141
142 double subleading_ke{-9999};
143 double nEnergy{-9999}, energyDiff{-9999}, energyFrac{-9999};
144
145 nEnergy = pnDaughters[0]->getEnergy() - pnDaughters[0]->getMass();
146 subleading_ke = -9999;
147 if (pnDaughters.size() > 1) {
148 subleading_ke = pnDaughters[1]->getEnergy() - pnDaughters[1]->getMass();
149 }
150 energyDiff = pnGamma->getEnergy() - nEnergy;
151 energyFrac = nEnergy / pnGamma->getEnergy();
152
153 if (eventType == EventType::single_neutron) {
154 histograms_.fill("1n_ke:2nd_h_ke", nEnergy, subleading_ke);
155 histograms_.fill("1n_neutron_energy", nEnergy);
156 histograms_.fill("1n_energy_diff", energyDiff);
157 histograms_.fill("1n_energy_frac", energyFrac);
158 } else if (eventType == EventType::two_neutrons) {
159 histograms_.fill("2n_n2_energy", subleading_ke);
160 auto energyFrac2n = (nEnergy + subleading_ke) / pnGamma->getEnergy();
161 histograms_.fill("2n_energy_frac", energyFrac2n);
162 histograms_.fill("2n_energy_other", pnGamma->getEnergy() - energyFrac2n);
163
164 } else if (eventType == EventType::charged_kaon) {
165 histograms_.fill("1kp_ke:2nd_h_ke", nEnergy, subleading_ke);
166 histograms_.fill("1kp_energy", nEnergy);
167 histograms_.fill("1kp_energy_diff", energyDiff);
168 histograms_.fill("1kp_energy_frac", energyFrac);
169 } else if (eventType == EventType::klong || eventType == EventType::kshort) {
170 histograms_.fill("1k0_ke:2nd_h_ke", nEnergy, subleading_ke);
171 histograms_.fill("1k0_energy", nEnergy);
172 histograms_.fill("1k0_energy_diff", energyDiff);
173 histograms_.fill("1k0_energy_frac", energyFrac);
174 }
175}
177 std::vector<std::string> labels = {"",
178 "Nothing hard", // 0
179 "1 n", // 1
180 "2 n", // 2
181 "#geq 3 n", // 3
182 "1 #pi", // 4
183 "2 #pi", // 5
184 "1 #pi_{0}", // 6
185 "1 #pi A", // 7
186 "1 #pi 2 A", // 8
187 "2 #pi A", // 9
188 "1 #pi_{0} A", // 10
189 "1 #pi_{0} 2 A", // 11
190 "#pi_{0} #pi A", // 12
191 "1 p", // 13
192 "2 p", // 14
193 "pn", // 15
194 "K^{0}_{L} X", // 16
195 "K X", // 17
196 "K^{0}_{S} X", // 18
197 "exotics", // 19
198 "multi-body", // 20
199 ""};
200
201 std::vector<TH1 *> hists = {
202 histograms_.get("event_type"),
203 histograms_.get("event_type_500mev"),
204 histograms_.get("event_type_2000mev"),
205
206 };
207
208 for (int ilabel{1}; ilabel < labels.size(); ++ilabel) {
209 for (auto &hist : hists) {
210 hist->GetXaxis()->SetBinLabel(ilabel, labels[ilabel - 1].c_str());
211 }
212 }
213
214 labels = {"",
215 "1 n", // 0
216 "K#pm X", // 1
217 "1 K^{0}", // 2
218 "2 n", // 3
219 "Soft", // 4
220 "Other", // 5
221 ""};
222
223 hists = {
224 histograms_.get("event_type_compact"),
225 histograms_.get("event_type_compact_500mev"),
226 histograms_.get("event_type_compact_2000mev"),
227 };
228
229 for (int ilabel{1}; ilabel < labels.size(); ++ilabel) {
230 for (auto &hist : hists) {
231 hist->GetXaxis()->SetBinLabel(ilabel, labels[ilabel - 1].c_str());
232 }
233 }
234
235 std::vector<std::string> n_labels = {"",
236 "nn", // 0
237 "pn", // 1
238 "#pi^{+}n", // 2
239 "#pi^{0}n", // 3
240 "other", // 4
241 ""};
242
243 TH1 *hist = histograms_.get("1n_event_type");
244 for (int ilabel{1}; ilabel < n_labels.size(); ++ilabel) {
245 hist->GetXaxis()->SetBinLabel(ilabel, n_labels[ilabel - 1].c_str());
246 }
247}
248
250 verbose_ = parameters.getParameter<bool>("verbose");
251 count_light_ions_ = parameters.getParameter<bool>("count_light_ions", true);
252}
253
255 // Get the particle map from the event. If the particle map is empty,
256 // don't process the event.
257 auto particleMap{event.getMap<int, ldmx::SimParticle>("SimParticles")};
258 if (particleMap.size() == 0) {
259 return;
260 }
261
262 // Get the recoil electron
263 auto [trackID, recoil] = Analysis::getRecoil(particleMap);
264 findRecoilProperties(recoil);
265
266 // Use the recoil electron to retrieve the gamma that underwent a
267 // photo-nuclear reaction.
268 auto pnGamma{Analysis::getPNGamma(particleMap, recoil, 2500.)};
269 if (pnGamma == nullptr) {
270 if (verbose_) {
271 std::cout << "[ PhotoNuclearDQM ]: PN Daughter is lost, skipping."
272 << std::endl;
273 }
274 return;
275 }
276 const auto pnDaughters{findDaughters(particleMap, pnGamma)};
277 findParticleKinematics(pnDaughters);
278
279 histograms_.fill("pn_particle_mult", pnGamma->getDaughters().size());
280 histograms_.fill("pn_gamma_energy", pnGamma->getEnergy());
281 histograms_.fill("pn_gamma_int_z", pnGamma->getEndPoint()[2]);
282 histograms_.fill("pn_gamma_vertex_x", pnGamma->getVertex()[0]);
283 histograms_.fill("pn_gamma_vertex_y", pnGamma->getVertex()[1]);
284 histograms_.fill("pn_gamma_vertex_z", pnGamma->getVertex()[2]);
285
286 // Classify the event
287 auto eventType{classifyEvent(pnDaughters, 200)};
288 auto eventType500MeV{classifyEvent(pnDaughters, 500)};
289 auto eventType2000MeV{classifyEvent(pnDaughters, 2000)};
290
291 auto eventTypeComp{classifyCompactEvent(pnGamma, pnDaughters, 200)};
292 auto eventTypeComp500MeV{classifyCompactEvent(pnGamma, pnDaughters, 500)};
293 auto eventTypeComp2000MeV{classifyCompactEvent(pnGamma, pnDaughters, 2000)};
294
295 histograms_.fill("event_type", static_cast<int>(eventType));
296 histograms_.fill("event_type_500mev", static_cast<int>(eventType500MeV));
297 histograms_.fill("event_type_2000mev", static_cast<int>(eventType2000MeV));
298
299 histograms_.fill("event_type_compact", static_cast<int>(eventTypeComp));
300 histograms_.fill("event_type_compact_500mev",
301 static_cast<int>(eventTypeComp500MeV));
302 histograms_.fill("event_type_compact_2000mev",
303 static_cast<int>(eventTypeComp2000MeV));
304
305 switch (eventType) {
306 case EventType::single_neutron:
307 if (pnDaughters.size() > 1) {
308 auto secondHardestPdgID{abs(pnDaughters[1]->getPdgID())};
309 auto nEventType{-10};
310 if (secondHardestPdgID == 2112) {
311 nEventType = 0; // n + n
312 } else if (secondHardestPdgID == 2212) {
313 nEventType = 1; // p + n
314 } else if (secondHardestPdgID == 211) {
315 nEventType = 2; // Pi+/- + n
316 } else if (secondHardestPdgID == 111) {
317 nEventType = 3; // Pi0 + n
318 } else {
319 nEventType = 4; // other
320 }
321 histograms_.fill("1n_event_type", nEventType);
322 }
323 [[fallthrough]]; // Remaining code is important for 1n as well
324 case EventType::two_neutrons:
325 case EventType::charged_kaon:
326 case EventType::klong:
327 case EventType::kshort:
328 findSubleadingKinematics(pnGamma, pnDaughters, eventType);
329 break;
330 default: // Nothing to do
331 break;
332 }
333}
334
335PhotoNuclearDQM::EventType PhotoNuclearDQM::classifyEvent(
336 const std::vector<const ldmx::SimParticle *> daughters, double threshold) {
337 short n{0}, p{0}, pi{0}, pi0{0}, exotic{0}, k0l{0}, kp{0}, k0s{0};
338
339 // Loop through all of the PN daughters and extract kinematic
340 // information.
341 for (const auto &daughter : daughters) {
342 // Calculate the kinetic energy
343 auto ke{daughter->getEnergy() - daughter->getMass()};
344
345 // Assuming the daughters are sorted by kinetic energy, if the kinetic
346 // energy is below threshold, we don't need to look at any further
347 // particles.
348 if (ke <= threshold) {
349 break;
350 }
351
352 // Get the PDG ID
353 auto pdgID{abs(daughter->getPdgID())};
354
355 if (pdgID == 2112) {
356 n++;
357 } else if (pdgID == 2212) {
358 p++;
359 } else if (pdgID == 211) {
360 pi++;
361 } else if (pdgID == 111) {
362 pi0++;
363 } else if (pdgID == 130) {
364 k0l++;
365 } else if (pdgID == 321) {
366 kp++;
367 } else if (pdgID == 310) {
368 k0s++;
369 } else {
370 exotic++;
371 }
372 }
373
374 int kaons = k0l + kp + k0s;
375 int nucleons = n + p;
376 int pions = pi + pi0;
377 int count = nucleons + pions + exotic + kaons;
378
379 if (count == 0) {
380 return EventType::nothing_hard;
381 }
382 if (count == 1) {
383 if (n == 1) {
384 return EventType::single_neutron;
385 } else if (p == 1) {
386 return EventType::single_proton;
387 } else if (pi0 == 1) {
388 return EventType::single_neutral_pion;
389 } else if (pi == 1) {
390 return EventType::single_charged_pion;
391 }
392 }
393 if (count == 2) {
394 if (n == 2) {
395 return EventType::two_neutrons;
396 } else if (n == 1 && p == 1) {
397 return EventType::proton_neutron;
398 } else if (p == 2) {
399 return EventType::two_protons;
400 } else if (pi == 2) {
401 return EventType::two_charged_pions;
402 } else if (pi == 1 && nucleons == 1) {
403 return EventType::single_charged_pion_and_nucleon;
404 } else if (pi0 == 1 && nucleons == 1) {
405 return EventType::single_neutral_pion_and_nucleon;
406 }
407 }
408
409 if (count == 3) {
410 if (pi == 1 && nucleons == 2) {
411 return EventType::single_charged_pion_and_two_nucleons;
412 } else if (pi == 2 && nucleons == 1) {
413 return EventType::two_charged_pions_and_nucleon;
414 } // else
415 else if (pi0 == 1 && nucleons == 2) {
416 return EventType::single_neutral_pion_and_two_nucleons;
417 } else if (pi0 == 1 && nucleons == 1 && pi == 1) {
418 return EventType::single_neutral_pion_charged_pion_and_nucleon;
419 }
420 }
421 if (count >= 3 && count == n) {
422 return EventType::three_or_more_neutrons;
423 }
424
425 if (kaons == 1) {
426 if (k0l == 1) {
427 return EventType::klong;
428 } else if (kp == 1) {
429 return EventType::charged_kaon;
430 } else if (k0s == 1) {
431 return EventType::kshort;
432 }
433 }
434 if (exotic == count && count != 0) {
435 return EventType::exotics;
436 }
437
438 return EventType::multibody;
439}
440
441PhotoNuclearDQM::CompactEventType PhotoNuclearDQM::classifyCompactEvent(
442 const ldmx::SimParticle *pnGamma,
443 const std::vector<const ldmx::SimParticle *> daughters, double threshold) {
444 short n{0}, n_t{0}, k0l{0}, kp{0}, k0s{0}, soft{0};
445
446 // Loop through all of the PN daughters and extract kinematic
447 // information.
448 for (const auto &daughter : daughters) {
449 // Calculate the kinetic energy
450 auto ke{daughter->getEnergy() - daughter->getMass()};
451
452 // Get the PDG ID
453 auto pdgID{abs(daughter->getPdgID())};
454
455 if (ke < 500) {
456 soft++;
457 continue;
458 }
459
460 if (ke >= 0.8 * pnGamma->getEnergy()) {
461 if (pdgID == 2112) {
462 n++;
463 } else if (pdgID == 130) {
464 k0l++;
465 } else if (pdgID == 321) {
466 kp++;
467 } else if (pdgID == 310) {
468 k0s++;
469 }
470 continue;
471 }
472
473 if ((pdgID == 2112) && ke > threshold) {
474 n_t++;
475 }
476 }
477
478 int neutral_kaons{k0l + k0s};
479
480 if (n != 0) {
481 return PhotoNuclearDQM::CompactEventType::single_neutron;
482 }
483 if (kp != 0) {
484 return PhotoNuclearDQM::CompactEventType::single_charged_kaon;
485 }
486 if (neutral_kaons != 0) {
487 return PhotoNuclearDQM::CompactEventType::single_neutral_kaon;
488 }
489 if (n_t == 2) {
490 return PhotoNuclearDQM::CompactEventType::two_neutrons;
491 }
492 if (soft == daughters.size()) {
493 return PhotoNuclearDQM::CompactEventType::soft;
494 }
495
496 return PhotoNuclearDQM::CompactEventType::other;
497}
498
499} // namespace dqm
500
501DECLARE_ANALYZER_NS(dqm, PhotoNuclearDQM)
Collection of utility functions useful for analysis.
#define DECLARE_ANALYZER_NS(NS, CLASS)
Macro which allows the framework to construct an analyzer given its name during configuration.
Class implementing an event buffer system for storing event data.
PhotoNuclearDQM(const std::string &name, framework::Process &process)
Constructor.
void findSubleadingKinematics(const ldmx::SimParticle *pnGamma, const std::vector< const ldmx::SimParticle * > &pnDaughters, const EventType eventType)
Fill histograms related to the kinematics and subleading particles for 1n, kaon, and 2n type events.
constexpr bool isLightIon(const int pdgCode) const
Check if the PDG code corresponds to a light ion.
std::vector< const ldmx::SimParticle * > findDaughters(const std::map< int, ldmx::SimParticle > &particleMap, const ldmx::SimParticle *parent) const
Find all daughter particles of a parent.
void analyze(const framework::Event &event) override
Process the event and create the histogram summaries.
EventType classifyEvent(const std::vector< const ldmx::SimParticle * > daughters, double threshold)
Method used to classify events.
void onProcessStart() override
Method executed before processing of events begins.
CompactEventType classifyCompactEvent(const ldmx::SimParticle *pnGamma, const std::vector< const ldmx::SimParticle * > daughters, double threshold)
Method used to classify events in a compact manner.
virtual ~PhotoNuclearDQM()
Destructor.
void findRecoilProperties(const ldmx::SimParticle *recoil)
Fill the recoil electron-histograms.
void findParticleKinematics(const std::vector< const ldmx::SimParticle * > &pnDaughters)
Fill histograms related to kinematics of PN products.
void configure(framework::config::Parameters &parameters) override
Configure this analyzer using the user specified parameters.
HistogramHelper histograms_
Interface class for making and filling histograms.
Implements an event buffer system for storing event data.
Definition Event.h:41
TH1 * get(const std::string &name)
Get a pointer to a histogram by name.
Definition Histograms.h:194
void fill(const std::string &name, const double &val)
Fill a 1D histogram.
Definition Histograms.h:166
Class which represents the process under execution.
Definition Process.h:36
Class encapsulating parameters for configuring a processor.
Definition Parameters.h:27
T getParameter(const std::string &name) const
Retrieve the parameter of the given name.
Definition Parameters.h:89
Class representing a simulated particle.
Definition SimParticle.h:23
double getEnergy() const
Get the energy of this particle [MeV].
Definition SimParticle.h:72
std::vector< double > getVertex() const
Get a vector containing the vertex of this particle in mm.
std::vector< int > getDaughters() const
Get a vector containing the track IDs of all daughter particles.
All classes in the ldmx-sw project use this namespace.
Definition PerfDict.cxx:45