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