LDMX Software
PhotoNuclearDQM.cxx
1
2#include "DQM/PhotoNuclearDQM.h"
3
4namespace dqm {
5
6PhotoNuclearDQM::PhotoNuclearDQM(const std::string &name,
7 framework::Process &process)
8 : framework::Analyzer(name, process) {}
9
10std::vector<const ldmx::SimParticle *> PhotoNuclearDQM::findDaughters(
11 const std::map<int, ldmx::SimParticle> &particleMap,
12 const ldmx::SimParticle *parent) const {
13 std::vector<const ldmx::SimParticle *> pnDaughters;
14 for (const auto &daughterTrackID : parent->getDaughters()) {
15 // skip daughters that weren't saved
16 if (particleMap.count(daughterTrackID) == 0) {
17 continue;
18 }
19
20 auto daughter{&(particleMap.at(daughterTrackID))};
21
22 // Get the PDG ID
23 auto pdgID{daughter->getPdgID()};
24
25 // Ignore photons and nuclei
26 if (pdgID == 22 ||
27 (pdgID > 10000 && (!count_light_ions_ || !isLightIon(pdgID)))) {
28 continue;
29 }
30 pnDaughters.push_back(daughter);
31 }
32
33 std::sort(pnDaughters.begin(), pnDaughters.end(),
34 [](const auto &lhs, const auto &rhs) {
35 double lhs_ke = lhs->getEnergy() - lhs->getMass();
36 double rhs_ke = rhs->getEnergy() - rhs->getMass();
37 return lhs_ke > rhs_ke;
38 });
39
40 return pnDaughters;
41}
43 histograms_.fill("recoil_vertex_x", recoil->getVertex()[0]);
44 histograms_.fill("recoil_vertex_y", recoil->getVertex()[1]);
45 histograms_.fill("recoil_vertex_z", recoil->getVertex()[2]);
46 histograms_.fill("recoil_vertex_x:recoil_vertex_y", recoil->getVertex()[0],
47 recoil->getVertex()[1]);
48}
50 const std::vector<const ldmx::SimParticle *> &pnDaughters) {
51 double hardest_ke{-1}, hardest_theta{-1};
52 double hardest_proton_ke{-1}, hardest_proton_theta{-1};
53 double hardest_neutron_ke{-1}, hardest_neutron_theta{-1};
54 double hardest_pion_ke{-1}, hardest_pion_theta{-1};
55 double total_ke{0};
56 double total_neutron_ke{0};
57 int neutron_multiplicity{0};
58 // Loop through all of the PN daughters and extract kinematic
59 // information.
60 for (const auto *daughter : pnDaughters) {
61 // skip daughters that weren't saved
62
63 // Get the PDG ID
64 auto pdgID{daughter->getPdgID()};
65
66 // Calculate the kinetic energy
67 double ke{daughter->getEnergy() - daughter->getMass()};
68 total_ke += ke;
69
70 std::vector<double> vec{daughter->getMomentum()};
71 TVector3 pvec(vec[0], vec[1], vec[2]);
72
73 // Calculate the polar angle
74 auto theta{pvec.Theta() * (180 / 3.14159)};
75
76 if (hardest_ke < ke) {
77 hardest_ke = ke;
78 hardest_theta = theta;
79 }
80
81 if (pdgID == 2112) {
82 total_neutron_ke += ke;
83 neutron_multiplicity++;
84 if (hardest_neutron_ke < ke) {
85 hardest_neutron_ke = ke;
86 hardest_neutron_theta = theta;
87 }
88 }
89
90 if ((pdgID == 2212) && (hardest_proton_ke < ke)) {
91 hardest_proton_ke = ke;
92 hardest_proton_theta = theta;
93 }
94
95 if (((std::abs(pdgID) == 211) || (pdgID == 111)) &&
96 (hardest_pion_ke < ke)) {
97 hardest_pion_ke = ke;
98 hardest_pion_theta = theta;
99 }
100 }
101 histograms_.fill("hardest_ke", hardest_ke);
102 histograms_.fill("hardest_theta", hardest_theta);
103 histograms_.fill("h_ke_h_theta", hardest_ke, hardest_theta);
104 histograms_.fill("hardest_p_ke", hardest_proton_ke);
105 histograms_.fill("hardest_p_theta", hardest_proton_theta);
106 histograms_.fill("hardest_n_ke", hardest_neutron_ke);
107 histograms_.fill("hardest_n_theta", hardest_neutron_theta);
108 histograms_.fill("hardest_pi_ke", hardest_pion_ke);
109 histograms_.fill("hardest_pi_theta", hardest_pion_theta);
110
111 histograms_.fill("pn_neutron_mult", neutron_multiplicity);
112 histograms_.fill("pn_total_ke", total_ke);
113 histograms_.fill("pn_total_neutron_ke", total_neutron_ke);
114}
115
117 const ldmx::SimParticle *pnGamma,
118 const std::vector<const ldmx::SimParticle *> &pnDaughters,
119 const PhotoNuclearDQM::EventType eventType) {
120 // Note: Assumes sorted by energy
121
122 double subleading_ke{-9999};
123 double nEnergy{-9999}, energyDiff{-9999}, energyFrac{-9999};
124
125 nEnergy = pnDaughters[0]->getEnergy() - pnDaughters[0]->getMass();
126 subleading_ke = -9999;
127 if (pnDaughters.size() > 1) {
128 subleading_ke = pnDaughters[1]->getEnergy() - pnDaughters[1]->getMass();
129 }
130 energyDiff = pnGamma->getEnergy() - nEnergy;
131 energyFrac = nEnergy / pnGamma->getEnergy();
132
133 if (eventType == EventType::single_neutron) {
134 histograms_.fill("1n_ke:2nd_h_ke", nEnergy, subleading_ke);
135 histograms_.fill("1n_neutron_energy", nEnergy);
136 histograms_.fill("1n_energy_diff", energyDiff);
137 histograms_.fill("1n_energy_frac", energyFrac);
138 } else if (eventType == EventType::two_neutrons) {
139 histograms_.fill("2n_n2_energy", subleading_ke);
140 auto energyFrac2n = (nEnergy + subleading_ke) / pnGamma->getEnergy();
141 histograms_.fill("2n_energy_frac", energyFrac2n);
142 histograms_.fill("2n_energy_other", pnGamma->getEnergy() - energyFrac2n);
143
144 } else if (eventType == EventType::charged_kaon) {
145 histograms_.fill("1kp_ke:2nd_h_ke", nEnergy, subleading_ke);
146 histograms_.fill("1kp_energy", nEnergy);
147 histograms_.fill("1kp_energy_diff", energyDiff);
148 histograms_.fill("1kp_energy_frac", energyFrac);
149 } else if (eventType == EventType::klong || eventType == EventType::kshort) {
150 histograms_.fill("1k0_ke:2nd_h_ke", nEnergy, subleading_ke);
151 histograms_.fill("1k0_energy", nEnergy);
152 histograms_.fill("1k0_energy_diff", energyDiff);
153 histograms_.fill("1k0_energy_frac", energyFrac);
154 }
155}
156
157void PhotoNuclearDQM::setHistLabels(const std::string &name,
158 const std::vector<std::string> &labels) {
159 auto histo{histograms_.get(name)};
160 for (std::size_t ibin{1}; ibin <= labels.size(); ibin++) {
161 histo->GetXaxis()->SetBinLabel(ibin, labels[ibin - 1].c_str());
162 }
163}
164
166 std::vector<std::string> labels = {"",
167 "Nothing hard", // 0
168 "1 n", // 1
169 "2 n", // 2
170 "#geq 3 n", // 3
171 "1 #pi", // 4
172 "2 #pi", // 5
173 "1 #pi_{0}", // 6
174 "1 #pi A", // 7
175 "1 #pi 2 A", // 8
176 "2 #pi A", // 9
177 "1 #pi_{0} A", // 10
178 "1 #pi_{0} 2 A", // 11
179 "#pi_{0} #pi A", // 12
180 "1 p", // 13
181 "2 p", // 14
182 "pn", // 15
183 "K^{0}_{L} X", // 16
184 "K X", // 17
185 "K^{0}_{S} X", // 18
186 "exotics", // 19
187 "multi-body", // 20
188 ""};
189
190 setHistLabels("event_type", labels);
191 setHistLabels("event_type_500mev", labels);
192 setHistLabels("event_type_2000mev", labels);
193
194 labels = {"",
195 "1 n", // 0
196 "K#pm X", // 1
197 "1 K^{0}", // 2
198 "2 n", // 3
199 "Soft", // 4
200 "Other", // 5
201 ""};
202
203 setHistLabels("event_type_compact", labels);
204 setHistLabels("event_type_compact_500mev", labels);
205 setHistLabels("event_type_compact_2000mev", labels);
206
207 setHistLabels("1n_event_type", {"nn", "pn", "#pi^{+}n", "#pi^{0}n", "other"});
208
210 "pn_vertex_volume",
211 {"Didn't happen", "Else", "W Cooling", "C Cooling", "PCB",
212 "CarbonBasePlate", "Absorber", "Sensor", "Glue", "Motherboard"});
213
214 setHistLabels("pn_interaction_material",
215 {"Didn't happen", "Else", "Si", "W", "FR4", "Steel", "Epoxy",
216 "PVT", "Glue", "Air"});
217
218} // end of onProcessStart
219
221 count_light_ions_ = parameters.getParameter<bool>("count_light_ions", true);
222}
223
225 // Get the particle map from the event. If the particle map is empty,
226 // don't process the event.
227 auto particleMap{event.getMap<int, ldmx::SimParticle>("SimParticles")};
228 if (particleMap.size() == 0) {
229 return;
230 }
231
232 // Get the recoil electron
233 auto [trackID, recoil] = Analysis::getRecoil(particleMap);
234 findRecoilProperties(recoil);
235
236 // Use the recoil electron to retrieve the gamma that underwent a
237 // photo-nuclear reaction.
238 auto pnGamma{Analysis::getPNGamma(particleMap, recoil, 2500.)};
239 if (pnGamma == nullptr) {
240 ldmx_log(warn) << "PN Daughter is lost, skipping";
241 return;
242 }
243
244 const auto pnDaughters{findDaughters(particleMap, pnGamma)};
245
246 if (!pnDaughters.empty()) {
247 auto pn_vertex_volume{pnDaughters[0]->getVertexVolume()};
248 auto pn_interaction_material{pnDaughters[0]->getInteractionMaterial()};
249
250 // Let's start with the PN vertex volume
251 if (pn_vertex_volume.find("W_cooling") != std::string::npos) {
252 // W_cooling_volume_X
253 histograms_.fill("pn_vertex_volume", 2);
254 } else if (pn_vertex_volume.find("C_volume") != std::string::npos) {
255 // C_volume_X
256 histograms_.fill("pn_vertex_volume", 3);
257 } else if (pn_vertex_volume.find("PCB_volume") != std::string::npos) {
258 histograms_.fill("pn_vertex_volume", 4);
259 } else if (pn_vertex_volume.find("CarbonBasePlate") != std::string::npos) {
260 // CarbonBasePlate_volume
261 histograms_.fill("pn_vertex_volume", 5);
262 } else if (pn_vertex_volume.find("W_front") != std::string::npos) {
263 // W_front_volume_X
264 histograms_.fill("pn_vertex_volume", 6);
265 } else if (pn_vertex_volume.find("Si_volume") != std::string::npos) {
266 histograms_.fill("pn_vertex_volume", 7);
267 } else if (pn_vertex_volume.find("Glue") != std::string::npos) {
268 histograms_.fill("pn_vertex_volume", 8);
269 } else if (pn_vertex_volume.find("motherboard") != std::string::npos) {
270 histograms_.fill("pn_vertex_volume", 9);
271
272 } else {
273 ldmx_log(debug) << " Else pn_vertex_volume = " << pn_vertex_volume
274 << " with pn_interaction_material = "
275 << pn_interaction_material;
276 histograms_.fill("pn_vertex_volume", 1);
277 }
278
279 // Now the interaction material
280 if (pn_interaction_material.find("G4_Si") != std::string::npos) {
281 histograms_.fill("pn_interaction_material", 2);
282 } else if (pn_interaction_material.find("G4_W") != std::string::npos) {
283 histograms_.fill("pn_interaction_material", 3);
284 } else if (pn_interaction_material.find("FR4") != std::string::npos) {
285 // Glass epoxy
286 histograms_.fill("pn_interaction_material", 4);
287 } else if (pn_interaction_material.find("Steel") != std::string::npos) {
288 histograms_.fill("pn_interaction_material", 5);
289 } else if (pn_interaction_material.find("Epoxy") != std::string::npos) {
290 // CarbonEpoxyComposite
291 histograms_.fill("pn_interaction_material", 6);
292 } else if (pn_interaction_material.find("Scintillator") !=
293 std::string::npos) {
294 // This is the notion for PVT
295 histograms_.fill("pn_interaction_material", 7);
296 } else if (pn_interaction_material.find("Glue") != std::string::npos) {
297 // This is the notion for PVT
298 histograms_.fill("pn_interaction_material", 8);
299 } else if (pn_interaction_material.find("G4_AIR") != std::string::npos) {
300 // Air
301 histograms_.fill("pn_interaction_material", 9);
302 } else {
303 ldmx_log(debug) << " Else pn_interaction_material = "
304 << pn_interaction_material
305 << " with pn_vertex_volume = " << pn_vertex_volume;
306 histograms_.fill("pn_interaction_material", 1);
307 }
308 } else {
309 histograms_.fill("pn_vertex_volume", 0);
310 histograms_.fill("pn_interaction_material", 0);
311 }
312
313 findParticleKinematics(pnDaughters);
314
315 histograms_.fill("pn_particle_mult", pnGamma->getDaughters().size());
316 histograms_.fill("pn_gamma_energy", pnGamma->getEnergy());
317 histograms_.fill("pn_gamma_int_x", pnGamma->getEndPoint()[0]);
318 histograms_.fill("pn_gamma_int_y", pnGamma->getEndPoint()[1]);
319 histograms_.fill("pn_gamma_int_x:pn_gamma_int_y", pnGamma->getEndPoint()[0],
320 pnGamma->getEndPoint()[1]);
321 histograms_.fill("pn_gamma_int_z", pnGamma->getEndPoint()[2]);
322 histograms_.fill("pn_gamma_vertex_x", pnGamma->getVertex()[0]);
323 histograms_.fill("pn_gamma_vertex_y", pnGamma->getVertex()[1]);
324 histograms_.fill("pn_gamma_vertex_z", pnGamma->getVertex()[2]);
325
326 // Classify the event
327 auto eventType{classifyEvent(pnDaughters, 200)};
328 auto eventType500MeV{classifyEvent(pnDaughters, 500)};
329 auto eventType2000MeV{classifyEvent(pnDaughters, 2000)};
330
331 auto eventTypeComp{classifyCompactEvent(pnGamma, pnDaughters, 200)};
332 auto eventTypeComp500MeV{classifyCompactEvent(pnGamma, pnDaughters, 500)};
333 auto eventTypeComp2000MeV{classifyCompactEvent(pnGamma, pnDaughters, 2000)};
334
335 histograms_.fill("event_type", static_cast<int>(eventType));
336 histograms_.fill("event_type_500mev", static_cast<int>(eventType500MeV));
337 histograms_.fill("event_type_2000mev", static_cast<int>(eventType2000MeV));
338
339 histograms_.fill("event_type_compact", static_cast<int>(eventTypeComp));
340 histograms_.fill("event_type_compact_500mev",
341 static_cast<int>(eventTypeComp500MeV));
342 histograms_.fill("event_type_compact_2000mev",
343 static_cast<int>(eventTypeComp2000MeV));
344
345 switch (eventType) {
346 case EventType::single_neutron:
347 if (pnDaughters.size() > 1) {
348 auto secondHardestPdgID{abs(pnDaughters[1]->getPdgID())};
349 auto nEventType{-10};
350 if (secondHardestPdgID == 2112) {
351 nEventType = 0; // n + n
352 } else if (secondHardestPdgID == 2212) {
353 nEventType = 1; // p + n
354 } else if (secondHardestPdgID == 211) {
355 nEventType = 2; // Pi+/- + n
356 } else if (secondHardestPdgID == 111) {
357 nEventType = 3; // Pi0 + n
358 } else {
359 nEventType = 4; // other
360 }
361 histograms_.fill("1n_event_type", nEventType);
362 }
363 [[fallthrough]]; // Remaining code is important for 1n as well
364 case EventType::two_neutrons:
365 case EventType::charged_kaon:
366 case EventType::klong:
367 case EventType::kshort:
368 findSubleadingKinematics(pnGamma, pnDaughters, eventType);
369 break;
370 default: // Nothing to do
371 break;
372 }
373}
374
375PhotoNuclearDQM::EventType PhotoNuclearDQM::classifyEvent(
376 const std::vector<const ldmx::SimParticle *> daughters, double threshold) {
377 short n{0}, p{0}, pi{0}, pi0{0}, exotic{0}, k0l{0}, kp{0}, k0s{0};
378
379 // Loop through all of the PN daughters and extract kinematic
380 // information.
381 for (const auto &daughter : daughters) {
382 // Calculate the kinetic energy
383 auto ke{daughter->getEnergy() - daughter->getMass()};
384
385 // Assuming the daughters are sorted by kinetic energy, if the kinetic
386 // energy is below threshold, we don't need to look at any further
387 // particles.
388 if (ke <= threshold) {
389 break;
390 }
391
392 // Get the PDG ID
393 auto pdgID{abs(daughter->getPdgID())};
394
395 if (pdgID == 2112) {
396 n++;
397 } else if (pdgID == 2212) {
398 p++;
399 } else if (pdgID == 211) {
400 pi++;
401 } else if (pdgID == 111) {
402 pi0++;
403 } else if (pdgID == 130) {
404 k0l++;
405 } else if (pdgID == 321) {
406 kp++;
407 } else if (pdgID == 310) {
408 k0s++;
409 } else {
410 exotic++;
411 }
412 }
413
414 int kaons = k0l + kp + k0s;
415 int nucleons = n + p;
416 int pions = pi + pi0;
417 int count = nucleons + pions + exotic + kaons;
418
419 if (count == 0) {
420 return EventType::nothing_hard;
421 }
422 if (count == 1) {
423 if (n == 1) {
424 return EventType::single_neutron;
425 } else if (p == 1) {
426 return EventType::single_proton;
427 } else if (pi0 == 1) {
428 return EventType::single_neutral_pion;
429 } else if (pi == 1) {
430 return EventType::single_charged_pion;
431 }
432 }
433 if (count == 2) {
434 if (n == 2) {
435 return EventType::two_neutrons;
436 } else if (n == 1 && p == 1) {
437 return EventType::proton_neutron;
438 } else if (p == 2) {
439 return EventType::two_protons;
440 } else if (pi == 2) {
441 return EventType::two_charged_pions;
442 } else if (pi == 1 && nucleons == 1) {
443 return EventType::single_charged_pion_and_nucleon;
444 } else if (pi0 == 1 && nucleons == 1) {
445 return EventType::single_neutral_pion_and_nucleon;
446 }
447 }
448
449 if (count == 3) {
450 if (pi == 1 && nucleons == 2) {
451 return EventType::single_charged_pion_and_two_nucleons;
452 } else if (pi == 2 && nucleons == 1) {
453 return EventType::two_charged_pions_and_nucleon;
454 } // else
455 else if (pi0 == 1 && nucleons == 2) {
456 return EventType::single_neutral_pion_and_two_nucleons;
457 } else if (pi0 == 1 && nucleons == 1 && pi == 1) {
458 return EventType::single_neutral_pion_charged_pion_and_nucleon;
459 }
460 }
461 if (count >= 3 && count == n) {
462 return EventType::three_or_more_neutrons;
463 }
464
465 if (kaons == 1) {
466 if (k0l == 1) {
467 return EventType::klong;
468 } else if (kp == 1) {
469 return EventType::charged_kaon;
470 } else if (k0s == 1) {
471 return EventType::kshort;
472 }
473 }
474 if (exotic == count && count != 0) {
475 return EventType::exotics;
476 }
477
478 return EventType::multibody;
479}
480
481PhotoNuclearDQM::CompactEventType PhotoNuclearDQM::classifyCompactEvent(
482 const ldmx::SimParticle *pnGamma,
483 const std::vector<const ldmx::SimParticle *> daughters, double threshold) {
484 short n{0}, n_t{0}, k0l{0}, kp{0}, k0s{0}, soft{0};
485
486 // Loop through all of the PN daughters and extract kinematic
487 // information.
488 for (const auto &daughter : daughters) {
489 // Calculate the kinetic energy
490 auto ke{daughter->getEnergy() - daughter->getMass()};
491
492 // Get the PDG ID
493 auto pdgID{abs(daughter->getPdgID())};
494
495 if (ke < 500) {
496 soft++;
497 continue;
498 }
499
500 if (ke >= 0.8 * pnGamma->getEnergy()) {
501 if (pdgID == 2112) {
502 n++;
503 } else if (pdgID == 130) {
504 k0l++;
505 } else if (pdgID == 321) {
506 kp++;
507 } else if (pdgID == 310) {
508 k0s++;
509 }
510 continue;
511 }
512
513 if ((pdgID == 2112) && ke > threshold) {
514 n_t++;
515 }
516 }
517
518 int neutral_kaons{k0l + k0s};
519
520 if (n != 0) {
521 return PhotoNuclearDQM::CompactEventType::single_neutron;
522 }
523 if (kp != 0) {
524 return PhotoNuclearDQM::CompactEventType::single_charged_kaon;
525 }
526 if (neutral_kaons != 0) {
527 return PhotoNuclearDQM::CompactEventType::single_neutral_kaon;
528 }
529 if (n_t == 2) {
530 return PhotoNuclearDQM::CompactEventType::two_neutrons;
531 }
532 if (soft == daughters.size()) {
533 return PhotoNuclearDQM::CompactEventType::soft;
534 }
535
536 return PhotoNuclearDQM::CompactEventType::other;
537}
538
539} // namespace dqm
540
541DECLARE_ANALYZER_NS(dqm, PhotoNuclearDQM)
#define DECLARE_ANALYZER_NS(NS, CLASS)
Macro which allows the framework to construct an analyzer given its name during configuration.
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 setHistLabels(const std::string &name, const std::vector< std::string > &labels)
Helper function to label categorical histos.
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.
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:42
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:29
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