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 : NuclearDQM(name, process) {}
9
10void PhotoNuclearDQM::configure(framework::config::Parameters& parameters) {
11 NuclearDQM::configure(parameters);
12 pn_collection_name_ = parameters.get<std::string>("pn_collection_name",
13 "PhotonuclearInteractions");
14 pn_pass_name_ = parameters.get<std::string>("pn_pass_name", "");
15}
16
17void PhotoNuclearDQM::findRecoilProperties(const ldmx::SimParticle* recoil) {
18 histograms_.fill("recoil_vertex_x", recoil->getVertex()[0]);
19 histograms_.fill("recoil_vertex_y", recoil->getVertex()[1]);
20 histograms_.fill("recoil_vertex_z", recoil->getVertex()[2]);
21 histograms_.fill("recoil_vertex_x:recoil_vertex_y", recoil->getVertex()[0],
22 recoil->getVertex()[1]);
23}
24
25void PhotoNuclearDQM::analyzeInteractionDetails(const framework::Event& event) {
26 // Check if PhotonuclearInteraction collection exists
27 if (!event.exists(pn_collection_name_, pn_pass_name_)) {
28 // Collection not present - PN tracing was not enabled
29 return;
30 }
31
32 // Get the PhotonuclearInteraction collection
33 const auto& pn_interactions =
34 event.getCollection<ldmx::PhotonuclearInteraction>(pn_collection_name_,
35 pn_pass_name_);
36
37 // Analyze full cascade genealogy information not available from SimParticles:
38 // - Target nucleus Z/A
39 // - Immediate cascade multiplicity
40 // - Full descendant genealogy tree (ALL particles, not just final state)
41
42 int n_interactions = pn_interactions.size();
43 histograms_.fill("pn_interaction_count", n_interactions);
44
45 for (const auto& interaction : pn_interactions) {
46 // Target nucleus information - UNIQUE to PhotonuclearInteraction
47 histograms_.fill("pn_target_z", interaction.getTargetZ());
48 histograms_.fill("pn_target_a", interaction.getTargetA());
49 histograms_.fill("pn_target_z:target_a", interaction.getTargetZ(),
50 interaction.getTargetA());
51
52 // Cascade multiplicity: how many particles created in initial cascade
53 int n_cascade_secondaries = interaction.getNumImmediateSecondaries();
54 histograms_.fill("pn_cascade_multiplicity", n_cascade_secondaries);
55
56 // Full cascade genealogy tree - includes ALL descendants (intermediate +
57 // final) This shows the complete cascade evolution, not just final state
58 // particles
59 auto descendant_map = interaction.getDescendantMap();
60 int total_descendants = 0;
61 for (const auto& [secondary_id, descendants] : descendant_map) {
62 int n_desc = descendants.size();
63 total_descendants += n_desc;
64 // How many particles (intermediate + final) descended from each cascade
65 // particle
66 histograms_.fill("pn_descendants_per_cascade_particle", n_desc);
67 }
68 histograms_.fill("pn_total_final_state_descendants", total_descendants);
69
70 // Cascade compactness: ratio shows what fraction of tree are immediate
71 // secondaries ~1.0 → Compact cascade, few branches (most particles are
72 // immediate secondaries) ~0.5 → Moderate branching (half the particles are
73 // from further generations) ~0.1 → Highly branched cascade with extensive
74 // decay/rescatter chains ~0.0 → Extreme branching (the "tungsten bomb"
75 // scenarios)
76 if (total_descendants > 0) {
77 double compactness_ratio =
78 static_cast<double>(n_cascade_secondaries) / total_descendants;
79 histograms_.fill("pn_cascade_evolution_ratio", compactness_ratio);
80 }
81 }
82}
83
84void PhotoNuclearDQM::analyze(const framework::Event& event) {
85 // Get the particle map from the event. If the particle map is empty,
86 // don't process the event.
87 auto particle_map{event.getMap<int, ldmx::SimParticle>(
88 sim_particles_coll_name_, sim_particles_passname_)};
89 if (particle_map.empty()) return;
90
91 // Get the recoil electron
92 auto [trackID, recoil] = analysis::getRecoil(particle_map);
93 findRecoilProperties(recoil);
94
95 // Use the recoil electron to retrieve the gamma that underwent a
96 // photo-nuclear reaction.
97 auto pn_gamma{analysis::getPNGamma(particle_map, recoil, 2500.)};
98 if (pn_gamma == nullptr) {
99 ldmx_log(warn) << "PN Daughter is lost, skipping";
100 return;
101 }
102
103 const auto pn_daughters{findDaughters(particle_map, pn_gamma)};
104
105 if (!pn_daughters.empty()) {
106 auto pn_vertex_volume{pn_daughters[0]->getVertexVolume()};
107 auto pn_interaction_material{pn_daughters[0]->getInteractionMaterial()};
108
109 // Let's start with the PN vertex volume
110 if (pn_vertex_volume.find("W_cooling") != std::string::npos) {
111 // W_cooling_volume_X
112 histograms_.fill("pn_vertex_volume", 2);
113 } else if (pn_vertex_volume.find("C_volume") != std::string::npos) {
114 // C_volume_X
115 histograms_.fill("pn_vertex_volume", 3);
116 } else if (pn_vertex_volume.find("PCB_volume") != std::string::npos) {
117 histograms_.fill("pn_vertex_volume", 4);
118 } else if (pn_vertex_volume.find("CarbonBasePlate") != std::string::npos) {
119 // CarbonBasePlate_volume
120 histograms_.fill("pn_vertex_volume", 5);
121 } else if (pn_vertex_volume.find("W_front") != std::string::npos) {
122 // W_front_volume_X
123 histograms_.fill("pn_vertex_volume", 6);
124 } else if (pn_vertex_volume.find("Si_volume") != std::string::npos) {
125 histograms_.fill("pn_vertex_volume", 7);
126 } else if (pn_vertex_volume.find("Glue") != std::string::npos) {
127 histograms_.fill("pn_vertex_volume", 8);
128 } else if (pn_vertex_volume.find("motherboard") != std::string::npos) {
129 histograms_.fill("pn_vertex_volume", 9);
130 } else {
131 ldmx_log(debug) << " Else pn_vertex_volume = " << pn_vertex_volume
132 << " with pn_interaction_material = "
133 << pn_interaction_material;
134 histograms_.fill("pn_vertex_volume", 1);
135 }
136
137 // Now the interaction material
138 if (pn_interaction_material.find("Silicon") != std::string::npos ||
139 pn_interaction_material.find("G4_Si") != std::string::npos) {
140 histograms_.fill("pn_interaction_material", 2);
141 } else if (pn_interaction_material.find("Tungsten") != std::string::npos ||
142 pn_interaction_material.find("G4_W") != std::string::npos) {
143 histograms_.fill("pn_interaction_material", 3);
144 } else if (pn_interaction_material.find("FR4") != std::string::npos) {
145 // Glass epoxy
146 histograms_.fill("pn_interaction_material", 4);
147 } else if (pn_interaction_material.find("Steel") != std::string::npos) {
148 histograms_.fill("pn_interaction_material", 5);
149 } else if (pn_interaction_material.find("Epoxy") != std::string::npos) {
150 // CarbonEpoxyComposite
151 histograms_.fill("pn_interaction_material", 6);
152 } else if (pn_interaction_material.find("Scintillator") !=
153 std::string::npos) {
154 // This is the notion for PVT
155 histograms_.fill("pn_interaction_material", 7);
156 } else if (pn_interaction_material.find("Glue") != std::string::npos) {
157 // This is the notion for PVT
158 histograms_.fill("pn_interaction_material", 8);
159 } else if (pn_interaction_material.find("Air") != std::string::npos ||
160 pn_interaction_material.find("G4_AIR") != std::string::npos) {
161 // Air
162 histograms_.fill("pn_interaction_material", 9);
163 } else {
164 ldmx_log(debug) << " Else pn_interaction_material = "
165 << pn_interaction_material
166 << " with pn_vertex_volume = " << pn_vertex_volume;
167 histograms_.fill("pn_interaction_material", 1);
168 }
169 } else {
170 histograms_.fill("pn_vertex_volume", 0);
171 histograms_.fill("pn_interaction_material", 0);
172 }
173
174 findParticleKinematics(pn_daughters, "pn");
175
176 histograms_.fill("pn_particle_mult", pn_gamma->getDaughters().size());
177 histograms_.fill("pn_gamma_energy", pn_gamma->getEnergy());
178 histograms_.fill("pn_gamma_int_x", pn_gamma->getEndPoint()[0]);
179 histograms_.fill("pn_gamma_int_y", pn_gamma->getEndPoint()[1]);
180 histograms_.fill("pn_gamma_int_x:pn_gamma_int_y", pn_gamma->getEndPoint()[0],
181 pn_gamma->getEndPoint()[1]);
182 histograms_.fill("pn_gamma_int_z", pn_gamma->getEndPoint()[2]);
183 histograms_.fill("pn_gamma_vertex_x", pn_gamma->getVertex()[0]);
184 histograms_.fill("pn_gamma_vertex_y", pn_gamma->getVertex()[1]);
185 histograms_.fill("pn_gamma_vertex_z", pn_gamma->getVertex()[2]);
186
187 // Classify the event
188 auto event_type{classifyEvent(pn_daughters, 200)};
189 auto event_type500_mev{classifyEvent(pn_daughters, 500)};
190 auto event_type2000_mev{classifyEvent(pn_daughters, 2000)};
191
192 auto event_type_comp{classifyCompactEvent(pn_gamma, pn_daughters, 200)};
193 auto event_type_comp500_mev{
194 classifyCompactEvent(pn_gamma, pn_daughters, 500)};
195 auto event_type_comp2000_mev{
196 classifyCompactEvent(pn_gamma, pn_daughters, 2000)};
197
198 histograms_.fill("event_type", static_cast<int>(event_type));
199 histograms_.fill("event_type_500mev", static_cast<int>(event_type500_mev));
200 histograms_.fill("event_type_2000mev", static_cast<int>(event_type2000_mev));
201 histograms_.fill("event_type_compact", static_cast<int>(event_type_comp));
202 histograms_.fill("event_type_compact_500mev",
203 static_cast<int>(event_type_comp500_mev));
204 histograms_.fill("event_type_compact_2000mev",
205 static_cast<int>(event_type_comp2000_mev));
206
207 switch (event_type) {
208 case EventType::single_neutron:
209 if (pn_daughters.size() > 1) {
210 auto second_hardest_pdg{std::abs(pn_daughters[1]->getPdgID())};
211 int n_event_type{-10};
212 if (second_hardest_pdg == 2112)
213 n_event_type = 0;
214 else if (second_hardest_pdg == 2212)
215 n_event_type = 1;
216 else if (second_hardest_pdg == 211)
217 n_event_type = 2;
218 else if (second_hardest_pdg == 111)
219 n_event_type = 3;
220 else
221 n_event_type = 4;
222 histograms_.fill("1n_event_type", n_event_type);
223 }
224 [[fallthrough]]; // Remaining code is important for 1n as well
225 case EventType::two_neutrons:
226 case EventType::charged_kaon:
227 case EventType::klong:
228 case EventType::kshort:
229 findSubleadingKinematics(pn_gamma, pn_daughters, event_type);
230 break;
231 default: // Nothing to do
232 break;
233 }
234
235 // Analyze detailed photonuclear interaction tracking if available
236 analyzeInteractionDetails(event);
237}
238
239} // namespace dqm
240
#define DECLARE_ANALYZER(CLASS)
Macro which allows the framework to construct an analyzer given its name during configuration.
Implements an event buffer system for storing event data.
Definition Event.h:42
bool exists(const std::string &name, const std::string &passName, bool unique=true) const
Check for the existence of an object or collection with the given name and pass name in the event.
Definition Event.cxx:105
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
Stores detailed information about a photonuclear interaction.
Class representing a simulated particle.
Definition SimParticle.h:24
std::vector< double > getVertex() const
Get a vector containing the vertex of this particle in mm.