LDMX Software
NuclearDQM.cxx
1
2#include "DQM/NuclearDQM.h"
3
4#include <Math/Vector3D.h> // IWYU pragma: keep
5
6#include <algorithm>
7
8namespace dqm {
9
10NuclearDQM::NuclearDQM(const std::string& name, framework::Process& process)
11 : framework::Analyzer(name, process) {}
12
13void NuclearDQM::configure(framework::config::Parameters& parameters) {
14 count_light_ions_ = parameters.get<bool>("count_light_ions", true);
15 sim_particles_coll_name_ =
16 parameters.get<std::string>("sim_particles_coll_name", "SimParticles");
17 sim_particles_passname_ =
18 parameters.get<std::string>("sim_particles_passname", "");
19}
20
21std::vector<const ldmx::SimParticle*> NuclearDQM::findDaughters(
22 const std::map<int, ldmx::SimParticle>& particleMap,
23 const ldmx::SimParticle* parent, int require_process_type) const {
24 std::vector<const ldmx::SimParticle*> daughters;
25
26 for (const auto& daughter_track_id : parent->getDaughters()) {
27 if (particleMap.count(daughter_track_id) == 0) continue;
28
29 auto daughter{&(particleMap.at(daughter_track_id))};
30
31 if (require_process_type >= 0 &&
32 daughter->getProcessType() != require_process_type)
33 continue;
34
35 auto pdg_id{daughter->getPdgID()};
36 if (pdg_id == 22 ||
37 (pdg_id > 10000 && (!count_light_ions_ || !isLightIon(pdg_id))))
38 continue;
39
40 daughters.push_back(daughter);
41 }
42
43 std::sort(daughters.begin(), daughters.end(),
44 [](const auto& lhs, const auto& rhs) {
45 return (lhs->getEnergy() - lhs->getMass()) >
46 (rhs->getEnergy() - rhs->getMass());
47 });
48
49 return daughters;
50}
51
52void NuclearDQM::findParticleKinematics(
53 const std::vector<const ldmx::SimParticle*>& daughters,
54 const std::string& prefix) {
55 double hardest_ke{-1}, hardest_theta{-1};
56 double hardest_proton_ke{-1}, hardest_proton_theta{-1};
57 double hardest_neutron_ke{-1}, hardest_neutron_theta{-1};
58 double hardest_pion_ke{-1}, hardest_pion_theta{-1};
59 double total_ke{0};
60 double total_neutron_ke{0};
61 int neutron_multiplicity{0};
62
63 for (const auto* daughter : daughters) {
64 auto pdg_id{daughter->getPdgID()};
65 double ke{daughter->getEnergy() - daughter->getMass()};
66 total_ke += ke;
67
68 std::vector<double> vec{daughter->getMomentum()};
69 ROOT::Math::XYZVector pvec(vec[0], vec[1], vec[2]);
70 auto theta{pvec.Theta() * (180 / 3.14159)};
71
72 if (hardest_ke < ke) {
73 hardest_ke = ke;
74 hardest_theta = theta;
75 }
76
77 if (pdg_id == 2112) {
78 total_neutron_ke += ke;
79 neutron_multiplicity++;
80 if (hardest_neutron_ke < ke) {
81 hardest_neutron_ke = ke;
82 hardest_neutron_theta = theta;
83 }
84 }
85
86 if (pdg_id == 2212 && hardest_proton_ke < ke) {
87 hardest_proton_ke = ke;
88 hardest_proton_theta = theta;
89 }
90
91 // charged and neutral pions grouped together, matching original PN logic
92 if ((std::abs(pdg_id) == 211 || pdg_id == 111) && hardest_pion_ke < ke) {
93 hardest_pion_ke = ke;
94 hardest_pion_theta = theta;
95 }
96 }
97
98 histograms_.fill("hardest_ke", hardest_ke);
99 histograms_.fill("hardest_theta", hardest_theta);
100 histograms_.fill("h_ke_h_theta", hardest_ke, hardest_theta);
101 histograms_.fill("hardest_p_ke", hardest_proton_ke);
102 histograms_.fill("hardest_p_theta", hardest_proton_theta);
103 histograms_.fill("hardest_n_ke", hardest_neutron_ke);
104 histograms_.fill("hardest_n_theta", hardest_neutron_theta);
105 histograms_.fill("hardest_pi_ke", hardest_pion_ke);
106 histograms_.fill("hardest_pi_theta", hardest_pion_theta);
107
108 histograms_.fill(prefix + "_neutron_mult", neutron_multiplicity);
109 histograms_.fill(prefix + "_total_ke", total_ke);
110 histograms_.fill(prefix + "_total_neutron_ke", total_neutron_ke);
111}
112
113void NuclearDQM::findExtendedKinematics(
114 const std::vector<const ldmx::SimParticle*>& daughters,
115 const std::string& prefix) {
116 // EN-specific kinematics: pi± and pi0 tracked separately, plus proton
117 // multiplicity, leading-particle-type summary, and all the generic
118 // hardest-particle quantities that findParticleKinematics would provide
119 // for PN (so EN does not need to call findParticleKinematics at all).
120 double hardest_ke{-1}, hardest_theta{-1};
121 double hardest_n_ke{-1}, hardest_n_theta{-1};
122 double hardest_p_ke{-1}, hardest_p_theta{-1};
123 double hardest_pi0_ke{-1}, hardest_pi0_theta{-1};
124 double total_ke{0}, total_neutron_ke{0};
125 int neutron_multiplicity{0};
126 int proton_multiplicity{0};
127 int charged_pion_multiplicity{0};
128 int neutral_pion_multiplicity{0};
129
130 for (const auto* daughter : daughters) {
131 auto pdg_id{daughter->getPdgID()};
132 double ke{daughter->getEnergy() - daughter->getMass()};
133 total_ke += ke;
134
135 std::vector<double> vec{daughter->getMomentum()};
136 ROOT::Math::XYZVector pvec(vec[0], vec[1], vec[2]);
137 auto theta{pvec.Theta() * (180 / 3.14159)};
138
139 if (hardest_ke < ke) {
140 hardest_ke = ke;
141 hardest_theta = theta;
142 }
143
144 if (pdg_id == 2112) {
145 neutron_multiplicity++;
146 total_neutron_ke += ke;
147 if (hardest_n_ke < ke) {
148 hardest_n_ke = ke;
149 hardest_n_theta = theta;
150 }
151 } else if (pdg_id == 2212) {
152 proton_multiplicity++;
153 if (hardest_p_ke < ke) {
154 hardest_p_ke = ke;
155 hardest_p_theta = theta;
156 }
157 } else if (std::abs(pdg_id) == 211) {
158 charged_pion_multiplicity++;
159 } else if (pdg_id == 111) {
160 neutral_pion_multiplicity++;
161 if (hardest_pi0_ke < ke) {
162 hardest_pi0_ke = ke;
163 hardest_pi0_theta = theta;
164 }
165 }
166 }
167
168 // Leading-particle-type histogram:
169 // bins: 0=pi±+X, 1=pi0+X, 2=K±+X, 3=KS/KL+X, 4=p+X, 5=n+X, 6=other+X
170 if (!daughters.empty()) {
171 auto leading_pdg{std::abs(daughters[0]->getPdgID())};
172 int leading_type{6};
173 if (leading_pdg == 211)
174 leading_type = 0;
175 else if (leading_pdg == 111)
176 leading_type = 1;
177 else if (leading_pdg == 321)
178 leading_type = 2;
179 else if (leading_pdg == 130 || leading_pdg == 310)
180 leading_type = 3;
181 else if (leading_pdg == 2212)
182 leading_type = 4;
183 else if (leading_pdg == 2112)
184 leading_type = 5;
185 histograms_.fill("leading_particle_type", leading_type);
186 }
187
188 histograms_.fill("hardest_ke", hardest_ke);
189 histograms_.fill("hardest_theta", hardest_theta);
190 histograms_.fill("h_ke_h_theta", hardest_ke, hardest_theta);
191 histograms_.fill("hardest_n_ke", hardest_n_ke);
192 histograms_.fill("hardest_n_theta", hardest_n_theta);
193 histograms_.fill("hardest_p_ke", hardest_p_ke);
194 histograms_.fill("hardest_p_theta", hardest_p_theta);
195 histograms_.fill("hardest_pi0_ke", hardest_pi0_ke);
196 histograms_.fill("hardest_pi0_theta", hardest_pi0_theta);
197 histograms_.fill(prefix + "_neutron_mult", neutron_multiplicity);
198 histograms_.fill(prefix + "_proton_mult", proton_multiplicity);
199 histograms_.fill(prefix + "_charged_pion_mult", charged_pion_multiplicity);
200 histograms_.fill(prefix + "_neutral_pion_mult", neutral_pion_multiplicity);
201 histograms_.fill(prefix + "_total_ke", total_ke);
202 histograms_.fill(prefix + "_total_neutron_ke", total_neutron_ke);
203}
204
205void NuclearDQM::findSubleadingKinematics(
206 const ldmx::SimParticle* initiator,
207 const std::vector<const ldmx::SimParticle*>& daughters,
208 EventType eventType) {
209 // Note: assumes daughters is sorted by kinetic energy descending
210
211 double subleading_ke{-9999};
212 double n_energy{-9999}, energy_diff{-9999}, energy_frac{-9999};
213
214 n_energy = daughters[0]->getEnergy() - daughters[0]->getMass();
215 if (daughters.size() > 1) {
216 subleading_ke = daughters[1]->getEnergy() - daughters[1]->getMass();
217 }
218 energy_diff = initiator->getEnergy() - n_energy;
219 energy_frac = n_energy / initiator->getEnergy();
220
221 if (eventType == EventType::single_neutron) {
222 histograms_.fill("1n_ke:2nd_h_ke", n_energy, subleading_ke);
223 histograms_.fill("1n_neutron_energy", n_energy);
224 histograms_.fill("1n_energy_diff", energy_diff);
225 histograms_.fill("1n_energy_frac", energy_frac);
226 } else if (eventType == EventType::two_neutrons) {
227 histograms_.fill("2n_n2_energy", subleading_ke);
228 auto energy_frac2n = (n_energy + subleading_ke) / initiator->getEnergy();
229 histograms_.fill("2n_energy_frac", energy_frac2n);
230 histograms_.fill("2n_energy_other", initiator->getEnergy() - energy_frac2n);
231 } else if (eventType == EventType::charged_kaon) {
232 histograms_.fill("1kp_ke:2nd_h_ke", n_energy, subleading_ke);
233 histograms_.fill("1kp_energy", n_energy);
234 histograms_.fill("1kp_energy_diff", energy_diff);
235 histograms_.fill("1kp_energy_frac", energy_frac);
236 } else if (eventType == EventType::klong || eventType == EventType::kshort) {
237 histograms_.fill("1k0_ke:2nd_h_ke", n_energy, subleading_ke);
238 histograms_.fill("1k0_energy", n_energy);
239 histograms_.fill("1k0_energy_diff", energy_diff);
240 histograms_.fill("1k0_energy_frac", energy_frac);
241 }
242}
243
244NuclearDQM::EventType NuclearDQM::classifyEvent(
245 const std::vector<const ldmx::SimParticle*>& daughters, double threshold) {
246 short n{0}, p{0}, pi{0}, pi0{0}, exotic{0}, k0l{0}, kp{0}, k0s{0};
247
248 for (const auto& daughter : daughters) {
249 auto ke{daughter->getEnergy() - daughter->getMass()};
250 // daughters are sorted by KE descending; stop when below threshold
251 if (ke <= threshold) break;
252
253 auto pdg_id{std::abs(daughter->getPdgID())};
254 if (pdg_id == 2112)
255 n++;
256 else if (pdg_id == 2212)
257 p++;
258 else if (pdg_id == 211)
259 pi++;
260 else if (pdg_id == 111)
261 pi0++;
262 else if (pdg_id == 130)
263 k0l++;
264 else if (pdg_id == 321)
265 kp++;
266 else if (pdg_id == 310)
267 k0s++;
268 else
269 exotic++;
270 }
271
272 int kaons{k0l + kp + k0s};
273 int nucleons{n + p};
274 int pions{pi + pi0};
275 int count{nucleons + pions + exotic + kaons};
276
277 if (count == 0) return EventType::nothing_hard;
278
279 if (count == 1) {
280 if (n == 1) return EventType::single_neutron;
281 if (p == 1) return EventType::single_proton;
282 if (pi0 == 1) return EventType::single_neutral_pion;
283 if (pi == 1) return EventType::single_charged_pion;
284 }
285 if (count == 2) {
286 if (n == 2) return EventType::two_neutrons;
287 if (n == 1 && p == 1) return EventType::proton_neutron;
288 if (p == 2) return EventType::two_protons;
289 if (pi == 2) return EventType::two_charged_pions;
290 if (pi == 1 && nucleons == 1)
291 return EventType::single_charged_pion_and_nucleon;
292 if (pi0 == 1 && nucleons == 1)
293 return EventType::single_neutral_pion_and_nucleon;
294 }
295 if (count == 3) {
296 if (pi == 1 && nucleons == 2)
297 return EventType::single_charged_pion_and_two_nucleons;
298 if (pi == 2 && nucleons == 1)
299 return EventType::two_charged_pions_and_nucleon;
300 if (pi0 == 1 && nucleons == 2)
301 return EventType::single_neutral_pion_and_two_nucleons;
302 if (pi0 == 1 && nucleons == 1 && pi == 1)
303 return EventType::single_neutral_pion_charged_pion_and_nucleon;
304 }
305 if (count >= 3 && count == n) return EventType::three_or_more_neutrons;
306
307 if (kaons == 1) {
308 if (k0l == 1) return EventType::klong;
309 if (kp == 1) return EventType::charged_kaon;
310 if (k0s == 1) return EventType::kshort;
311 }
312 if (exotic == count && count != 0) return EventType::exotics;
313
314 return EventType::multibody;
315}
316
317NuclearDQM::CompactEventType NuclearDQM::classifyCompactEvent(
318 const ldmx::SimParticle* initiator,
319 const std::vector<const ldmx::SimParticle*>& daughters, double threshold) {
320 short n{0}, n_t{0}, k0l{0}, kp{0}, k0s{0}, soft{0};
321
322 for (const auto& daughter : daughters) {
323 auto ke{daughter->getEnergy() - daughter->getMass()};
324 auto pdg_id{std::abs(daughter->getPdgID())};
325
326 if (ke < 500) {
327 soft++;
328 continue;
329 }
330
331 if (ke >= 0.8 * initiator->getEnergy()) {
332 if (pdg_id == 2112)
333 n++;
334 else if (pdg_id == 130)
335 k0l++;
336 else if (pdg_id == 321)
337 kp++;
338 else if (pdg_id == 310)
339 k0s++;
340 continue;
341 }
342
343 if (pdg_id == 2112 && ke > threshold) n_t++;
344 }
345
346 int neutral_kaons{k0l + k0s};
347 if (n != 0) return CompactEventType::single_neutron;
348 if (kp != 0) return CompactEventType::single_charged_kaon;
349 if (neutral_kaons != 0) return CompactEventType::single_neutral_kaon;
350 if (n_t == 2) return CompactEventType::two_neutrons;
351 if (soft == static_cast<short>(daughters.size()))
352 return CompactEventType::soft;
353 return CompactEventType::other;
354}
355
356} // namespace dqm
EventType
Classification of PN/EN events by the hard particles produced above a kinetic-energy threshold.
Definition NuclearDQM.h:27
CompactEventType
Compact classification focusing on very-high-energy single particles.
Definition NuclearDQM.h:54
Class which represents the process under execution.
Definition Process.h:34
Class encapsulating parameters for configuring a processor.
Definition Parameters.h:26
const T & get(const std::string &name) const
Retrieve the parameter of the given name.
Definition Parameters.h:75
Class representing a simulated particle.
Definition SimParticle.h:25
double getEnergy() const
Get the energy of this particle [MeV].
Definition SimParticle.h:74
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.