LDMX Software
EcalVetoProcessor.cxx
2
3// LDMX
5#include "DetDescr/SimSpecialID.h"
6#include "Ecal/Event/EcalHit.h"
8#include "SimCore/Event/SimParticle.h"
10
11/*~~~~~~~~~~~*/
12/* Tools */
13/*~~~~~~~~~~~*/
14#include "Tools/AnalysisUtils.h"
15
16// C++
17#include <stdlib.h>
18
19#include <algorithm>
20#include <cmath>
21#include <fstream>
22
23// ROOT (MIP tracking)
24#include "TDecompSVD.h"
25#include "TMatrixD.h"
26#include "TVector3.h"
27
28namespace ecal {
29
31 const ldmx::EcalVetoResult &result) {
32 // Base variables
33 bdtFeatures_.push_back(result.getNReadoutHits());
34 bdtFeatures_.push_back(result.getSummedDet());
35 bdtFeatures_.push_back(result.getSummedTightIso());
36 bdtFeatures_.push_back(result.getMaxCellDep());
37 bdtFeatures_.push_back(result.getShowerRMS());
38 bdtFeatures_.push_back(result.getXStd());
39 bdtFeatures_.push_back(result.getYStd());
40 bdtFeatures_.push_back(result.getAvgLayerHit());
41 bdtFeatures_.push_back(result.getStdLayerHit());
42 bdtFeatures_.push_back(result.getDeepestLayerHit());
43 bdtFeatures_.push_back(result.getEcalBackEnergy());
44 // MIP tracking
45 bdtFeatures_.push_back(result.getNStraightTracks());
46 // bdtFeatures_.push_back(result.getNLinregTracks());
47 bdtFeatures_.push_back(result.getFirstNearPhLayer());
48 bdtFeatures_.push_back(result.getNNearPhHits());
49 bdtFeatures_.push_back(result.getPhotonTerritoryHits());
50 bdtFeatures_.push_back(result.getEPSep());
51 bdtFeatures_.push_back(result.getEPDot());
52 // Longitudinal segment variables
53 bdtFeatures_.push_back(result.getEnergySeg()[0]);
54 bdtFeatures_.push_back(result.getXMeanSeg()[0]);
55 bdtFeatures_.push_back(result.getYMeanSeg()[0]);
56 bdtFeatures_.push_back(result.getLayerMeanSeg()[0]);
57 bdtFeatures_.push_back(result.getEnergySeg()[1]);
58 bdtFeatures_.push_back(result.getYMeanSeg()[2]);
60 bdtFeatures_.push_back(result.getEleContEnergy()[0][0]);
61 bdtFeatures_.push_back(result.getEleContEnergy()[1][0]);
62 bdtFeatures_.push_back(result.getEleContYMean()[0][0]);
63 bdtFeatures_.push_back(result.getEleContEnergy()[0][1]);
64 bdtFeatures_.push_back(result.getEleContEnergy()[1][1]);
65 bdtFeatures_.push_back(result.getEleContYMean()[0][1]);
67 bdtFeatures_.push_back(result.getPhContNHits()[0][0]);
68 bdtFeatures_.push_back(result.getPhContYMean()[0][0]);
69 bdtFeatures_.push_back(result.getPhContNHits()[0][1]);
71 bdtFeatures_.push_back(result.getOutContEnergy()[0][0]);
72 bdtFeatures_.push_back(result.getOutContEnergy()[1][0]);
73 bdtFeatures_.push_back(result.getOutContEnergy()[2][0]);
74 bdtFeatures_.push_back(result.getOutContNHits()[0][0]);
75 bdtFeatures_.push_back(result.getOutContXMean()[0][0]);
76 bdtFeatures_.push_back(result.getOutContYMean()[0][0]);
77 bdtFeatures_.push_back(result.getOutContYMean()[1][0]);
78 bdtFeatures_.push_back(result.getOutContYStd()[0][0]);
79 bdtFeatures_.push_back(result.getOutContEnergy()[0][1]);
80 bdtFeatures_.push_back(result.getOutContEnergy()[1][1]);
81 bdtFeatures_.push_back(result.getOutContEnergy()[2][1]);
82 bdtFeatures_.push_back(result.getOutContLayerMean()[0][1]);
83 bdtFeatures_.push_back(result.getOutContLayerStd()[0][1]);
84 bdtFeatures_.push_back(result.getOutContEnergy()[0][2]);
85 bdtFeatures_.push_back(result.getOutContLayerMean()[0][2]);
86}
87
89 verbose_ = parameters.getParameter<bool>("verbose");
90 featureListName_ = parameters.getParameter<std::string>("feature_list_name");
91 // Load BDT ONNX file
92 rt_ = std::make_unique<ldmx::Ort::ONNXRuntime>(
93 parameters.getParameter<std::string>("bdt_file"));
94
95 // Read in arrays holding 68% containment radius per layer
96 // for different bins in momentum/angle
97 rocFileName_ = parameters.getParameter<std::string>("roc_file");
98 if (!std::ifstream(rocFileName_).good()) {
99 EXCEPTION_RAISE(
100 "EcalVetoProcessor",
101 "The specified RoC file '" + rocFileName_ + "' does not exist!");
102 } else {
103 std::ifstream rocfile(rocFileName_);
104 std::string line, value;
105
106 // Extract the first line in the file
107 std::getline(rocfile, line);
108 std::vector<double> values;
109
110 // Read data, line by line
111 while (std::getline(rocfile, line)) {
112 std::stringstream ss(line);
113 values.clear();
114 while (std::getline(ss, value, ',')) {
115 double f_value = (value != "") ? std::stof(value) : -1.0;
116 values.push_back(f_value);
117 }
118 roc_range_values_.push_back(values);
119 }
120 }
121
122 nEcalLayers_ = parameters.getParameter<int>("num_ecal_layers");
123
124 bdtCutVal_ = parameters.getParameter<double>("disc_cut");
125 ecalLayerEdepRaw_.resize(nEcalLayers_, 0);
126 ecalLayerEdepReadout_.resize(nEcalLayers_, 0);
127 ecalLayerTime_.resize(nEcalLayers_, 0);
128
129 beamEnergyMeV_ = parameters.getParameter<double>("beam_energy");
130 linreg_radius_ = parameters.getParameter<double>("linreg_radius");
131
132 // Set the collection name as defined in the configuration
133 collectionName_ = parameters.getParameter<std::string>("collection_name");
134 rec_pass_name_ = parameters.getParameter<std::string>("rec_pass_name");
135 rec_coll_name_ = parameters.getParameter<std::string>("rec_coll_name");
136}
137
138void EcalVetoProcessor::clearProcessor() {
139 cellMap_.clear();
140 cellMapTightIso_.clear();
141 bdtFeatures_.clear();
142
143 nReadoutHits_ = 0;
144 summedDet_ = 0;
145 summedTightIso_ = 0;
146 maxCellDep_ = 0;
147 showerRMS_ = 0;
148 xStd_ = 0;
149 yStd_ = 0;
150 avgLayerHit_ = 0;
151 stdLayerHit_ = 0;
152 deepestLayerHit_ = 0;
153 ecalBackEnergy_ = 0;
154 // MIP tracking
156 nLinregTracks_ = 0;
158 nNearPhHits_ = 0;
159 epAng_ = 0;
160 epSep_ = 0;
161 epDot_ = 0;
163
164 std::fill(ecalLayerEdepRaw_.begin(), ecalLayerEdepRaw_.end(), 0);
165 std::fill(ecalLayerEdepReadout_.begin(), ecalLayerEdepReadout_.end(), 0);
166 std::fill(ecalLayerTime_.begin(), ecalLayerTime_.end(), 0);
167}
168
170 // Get the Ecal Geometry
171 geometry_ = &getCondition<ldmx::EcalGeometry>(
172 ldmx::EcalGeometry::CONDITIONS_OBJECT_NAME);
173
175
176 clearProcessor();
177
178 // Get the collection of Ecal scoring plane hits. If it doesn't exist,
179 // don't bother adding any truth tracking information.
180
181 std::vector<double> recoilP;
182 std::vector<float> recoilPos;
183 std::vector<double> recoilPAtTarget;
184 std::vector<float> recoilPosAtTarget;
185
186 if (verbose_) {
187 ldmx_log(debug) << " Loop through all of the sim particles and find the "
188 "recoil electron";
189 }
190
191 if (event.exists("EcalScoringPlaneHits")) {
192 //
193 // Loop through all of the sim particles and find the recoil electron.
194 //
195
196 // Get the collection of simulated particles from the event
197 auto particleMap{event.getMap<int, ldmx::SimParticle>("SimParticles")};
198
199 // Search for the recoil electron
200 auto [recoilTrackID, recoilElectron] = Analysis::getRecoil(particleMap);
201
202 // Find ECAL SP hit for recoil electron
203 auto ecalSpHits{
204 event.getCollection<ldmx::SimTrackerHit>("EcalScoringPlaneHits")};
205 float pmax = 0;
206 for (ldmx::SimTrackerHit &spHit : ecalSpHits) {
207 ldmx::SimSpecialID hit_id(spHit.getID());
208 if (hit_id.plane() != 31 || spHit.getMomentum()[2] <= 0) continue;
209
210 if (spHit.getTrackID() == recoilTrackID) {
211 if (sqrt(pow(spHit.getMomentum()[0], 2) +
212 pow(spHit.getMomentum()[1], 2) +
213 pow(spHit.getMomentum()[2], 2)) > pmax) {
214 recoilP = spHit.getMomentum();
215 recoilPos = spHit.getPosition();
216 pmax = sqrt(pow(recoilP[0], 2) + pow(recoilP[1], 2) +
217 pow(recoilP[2], 2));
218 }
219 }
220 }
221
222 // Find target SP hit for recoil electron
223 if (event.exists("TargetScoringPlaneHits")) {
224 std::vector<ldmx::SimTrackerHit> targetSpHits =
225 event.getCollection<ldmx::SimTrackerHit>("TargetScoringPlaneHits");
226 pmax = 0;
227 for (ldmx::SimTrackerHit &spHit : targetSpHits) {
228 ldmx::SimSpecialID hit_id(spHit.getID());
229 if (hit_id.plane() != 1 || spHit.getMomentum()[2] <= 0) continue;
230
231 if (spHit.getTrackID() == recoilTrackID) {
232 if (sqrt(pow(spHit.getMomentum()[0], 2) +
233 pow(spHit.getMomentum()[1], 2) +
234 pow(spHit.getMomentum()[2], 2)) > pmax) {
235 recoilPAtTarget = spHit.getMomentum();
236 recoilPosAtTarget = spHit.getPosition();
237 pmax =
238 sqrt(pow(recoilPAtTarget[0], 2) + pow(recoilPAtTarget[1], 2) +
239 pow(recoilPAtTarget[2], 2));
240 }
241 }
242 }
243 }
244 }
245
246 if (verbose_) {
247 ldmx_log(debug) << " Get projected trajectories for electron and photon";
248 }
249 // Get projected trajectories for electron and photon
250 std::vector<XYCoords> ele_trajectory, photon_trajectory;
251 if (recoilP.size() > 0) {
252 ele_trajectory = getTrajectory(recoilP, recoilPos);
253 std::vector<double> pvec = recoilPAtTarget.size()
254 ? recoilPAtTarget
255 : std::vector<double>{0.0, 0.0, 0.0};
256 std::vector<float> posvec = recoilPosAtTarget.size()
257 ? recoilPosAtTarget
258 : std::vector<float>{0.0, 0.0, 0.0};
259 photon_trajectory =
260 getTrajectory({-pvec[0], -pvec[1], beamEnergyMeV_ - pvec[2]}, posvec);
261 }
262
263 float recoilPMag =
264 recoilP.size()
265 ? sqrt(pow(recoilP[0], 2) + pow(recoilP[1], 2) + pow(recoilP[2], 2))
266 : -1.0;
267 float recoilTheta =
268 recoilPMag > 0 ? acos(recoilP[2] / recoilPMag) * 180.0 / M_PI : -1.0;
269
270 if (verbose_) {
271 ldmx_log(debug) << " Build Radii of containment (ROC)";
272 }
273 // Use the appropriate containment radii for the recoil electron
274 std::vector<double> roc_values_bin0(roc_range_values_[0].begin() + 4,
275 roc_range_values_[0].end());
276 std::vector<double> ele_radii = roc_values_bin0;
277 double theta_min, theta_max, p_min, p_max;
278 bool inrange;
279
280 for (int i = 0; i < roc_range_values_.size(); i++) {
281 theta_min = roc_range_values_[i][0];
282 theta_max = roc_range_values_[i][1];
283 p_min = roc_range_values_[i][2];
284 p_max = roc_range_values_[i][3];
285 inrange = true;
286
287 if (theta_min != -1.0) {
288 inrange = inrange && (recoilTheta >= theta_min);
289 }
290 if (theta_max != -1.0) {
291 inrange = inrange && (recoilTheta < theta_max);
292 }
293 if (p_min != -1.0) {
294 inrange = inrange && (recoilPMag >= p_min);
295 }
296 if (p_max != -1.0) {
297 inrange = inrange && (recoilPMag < p_max);
298 }
299 if (inrange) {
300 std::vector<double> roc_values_bini(roc_range_values_[i].begin() + 4,
301 roc_range_values_[i].end());
302 ele_radii = roc_values_bini;
303 }
304 }
305 // Use default RoC bin for photon
306 std::vector<double> photon_radii = roc_values_bin0;
307
308 // Get the collection of digitized Ecal hits from the event.
309 const std::vector<ldmx::EcalHit> ecalRecHits =
310 event.getCollection<ldmx::EcalHit>(rec_coll_name_, rec_pass_name_);
311
312 ldmx::EcalID globalCentroid =
313 GetShowerCentroidIDAndRMS(ecalRecHits, showerRMS_);
314 /* ~~ Fill the hit map ~~ O(n) */
315 fillHitMap(ecalRecHits, cellMap_);
316 bool doTight = true;
317 /* ~~ Fill the isolated hit maps ~~ O(n) */
318 fillIsolatedHitMap(ecalRecHits, globalCentroid, cellMap_, cellMapTightIso_,
319 doTight);
320
321 // Loop over the hits from the event to calculate the rest of the important
322 // quantities
323
324 float wavgLayerHit = 0;
325 float xMean = 0;
326 float yMean = 0;
327
328 // Containment variables
329 unsigned int nregions = 5;
330 std::vector<float> electronContainmentEnergy(nregions, 0.0);
331 std::vector<float> photonContainmentEnergy(nregions, 0.0);
332 std::vector<float> outsideContainmentEnergy(nregions, 0.0);
333 std::vector<int> outsideContainmentNHits(nregions, 0);
334 std::vector<float> outsideContainmentXmean(nregions, 0.0);
335 std::vector<float> outsideContainmentYmean(nregions, 0.0);
336 std::vector<float> outsideContainmentXstd(nregions, 0.0);
337 std::vector<float> outsideContainmentYstd(nregions, 0.0);
338 // Longitudinal segmentation
339 std::vector<int> segLayers = {0, 6, 17, 34};
340 unsigned int nsegments = segLayers.size() - 1;
341 std::vector<float> energySeg(nsegments, 0.0);
342 std::vector<float> xMeanSeg(nsegments, 0.0);
343 std::vector<float> xStdSeg(nsegments, 0.0);
344 std::vector<float> yMeanSeg(nsegments, 0.0);
345 std::vector<float> yStdSeg(nsegments, 0.0);
346 std::vector<float> layerMeanSeg(nsegments, 0.0);
347 std::vector<float> layerStdSeg(nsegments, 0.0);
348 std::vector<std::vector<float>> eContEnergy(
349 nregions, std::vector<float>(nsegments, 0.0));
350 std::vector<std::vector<float>> eContXMean(
351 nregions, std::vector<float>(nsegments, 0.0));
352 std::vector<std::vector<float>> eContYMean(
353 nregions, std::vector<float>(nsegments, 0.0));
354 std::vector<std::vector<float>> gContEnergy(
355 nregions, std::vector<float>(nsegments, 0.0));
356 std::vector<std::vector<int>> gContNHits(nregions,
357 std::vector<int>(nsegments, 0));
358 std::vector<std::vector<float>> gContXMean(
359 nregions, std::vector<float>(nsegments, 0.0));
360 std::vector<std::vector<float>> gContYMean(
361 nregions, std::vector<float>(nsegments, 0.0));
362 std::vector<std::vector<float>> oContEnergy(
363 nregions, std::vector<float>(nsegments, 0.0));
364 std::vector<std::vector<int>> oContNHits(nregions,
365 std::vector<int>(nsegments, 0));
366 std::vector<std::vector<float>> oContXMean(
367 nregions, std::vector<float>(nsegments, 0.0));
368 std::vector<std::vector<float>> oContYMean(
369 nregions, std::vector<float>(nsegments, 0.0));
370 std::vector<std::vector<float>> oContXStd(nregions,
371 std::vector<float>(nsegments, 0.0));
372 std::vector<std::vector<float>> oContYStd(nregions,
373 std::vector<float>(nsegments, 0.0));
374 std::vector<std::vector<float>> oContLayerMean(
375 nregions, std::vector<float>(nsegments, 0.0));
376 std::vector<std::vector<float>> oContLayerStd(
377 nregions, std::vector<float>(nsegments, 0.0));
378
379 // MIP tracking: vector of hits to be used in the MIP tracking algorithm. All
380 // hits inside the electron ROC (or all hits in the ECal if the event is
381 // missing an electron) will be included.
382 std::vector<HitData> trackingHitList;
383
384 if (verbose_) {
385 ldmx_log(debug)
386 << " Loop over the hits from the event to calculate the BDT features";
387 }
388
389 for (const ldmx::EcalHit &hit : ecalRecHits) {
390 // Layer-wise quantities
391 ldmx::EcalID id(hit.getID());
392 ecalLayerEdepRaw_[id.layer()] =
393 ecalLayerEdepRaw_[id.layer()] + hit.getEnergy();
394 if (id.layer() >= 20) ecalBackEnergy_ += hit.getEnergy();
395 if (maxCellDep_ < hit.getEnergy()) maxCellDep_ = hit.getEnergy();
396 if (hit.getEnergy() > 0) {
397 nReadoutHits_++;
398 ecalLayerEdepReadout_[id.layer()] += hit.getEnergy();
399 ecalLayerTime_[id.layer()] += (hit.getEnergy()) * hit.getTime();
400 auto [x, y, z] = geometry_->getPosition(id);
401 xMean += x * hit.getEnergy();
402 yMean += y * hit.getEnergy();
403 avgLayerHit_ += id.layer();
404 wavgLayerHit += id.layer() * hit.getEnergy();
405 if (deepestLayerHit_ < id.layer()) {
406 deepestLayerHit_ = id.layer();
407 }
408 XYCoords xy_pair = std::make_pair(x, y);
409 float distance_ele_trajectory =
410 ele_trajectory.size()
411 ? sqrt(
412 pow((xy_pair.first - ele_trajectory[id.layer()].first), 2) +
413 pow((xy_pair.second - ele_trajectory[id.layer()].second),
414 2))
415 : -1.0;
416 float distance_photon_trajectory =
417 photon_trajectory.size()
418 ? sqrt(
419 pow((xy_pair.first - photon_trajectory[id.layer()].first),
420 2) +
421 pow((xy_pair.second - photon_trajectory[id.layer()].second),
422 2))
423 : -1.0;
424
425 // Decide which longitudinal segment the hit is in and add to sums
426 for (unsigned int iseg = 0; iseg < nsegments; iseg++) {
427 if (id.layer() >= segLayers[iseg] &&
428 id.layer() <= segLayers[iseg + 1] - 1) {
429 energySeg[iseg] += hit.getEnergy();
430 xMeanSeg[iseg] += xy_pair.first * hit.getEnergy();
431 yMeanSeg[iseg] += xy_pair.second * hit.getEnergy();
432 layerMeanSeg[iseg] += id.layer() * hit.getEnergy();
433
434 // Decide which containment region the hit is in and add to sums
435 for (unsigned int ireg = 0; ireg < nregions; ireg++) {
436 if (distance_ele_trajectory >= ireg * ele_radii[id.layer()] &&
437 distance_ele_trajectory < (ireg + 1) * ele_radii[id.layer()]) {
438 eContEnergy[ireg][iseg] += hit.getEnergy();
439 eContXMean[ireg][iseg] += xy_pair.first * hit.getEnergy();
440 eContYMean[ireg][iseg] += xy_pair.second * hit.getEnergy();
441 }
442 if (distance_photon_trajectory >= ireg * photon_radii[id.layer()] &&
443 distance_photon_trajectory <
444 (ireg + 1) * photon_radii[id.layer()]) {
445 gContEnergy[ireg][iseg] += hit.getEnergy();
446 gContNHits[ireg][iseg] += 1;
447 gContXMean[ireg][iseg] += xy_pair.first * hit.getEnergy();
448 gContYMean[ireg][iseg] += xy_pair.second * hit.getEnergy();
449 }
450 if (distance_ele_trajectory > (ireg + 1) * ele_radii[id.layer()] &&
451 distance_photon_trajectory >
452 (ireg + 1) * photon_radii[id.layer()]) {
453 oContEnergy[ireg][iseg] += hit.getEnergy();
454 oContNHits[ireg][iseg] += 1;
455 oContXMean[ireg][iseg] += xy_pair.first * hit.getEnergy();
456 oContYMean[ireg][iseg] += xy_pair.second * hit.getEnergy();
457 oContLayerMean[ireg][iseg] += id.layer() * hit.getEnergy();
458 }
459 }
460 }
461 }
462
463 // Decide which containment region the hit is in and add to sums
464 for (unsigned int ireg = 0; ireg < nregions; ireg++) {
465 if (distance_ele_trajectory >= ireg * ele_radii[id.layer()] &&
466 distance_ele_trajectory < (ireg + 1) * ele_radii[id.layer()])
467 electronContainmentEnergy[ireg] += hit.getEnergy();
468 if (distance_photon_trajectory >= ireg * photon_radii[id.layer()] &&
469 distance_photon_trajectory < (ireg + 1) * photon_radii[id.layer()])
470 photonContainmentEnergy[ireg] += hit.getEnergy();
471 if (distance_ele_trajectory > (ireg + 1) * ele_radii[id.layer()] &&
472 distance_photon_trajectory >
473 (ireg + 1) * photon_radii[id.layer()]) {
474 outsideContainmentEnergy[ireg] += hit.getEnergy();
475 outsideContainmentNHits[ireg] += 1;
476 outsideContainmentXmean[ireg] += xy_pair.first * hit.getEnergy();
477 outsideContainmentYmean[ireg] += xy_pair.second * hit.getEnergy();
478 }
479 }
480
481 // MIP tracking: Decide whether hit should be added to trackingHitList
482 // If inside e- RoC or if etraj is missing, use the hit for tracking:
483 if (distance_ele_trajectory >= ele_radii[id.layer()] ||
484 distance_ele_trajectory == -1.0) {
485 HitData hd;
486 hd.pos = TVector3(xy_pair.first, xy_pair.second,
487 geometry_->getZPosition(id.layer()));
488 hd.layer = id.layer();
489 trackingHitList.push_back(hd);
490 }
491 }
492 } // end loop over rechits
493
494 for (const auto &[id, energy] : cellMapTightIso_) {
495 if (energy > 0) summedTightIso_ += energy;
496 }
497
498 for (int iLayer = 0; iLayer < ecalLayerEdepReadout_.size(); iLayer++) {
499 ecalLayerTime_[iLayer] =
500 ecalLayerTime_[iLayer] / ecalLayerEdepReadout_[iLayer];
501 summedDet_ += ecalLayerEdepReadout_[iLayer];
502 }
503
504 if (nReadoutHits_ > 0) {
505 avgLayerHit_ /= nReadoutHits_;
506 wavgLayerHit /= summedDet_;
507 xMean /= summedDet_;
508 yMean /= summedDet_;
509 } else {
510 wavgLayerHit = 0;
511 avgLayerHit_ = 0;
512 xMean = 0;
513 yMean = 0;
514 }
515
516 // If necessary, quotient out the total energy from the means
517 for (unsigned int iseg = 0; iseg < nsegments; iseg++) {
518 if (energySeg[iseg] > 0) {
519 xMeanSeg[iseg] /= energySeg[iseg];
520 yMeanSeg[iseg] /= energySeg[iseg];
521 layerMeanSeg[iseg] /= energySeg[iseg];
522 }
523 for (unsigned int ireg = 0; ireg < nregions; ireg++) {
524 if (eContEnergy[ireg][iseg] > 0) {
525 eContXMean[ireg][iseg] /= eContEnergy[ireg][iseg];
526 eContYMean[ireg][iseg] /= eContEnergy[ireg][iseg];
527 }
528 if (gContEnergy[ireg][iseg] > 0) {
529 gContXMean[ireg][iseg] /= gContEnergy[ireg][iseg];
530 gContYMean[ireg][iseg] /= gContEnergy[ireg][iseg];
531 }
532 if (oContEnergy[ireg][iseg] > 0) {
533 oContXMean[ireg][iseg] /= oContEnergy[ireg][iseg];
534 oContYMean[ireg][iseg] /= oContEnergy[ireg][iseg];
535 oContLayerMean[ireg][iseg] /= oContEnergy[ireg][iseg];
536 }
537 }
538 }
539
540 for (unsigned int ireg = 0; ireg < nregions; ireg++) {
541 if (outsideContainmentEnergy[ireg] > 0) {
542 outsideContainmentXmean[ireg] /= outsideContainmentEnergy[ireg];
543 outsideContainmentYmean[ireg] /= outsideContainmentEnergy[ireg];
544 }
545 }
546
547 // Loop over hits a second time to find the standard deviations.
548 for (const ldmx::EcalHit &hit : ecalRecHits) {
549 ldmx::EcalID id(hit.getID());
550 auto [x, y, z] = geometry_->getPosition(id);
551 if (hit.getEnergy() > 0) {
552 xStd_ += pow((x - xMean), 2) * hit.getEnergy();
553 yStd_ += pow((y - yMean), 2) * hit.getEnergy();
554 stdLayerHit_ += pow((id.layer() - wavgLayerHit), 2) * hit.getEnergy();
555 }
556 XYCoords xy_pair = std::make_pair(x, y);
557 float distance_ele_trajectory =
558 ele_trajectory.size()
559 ? sqrt(pow((xy_pair.first - ele_trajectory[id.layer()].first), 2) +
560 pow((xy_pair.second - ele_trajectory[id.layer()].second), 2))
561 : -1.0;
562 float distance_photon_trajectory =
563 photon_trajectory.size()
564 ? sqrt(pow((xy_pair.first - photon_trajectory[id.layer()].first),
565 2) +
566 pow((xy_pair.second - photon_trajectory[id.layer()].second),
567 2))
568 : -1.0;
569
570 for (unsigned int iseg = 0; iseg < nsegments; iseg++) {
571 if (id.layer() >= segLayers[iseg] &&
572 id.layer() <= segLayers[iseg + 1] - 1) {
573 xStdSeg[iseg] +=
574 pow((xy_pair.first - xMeanSeg[iseg]), 2) * hit.getEnergy();
575 yStdSeg[iseg] +=
576 pow((xy_pair.second - yMeanSeg[iseg]), 2) * hit.getEnergy();
577 layerStdSeg[iseg] +=
578 pow((id.layer() - layerMeanSeg[iseg]), 2) * hit.getEnergy();
579
580 for (unsigned int ireg = 0; ireg < nregions; ireg++) {
581 if (distance_ele_trajectory > (ireg + 1) * ele_radii[id.layer()] &&
582 distance_photon_trajectory >
583 (ireg + 1) * photon_radii[id.layer()]) {
584 oContXStd[ireg][iseg] +=
585 pow((xy_pair.first - oContXMean[ireg][iseg]), 2) *
586 hit.getEnergy();
587 oContYStd[ireg][iseg] +=
588 pow((xy_pair.second - oContYMean[ireg][iseg]), 2) *
589 hit.getEnergy();
590 oContLayerStd[ireg][iseg] +=
591 pow((id.layer() - oContLayerMean[ireg][iseg]), 2) *
592 hit.getEnergy();
593 }
594 }
595 }
596 }
597
598 for (unsigned int ireg = 0; ireg < nregions; ireg++) {
599 if (distance_ele_trajectory > (ireg + 1) * ele_radii[id.layer()] &&
600 distance_photon_trajectory > (ireg + 1) * photon_radii[id.layer()]) {
601 outsideContainmentXstd[ireg] +=
602 pow((xy_pair.first - outsideContainmentXmean[ireg]), 2) *
603 hit.getEnergy();
604 outsideContainmentYstd[ireg] +=
605 pow((xy_pair.second - outsideContainmentYmean[ireg]), 2) *
606 hit.getEnergy();
607 }
608 }
609 } // end loop over rechits (2nd time)
610
611 if (nReadoutHits_ > 0) {
612 xStd_ = sqrt(xStd_ / summedDet_);
613 yStd_ = sqrt(yStd_ / summedDet_);
614 stdLayerHit_ = sqrt(stdLayerHit_ / summedDet_);
615 } else {
616 xStd_ = 0;
617 yStd_ = 0;
618 stdLayerHit_ = 0;
619 }
620
621 // Quotient out the total energies from the standard deviations if possible
622 // and take root
623 for (unsigned int iseg = 0; iseg < nsegments; iseg++) {
624 if (energySeg[iseg] > 0) {
625 xStdSeg[iseg] = sqrt(xStdSeg[iseg] / energySeg[iseg]);
626 yStdSeg[iseg] = sqrt(yStdSeg[iseg] / energySeg[iseg]);
627 layerStdSeg[iseg] = sqrt(layerStdSeg[iseg] / energySeg[iseg]);
628 }
629 for (unsigned int ireg = 0; ireg < nregions; ireg++) {
630 if (oContEnergy[ireg][iseg] > 0) {
631 oContXStd[ireg][iseg] =
632 sqrt(oContXStd[ireg][iseg] / oContEnergy[ireg][iseg]);
633 oContYStd[ireg][iseg] =
634 sqrt(oContYStd[ireg][iseg] / oContEnergy[ireg][iseg]);
635 oContLayerStd[ireg][iseg] =
636 sqrt(oContLayerStd[ireg][iseg] / oContEnergy[ireg][iseg]);
637 }
638 }
639 }
640
641 for (unsigned int ireg = 0; ireg < nregions; ireg++) {
642 if (outsideContainmentEnergy[ireg] > 0) {
643 outsideContainmentXstd[ireg] =
644 sqrt(outsideContainmentXstd[ireg] / outsideContainmentEnergy[ireg]);
645 outsideContainmentYstd[ireg] =
646 sqrt(outsideContainmentYstd[ireg] / outsideContainmentEnergy[ireg]);
647 }
648 }
649
650 if (verbose_) {
651 ldmx_log(debug) << " Find out if the recoil electron is fiducial";
652 }
653
654 // Find the location of the recoil electron
655 // Ecal face is not where the first layer starts,
656 // defined in DetDescr/python/EcalGeometry.py
657 const float dz_from_face{7.932};
658 float drifted_recoil_x{-9999.};
659 float drifted_recoil_y{-9999.};
660 if (recoilP.size() > 0) {
661 drifted_recoil_x =
662 (dz_from_face * (recoilP[0] / recoilP[2])) + recoilPos[0];
663 drifted_recoil_y =
664 (dz_from_face * (recoilP[1] / recoilP[2])) + recoilPos[1];
665 }
666 const int recoil_layer_index = 0;
667
668 // Check if it's fiducial
669 bool inside{false};
670 // At module level
671 const auto ecalID = geometry_->getID(drifted_recoil_x, drifted_recoil_y,
672 recoil_layer_index, true);
673 if (!ecalID.null()) {
674 // If fiducial at module level, check at cell level
675 const auto cellID =
676 geometry_->getID(drifted_recoil_x, drifted_recoil_y, recoil_layer_index,
677 ecalID.getModuleID(), true);
678 if (!cellID.null()) {
679 inside = true;
680 }
681 }
682
683 if (!inside) {
684 ldmx_log(info) << "This event is non-fiducial in ECAL";
685 }
686
687 // ------------------------------------------------------
688 // MIP tracking starts here
689
690 /* Goal: Calculate
691 * nStraightTracks (self-explanatory),
692 * nLinregTracks (tracks found by linreg algorithm),
693 */
694
695 // Find epAng and epSep, and prepare EP trajectory vectors:
696 TVector3 e_traj_start;
697 TVector3 e_traj_end;
698 TVector3 p_traj_start;
699 TVector3 p_traj_end;
700 if (ele_trajectory.size() > 0 && photon_trajectory.size() > 0) {
701 // Create TVector3s marking the start and endpoints of each projected
702 // trajectory
703 e_traj_start.SetXYZ(ele_trajectory[0].first, ele_trajectory[0].second,
705 e_traj_end.SetXYZ(ele_trajectory[(nEcalLayers_ - 1)].first,
706 ele_trajectory[(nEcalLayers_ - 1)].second,
707 geometry_->getZPosition((nEcalLayers_ - 1)));
708 p_traj_start.SetXYZ(photon_trajectory[0].first, photon_trajectory[0].second,
710 p_traj_end.SetXYZ(photon_trajectory[(nEcalLayers_ - 1)].first,
711 photon_trajectory[(nEcalLayers_ - 1)].second,
712 geometry_->getZPosition((nEcalLayers_ - 1)));
713
714 TVector3 evec = e_traj_end - e_traj_start;
715 TVector3 e_norm = evec.Unit();
716 TVector3 pvec = p_traj_end - p_traj_start;
717 TVector3 p_norm = pvec.Unit();
718 epDot_ = e_norm.Dot(p_norm);
719 epAng_ = acos(epDot_) * 180.0 / M_PI;
720 epSep_ = sqrt(pow(e_traj_start.X() - p_traj_start.X(), 2) +
721 pow(e_traj_start.Y() - p_traj_start.Y(), 2));
722 } else {
723 // Electron trajectory is missing, so all hits in the Ecal are fair game.
724 // Pick e/ptraj so that they won't restrict the tracking algorithm (place
725 // them far outside the ECal).
726 e_traj_start = TVector3(999, 999, geometry_->getZPosition(0)); // 0);
727 e_traj_end = TVector3(
728 999, 999, geometry_->getZPosition((nEcalLayers_ - 1))); // 999);
729 p_traj_start = TVector3(1000, 1000, geometry_->getZPosition(0)); // 0);
730 p_traj_end = TVector3(
731 1000, 1000, geometry_->getZPosition((nEcalLayers_ - 1))); // 1000);
732 /*ensures event will not be vetoed by angle/separation cut */
733 epAng_ = 999.;
734 epSep_ = 999.;
735 epDot_ = 999.;
736 }
737
738 // Near photon step: Find the first layer of the ECal where a hit near the
739 // projected photon trajectory is found Currently unusued pending further
740 // study; performance has dropped between v9 and v12. Currently used in
741 // segmipBDT
742 firstNearPhLayer_ = nEcalLayers_ - 1;
743
744 if (photon_trajectory.size() !=
745 0) { // If no photon trajectory, leave this at the default (ECal back)
746 for (std::vector<HitData>::iterator it = trackingHitList.begin();
747 it != trackingHitList.end(); ++it) {
748 float ehDist =
749 sqrt(pow((*it).pos.X() - photon_trajectory[(*it).layer].first, 2) +
750 pow((*it).pos.Y() - photon_trajectory[(*it).layer].second, 2));
751 if (ehDist < 8.7) {
752 nNearPhHits_++;
753 if ((*it).layer < firstNearPhLayer_) {
754 firstNearPhLayer_ = (*it).layer;
755 }
756 }
757 }
758 }
759
760 // Territories limited to trackingHitList
761 TVector3 gToe = (e_traj_start - p_traj_start).Unit();
762 TVector3 origin = p_traj_start + 0.5 * 8.7 * gToe;
763 if (ele_trajectory.size() > 0) {
764 for (auto &hitData : trackingHitList) {
765 TVector3 hitPos = hitData.pos;
766 TVector3 hitPrime = hitPos - origin;
767 if (hitPrime.Dot(gToe) <= 0) {
769 }
770 }
771 } else {
772 photonTerritoryHits_ = nReadoutHits_;
773 }
774
775 // ------------------------------------------------------
776 // Find straight MIP tracks:
777
778 std::sort(trackingHitList.begin(), trackingHitList.end(),
779 [](HitData ha, HitData hb) { return ha.layer > hb.layer; });
780 // For merging tracks: Need to keep track of existing tracks
781 // Candidate tracks to merge in will always be in front of the current track
782 // (lower z), so only store the last hit 3-layer vector: each track = vector
783 // of 3-tuples (xy+layer).
784 std::vector<std::vector<HitData>> track_list;
785
786 // print trackingHitList
787 if (verbose_) {
788 ldmx_log(debug) << "====== Tracking hit list (original) length "
789 << trackingHitList.size() << " ======";
790 for (int i = 0; i < trackingHitList.size(); i++) {
791 std::cout << "[" << trackingHitList[i].pos.X() << ", "
792 << trackingHitList[i].pos.Y() << ", "
793 << trackingHitList[i].layer << "], ";
794 }
795 std::cout << std::endl;
796 ldmx_log(debug) << "====== END OF Tracking hit list ======";
797 }
798
799 // in v14 minR is 4.17 mm
800 // while maxR is 4.81 mm
801 float cellWidth = 2 * geometry_->getCellMaxR();
802 for (int iHit = 0; iHit < trackingHitList.size(); iHit++) {
803 // list of hit numbers in track (34 = maximum theoretical length)
804 int track[34];
805 int currenthit{iHit};
806 int trackLen{1};
807
808 track[0] = iHit;
809
810 // Search for hits to add to the track:
811 // repeatedly find hits in the front two layers with same x & y positions
812 // but since v14 the odd layers are offset, so we allow half a cellWidth
813 // deviation and then add to track until no more hits are found
814 int jHit = iHit;
815 while (jHit < trackingHitList.size()) {
816 if ((trackingHitList[jHit].layer ==
817 trackingHitList[currenthit].layer - 1 ||
818 trackingHitList[jHit].layer ==
819 trackingHitList[currenthit].layer - 2) &&
820 abs(trackingHitList[jHit].pos.X() -
821 trackingHitList[currenthit].pos.X()) <= 0.5 * cellWidth &&
822 abs(trackingHitList[jHit].pos.Y() -
823 trackingHitList[currenthit].pos.Y()) <= 0.5 * cellWidth) {
824 track[trackLen] = jHit;
825 trackLen++;
826 currenthit = jHit;
827 }
828 jHit++;
829 }
830
831 // Confirm that the track is valid:
832 if (trackLen < 2) continue; // Track must contain at least 2 hits
833 float closest_e = distTwoLines(trackingHitList[track[0]].pos,
834 trackingHitList[track[trackLen - 1]].pos,
835 e_traj_start, e_traj_end);
836 float closest_p = distTwoLines(trackingHitList[track[0]].pos,
837 trackingHitList[track[trackLen - 1]].pos,
838 p_traj_start, p_traj_end);
839 // Make sure that the track is near the photon trajectory and away from the
840 // electron trajectory Details of these constraints may be revised
841 if (closest_p > cellWidth and closest_e < 2 * cellWidth) continue;
842 if (trackLen < 4 and closest_e > closest_p) continue;
843 if (verbose_) {
844 ldmx_log(debug) << "====== After rejection for MIP tracking ======";
845 ldmx_log(debug) << "current hit: [" << trackingHitList[iHit].pos.X()
846 << ", " << trackingHitList[iHit].pos.Y() << ", "
847 << trackingHitList[iHit].layer << "]";
848
849 for (int k = 0; k < trackLen; k++) {
850 ldmx_log(debug) << "track[" << k << "] position = ["
851 << trackingHitList[track[k]].pos.X() << ", "
852 << trackingHitList[track[k]].pos.Y() << ", "
853 << trackingHitList[track[k]].layer << "]";
854 }
855 }
856
857 // if track found, increment nStraightTracks and remove all hits in track
858 // from future consideration
859 if (trackLen >= 2) {
860 std::vector<HitData> temp_track_list;
861 int n_remove = 0;
862 for (int kHit = 0; kHit < trackLen; kHit++) {
863 temp_track_list.push_back(trackingHitList[track[kHit] - n_remove]);
864 trackingHitList.erase(trackingHitList.begin() + track[kHit] - n_remove);
865 n_remove++;
866 }
867 // print trackingHitList
868 if (verbose_) {
869 ldmx_log(debug) << "====== Tracking hit list (after erase) length "
870 << trackingHitList.size() << " ======";
871 for (int i = 0; i < trackingHitList.size(); i++) {
872 std::cout << "[" << trackingHitList[i].pos.X() << ", "
873 << trackingHitList[i].pos.Y() << ", "
874 << trackingHitList[i].layer << "] ";
875 }
876 std::cout << std::endl;
877 ldmx_log(debug) << "====== END OF Tracking hit list ======";
878 }
879
880 track_list.push_back(temp_track_list);
881 // The *current* hit will have been removed, so iHit is currently pointing
882 // to the next hit. Decrement iHit so no hits will get skipped by iHit++
883 iHit--;
884 }
885 }
886
887 ldmx_log(debug) << "Straight tracks found (before merge): "
888 << track_list.size();
889 if (verbose_) {
890 for (int iTrack = 0; iTrack < track_list.size(); iTrack++) {
891 ldmx_log(debug) << "Track " << iTrack << ":";
892 for (int iHit = 0; iHit < track_list[iTrack].size(); iHit++) {
893 std::cout << " Hit " << iHit << ": ["
894 << track_list[iTrack][iHit].pos.X() << ", "
895 << track_list[iTrack][iHit].pos.Y() << ", "
896 << track_list[iTrack][iHit].layer << "]" << std::endl;
897 }
898 std::cout << std::endl;
899 }
900 }
901
902 // Optional addition: Merge nearby straight tracks. Not necessary for veto.
903 // Criteria: consider tail of track. Merge if head of next track is 1/2
904 // layers behind, within 1 cell of xy position.
905 ldmx_log(debug) << "Beginning track merging using " << track_list.size()
906 << " tracks";
907
908 for (int track_i = 0; track_i < track_list.size(); track_i++) {
909 // for each track, check the remainder of the track list for compatible
910 // tracks
911 std::vector<HitData> base_track = track_list[track_i];
912 HitData tail_hitdata = base_track.back(); // xylayer of last hit in track
913 if (verbose_) ldmx_log(debug) << " Considering track " << track_i;
914 for (int track_j = track_i + 1; track_j < track_list.size(); track_j++) {
915 std::vector<HitData> checking_track = track_list[track_j];
916 HitData head_hitdata = checking_track.front();
917 // if 1-2 layers behind, and xy within one cell...
918 if ((head_hitdata.layer == tail_hitdata.layer + 1 ||
919 head_hitdata.layer == tail_hitdata.layer + 2) &&
920 pow(pow(head_hitdata.pos.X() - tail_hitdata.pos.X(), 2) +
921 pow(head_hitdata.pos.Y() - tail_hitdata.pos.Y(), 2),
922 0.5) <= cellWidth) {
923 // ...then append the second track to the first one and delete it
924 // NOTE: TO ADD: (trackingHitList[iHit].pos -
925 // trackingHitList[jHit].pos).Mag()
926 if (verbose_) {
927 ldmx_log(debug) << " ** Compatible track found at index "
928 << track_j;
929 ldmx_log(debug) << " Tail xylayer: " << head_hitdata.pos.X()
930 << "," << head_hitdata.pos.Y() << ","
931 << head_hitdata.layer;
932 ldmx_log(debug) << " Head xylayer: " << tail_hitdata.pos.X()
933 << "," << tail_hitdata.pos.Y() << ","
934 << tail_hitdata.layer;
935 }
936 for (int hit_k = 0; hit_k < checking_track.size(); hit_k++) {
937 base_track.push_back(track_list[track_j][hit_k]);
938 }
939 track_list[track_i] = base_track;
940 track_list.erase(track_list.begin() + track_j);
941 break;
942 }
943 }
944 }
945 nStraightTracks_ = track_list.size();
946 // print the track list
947 ldmx_log(debug) << "Straight tracks found (after merge): "
949 for (int track_i = 0; track_i < track_list.size(); track_i++) {
950 ldmx_log(debug) << "Track " << track_i << ":";
951 for (int hit_i = 0; hit_i < track_list[track_i].size(); hit_i++) {
952 ldmx_log(debug) << " Hit " << hit_i << ": ["
953 << track_list[track_i][hit_i].pos.X() << ", "
954 << track_list[track_i][hit_i].pos.Y() << ", "
955 << track_list[track_i][hit_i].layer << "]";
956 }
957 }
958
959 // ------------------------------------------------------
960 // Linreg tracking:
961 ldmx_log(debug) << "Finding linreg tracks from a total of "
962 << trackingHitList.size() << " hits using a radius of "
963 << linreg_radius_ << " mm";
964
965 for (int iHit = 0; iHit < trackingHitList.size(); iHit++) {
966 // Hits being considered at a given time
967 std::vector<int> hitsInRegion;
968 TMatrixD Vm(3, 3);
969 TMatrixD hdt(3, 3);
970 TVector3 slopeVec;
971 TVector3 hmean;
972 TVector3 hpoint;
973 float r_corr_best{0.0};
974 // Temp array having 3 potential hits
975 int hitNums[3];
976 // From the above which are passing the correlation reqs
977 int bestHitNums[3];
978
979 hitsInRegion.push_back(iHit);
980 // Find all hits within 2 cells of the primary hit:
981 for (int jHit = 0; jHit < trackingHitList.size(); jHit++) {
982 // Dont try to put hits on the same layer to the lin-reg track
983 if (trackingHitList[iHit].pos(2) == trackingHitList[jHit].pos(2)) {
984 continue;
985 }
986 float dstToHit =
987 (trackingHitList[iHit].pos - trackingHitList[jHit].pos).Mag();
988 // This distance optimized to give the best significance
989 // it used to be 2*cellWidth, i.e. 4.81 mm
990 // note, the layers in the back have a separation of 22.3
991 if (dstToHit <= 2 * linreg_radius_) {
992 hitsInRegion.push_back(jHit);
993 }
994 }
995 // Found a track that passed the lin-reg reqs
996 bool bestLinRegFound{false};
997 if (verbose_) {
998 ldmx_log(debug) << "There are " << hitsInRegion.size()
999 << " hits within a radius of " << linreg_radius_ << " mm";
1000 }
1001 // Look at combinations of hits within the region (do not consider the same
1002 // combination twice):
1003 hitNums[0] = iHit;
1004 for (int jHitInReg = 1; jHitInReg < hitsInRegion.size() - 1; jHitInReg++) {
1005 // We require (exactly) 3 hits for the lin-reg track building
1006 if (hitsInRegion.size() < 3) break;
1007 hitNums[1] = hitsInRegion[jHitInReg];
1008 for (int kHitReg = jHitInReg + 1; kHitReg < hitsInRegion.size();
1009 kHitReg++) {
1010 hitNums[2] = hitsInRegion[kHitReg];
1011 for (int hInd = 0; hInd < 3; hInd++) {
1012 // hmean = geometric mean, subtract off from hits to improve SVD
1013 // performance
1014 hmean(hInd) = (trackingHitList[hitNums[0]].pos(hInd) +
1015 trackingHitList[hitNums[1]].pos(hInd) +
1016 trackingHitList[hitNums[2]].pos(hInd)) /
1017 3.0;
1018 }
1019 for (int hInd = 0; hInd < 3; hInd++) {
1020 for (int lInd = 0; lInd < 3; lInd++) {
1021 hdt(hInd, lInd) =
1022 trackingHitList[hitNums[hInd]].pos(lInd) - hmean(lInd);
1023 }
1024 }
1025
1026 // Perform "linreg" on selected points
1027 // Calculate the determinant of the matrix
1028 double determinant =
1029 hdt(0, 0) * (hdt(1, 1) * hdt(2, 2) - hdt(1, 2) * hdt(2, 1)) -
1030 hdt(0, 1) * (hdt(1, 0) * hdt(2, 2) - hdt(1, 2) * hdt(2, 0)) +
1031 hdt(0, 2) * (hdt(1, 0) * hdt(2, 1) - hdt(1, 1) * hdt(2, 0));
1032 // Exit early if the matrix is singular (i.e. det = 0)
1033 if (determinant == 0) continue;
1034 // Perform matrix decomposition with SVD
1035 TDecompSVD svdObj(hdt);
1036 bool decomposed = svdObj.Decompose();
1037 if (!decomposed) continue;
1038
1039 // First col of V matrix is the slope of the best-fit line
1040 Vm = svdObj.GetV();
1041 for (int hInd = 0; hInd < 3; hInd++) {
1042 slopeVec(hInd) = Vm[0][hInd];
1043 }
1044 // hmean, hpoint are points on the best-fit line
1045 hpoint = slopeVec + hmean;
1046 // linreg complete: Now have best-fit line for 3 hits under
1047 // consideration Check whether the track is valid: r^2 must be high,
1048 // and the track must plausibly originate from the photon
1049 float closest_e = distTwoLines(hmean, hpoint, e_traj_start, e_traj_end);
1050 float closest_p = distTwoLines(hmean, hpoint, p_traj_start, p_traj_end);
1051 // Projected track must be close to the photon; details may change after
1052 // future study.
1053 if (closest_p > cellWidth or closest_e < 1.5 * cellWidth) continue;
1054 // find r^2
1055 // ~variance
1056 float vrnc = (trackingHitList[hitNums[0]].pos - hmean).Mag() +
1057 (trackingHitList[hitNums[1]].pos - hmean).Mag() +
1058 (trackingHitList[hitNums[2]].pos - hmean).Mag();
1059 // sum of |errors|
1060 float sumerr =
1061 distPtToLine(trackingHitList[hitNums[0]].pos, hmean, hpoint) +
1062 distPtToLine(trackingHitList[hitNums[1]].pos, hmean, hpoint) +
1063 distPtToLine(trackingHitList[hitNums[2]].pos, hmean, hpoint);
1064 float r_corr = 1 - sumerr / vrnc;
1065 // Check whether r^2 exceeds a low minimum r_corr: "Fake" tracks are
1066 // still much more common in background, so making the algorithm
1067 // oversensitive doesn't lower performance significantly
1068 if (r_corr > r_corr_best and r_corr > .6) {
1069 r_corr_best = r_corr;
1070 // Only looking for 3-hit tracks currently
1071 bestLinRegFound = true;
1072 for (int k = 0; k < 3; k++) {
1073 bestHitNums[k] = hitNums[k];
1074 }
1075 }
1076 } // end loop on hits in the region
1077 } // end 2nd loop on hits in the region
1078
1079 // Continue early if not hits on track
1080 if (!bestLinRegFound) continue;
1081 // Otherwise increase the number of lin-reg tracks
1083 ldmx_log(debug) << " Lin-reg track " << nLinregTracks_;
1084 for (int finalHitIndx = 0; finalHitIndx < 3; finalHitIndx++) {
1085 ldmx_log(debug) << " Hit " << finalHitIndx << " ["
1086 << trackingHitList[bestHitNums[finalHitIndx]].pos(0)
1087 << ", "
1088 << trackingHitList[bestHitNums[finalHitIndx]].pos(1)
1089 << ", "
1090 << trackingHitList[bestHitNums[finalHitIndx]].pos(2)
1091 << "] ";
1092 }
1093
1094 // Exclude all hits in a found track from further consideration:
1095 for (int lHit = 0; lHit < 3; lHit++) {
1096 trackingHitList.erase(trackingHitList.begin() + bestHitNums[lHit]);
1097 }
1098 iHit--;
1099 } // end loop on all hits
1100
1101 ldmx_log(info) << " MIP tracking completed; found " << nStraightTracks_
1102 << " straight tracks and " << nLinregTracks_
1103 << " lin-reg tracks";
1104
1105 result.setVariables(
1106 nReadoutHits_, deepestLayerHit_, summedDet_, summedTightIso_, maxCellDep_,
1107 showerRMS_, xStd_, yStd_, avgLayerHit_, stdLayerHit_, ecalBackEnergy_,
1109 photonTerritoryHits_, epAng_, epSep_, epDot_, electronContainmentEnergy,
1110 photonContainmentEnergy, outsideContainmentEnergy,
1111 outsideContainmentNHits, outsideContainmentXstd, outsideContainmentYstd,
1112 energySeg, xMeanSeg, yMeanSeg, xStdSeg, yStdSeg, layerMeanSeg,
1113 layerStdSeg, eContEnergy, eContXMean, eContYMean, gContEnergy, gContNHits,
1114 gContXMean, gContYMean, oContEnergy, oContNHits, oContXMean, oContYMean,
1115 oContXStd, oContYStd, oContLayerMean, oContLayerStd,
1116 ecalLayerEdepReadout_, recoilP, recoilPos);
1117
1118 buildBDTFeatureVector(result);
1119 ldmx::Ort::FloatArrays inputs({bdtFeatures_});
1120 float pred = rt_->run({featureListName_}, inputs, {"probabilities"})[0].at(1);
1121 // Other considerations were (nLinregTracks_ == 0) && (firstNearPhLayer_ >=
1122 // 6)
1123 // && (epAng_ > 3.0 && epAng_ < 900 || epSep_ > 10.0 && epSep_ < 900)
1124 bool passesTrackingVeto = (nStraightTracks_ < 3);
1125 result.setVetoResult(pred > bdtCutVal_ && passesTrackingVeto);
1126 result.setDiscValue(pred);
1127 ldmx_log(debug) << " The pred > bdtCutVal = " << (pred > bdtCutVal_);
1128
1129 // Persist in the event if the recoil ele is fiducial
1130 result.setFiducial(inside);
1131
1132 // If the event passes the veto, keep it. Otherwise,
1133 // drop the event.
1134 if (result.passesVeto()) {
1136 } else {
1138 }
1139
1140 event.add(collectionName_, result);
1141}
1142
1143/* Function to calculate the energy weighted shower centroid */
1144ldmx::EcalID EcalVetoProcessor::GetShowerCentroidIDAndRMS(
1145 const std::vector<ldmx::EcalHit> &ecalRecHits, double &showerRMS) {
1146 auto wgtCentroidCoords = std::make_pair<float, float>(0., 0.);
1147 float sumEdep = 0;
1148 ldmx::EcalID returnCellId;
1149
1150 // Calculate Energy Weighted Centroid
1151 for (const ldmx::EcalHit &hit : ecalRecHits) {
1152 ldmx::EcalID id(hit.getID());
1153 CellEnergyPair cell_energy_pair = std::make_pair(id, hit.getEnergy());
1154 auto [x, y, z] = geometry_->getPosition(id);
1155 XYCoords centroidCoords = std::make_pair(x, y);
1156 wgtCentroidCoords.first = wgtCentroidCoords.first +
1157 centroidCoords.first * cell_energy_pair.second;
1158 wgtCentroidCoords.second = wgtCentroidCoords.second +
1159 centroidCoords.second * cell_energy_pair.second;
1160 sumEdep += cell_energy_pair.second;
1161 }
1162 wgtCentroidCoords.first = (sumEdep > 1E-6) ? wgtCentroidCoords.first / sumEdep
1163 : wgtCentroidCoords.first;
1164 wgtCentroidCoords.second = (sumEdep > 1E-6)
1165 ? wgtCentroidCoords.second / sumEdep
1166 : wgtCentroidCoords.second;
1167 // Find Nearest Cell to Centroid
1168 float maxDist = 1e6;
1169 for (const ldmx::EcalHit &hit : ecalRecHits) {
1170 auto [x, y, z] = geometry_->getPosition(hit.getID());
1171 XYCoords centroidCoords = std::make_pair(x, y);
1172
1173 float deltaR =
1174 pow(pow((centroidCoords.first - wgtCentroidCoords.first), 2) +
1175 pow((centroidCoords.second - wgtCentroidCoords.second), 2),
1176 .5);
1177 showerRMS += deltaR * hit.getEnergy();
1178 if (deltaR < maxDist) {
1179 maxDist = deltaR;
1180 returnCellId = ldmx::EcalID(hit.getID());
1181 }
1182 }
1183 if (sumEdep > 0) showerRMS = showerRMS / sumEdep;
1184 // flatten
1185 return ldmx::EcalID(0, returnCellId.module(), returnCellId.cell());
1186}
1187
1192 const std::vector<ldmx::EcalHit> &ecalRecHits,
1193 std::map<ldmx::EcalID, float> &cellMap) {
1194 for (const ldmx::EcalHit &hit : ecalRecHits) {
1195 ldmx::EcalID id(hit.getID());
1196 cellMap.emplace(id, hit.getEnergy());
1197 }
1198}
1199
1200void EcalVetoProcessor::fillIsolatedHitMap(
1201 const std::vector<ldmx::EcalHit> &ecalRecHits, ldmx::EcalID globalCentroid,
1202 std::map<ldmx::EcalID, float> &cellMap,
1203 std::map<ldmx::EcalID, float> &cellMapIso, bool doTight) {
1204 for (const ldmx::EcalHit &hit : ecalRecHits) {
1205 auto isolatedHit = std::make_pair(true, ldmx::EcalID());
1206 ldmx::EcalID id(hit.getID());
1207 if (doTight) {
1208 // Disregard hits that are on the centroid.
1209 if (id == globalCentroid) continue;
1210
1211 // Skip hits that are on centroid inner ring
1212 if (geometry_->isNN(globalCentroid, id)) {
1213 continue;
1214 }
1215 }
1216
1217 // Skip hits that have a readout neighbor
1218 // Get neighboring cell id's and try to look them up in the full cell map
1219 // (constant speed algo.)
1220 // these ideas are only cell/module (must ignore layer)
1221 std::vector<ldmx::EcalID> cellNbrIds = geometry_->getNN(id);
1222
1223 for (int k = 0; k < cellNbrIds.size(); k++) {
1224 // update neighbor ID to the current layer
1225 cellNbrIds[k] = ldmx::EcalID(id.layer(), cellNbrIds[k].module(),
1226 cellNbrIds[k].cell());
1227 // look in cell hit map to see if it is there
1228 if (cellMap.find(cellNbrIds[k]) != cellMap.end()) {
1229 isolatedHit = std::make_pair(false, cellNbrIds[k]);
1230 break;
1231 }
1232 }
1233 if (!isolatedHit.first) {
1234 continue;
1235 }
1236 // Insert isolated hit
1237 cellMapIso.emplace(id, hit.getEnergy());
1238 }
1239}
1240
1241/* Calculate where trajectory intersects ECAL layers using position and momentum
1242 * at scoring plane */
1243std::vector<std::pair<float, float>> EcalVetoProcessor::getTrajectory(
1244 std::vector<double> momentum, std::vector<float> position) {
1245 std::vector<XYCoords> positions;
1246 for (int iLayer = 0; iLayer < nEcalLayers_; iLayer++) {
1247 float posX =
1248 position[0] + (momentum[0] / momentum[2]) *
1249 (geometry_->getZPosition(iLayer) - position[2]);
1250 float posY =
1251 position[1] + (momentum[1] / momentum[2]) *
1252 (geometry_->getZPosition(iLayer) - position[2]);
1253 positions.push_back(std::make_pair(posX, posY));
1254 }
1255 return positions;
1256}
1257
1258// MIP tracking functions:
1259
1260float EcalVetoProcessor::distTwoLines(TVector3 v1, TVector3 v2, TVector3 w1,
1261 TVector3 w2) {
1262 TVector3 e1 = v1 - v2;
1263 TVector3 e2 = w1 - w2;
1264 TVector3 crs = e1.Cross(e2);
1265 if (crs.Mag() == 0) {
1266 return 100.0; // arbitrary large number; edge case that shouldn't cause
1267 // problems.
1268 } else {
1269 return std::abs(crs.Dot(v1 - w1) / crs.Mag());
1270 }
1271}
1272
1273float EcalVetoProcessor::distPtToLine(TVector3 h1, TVector3 p1, TVector3 p2) {
1274 return ((h1 - p1).Cross(h1 - p2)).Mag() / (p1 - p2).Mag();
1275}
1276
1277} // namespace ecal
1278
1279DECLARE_PRODUCER_NS(ecal, EcalVetoProcessor);
Collection of utility functions useful for analysis.
Class that translates raw positions of ECal module hits into cells in a hexagonal readout.
Class that determines if event is vetoable using ECAL hit information.
Class providing string constants for the event model.
#define DECLARE_PRODUCER_NS(NS, 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.
float epSep_
Distance between the projected photon and electron trajectories at the ECal face.
float distTwoLines(TVector3 v1, TVector3 v2, TVector3 w1, TVector3 w2)
Returns the distance between the lines v and w, with v defined to pass through the points (v1,...
void configure(framework::config::Parameters &parameters) override
Configure the processor using the given user specified parameters.
void produce(framework::Event &event) override
Process the event and put new data products into it.
void buildBDTFeatureVector(const ldmx::EcalVetoResult &result)
int photonTerritoryHits_
Number of hits in the photon territory.
float epAng_
Angular separation between the projected photon and electron trajectories (currently unused)
int nStraightTracks_
Number of "straight" tracks found in the event.
int nLinregTracks_
Number of "linreg" tracks found in the event.
int firstNearPhLayer_
First ECal layer in which a hit is found near the photon.
float epDot_
Dot product of the photon and electron momenta unit vectors.
float distPtToLine(TVector3 h1, TVector3 p1, TVector3 p2)
Return the minimum distance between the point h1 and the line passing through points p1 and p2.
std::string collectionName_
Name of the collection which will containt the results.
void fillHitMap(const std::vector< ldmx::EcalHit > &ecalRecHits, std::map< ldmx::EcalID, float > &cellMap_)
Function to load up empty vector of hit maps.
int nNearPhHits_
Number of hits near the photon trajectory.
const ldmx::EcalGeometry * geometry_
handle to current geometry (to share with member functions)
void setStorageHint(framework::StorageControl::Hint hint)
Mark the current event as having the given storage control hint from this module.
Implements an event buffer system for storing event data.
Definition Event.h:41
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:92
Class encapsulating parameters for configuring a processor.
Definition Parameters.h:27
T getParameter(const std::string &name) const
Retrieve the parameter of the given name.
Definition Parameters.h:89
std::vector< EcalID > getNN(EcalID id) const
Get the Nearest Neighbors of the input ID.
EcalID getID(double x, double y, double z, bool fallible=false) const
Get a cell's ID number from its position.
std::tuple< double, double, double > getPosition(EcalID id) const
Get a cell's position from its ID number.
double getZPosition(int layer) const
Get the z-coordinate given the layer id.
bool isNN(EcalID centroid, EcalID probe) const
Check if the probe id is one of the nearest neightbors of the centroid id.
double getCellMaxR() const
Get the center-to-corner radius of the cell hexagons.
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
int cell() const
Get the value of the cell field from the ID.
Definition EcalID.h:111
int module() const
Get the value of the module field from the ID.
Definition EcalID.h:87
void setVariables(int nReadoutHits, int deepestLayerHit, float summedDet, float summedTightIso, float maxCellDep, float showerRMS, float xStd, float yStd, float avgLayerHit, float stdLayerHit, float ecalBackEnergy, int nStraightTracks, int nLinregTracks, int firstNearPhLayer, int nNearPhHits, int photonTerritoryHits, float epAng, float epSep, float epDot, std::vector< float > electronContainmentEnergy, std::vector< float > photonContainmentEnergy, std::vector< float > outsideContainmentEnergy, std::vector< int > outsideContainmentNHits, std::vector< float > outsideContainmentXStd, std::vector< float > outsideContainmentYStd, std::vector< float > energySeg, std::vector< float > xMeanSeg, std::vector< float > yMeanSeg, std::vector< float > xStdSeg, std::vector< float > yStdSeg, std::vector< float > layerMeanSeg, std::vector< float > layerStdSeg, std::vector< std::vector< float > > eContEnergy, std::vector< std::vector< float > > eContXMean, std::vector< std::vector< float > > eContYMean, std::vector< std::vector< float > > gContEnergy, std::vector< std::vector< int > > gContNHits, std::vector< std::vector< float > > gContXMean, std::vector< std::vector< float > > gContYMean, std::vector< std::vector< float > > oContEnergy, std::vector< std::vector< int > > oContNHits, std::vector< std::vector< float > > oContXMean, std::vector< std::vector< float > > oContYMean, std::vector< std::vector< float > > oContXStd, std::vector< std::vector< float > > oContYStd, std::vector< std::vector< float > > oContLayerMean, std::vector< std::vector< float > > oContLayerStd, std::vector< float > EcalLayerEdepReadout, std::vector< double > recoilP, std::vector< float > recoilPos)
Set the sim particle and 'is findable' flag.
bool passesVeto() const
Checks if the event passes the Ecal veto.
int getNStraightTracks() const
Number of straight tracks found.
Class representing a simulated particle.
Definition SimParticle.h:23
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.
constexpr StorageControl::Hint hint_shouldKeep
storage control hint alias for backwards compatibility
constexpr StorageControl::Hint hint_shouldDrop
storage control hint alias for backwards compatibility