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