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