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 *> pn_daughters;
14 for (const auto &daughter_track_id : parent->getDaughters()) {
15 // skip daughters that weren't saved
16 if (particleMap.count(daughter_track_id) == 0) {
17 continue;
18 }
19
20 auto daughter{&(particleMap.at(daughter_track_id))};
21
22 // Get the PDG ID
23 auto pdg_id{daughter->getPdgID()};
24
25 // Ignore photons and nuclei
26 if (pdg_id == 22 ||
27 (pdg_id > 10000 && (!count_light_ions_ || !isLightIon(pdg_id)))) {
28 continue;
29 }
30 pn_daughters.push_back(daughter);
31 }
32
33 std::sort(pn_daughters.begin(), pn_daughters.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 pn_daughters;
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 pdg_id{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 ROOT::Math::XYZVector 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 (pdg_id == 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 ((pdg_id == 2212) && (hardest_proton_ke < ke)) {
91 hardest_proton_ke = ke;
92 hardest_proton_theta = theta;
93 }
94
95 if (((std::abs(pdg_id) == 211) || (pdg_id == 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 n_energy{-9999}, energy_diff{-9999}, energy_frac{-9999};
124
125 n_energy = 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 energy_diff = pnGamma->getEnergy() - n_energy;
131 energy_frac = n_energy / pnGamma->getEnergy();
132
133 if (eventType == EventType::single_neutron) {
134 histograms_.fill("1n_ke:2nd_h_ke", n_energy, subleading_ke);
135 histograms_.fill("1n_neutron_energy", n_energy);
136 histograms_.fill("1n_energy_diff", energy_diff);
137 histograms_.fill("1n_energy_frac", energy_frac);
138 } else if (eventType == EventType::two_neutrons) {
139 histograms_.fill("2n_n2_energy", subleading_ke);
140 auto energy_frac2n = (n_energy + subleading_ke) / pnGamma->getEnergy();
141 histograms_.fill("2n_energy_frac", energy_frac2n);
142 histograms_.fill("2n_energy_other", pnGamma->getEnergy() - energy_frac2n);
143
144 } else if (eventType == EventType::charged_kaon) {
145 histograms_.fill("1kp_ke:2nd_h_ke", n_energy, subleading_ke);
146 histograms_.fill("1kp_energy", n_energy);
147 histograms_.fill("1kp_energy_diff", energy_diff);
148 histograms_.fill("1kp_energy_frac", energy_frac);
149 } else if (eventType == EventType::klong || eventType == EventType::kshort) {
150 histograms_.fill("1k0_ke:2nd_h_ke", n_energy, subleading_ke);
151 histograms_.fill("1k0_energy", n_energy);
152 histograms_.fill("1k0_energy_diff", energy_diff);
153 histograms_.fill("1k0_energy_frac", energy_frac);
154 }
155}
156
158 count_light_ions_ = parameters.get<bool>("count_light_ions", true);
159 sim_particles_coll_name_ =
160 parameters.get<std::string>("sim_particles_coll_name");
161 sim_particles_passname_ =
162 parameters.get<std::string>("sim_particles_passname");
163}
164
166 // Get the particle map from the event. If the particle map is empty,
167 // don't process the event.
168 auto particle_map{event.getMap<int, ldmx::SimParticle>(
169 sim_particles_coll_name_, sim_particles_passname_)};
170 if (particle_map.size() == 0) {
171 return;
172 }
173
174 // Get the recoil electron
175 auto [trackID, recoil] = analysis::getRecoil(particle_map);
176 findRecoilProperties(recoil);
177
178 // Use the recoil electron to retrieve the gamma that underwent a
179 // photo-nuclear reaction.
180 auto pn_gamma{analysis::getPNGamma(particle_map, recoil, 2500.)};
181 if (pn_gamma == nullptr) {
182 ldmx_log(warn) << "PN Daughter is lost, skipping";
183 return;
184 }
185
186 const auto pn_daughters{findDaughters(particle_map, pn_gamma)};
187
188 if (!pn_daughters.empty()) {
189 auto pn_vertex_volume{pn_daughters[0]->getVertexVolume()};
190 auto pn_interaction_material{pn_daughters[0]->getInteractionMaterial()};
191
192 // Let's start with the PN vertex volume
193 if (pn_vertex_volume.find("W_cooling") != std::string::npos) {
194 // W_cooling_volume_X
195 histograms_.fill("pn_vertex_volume", 2);
196 } else if (pn_vertex_volume.find("C_volume") != std::string::npos) {
197 // C_volume_X
198 histograms_.fill("pn_vertex_volume", 3);
199 } else if (pn_vertex_volume.find("PCB_volume") != std::string::npos) {
200 histograms_.fill("pn_vertex_volume", 4);
201 } else if (pn_vertex_volume.find("CarbonBasePlate") != std::string::npos) {
202 // CarbonBasePlate_volume
203 histograms_.fill("pn_vertex_volume", 5);
204 } else if (pn_vertex_volume.find("W_front") != std::string::npos) {
205 // W_front_volume_X
206 histograms_.fill("pn_vertex_volume", 6);
207 } else if (pn_vertex_volume.find("Si_volume") != std::string::npos) {
208 histograms_.fill("pn_vertex_volume", 7);
209 } else if (pn_vertex_volume.find("Glue") != std::string::npos) {
210 histograms_.fill("pn_vertex_volume", 8);
211 } else if (pn_vertex_volume.find("motherboard") != std::string::npos) {
212 histograms_.fill("pn_vertex_volume", 9);
213
214 } else {
215 ldmx_log(debug) << " Else pn_vertex_volume = " << pn_vertex_volume
216 << " with pn_interaction_material = "
217 << pn_interaction_material;
218 histograms_.fill("pn_vertex_volume", 1);
219 }
220
221 // Now the interaction material
222 if (pn_interaction_material.find("Silicon") != std::string::npos ||
223 pn_interaction_material.find("G4_Si") != std::string::npos) {
224 histograms_.fill("pn_interaction_material", 2);
225 } else if (pn_interaction_material.find("Tungsten") != std::string::npos ||
226 pn_interaction_material.find("G4_W") != std::string::npos) {
227 histograms_.fill("pn_interaction_material", 3);
228 } else if (pn_interaction_material.find("FR4") != std::string::npos) {
229 // Glass epoxy
230 histograms_.fill("pn_interaction_material", 4);
231 } else if (pn_interaction_material.find("Steel") != std::string::npos) {
232 histograms_.fill("pn_interaction_material", 5);
233 } else if (pn_interaction_material.find("Epoxy") != std::string::npos) {
234 // CarbonEpoxyComposite
235 histograms_.fill("pn_interaction_material", 6);
236 } else if (pn_interaction_material.find("Scintillator") !=
237 std::string::npos) {
238 // This is the notion for PVT
239 histograms_.fill("pn_interaction_material", 7);
240 } else if (pn_interaction_material.find("Glue") != std::string::npos) {
241 // This is the notion for PVT
242 histograms_.fill("pn_interaction_material", 8);
243 } else if (pn_interaction_material.find("Air") != std::string::npos ||
244 pn_interaction_material.find("G4_AIR") != std::string::npos) {
245 // Air
246 histograms_.fill("pn_interaction_material", 9);
247 } else {
248 ldmx_log(debug) << " Else pn_interaction_material = "
249 << pn_interaction_material
250 << " with pn_vertex_volume = " << pn_vertex_volume;
251 histograms_.fill("pn_interaction_material", 1);
252 }
253 } else {
254 histograms_.fill("pn_vertex_volume", 0);
255 histograms_.fill("pn_interaction_material", 0);
256 }
257
258 findParticleKinematics(pn_daughters);
259
260 histograms_.fill("pn_particle_mult", pn_gamma->getDaughters().size());
261 histograms_.fill("pn_gamma_energy", pn_gamma->getEnergy());
262 histograms_.fill("pn_gamma_int_x", pn_gamma->getEndPoint()[0]);
263 histograms_.fill("pn_gamma_int_y", pn_gamma->getEndPoint()[1]);
264 histograms_.fill("pn_gamma_int_x:pn_gamma_int_y", pn_gamma->getEndPoint()[0],
265 pn_gamma->getEndPoint()[1]);
266 histograms_.fill("pn_gamma_int_z", pn_gamma->getEndPoint()[2]);
267 histograms_.fill("pn_gamma_vertex_x", pn_gamma->getVertex()[0]);
268 histograms_.fill("pn_gamma_vertex_y", pn_gamma->getVertex()[1]);
269 histograms_.fill("pn_gamma_vertex_z", pn_gamma->getVertex()[2]);
270
271 // Classify the event
272 auto event_type{classifyEvent(pn_daughters, 200)};
273 auto event_type500_me_v{classifyEvent(pn_daughters, 500)};
274 auto event_type2000_me_v{classifyEvent(pn_daughters, 2000)};
275
276 auto event_type_comp{classifyCompactEvent(pn_gamma, pn_daughters, 200)};
277 auto event_type_comp500_me_v{
278 classifyCompactEvent(pn_gamma, pn_daughters, 500)};
279 auto event_type_comp2000_me_v{
280 classifyCompactEvent(pn_gamma, pn_daughters, 2000)};
281
282 histograms_.fill("event_type", static_cast<int>(event_type));
283 histograms_.fill("event_type_500mev", static_cast<int>(event_type500_me_v));
284 histograms_.fill("event_type_2000mev", static_cast<int>(event_type2000_me_v));
285
286 histograms_.fill("event_type_compact", static_cast<int>(event_type_comp));
287 histograms_.fill("event_type_compact_500mev",
288 static_cast<int>(event_type_comp500_me_v));
289 histograms_.fill("event_type_compact_2000mev",
290 static_cast<int>(event_type_comp2000_me_v));
291
292 switch (event_type) {
293 case EventType::single_neutron:
294 if (pn_daughters.size() > 1) {
295 auto second_hardest_pdg_id{abs(pn_daughters[1]->getPdgID())};
296 auto n_event_type{-10};
297 if (second_hardest_pdg_id == 2112) {
298 n_event_type = 0; // n + n
299 } else if (second_hardest_pdg_id == 2212) {
300 n_event_type = 1; // p + n
301 } else if (second_hardest_pdg_id == 211) {
302 n_event_type = 2; // Pi+/- + n
303 } else if (second_hardest_pdg_id == 111) {
304 n_event_type = 3; // Pi0 + n
305 } else {
306 n_event_type = 4; // other
307 }
308 histograms_.fill("1n_event_type", n_event_type);
309 }
310 [[fallthrough]]; // Remaining code is important for 1n as well
311 case EventType::two_neutrons:
312 case EventType::charged_kaon:
313 case EventType::klong:
314 case EventType::kshort:
315 findSubleadingKinematics(pn_gamma, pn_daughters, event_type);
316 break;
317 default: // Nothing to do
318 break;
319 }
320}
321
322PhotoNuclearDQM::EventType PhotoNuclearDQM::classifyEvent(
323 const std::vector<const ldmx::SimParticle *> daughters, double threshold) {
324 short n{0}, p{0}, pi{0}, pi0{0}, exotic{0}, k0l{0}, kp{0}, k0s{0};
325
326 // Loop through all of the PN daughters and extract kinematic
327 // information.
328 for (const auto &daughter : daughters) {
329 // Calculate the kinetic energy
330 auto ke{daughter->getEnergy() - daughter->getMass()};
331
332 // Assuming the daughters are sorted by kinetic energy, if the kinetic
333 // energy is below threshold, we don't need to look at any further
334 // particles.
335 if (ke <= threshold) {
336 break;
337 }
338
339 // Get the PDG ID
340 auto pdg_id{abs(daughter->getPdgID())};
341
342 if (pdg_id == 2112) {
343 n++;
344 } else if (pdg_id == 2212) {
345 p++;
346 } else if (pdg_id == 211) {
347 pi++;
348 } else if (pdg_id == 111) {
349 pi0++;
350 } else if (pdg_id == 130) {
351 k0l++;
352 } else if (pdg_id == 321) {
353 kp++;
354 } else if (pdg_id == 310) {
355 k0s++;
356 } else {
357 exotic++;
358 }
359 }
360
361 int kaons = k0l + kp + k0s;
362 int nucleons = n + p;
363 int pions = pi + pi0;
364 int count = nucleons + pions + exotic + kaons;
365
366 if (count == 0) {
367 return EventType::nothing_hard;
368 }
369 if (count == 1) {
370 if (n == 1) {
371 return EventType::single_neutron;
372 } else if (p == 1) {
373 return EventType::single_proton;
374 } else if (pi0 == 1) {
375 return EventType::single_neutral_pion;
376 } else if (pi == 1) {
377 return EventType::single_charged_pion;
378 }
379 }
380 if (count == 2) {
381 if (n == 2) {
382 return EventType::two_neutrons;
383 } else if (n == 1 && p == 1) {
384 return EventType::proton_neutron;
385 } else if (p == 2) {
386 return EventType::two_protons;
387 } else if (pi == 2) {
388 return EventType::two_charged_pions;
389 } else if (pi == 1 && nucleons == 1) {
390 return EventType::single_charged_pion_and_nucleon;
391 } else if (pi0 == 1 && nucleons == 1) {
392 return EventType::single_neutral_pion_and_nucleon;
393 }
394 }
395
396 if (count == 3) {
397 if (pi == 1 && nucleons == 2) {
398 return EventType::single_charged_pion_and_two_nucleons;
399 } else if (pi == 2 && nucleons == 1) {
400 return EventType::two_charged_pions_and_nucleon;
401 } // else
402 else if (pi0 == 1 && nucleons == 2) {
403 return EventType::single_neutral_pion_and_two_nucleons;
404 } else if (pi0 == 1 && nucleons == 1 && pi == 1) {
405 return EventType::single_neutral_pion_charged_pion_and_nucleon;
406 }
407 }
408 if (count >= 3 && count == n) {
409 return EventType::three_or_more_neutrons;
410 }
411
412 if (kaons == 1) {
413 if (k0l == 1) {
414 return EventType::klong;
415 } else if (kp == 1) {
416 return EventType::charged_kaon;
417 } else if (k0s == 1) {
418 return EventType::kshort;
419 }
420 }
421 if (exotic == count && count != 0) {
422 return EventType::exotics;
423 }
424
425 return EventType::multibody;
426}
427
428PhotoNuclearDQM::CompactEventType PhotoNuclearDQM::classifyCompactEvent(
429 const ldmx::SimParticle *pnGamma,
430 const std::vector<const ldmx::SimParticle *> daughters, double threshold) {
431 short n{0}, n_t{0}, k0l{0}, kp{0}, k0s{0}, soft{0};
432
433 // Loop through all of the PN daughters and extract kinematic
434 // information.
435 for (const auto &daughter : daughters) {
436 // Calculate the kinetic energy
437 auto ke{daughter->getEnergy() - daughter->getMass()};
438
439 // Get the PDG ID
440 auto pdg_id{abs(daughter->getPdgID())};
441
442 if (ke < 500) {
443 soft++;
444 continue;
445 }
446
447 if (ke >= 0.8 * pnGamma->getEnergy()) {
448 if (pdg_id == 2112) {
449 n++;
450 } else if (pdg_id == 130) {
451 k0l++;
452 } else if (pdg_id == 321) {
453 kp++;
454 } else if (pdg_id == 310) {
455 k0s++;
456 }
457 continue;
458 }
459
460 if ((pdg_id == 2112) && ke > threshold) {
461 n_t++;
462 }
463 }
464
465 int neutral_kaons{k0l + k0s};
466
467 if (n != 0) {
468 return PhotoNuclearDQM::CompactEventType::single_neutron;
469 }
470 if (kp != 0) {
471 return PhotoNuclearDQM::CompactEventType::single_charged_kaon;
472 }
473 if (neutral_kaons != 0) {
474 return PhotoNuclearDQM::CompactEventType::single_neutral_kaon;
475 }
476 if (n_t == 2) {
477 return PhotoNuclearDQM::CompactEventType::two_neutrons;
478 }
479 if (soft == daughters.size()) {
480 return PhotoNuclearDQM::CompactEventType::soft;
481 }
482
483 return PhotoNuclearDQM::CompactEventType::other;
484}
485
486} // namespace dqm
487
#define DECLARE_ANALYZER(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.
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.
HistogramPool histograms_
helper object for making and filling histograms
Implements an event buffer system for storing event data.
Definition Event.h:42
void fill(const std::string &name, const T &val)
Fill a 1D histogram.
Class which represents the process under execution.
Definition Process.h:37
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
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.