LDMX Software
EcalWABRecProcessor.cxx
1
9
10#include <iomanip>
11#include <numbers> // For std::numbers::pi
12#include <numeric>
13
15#include "DetDescr/EcalID.h"
16#include "DetDescr/SimSpecialID.h"
17#include "Ecal/Event/EcalHit.h"
20#include "Tracking/Event/StraightTrack.h"
21
22namespace ecal {
23
24// 68% Electron Radii of Containment for various theta ranges
25const std::vector<float> RADIUS_68_THETA_0_TO_10 = {
26 10.12233413, 9.921772, 11.38255086, 11.67991867, 13.14337347,
27 13.17120624, 16.80994665, 17.83787244, 22.44684374, 23.74239886,
28 28.60564083, 30.27889678, 34.86404888, 36.39009394, 41.29309474,
29 43.34682279, 48.55982854, 50.80565589, 55.29496257, 57.92737879,
30 60.64828824, 65.51760517, 68.26709803, 76.32877518, 84.61219467,
31 98.21320491, 110.9880892, 120.6762931, 140.6174478, 136.4979268,
32 145.579465, 154.9803228, 164.7005, 174.7399968};
33const std::vector<float> RADIUS_68_THETA_10_TO_15 = {
34 10.82307758, 11.17850518, 16.2185281, 18.62488713, 22.63408229,
35 24.71769042, 30.11217538, 32.69939046, 37.99753196, 40.81619543,
36 45.89054775, 49.03066318, 54.00440948, 59.31733555, 63.40789682,
37 64.77580021, 73.00113678, 73.25561396, 78.8914776, 86.73962133,
38 97.05926327, 96.6932739, 111.6226151, 106.5960265, 109.477541,
39 128.0220711, 145.4137195, 210.3582819, 199.6355662, 184.2513208,
40 195.076552, 206.2322029, 217.7182737, 229.5347642};
41
42const std::vector<float> RADIUS_68_THETA_15_TO_20 = {
43 12.79450901, 13.02698578, 21.27450933, 25.66008312, 31.78592103,
44 35.99689874, 44.37101115, 48.82709363, 55.05972458, 59.68948687,
45 65.39866214, 70.59280337, 76.06007787, 82.22695257, 87.50371819,
46 90.60099831, 96.34848268, 101.4928478, 106.7157092, 105.0540604,
47 110.0653355, 148.3428736, 133.1449443, 146.997265, 173.3954389,
48 175.4329544, 184.3003543, 259.8415751, 215.0165, 231.1288534,
49 243.4440277, 256.085458, 269.0531444, 282.3470868};
50
51const std::vector<float> RADIUS_68_THETA_20_TO_30 = {
52 14.16989595, 15.4488322, 28.31044668, 37.54285657, 48.57288885,
53 57.04243339, 68.99836079, 75.33388728, 85.00572867, 91.52574074,
54 102.5044698, 106.5315986, 116.2341378, 127.1121442, 133.8866375,
55 144.5121759, 162.1726963, 160.2986579, 171.386638, 182.5653112,
56 205.5853241, 196.3113071, 200.5907513, 228.7275694, 234.0298491,
57 251.7701385, 293.9351568, 310.521898, 344.1455457, 293.3518953,
58 303.2401036, 313.128312, 323.0165203, 332.9047287};
59
60const std::vector<float> RADIUS_68_THETA_30_TO_90 = {
61 22.50983127, 26.44537503, 58.24642887, 90.59076279, 130.0592014,
62 157.4611392, 184.2187293, 202.6994588, 225.3488816, 243.3454167,
63 269.2456428, 280.6119298, 303.8591523, 322.0522722, 335.1780181,
64 350.3398234, 353.7763544, 373.9942362, 382.9453608, 401.9703438,
65 441.6281859, 432.5241826, 455.2878243, 492.2888656, 502.6653722,
66 480.2334627, 566.5438302, 505.7032783, 556.1650321, 596.9112032,
67 616.1614593, 635.4117154, 654.6619715, 673.9122276};
68
70 // Set the collection name as defined in the configuration
71 sp_pass_name_ = parameters.get<std::string>("sp_pass_name");
72 collection_name_ = parameters.get<std::string>("collection_name");
73 rec_pass_name_ = parameters.get<std::string>("rec_pass_name");
74 rec_coll_name_ = parameters.get<std::string>("rec_coll_name");
75 track_pass_name_ = parameters.get<std::string>("track_pass_name");
76 track_coll_name_ = parameters.get<std::string>("track_coll_name");
77}
78
80 // Define start time for processing
81 auto start = std::chrono::high_resolution_clock::now();
82 nevents_++;
83
84 // Keep track of event progress where:
85 // 0: No tracks found
86 // 1: Track found but not enough info to reconstruct either electron or photon
87 // 2: Track found and enough info to reconstruct electron
88 // 3: Track found and enough info to reconstruct electron and photon
89 int progress_num = 0;
90
91 // Get the Ecal Geometry
93 ldmx::EcalGeometry::CONDITIONS_OBJECT_NAME);
94
95 // Get the collection of ecal_rec_hits and tracks
96 const std::vector<ldmx::EcalHit> ecal_rec_hits =
97 event.getCollection<ldmx::EcalHit>(rec_coll_name_, rec_pass_name_);
98 const std::vector<ldmx::StraightTrack> linear_tracks =
99 event.getCollection<ldmx::StraightTrack>(track_coll_name_,
100 track_pass_name_);
101
102 // Define variables to save recoil electron/photon information (SP)
103 std::vector<double> recoil_e_p, recoil_y_p;
104 std::vector<float> recoil_e_pos, recoil_y_pos;
105
106 // Result object that stores kinematic variables
107 ldmx::EcalWABResult result;
108
109 // Define kinematic variables
110 const std::vector<float> z_hat = {0, 0, 1};
111 float true_theta_electron = -9.;
112 float true_theta_photon = -9.;
113 float true_phi_electron = -9.;
114 float true_phi_photon = -9.;
115 float rec_theta_electron = -9.;
116 float rec_theta_photon = -9.;
117 float rec_phi_electron = -9.;
118 float rec_phi_photon = -9.;
119 float true_theta_diff_electron_photon = -9.;
120 float true_phi_diff_electron_photon = -9.;
121 float rec_theta_diff_electron_photon = -9.;
122 float rec_phi_diff_electron_photon = -9.;
123 float true_rec_theta_diff_electron = -9.;
124 float true_rec_phi_diff_electron = -9.;
125 float true_rec_theta_diff_photon = -9.;
126 float true_rec_phi_diff_photon = -9.;
127 float true_electron_shower_energy = -999.;
128 float true_photon_shower_energy = -999.;
129 float rec_electron_shower_energy = -999.;
130 float rec_photon_shower_energy = -999.;
131
132 // Create lists for rec hits_ and electron/photon shower hits_
133 std::vector<std::array<float, 6>> rec_hit_list;
134 std::vector<std::array<float, 6>> ele_hit_list;
135 std::vector<std::array<float, 6>> phot_hit_list;
136
137 // Save rec hit info to rec_hit_list
138 for (const ldmx::EcalHit& hit : ecal_rec_hits) {
139 ldmx::EcalID id(hit.getID());
140 auto pos = geometry->getPosition(id);
141 auto [x_, y_, z_] = std::apply(
142 [](double a, double b, double c) {
143 return std::make_tuple(static_cast<float>(a), static_cast<float>(b),
144 static_cast<float>(c));
145 },
146 pos);
147 float energy = hit.getEnergy();
148 float layer_num = id.layer();
149 rec_hit_list.push_back({x_, y_, z_, layer_num, 0, energy});
150 }
151
152 if (event.exists("TargetScoringPlaneHits", sp_pass_name_)) {
153 //
154 // Loop through all of the sim particles and find the recoil
155 // photon/electron.
156 //
157
158 // Find Target SP hit for recoil photon/electron
159 const std::vector<ldmx::SimTrackerHit> target_sp_hits =
160 event.getCollection<ldmx::SimTrackerHit>("TargetScoringPlaneHits",
161 sp_pass_name_);
162 float photon_p_zmax = 0, electron_p_zmax = 0;
163 for (const ldmx::SimTrackerHit& sp_hit : target_sp_hits) {
164 ldmx::SimSpecialID hit_id(sp_hit.getID());
165 if (hit_id.plane() != 1 || sp_hit.getMomentum()[2] <= 0) continue;
166
167 if (sp_hit.getPdgID() == 11) {
168 if (sp_hit.getMomentum()[2] > electron_p_zmax) {
169 recoil_e_p = sp_hit.getMomentum();
170 true_electron_shower_energy = sp_hit.getEnergy();
171 recoil_e_pos = sp_hit.getPosition();
172 electron_p_zmax = recoil_e_p[2];
173 }
174 }
175 if (sp_hit.getPdgID() == 22) {
176 if (sp_hit.getMomentum()[2] > photon_p_zmax) {
177 recoil_y_p = sp_hit.getMomentum();
178 true_photon_shower_energy = sp_hit.getEnergy();
179 recoil_y_pos = sp_hit.getPosition();
180 photon_p_zmax = recoil_y_p[2];
181 }
182 }
183 }
184
185 // Calculating true theta values using SP hit parameters
186 if (recoil_y_p.size() == 3 &&
187 std::sqrt((recoil_y_p[0]) * (recoil_y_p[0]) +
188 (recoil_y_p[1]) * (recoil_y_p[1]) +
189 (recoil_y_p[2]) * (recoil_y_p[2])) != 0 &&
190 recoil_e_p.size() == 3 &&
191 std::sqrt((recoil_e_p[0]) * (recoil_e_p[0]) +
192 (recoil_e_p[1]) * (recoil_e_p[1]) +
193 (recoil_e_p[2]) * (recoil_e_p[2])) != 0) {
194 true_theta_electron =
195 (180 / std::numbers::pi) *
196 std::acos(std::inner_product(recoil_e_p.begin(), recoil_e_p.end(),
197 z_hat.begin(), 0.0) /
198 std::sqrt((recoil_e_p[0]) * (recoil_e_p[0]) +
199 (recoil_e_p[1]) * (recoil_e_p[1]) +
200 (recoil_e_p[2]) * (recoil_e_p[2])));
201 true_theta_photon =
202 (180 / std::numbers::pi) *
203 std::acos(std::inner_product(recoil_y_p.begin(), recoil_y_p.end(),
204 z_hat.begin(), 0.0) /
205 std::sqrt((recoil_y_p[0]) * (recoil_y_p[0]) +
206 (recoil_y_p[1]) * (recoil_y_p[1]) +
207 (recoil_y_p[2]) * (recoil_y_p[2])));
208 }
209
210 // Calculating true phi values using SP hit parameters
211 if (recoil_y_p.size() == 3 && recoil_y_p[2] != 0 &&
212 recoil_e_p.size() == 3 && recoil_e_p[2] != 0) {
213 true_phi_electron =
214 (180 / std::numbers::pi) * std::atan(recoil_e_p[1] / recoil_e_p[0]);
215 if (recoil_e_p[1] < 0) {
216 true_phi_electron += 180;
217 }
218 if (recoil_e_p[0] < 0 && recoil_e_p[1] > 0) {
219 true_phi_electron += 360;
220 }
221 true_phi_photon =
222 (180 / std::numbers::pi) * std::atan(recoil_y_p[1] / recoil_y_p[0]);
223 if (recoil_y_p[1] < 0) {
224 true_phi_photon += 180;
225 }
226 if (recoil_y_p[0] < 0 && recoil_y_p[1] > 0) {
227 true_phi_photon += 360;
228 }
229 }
230
231 // Calculating true delta_phi/delta_theta using SP hit parameters
232 if (recoil_y_p.size() == 3 && recoil_e_p.size() == 3) {
233 std::array<double, 2> phi_diff_electron_arr = {recoil_e_p[0],
234 recoil_e_p[1]};
235 std::array<double, 2> phi_diff_photon_arr = {recoil_y_p[0],
236 recoil_y_p[1]};
237 std::array<double, 2> theta_diff_electron_arr = {recoil_e_p[2],
238 recoil_e_p[0]};
239 std::array<double, 2> theta_diff_photon_arr = {recoil_y_p[2],
240 recoil_y_p[0]};
241
242 true_theta_diff_electron_photon =
243 (180 / std::numbers::pi) *
244 std::acos(
245 std::inner_product(theta_diff_electron_arr.begin(),
246 theta_diff_electron_arr.end(),
247 theta_diff_photon_arr.begin(), 0.0) /
248 (std::sqrt((theta_diff_electron_arr[0]) *
249 (theta_diff_electron_arr[0]) +
250 (theta_diff_electron_arr[1]) *
251 (theta_diff_electron_arr[1])) *
252 std::sqrt(
253 (theta_diff_photon_arr[0]) * (theta_diff_photon_arr[0]) +
254 (theta_diff_photon_arr[1]) * (theta_diff_photon_arr[1]))));
255 true_phi_diff_electron_photon =
256 (180 / std::numbers::pi) *
257 std::acos(
258 std::inner_product(phi_diff_electron_arr.begin(),
259 phi_diff_electron_arr.end(),
260 phi_diff_photon_arr.begin(), 0.0) /
261 (std::sqrt(
262 (phi_diff_electron_arr[0]) * (phi_diff_electron_arr[0]) +
263 (phi_diff_electron_arr[1]) * (phi_diff_electron_arr[1])) *
264 std::sqrt((phi_diff_photon_arr[0]) * (phi_diff_photon_arr[0]) +
265 (phi_diff_photon_arr[1]) * (phi_diff_photon_arr[1]))));
266 }
267 }
268
269 // Defining variables to save best fit results
270 std::pair<Eigen::VectorXd, Eigen::VectorXd> linear_fit_coeffs;
271 std::tuple<Eigen::VectorXd, float, int, Eigen::MatrixXd, int> best_x_result;
272 std::tuple<Eigen::VectorXd, float, int, Eigen::MatrixXd, int> best_y_result;
273 std::get<1>(best_x_result) = 10e99;
274 std::get<1>(best_y_result) = 10e99;
275
276 // Looping over tracks to find best fit to hits_
277 std::vector<float> ele_roc = RADIUS_68_THETA_30_TO_90;
278 for (const ldmx::StraightTrack& track : linear_tracks) {
279 progress_num = 1;
280 // Determining the RoC value to use based on recoil electron (track) theta
281 std::vector<double> track_vec = {track.getSlopeX(), track.getSlopeY(), 1};
282 float track_theta =
283 (180 / std::numbers::pi) *
284 std::acos(std::inner_product(track_vec.begin(), track_vec.end(),
285 z_hat.begin(), 0.0) /
286 std::sqrt((track_vec[0]) * (track_vec[0]) +
287 (track_vec[1]) * (track_vec[1]) + 1));
288 if (track_theta <= 10) {
289 ele_roc = RADIUS_68_THETA_0_TO_10;
290 } else if (track_theta > 10 && track_theta <= 15) {
291 ele_roc = RADIUS_68_THETA_10_TO_15;
292 } else if (track_theta > 15 && track_theta <= 20) {
293 ele_roc = RADIUS_68_THETA_15_TO_20;
294 } else if (track_theta > 20 && track_theta <= 30) {
295 ele_roc = RADIUS_68_THETA_20_TO_30;
296 }
297
298 // Labeling hits_ as electron (1) or photon (0)
299 for (std::array<float, 6>& hit : rec_hit_list) {
300 if (std::sqrt(
301 (hit[0] - (track.getSlopeX() * hit[2] + track.getInterceptX())) *
302 (hit[0] -
303 (track.getSlopeX() * hit[2] + track.getInterceptX())) +
304 (hit[1] - (track.getSlopeY() * hit[2] + track.getInterceptY())) *
305 (hit[1] - (track.getSlopeY() * hit[2] +
306 track.getInterceptY()))) < ele_roc[hit[3]]) {
307 hit[4] = 1;
308 }
309 }
310 // Create vectors to hold electron/photon hits_ specifically
311 std::vector<float> ele_hit_list_x, ele_hit_list_y, ele_hit_list_z;
312 std::vector<float> phot_hit_list_x, phot_hit_list_y, phot_hit_list_z;
313
314 // Use labels to sort hits_ as electron/photon and calculate shower energies
315 for (const auto& hit : rec_hit_list) {
316 if (hit[4] == 1) {
317 ele_hit_list.push_back(hit);
318 ele_hit_list_x.push_back(hit[0]);
319 ele_hit_list_y.push_back(hit[1]);
320 ele_hit_list_z.push_back(hit[2]);
321 } else if (hit[4] == 0) {
322 phot_hit_list.push_back(hit);
323 phot_hit_list_x.push_back(hit[0]);
324 phot_hit_list_y.push_back(hit[1]);
325 phot_hit_list_z.push_back(hit[2]);
326 }
327 }
328
329 // Fit both photon/electron or just electron hits_ based on # of viable
330 // showers
331 if (phot_hit_list.size() >= 3 && ele_hit_list.size() >= 3) {
332 progress_num =
333 3; // Set progress_num to 3 to halt electron-only reconstruction
334
335 // Generate guesses and error vectors for vertex constrained fit
336 std::vector<double> x_guess = {
337 track.getSlopeX(),
338 (phot_hit_list.back()[0] - phot_hit_list[0][0]) /
339 (phot_hit_list.back()[2] - phot_hit_list[0][2]),
340 track.getInterceptX()};
341 std::vector<double> y_guess = {
342 track.getSlopeY(),
343 (phot_hit_list.back()[1] - phot_hit_list[0][1]) /
344 (phot_hit_list.back()[2] - phot_hit_list[0][2]),
345 track.getInterceptY()};
346 std::vector<float> phot_hit_error(phot_hit_list.size(),
347 0.456435464588 * 4.816);
348 std::vector<float> ele_hit_error(ele_hit_list.size(),
349 0.456435464588 * 4.816);
350
351 int max_iter = 200;
352 // Carry out fit
353 std::tuple<Eigen::VectorXd, float, int, Eigen::MatrixXd, int> x_result =
354 fit2DTracksConstrained(ele_hit_list_z, ele_hit_list_x, ele_hit_error,
355 phot_hit_list_z, phot_hit_list_x,
356 phot_hit_error, x_guess, max_iter, 0, 0.001,
357 10.0);
358
359 std::tuple<Eigen::VectorXd, float, int, Eigen::MatrixXd, int> y_result =
360 fit2DTracksConstrained(ele_hit_list_z, ele_hit_list_y, ele_hit_error,
361 phot_hit_list_z, phot_hit_list_y,
362 phot_hit_error, y_guess, max_iter, 0, 0.001,
363 40.0);
364
365 // Update best fit variables if current fit is an improvement
366 if ((std::get<1>(x_result) + std::get<1>(y_result)) / 2 <
367 (std::get<1>(best_x_result) + std::get<1>(best_y_result)) / 2) {
368 best_x_result = x_result;
369 best_y_result = y_result;
370 }
371 }
372
373 // If there isn't enough info for both electron and photon reconstruction,
374 // reconstruct electron if possible
375 else if (ele_hit_list.size() >= 3) {
376 if (progress_num != 3) {
377 progress_num = 2; // Set progress_num to 3 to indicate electron-only
378 // reconstruction
379 linear_fit_coeffs =
380 polyfitXYvsZ(ele_hit_list_x, ele_hit_list_y, ele_hit_list_z, 1);
381 }
382 }
383 }
384
385 // Calculate kinematic variables for electron and/or photon
386 // based on # of viable showers (with reconstruction information)
387 if (std::get<0>(best_x_result).size() != 0) {
388 rec_electron_shower_energy = 0;
389 rec_photon_shower_energy = 0;
390 for (const auto& hit : ele_hit_list) {
391 rec_electron_shower_energy += hit[5];
392 }
393 for (const auto& hit : phot_hit_list) {
394 rec_photon_shower_energy += hit[5];
395 }
396
397 std::vector<double> ele_params = {std::get<0>(best_x_result)(0),
398 std::get<0>(best_y_result)(0)};
399 std::vector<double> phot_params = {std::get<0>(best_x_result)(1),
400 std::get<0>(best_y_result)(1)};
401 std::vector<double> ele_params_x = {std::get<0>(best_x_result)(0)};
402 std::vector<double> phot_params_x = {std::get<0>(best_x_result)(1)};
403
404 rec_theta_electron =
405 (180 / std::numbers::pi) *
406 std::acos(1 / std::sqrt((ele_params[0]) * (ele_params[0]) +
407 (ele_params[1]) * (ele_params[1]) + 1));
408 rec_theta_photon =
409 (180 / std::numbers::pi) *
410 std::acos(1 / std::sqrt((phot_params[0]) * (phot_params[0]) +
411 (phot_params[1]) * (phot_params[1]) + 1));
412
413 rec_phi_electron =
414 (180 / std::numbers::pi) * std::atan(ele_params[1] / ele_params[0]);
415 if (ele_params[1] < 0) {
416 rec_phi_electron += 180;
417 }
418 if (ele_params[0] < 0 && ele_params[1] > 0) {
419 rec_phi_electron += 360;
420 }
421 rec_phi_photon =
422 (180 / std::numbers::pi) * std::atan(phot_params[1] / phot_params[0]);
423 if (phot_params[1] < 0) {
424 rec_phi_photon += 180;
425 }
426 if (phot_params[0] < 0 && phot_params[1] > 0) {
427 rec_phi_photon += 360;
428 }
429
430 rec_theta_diff_electron_photon =
431 (180 / std::numbers::pi) *
432 std::acos(std::inner_product(ele_params_x.begin(), ele_params_x.end(),
433 phot_params_x.begin(), 1.0) /
434 (std::sqrt((ele_params_x[0]) * (ele_params_x[0]) + (1)) *
435 std::sqrt((phot_params_x[0]) * (phot_params_x[0]) + 1)));
436 rec_phi_diff_electron_photon =
437 (180 / std::numbers::pi) *
438 std::acos(std::inner_product(ele_params.begin(), ele_params.end(),
439 phot_params.begin(), 0.0) /
440 (std::sqrt((ele_params[0]) * (ele_params[0]) +
441 (ele_params[1]) * (ele_params[1])) *
442 std::sqrt((phot_params[0]) * (phot_params[0]) +
443 (phot_params[1]) * (phot_params[1]))));
444
445 if (recoil_y_p.size() == 3 && recoil_y_p[2] != 0 &&
446 recoil_e_p.size() == 3 && recoil_e_p[2] != 0) {
447 true_rec_theta_diff_electron =
448 (180 / std::numbers::pi) *
449 std::acos(std::inner_product(ele_params_x.begin(), ele_params_x.end(),
450 recoil_e_p.begin(), recoil_e_p[2]) /
451 (std::sqrt((ele_params_x[0]) * (ele_params_x[0]) + 1) *
452 std::sqrt((recoil_e_p[0]) * (recoil_e_p[0]) +
453 (recoil_e_p[2]) * (recoil_e_p[2]))));
454 true_rec_theta_diff_photon =
455 (180 / std::numbers::pi) *
456 std::acos(std::inner_product(phot_params_x.begin(),
457 phot_params_x.end(), recoil_y_p.begin(),
458 recoil_y_p[2]) /
459 (std::sqrt((phot_params_x[0]) * (phot_params_x[0]) + 1) *
460 std::sqrt((recoil_y_p[0]) * (recoil_y_p[0]) +
461 (recoil_y_p[2]) * (recoil_y_p[2]))));
462 true_rec_phi_diff_electron =
463 (180 / std::numbers::pi) *
464 std::acos(std::inner_product(ele_params.begin(), ele_params.end(),
465 recoil_e_p.begin(), 0) /
466 (std::sqrt((ele_params[0]) * (ele_params[0]) +
467 (ele_params[1]) * (ele_params[1])) *
468 std::sqrt((recoil_e_p[0]) * (recoil_e_p[0]) +
469 (recoil_e_p[1]) * (recoil_e_p[1]))));
470 true_rec_phi_diff_photon =
471 (180 / std::numbers::pi) *
472 std::acos(std::inner_product(phot_params.begin(), phot_params.end(),
473 recoil_y_p.begin(), 0) /
474 (std::sqrt((phot_params[0]) * (phot_params[0]) +
475 (phot_params[1]) * (phot_params[1])) *
476 std::sqrt((recoil_y_p[0]) * (recoil_y_p[0]) +
477 (recoil_y_p[1]) * (recoil_y_p[1]))));
478 }
479 } else if (progress_num == 2) {
480 rec_electron_shower_energy = 0;
481 for (const auto& hit : ele_hit_list) {
482 rec_electron_shower_energy += hit[5];
483 }
484
485 std::vector<double> ele_params = {linear_fit_coeffs.first(1),
486 linear_fit_coeffs.second(1)};
487 std::vector<double> ele_params_x = {linear_fit_coeffs.first(1)};
488
489 rec_theta_electron =
490 (180 / std::numbers::pi) *
491 std::acos(1 / std::sqrt((ele_params[0]) * (ele_params[0]) +
492 (ele_params[1]) * (ele_params[1]) + 1));
493 rec_phi_electron =
494 (180 / std::numbers::pi) * std::atan(ele_params[1] / ele_params[0]);
495 if (ele_params[1] < 0) {
496 rec_phi_electron += 180;
497 }
498 if (ele_params[0] < 0 && ele_params[1] > 0) {
499 rec_phi_electron += 360;
500 }
501
502 if (recoil_y_p.size() == 3 && recoil_y_p[2] != 0 &&
503 recoil_e_p.size() == 3 && recoil_e_p[2] != 0) {
504 true_rec_theta_diff_electron =
505 (180 / std::numbers::pi) *
506 std::acos(std::inner_product(ele_params_x.begin(), ele_params_x.end(),
507 recoil_e_p.begin(), recoil_e_p[2]) /
508 (std::sqrt((ele_params_x[0]) * (ele_params_x[0]) + 1) *
509 std::sqrt((recoil_e_p[0]) * (recoil_e_p[0]) +
510 (recoil_e_p[2]) * (recoil_e_p[2]))));
511 true_rec_phi_diff_electron =
512 (180 / std::numbers::pi) *
513 std::acos(std::inner_product(ele_params.begin(), ele_params.end(),
514 recoil_e_p.begin(), 0) /
515 (std::sqrt((ele_params[0]) * (ele_params[0]) +
516 (ele_params[1]) * (ele_params[1])) *
517 std::sqrt((recoil_e_p[0]) * (recoil_e_p[0]) +
518 (recoil_e_p[1]) * (recoil_e_p[1]))));
519 }
520
521 // Set photon variables to non-physical value
522 // that corresponds to electron-only reco case
523 rec_theta_photon = -5.;
524 rec_phi_photon = -5.;
525 rec_theta_diff_electron_photon = -5.;
526 rec_phi_diff_electron_photon = -5.;
527 true_rec_theta_diff_photon = -5.;
528 true_rec_phi_diff_photon = -5.;
529 }
530
531 // Setting output object equal to calculated variables
532 result.setVariables(
533 true_theta_electron, true_theta_photon, true_phi_electron,
534 true_phi_photon, rec_theta_electron, rec_theta_photon, rec_phi_electron,
535 rec_phi_photon, true_theta_diff_electron_photon,
536 true_phi_diff_electron_photon, rec_theta_diff_electron_photon,
537 rec_phi_diff_electron_photon, true_rec_theta_diff_electron,
538 true_rec_phi_diff_electron, true_rec_theta_diff_photon,
539 true_rec_phi_diff_photon, true_electron_shower_energy,
540 true_photon_shower_energy, rec_electron_shower_energy,
541 rec_photon_shower_energy, progress_num);
542
543 event.add(collection_name_, result);
544
545 // Calculate processing time for event
546 auto end = std::chrono::high_resolution_clock::now();
547 auto diff = end - start;
548 processing_time_ += std::chrono::duration<float, std::milli>(diff).count();
549}
550
552 ldmx_log(info) << "AVG Time/Event: " << std::fixed << std::setprecision(2)
553 << processing_time_ / nevents_ << " ms";
554}
555
556std::tuple<Eigen::VectorXd, float, int, Eigen::MatrixXd, int>
557EcalWABRecProcessor::fit2DTracksConstrained(
558 const std::vector<float>& x1, const std::vector<float>& y1,
559 const std::vector<float>& s1, const std::vector<float>& x2,
560 const std::vector<float>& y2, const std::vector<float>& s2,
561 const std::vector<double>& guess, int max_iter, int verbose, float d_chisq,
562 float abs_lim) {
563 /*
564 Function that fits two 2D tracks with a vertex constraint (same intercepts).
565 The fitted model is initially defined as:
566 y1 = par[0] * x1 + abs_lim * tanh(par[2] / abs_lim)
567 y2 = par[1] * x2 + abs_lim * tanh(par[2] / abs_lim)
568 After updating parameters the fitted values are computed as:
569 y1 = par[0] * (x1 - par[2])
570 y2 = par[1] * (x2 - par[2])
571
572 Inputs:
573 x1, y1, s1 : measured coordinates and errors for track 1
574 x2, y2, s2 : measured coordinates and errors for track 2
575 guess : initial guess for the parameter vector (size 3)
576 max_iter : maximum number of iterations (default 20)
577 verbose : level of verbose (default 0)
578 d_chisq : stopping criterion for chi-squared improvement (default
579 0.001) abs_lim : a parameter used in the fit (default 10) Returns: A tuple
580 containing: par : fitted parameters (Eigen::VectorXd of size 3) chisq :
581 chi-squared at minimum (float) ndof : number of degrees of freedom (int)
582 cov : covariance matrix (Eigen::MatrixXd 3x3)
583 niter : number of iterations used (int)
584 */
585 // Copy the initial guess into a 3-element parameter vector
586 Eigen::VectorXd par(3);
587 par(0) = guess[0];
588 par(1) = guess[1];
589 par(2) = guess[2];
590
591 // Determine number of points in each track and total
592 int n1 = x1.size();
593 int n2 = x2.size();
594 int n = n1 + n2;
595
596 // Concatenate x_, y_, and s into Eigen vectors of size n.
597 Eigen::VectorXd x(n), y(n), s(n);
598 for (int i = 0; i < n1; ++i) {
599 x(i) = x1[i];
600 y(i) = y1[i];
601 s(i) = s1[i];
602 }
603 for (int i = 0; i < n2; ++i) {
604 x(n1 + i) = x2[i];
605 y(n1 + i) = y2[i];
606 s(n1 + i) = s2[i];
607 }
608
609 // Build the weight matrix W = diag(1/s_i^2)
610 Eigen::MatrixXd w = Eigen::MatrixXd::Zero(n, n);
611 for (int i = 0; i < n; ++i) {
612 w(i, i) = 1.0 / ((s(i)) * (s(i)));
613 }
614
615 float chi_sq = 0.0;
616 float old_chi_sq = 1e12; // a large initial value
617 int n_iter = 0;
618 Eigen::MatrixXd cov(3, 3); // covariance matrix
619
620 // Iterative fitting loop
621 for (int iter = 0; iter < max_iter; ++iter) {
622 n_iter = iter + 1;
623
624 // Compute fitted y coordinates for each track using the current
625 // parameters. For track 1: y1_fit = par[0] * x1 + abs_lim *
626 // tanh(par[2]/abs_lim) For track 2: y2_fit = par[1] * x2 + abs_lim *
627 // tanh(par[2]/abs_lim)
628 Eigen::VectorXd y1_fit(n1), y2_fit(n2);
629 float tanh_term = std::tanh(par(2) / abs_lim);
630 for (int i = 0; i < n1; ++i) {
631 y1_fit(i) = par(0) * x1[i] + abs_lim * tanh_term;
632 }
633 for (int i = 0; i < n2; ++i) {
634 y2_fit(i) = par(1) * x2[i] + abs_lim * tanh_term;
635 }
636
637 // Concatenate the fitted values
638 Eigen::VectorXd y_fit(n);
639 for (int i = 0; i < n1; ++i) {
640 y_fit(i) = y1_fit(i);
641 }
642 for (int i = 0; i < n2; ++i) {
643 y_fit(n1 + i) = y2_fit(i);
644 }
645
646 // Compute chi-squared: sum_i [ (y_fit[i]-y_[i])^2 / s[i]^2 ]
647 chi_sq = 0.0;
648 for (int i = 0; i < n; ++i) {
649 float diff = y_fit(i) - y(i);
650 chi_sq += (diff * diff) / ((s(i)) * (s(i)));
651 }
652
653 if (verbose > 0) {
654 ldmx_log(debug) << "Before iteration " << iter << ", chi_sq = " << chi_sq;
655 ldmx_log(debug) << "Track 1 residuals: ";
656 for (int i = 0; i < n1; ++i) {
657 ldmx_log(debug) << (y1_fit(i) - y1[i]);
658 }
659 ldmx_log(debug) << "Track 2 residuals: ";
660 for (int i = 0; i < n2; ++i) {
661 ldmx_log(debug) << (y2_fit(i) - y2[i]);
662 }
663 }
664
665 // Compute the derivatives (Jacobian components)
666 // For track 1:
667 // dy1/dpar0 = x1, dy1/dpar1 = 0, dy1/dpar2 =
668 // (1/cosh(par[2]/abs_lim))^2 (constant for all points)
669 // For track 2:
670 // dy2/dpar0 = 0, dy2/dpar1 = x2, dy2/dpar2 =
671 // (1/cosh(par[2]/abs_lim))^2
672 Eigen::VectorXd dy1_dpar_0(n1), dy1_dpar_1 = Eigen::VectorXd::Zero(n1),
673 dy1_dpar_2(n1);
674 Eigen::VectorXd dy2_dpar_0 = Eigen::VectorXd::Zero(n2), dy2_dpar_1(n2),
675 dy2_dpar_2(n2);
676
677 float d_term = 1.0 / std::cosh(par(2) / abs_lim);
678 d_term = d_term * d_term; // square it
679 for (int i = 0; i < n1; ++i) {
680 dy1_dpar_0(i) = x1[i];
681 dy1_dpar_2(i) = d_term;
682 }
683 for (int i = 0; i < n2; ++i) {
684 dy2_dpar_1(i) = x2[i];
685 dy2_dpar_2(i) = d_term;
686 }
687
688 // Concatenate the derivatives for both tracks into full vectors of length
689 // n.
690 Eigen::VectorXd dy_dpar_0(n), dy_dpar_1(n), dy_dpar_2(n);
691 for (int i = 0; i < n1; ++i) {
692 dy_dpar_0(i) = dy1_dpar_0(i);
693 dy_dpar_1(i) = dy1_dpar_1(i);
694 dy_dpar_2(i) = dy1_dpar_2(i);
695 }
696 for (int i = 0; i < n2; ++i) {
697 dy_dpar_0(n1 + i) = dy2_dpar_0(i);
698 dy_dpar_1(n1 + i) = dy2_dpar_1(i);
699 dy_dpar_2(n1 + i) = dy2_dpar_2(i);
700 }
701
702 // Build the "A" matrix (the Jacobian) in its transposed form (3 x n)
703 Eigen::MatrixXd a_trans(3, n);
704 a_trans.row(0) = dy_dpar_0.transpose();
705 a_trans.row(1) = dy_dpar_1.transpose();
706 a_trans.row(2) = dy_dpar_2.transpose();
707
708 // The Jacobian (n x 3) is the transpose of a_trans.
709 Eigen::MatrixXd a = a_trans.transpose();
710
711 // The residual vector (difference between measured and fitted y values)
712 Eigen::VectorXd dy_vec = y - y_fit;
713
714 // Compute the (3 x 3) matrix: M = a_trans * W * a
715 Eigen::MatrixXd temp = a_trans * w; // 3 x n
716 Eigen::MatrixXd temp2 = temp * a; // 3 x 3
717
718 // Add a regularization term to ensure numerical stability.
719 Eigen::MatrixXd reg =
720 1e-10 * Eigen::MatrixXd::Identity(temp2.rows(), temp2.cols());
721 Eigen::MatrixXd temp2_reg = temp2 + reg;
722
723 // Invert the matrix to obtain the covariance matrix.
724 cov = temp2_reg.inverse();
725
726 // Compute the parameter correction: dpar = cov * a_trans * W * dy_vec
727 Eigen::MatrixXd temp4 = cov * a_trans; // 3 x n
728 Eigen::MatrixXd temp5 = temp4 * w; // 3 x n
729 Eigen::VectorXd dpar = temp5 * dy_vec; // 3 x 1
730
731 // Update the parameters
732 par += dpar;
733
734 // After the update, the fitted y values are recalculated with a different
735 // formula:
736 // y1_fit = par[0]*(x1 - par[2])
737 // y2_fit = par[1]*(x2 - par[2])
738 for (int i = 0; i < n1; ++i) {
739 y1_fit(i) = par(0) * (x1[i] - par(2));
740 }
741 for (int i = 0; i < n2; ++i) {
742 y2_fit(i) = par(1) * (x2[i] - par(2));
743 }
744 for (int i = 0; i < n1; ++i) {
745 y_fit(i) = y1_fit(i);
746 }
747 for (int i = 0; i < n2; ++i) {
748 y_fit(n1 + i) = y2_fit(i);
749 }
750
751 // Recompute chi-squared with the updated fitted values.
752 float new_chi_sq = 0.0;
753 for (int i = 0; i < n; ++i) {
754 float diff = y_fit(i) - y(i);
755 new_chi_sq += (diff * diff) / ((s(i)) * (s(i)));
756 }
757 chi_sq = new_chi_sq;
758
759 // Check for convergence
760 if (iter > 0) {
761 if (std::abs(chi_sq - old_chi_sq) < d_chisq) {
762 break;
763 }
764 }
765 old_chi_sq = chi_sq;
766 } // end for loop
767
768 if (verbose > 0) {
769 ldmx_log(debug) << "At the end chi_sq = " << chi_sq;
770 ldmx_log(debug) << "Scaled residuals for track 1:";
771 for (int i = 0; i < n1; ++i) {
772 float fit_val = par(0) * (x1[i] - par(2));
773 ldmx_log(debug) << 10000 * (fit_val - y1[i]);
774 }
775 ldmx_log(debug) << "Scaled residuals for track 2:";
776 for (int i = 0; i < n2; ++i) {
777 float fit_val = par(1) * (x2[i] - par(2));
778 ldmx_log(debug) << 10000 * (fit_val - y2[i]);
779 }
780 }
781
782 int ndof = n1 + n2 - 3;
783 return std::make_tuple(par, chi_sq, ndof, cov, n_iter);
784}
785
786std::pair<Eigen::VectorXd, Eigen::VectorXd> EcalWABRecProcessor::polyfitXYvsZ(
787 const std::vector<float>& x_, const std::vector<float>& y_,
788 const std::vector<float>& z_, int degree) {
789 /*
790 Function that fits two polynomials (x_ vs. z and y vs. z_) to 3D hit
791 position data using a least-squares method. The fitted models are defined
792 as: x = a₀
793 + a₁ * z + a₂ * z_² + ... + aₙ * zⁿ
794 y = b₀ + b₁ * z + b₂ * z_² + ... + bₙ * zⁿ
795 where n is the specified polynomial degree.
796
797 Inputs:
798 x_, y_, z : measured coordinates for the tracks;
799 x and y are the dependent variables, and z is the independent
800 variable (all provided as std::vector<float>) degree : degree of the
801 polynomial to be fitted (int)
802
803 Returns:
804 A pair containing:
805 first : polynomial coefficients for the x vs. z fit (Eigen::VectorXd)
806 second : polynomial coefficients for the y vs. z fit (Eigen::VectorXd)
807
808 Notes:
809 The polynomial is represented with the constant term first (i.e., [a₀, a₁,
810 ..., aₙ]), so the linear term (slope) is located at index_ 1.
811 */
812 const size_t n = z_.size();
813 if (n == 0 || x_.size() != n || y_.size() != n) {
814 throw std::invalid_argument(
815 "Vectors x_, y_, and z must be non-empty and have the same size.");
816 }
817
818 // Construct the Vandermonde (design) matrix A (n x (degree + 1)):
819 // Each row i: [1, z_[i], z_[i]^2, ..., z_[i]^degree]
820 Eigen::MatrixXd a(n, degree + 1);
821 for (size_t i = 0; i < n; ++i) {
822 float term = 1.0;
823 for (int j = 0; j <= degree; ++j) {
824 a(i, j) = term;
825 term *= z_[i];
826 }
827 }
828
829 // Map the x and y data into Eigen vectors.
830 Eigen::VectorXd bx(n), by(n);
831 for (size_t i = 0; i < n; ++i) {
832 bx(i) = x_[i];
833 by(i) = y_[i];
834 }
835
836 // Solve the least-squares problems:
837 // A * coeffsX ≈ bx and A * coeffsY ≈ by
838 Eigen::VectorXd coeffs_x = a.colPivHouseholderQr().solve(bx);
839 Eigen::VectorXd coeffs_y = a.colPivHouseholderQr().solve(by);
840
841 return {coeffs_x, coeffs_y};
842}
843} // namespace ecal
844
Class that translates raw positions of ECal module hits into cells in a hexagonal readout.
Class that defines an ECal detector ID with a cell number.
Class that reconstructs important kinematic variables for WAB studies.
Class used to encapsulate the results obtained from EcalWABRecProcessor.
#define DECLARE_PRODUCER(CLASS)
Macro which allows the framework to construct a producer given its name during configuration.
Class which encapsulates information from a hit in a simulated tracking detector.
void produce(framework::Event &event) override
Process the event and put new data products into it.
std::string collection_name_
Name of the collection which will contain the results.
void onProcessEnd() override
Callback for the EventProcessor to take any necessary action when the processing of events finishes,...
void configure(framework::config::Parameters &parameters) override
Callback for the EventProcessor to configure itself from the given set of parameters.
const T & getCondition(const std::string &condition_name)
Access a conditions object for the current event.
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 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
Translation between real-space positions and cell IDs within the ECal.
std::tuple< double, double, double > getPosition(EcalID id) const
Get a cell's position from its ID number.
Stores reconstructed hit information from the ECAL.
Definition EcalHit.h:19
Extension of DetectorID providing access to ECal layers and cell numbers in a hex grid.
Definition EcalID.h:20
Implements detector ids for special simulation-derived hits like scoring planes.
int plane() const
Get the value of the plane field from the ID, if it is a scoring plane.
Represents a simulated tracker hit in the simulation.