LDMX Software
LinearSeedFinder.cxx
1#include "Tracking/Reco/LinearSeedFinder.h"
2
3#include "Eigen/Dense"
4
5namespace tracking {
6namespace reco {
7
8LinearSeedFinder::LinearSeedFinder(const std::string& name,
9 framework::Process& process)
10 : TrackingGeometryUser(name, process) {}
11
13 truth_matching_tool_ = std::make_shared<tracking::sim::TruthMatchingTool>();
14}
15
17 // Output seed name
18 out_seed_collection_ = parameters.get<std::string>(
19 "out_seed_collection", getName() + "LinearRecoilSeedTracks");
20
21 // Input strip hits_
23 parameters.get<std::string>("input_hits_collection", "DigiRecoilSimHits");
25 parameters.get<std::string>("input_rec_hits_collection", "EcalRecHits");
26
27 input_pass_name_ = parameters.get<std::string>("input_pass_name", "");
28
29 sim_particles_passname_ =
30 parameters.get<std::string>("sim_particles_passname");
31
32 sim_particles_events_passname_ =
33 parameters.get<std::string>("sim_particles_events_passname");
34
35 // the uncertainty is sigma_x = 6 microns and sigma_y = 20./sqrt(12)
36 recoil_uncertainty_ =
37 parameters.get<std::vector<double>>("recoil_uncertainty", {0.006, 0.085});
38 ecal_uncertainty_ = parameters.get<double>("ecal_uncertainty", {3.87});
39 ecal_distance_threshold_ = parameters.get<double>("ecal_distance_threshold");
40 ecal_first_layer_z_threshold_ =
41 parameters.get<double>("ecal_first_layer_z_threshold");
42
43 layer12_midpoint_ = parameters.get<double>("layer12_midpoint");
44 layer23_midpoint_ = parameters.get<double>("layer23_midpoint");
45 layer34_midpoint_ = parameters.get<double>("layer34_midpoint");
46}
47
49 auto start = std::chrono::high_resolution_clock::now();
50 std::vector<ldmx::StraightTrack> straight_seed_tracks;
51 n_events_++;
52 auto tg{geometry()};
53
54 const auto& recoil_hits = event.getCollection<ldmx::Measurement>(
55 input_hits_collection_, input_pass_name_);
56 const auto& ecal_rec_hit = event.getCollection<ldmx::EcalHit>(
57 input_rec_hits_collection_, input_pass_name_);
58
59 std::vector<std::array<double, 3>> first_layer_ecal_rec_hits;
60
61 // Find RecHits at first layer_ of ECal
62 for (const auto& x_ecal : ecal_rec_hit) {
63 if (x_ecal.getZPos() < ecal_first_layer_z_threshold_) {
64 first_layer_ecal_rec_hits.push_back(
65 {x_ecal.getZPos(), x_ecal.getXPos(), x_ecal.getYPos()});
66 } // if first layer_ of Ecal
67 } // for positions in ecalRecHit
68
69 // Check if we would fit empty seeds, if so: end tracking
70 if ((recoil_hits.size() < 2) || (first_layer_ecal_rec_hits.empty()) ||
71 (uniqueLayersHit(recoil_hits) < 2)) {
72 n_missing_++;
73 n_seeds_ += straight_seed_tracks.size();
74 event.add(out_seed_collection_, straight_seed_tracks);
75 return;
76 }
77
78 // Setup truth map
79 std::map<int, ldmx::SimParticle> particle_map;
80 if (event.exists("SimParticles", sim_particles_events_passname_)) {
81 particle_map = event.getMap<int, ldmx::SimParticle>(
82 "SimParticles", sim_particles_passname_);
83 truth_matching_tool_->setup(particle_map, recoil_hits);
84 }
85
86 std::vector<
87 std::tuple<ldmx::Measurement, ldmx::SimTrackerHit, ldmx::SimTrackerHit>>
88 first_two_layers;
89 std::vector<
90 std::tuple<ldmx::Measurement, ldmx::SimTrackerHit, ldmx::SimTrackerHit>>
91 second_two_layers;
92
93 const auto& recoil_sim_hits = event.getCollection<ldmx::SimTrackerHit>(
94 "RecoilSimHits", input_pass_name_);
95 const auto& scoring_hits = event.getCollection<ldmx::SimTrackerHit>(
96 "TargetScoringPlaneHits", input_pass_name_);
97
98 // Index all sim hits_ by track ID
99 std::unordered_map<int, std::vector<const ldmx::SimTrackerHit*>>
100 sim_hits_by_track_id;
101 for (const auto& hit : recoil_sim_hits) {
102 sim_hits_by_track_id[hit.getTrackID()].push_back(&hit);
103 } // for sim hits_
104
105 // Index scoring hits_ by track ID (one positive scoring plane hit / ID)
106 std::unordered_map<int, const ldmx::SimTrackerHit*> scoring_hit_map;
107 for (const auto& sp_hit : scoring_hits) {
108 if (sp_hit.getPosition()[2] > 0)
109 scoring_hit_map[sp_hit.getTrackID()] = &sp_hit;
110 } // for sp hits_
111
112 for (const auto& point : recoil_hits) {
113 // x is in tracking coordinates, z is in ldmx coordinates
114 float x = point.getGlobalPosition()[0];
115 // need to do a size check here since getTrackIds is a std::vector
116 // which could be empty
117 auto track_ids = point.getTrackIds();
118 int track_id = (track_ids.size() > 0) ? track_ids.at(0) : -1;
119
120 // get the key value = track_id
121 auto sim_range_it = sim_hits_by_track_id.find(track_id);
122 if (sim_range_it == sim_hits_by_track_id.end()) continue;
123
124 // access map value at track_id
125 const auto& sim_hits = sim_range_it->second;
126
127 for (const auto* sim_hit : sim_hits) {
128 float z = sim_hit->getPosition()[2];
129
130 if (x < layer12_midpoint_) {
131 if (z < layer12_midpoint_) {
132 auto sp_it = scoring_hit_map.find(track_id);
133 if (sp_it != scoring_hit_map.end()) {
134 first_two_layers.emplace_back(point, *sim_hit, *sp_it->second);
135 break;
136 } // add the associated scoring plane hit (will be needed for 3D
137 // reconstruction)
138 } // associate 1st layer_ sim hit
139 } // check if recoil hit is 1st layer_
140 else if (x < layer23_midpoint_) {
141 if (z > layer12_midpoint_ && z < layer23_midpoint_) {
142 first_two_layers.emplace_back(point, *sim_hit, ldmx::SimTrackerHit());
143 break;
144 } // associate 2nd layer_ sim hit
145 } // check if recoil hit is 2nd layer_
146 else if (x < layer34_midpoint_) {
147 if (z > layer23_midpoint_ && z < layer34_midpoint_) {
148 auto sp_it = scoring_hit_map.find(track_id);
149 if (sp_it != scoring_hit_map.end()) {
150 second_two_layers.emplace_back(point, *sim_hit, *sp_it->second);
151 break;
152 } // add the associated scoring plane hit (will be needed for 3D
153 // reconstruction)
154 } // associate 3rd layer_ sim hits_
155 } // check if recoil hit is 3rd layer_
156 else {
157 if (z > layer34_midpoint_) {
158 second_two_layers.emplace_back(point, *sim_hit,
160 break;
161 } // associate 4th layer_ sim hits_
162 } // check if recoil hits_ is 4th layer_
163
164 } // loop through simhits
165 } // loop through recoil hits_
166
167 // Reconstruct 3D sensor points on which to do fitting
168 auto first_sensor_combos = processMeasurements(first_two_layers, tg);
169 auto second_sensor_combos = processMeasurements(second_two_layers, tg);
170
171 for (const auto& [first_combo_3d_point, first_layer_one, first_layer_two] :
172 first_sensor_combos) {
173 std::tuple<std::array<double, 3>, ldmx::Measurement,
174 std::optional<ldmx::Measurement>>
175 first_sensor_point;
176
177 if (first_layer_two.has_value()) {
178 first_sensor_point = {first_combo_3d_point,
179 std::get<ldmx::Measurement>(first_layer_one),
180 std::get<ldmx::Measurement>(*first_layer_two)};
181 } // check whether we did reconstruction or...
182 else {
183 first_sensor_point = {first_combo_3d_point,
184 std::get<ldmx::Measurement>(first_layer_one),
185 std::nullopt};
186 } //...we are taking only one layer_ as the measurement (axial or stereo)
187
188 for (const auto& [second_combo_3d_point, second_layer_one,
189 second_layer_two] : second_sensor_combos) {
190 std::tuple<std::array<double, 3>, ldmx::Measurement,
191 std::optional<ldmx::Measurement>>
192 second_sensor_point;
193 if (second_layer_two.has_value()) {
194 second_sensor_point = {second_combo_3d_point,
195 std::get<ldmx::Measurement>(second_layer_one),
196 std::get<ldmx::Measurement>(*second_layer_two)};
197 } // check whether we did reconstruction or...
198 else {
199 second_sensor_point = {second_combo_3d_point,
200 std::get<ldmx::Measurement>(second_layer_one),
201 std::nullopt};
202 } //...we are taking only one layer_ as the measurement (axial or stereo)
203
204 for (const auto& rec_hit : first_layer_ecal_rec_hits) {
205 // Do fitting on 2 sensor + 1 recHit combinations = 1 degree of freedom
206 // for linear fit
207 ldmx::StraightTrack seed_track =
208 seedTracker(first_sensor_point, second_sensor_point, rec_hit);
209
210 // Seed passed RecHit distance check, add it
211 if (seed_track.getChi2() > 0.0) {
212 straight_seed_tracks.push_back(seed_track);
213 } // if chi2 > 0
214 } // for rec_hits
215 } // for second recoil tracker
216 } // for first recoil tracker
217
218 n_seeds_ += straight_seed_tracks.size();
219 event.add(out_seed_collection_, straight_seed_tracks);
220
221 auto end = std::chrono::high_resolution_clock::now();
222
223 auto diff = end - start;
224 processing_time_ += std::chrono::duration<double, std::milli>(diff).count();
225
226 first_layer_ecal_rec_hits.clear();
227 straight_seed_tracks.clear();
228
229} // produce
230
231ldmx::StraightTrack LinearSeedFinder::seedTracker(
232 const std::tuple<std::array<double, 3>, ldmx::Measurement,
233 std::optional<ldmx::Measurement>>
234 recoil_one,
235 const std::tuple<std::array<double, 3>, ldmx::Measurement,
236 std::optional<ldmx::Measurement>>
237 recoil_two,
238 const std::array<double, 3> ecal_one) {
239 auto [sensor1, layer1, layer2] = recoil_one;
240 auto [sensor2, layer3, layer4] = recoil_two;
241 std::vector<ldmx::Measurement> all_points;
242
243 // TODO: in the case where we don't have all 4 hits_, we will be fitting a
244 // sensor (weighted average of two layers) + single layer_
245 // TODO: or fitting two single layers. Currently, the single layer_ point has
246 // the uncertainty of a sensor assigned to it,
247 // TODO: but this is not a realistic uncertainty for a single layer_...
248 // IF all layers are well-defined, this sequence will add layer1, 2, 3, 4 to
249 // the allPoints vector
250 all_points.push_back(layer1);
251
252 // if layer2 doesn't exist (has_value == False), then the layer1 we added
253 // is either layer1 or 2, depending on which one has_value
254 if (layer2.has_value()) {
255 all_points.push_back(*layer2);
256 }
257
258 all_points.push_back(layer3);
259
260 // if layer4 doesn't exist (has_value == False), then the layer3 we added
261 // is either layer3 or 4, depending on which one has_value
262 if (layer4.has_value()) {
263 all_points.push_back(*layer4);
264 }
265
266 // Fit the 3 points to a 3D straight line, find track location at first layer_
267 // of Ecal, check distance to recHit used in fitting
268 // m = slope ; b = intercept
269 auto [m_x, b_x, m_y, b_y, seed_cov] = fit3DLine(sensor1, sensor2, ecal_one);
270 std::array<double, 3> temp_extrapolated_point = {
271 ecal_one[0], m_x * ecal_one[0] + b_x, m_y * ecal_one[0] + b_y};
272 double temp_distance = calculateDistance(temp_extrapolated_point, ecal_one);
273
275
276 if (temp_distance < ecal_distance_threshold_) {
277 trk.setSlopeX(m_x);
278 trk.setInterceptX(b_x);
279 trk.setSlopeY(m_y);
280 trk.setInterceptY(b_y);
281 trk.setTheta(std::atan2(m_y, std::sqrt(1 + m_x * m_x)));
282 trk.setPhi(std::atan2(m_x, 1.0));
283
284 trk.setAllSensorPoints(all_points);
285 trk.setFirstSensorPosition(sensor1);
286 trk.setSecondSensorPosition(sensor2);
287 trk.setFirstLayerEcalRecHit(ecal_one);
288 trk.setDistancetoRecHit(temp_distance);
289
290 trk.setTargetLocation(0.0, b_x, b_y);
291 trk.setEcalLayer1Location(temp_extrapolated_point);
292 trk.setChi2(
293 globalChiSquare(sensor1, sensor2, ecal_one, m_x, m_y, b_x, b_y));
294 trk.setNhits(3);
295 trk.setNdf(1);
296
297 trk.setCov(seed_cov);
298
299 // truth matching
300 if (truth_matching_tool_->configured()) {
301 auto truth_info = truth_matching_tool_->truthMatch(all_points);
302 trk.setTrackID(truth_info.track_id_);
303 trk.setPdgID(truth_info.pdg_id_);
304 trk.setTruthProb(truth_info.truth_prob_);
305 }
306
307 return trk;
308
309 } // if (track is close enough to EcalRecHit)
310 else {
311 trk.setChi2(-1);
312 return trk;
313 } // else (does not pass the threshold)
314} // SeedTracker
315
317 ldmx_log(info) << "AVG Time/Event: " << std::fixed << std::setprecision(1)
318 << processing_time_ / n_events_ << " ms";
319 ldmx_log(info) << "Total Seeds/Events: " << n_seeds_ << "/" << n_events_;
320 ldmx_log(info) << "not enough seed points " << n_missing_;
321
322} // onProcessEnd
323
324std::array<double, 3> LinearSeedFinder::getPointAtZ(
325 std::array<double, 3> target, std::array<double, 3> measurement,
326 double z_target) {
327 double slope_x = (measurement[1] - target[0]) / (measurement[0] - target[2]);
328 double slope_y = (measurement[2] - target[1]) / (measurement[0] - target[2]);
329
330 double intercept_x = target[0] - slope_x * target[2];
331 double intercept_y = target[1] - slope_y * target[2];
332
333 double x_target = slope_x * z_target + intercept_x;
334 double y_target = slope_y * z_target + intercept_y;
335
336 return {z_target, x_target, y_target};
337}
338
339std::vector<std::tuple<
340 std::array<double, 3>,
341 std::tuple<ldmx::Measurement, ldmx::SimTrackerHit, ldmx::SimTrackerHit>,
342 std::optional<std::tuple<ldmx::Measurement, ldmx::SimTrackerHit,
344LinearSeedFinder::processMeasurements(
345 const std::vector<std::tuple<ldmx::Measurement, ldmx::SimTrackerHit,
346 ldmx::SimTrackerHit>>& measurements,
347 const geo::TrackersTrackingGeometry& tg) {
348 std::vector<
349 std::tuple<ldmx::Measurement, ldmx::SimTrackerHit, ldmx::SimTrackerHit>>
350 axial_measurements;
351 std::vector<
352 std::tuple<ldmx::Measurement, ldmx::SimTrackerHit, ldmx::SimTrackerHit>>
353 stereo_measurements;
354 std::vector<std::tuple<
355 std::array<double, 3>,
356 std::tuple<ldmx::Measurement, ldmx::SimTrackerHit, ldmx::SimTrackerHit>,
357 std::optional<std::tuple<ldmx::Measurement, ldmx::SimTrackerHit,
359 points_with_measurement;
360 Acts::Vector3 dummy{0., 0., 0.};
361
362 // Separate measurements into axial and stereo based on layerID
363 for (const auto& [measurement, sim_hit, scoring_hit] : measurements) {
364 if (measurement.getLayerID() % 2 == 0) {
365 axial_measurements.emplace_back(measurement, sim_hit, scoring_hit);
366 } else {
367 stereo_measurements.emplace_back(measurement, sim_hit, scoring_hit);
368 }
369 }
370
371 // If there are measurements in both axial and stereo layer_:
372 // Iterate over axial and stereo measurements to compute 3D points
373 if (!axial_measurements.empty() && !stereo_measurements.empty()) {
374 for (const auto& axial : axial_measurements) {
375 const auto& [axial_meas, axial_hit, axial_sp] = axial;
376
377 for (const auto& stereo : stereo_measurements) {
378 const auto& [stereo_meas, stereo_hit, stereo_sp] = stereo;
379
380 const Acts::Surface* axial_surface =
381 tg.getSurface(axial_meas.getLayerID());
382 const Acts::Surface* stereo_surface =
383 tg.getSurface(stereo_meas.getLayerID());
384
385 if (!axial_surface || !stereo_surface) continue;
386
387 std::vector<ldmx::SimTrackerHit> sim_hits = {axial_hit, stereo_hit};
388 Acts::Vector3 space_point =
389 simple3DHitV2(axial_meas, *axial_surface, stereo_meas,
390 *stereo_surface, axial_sp, sim_hits);
391
392 points_with_measurement.push_back(
393 {convertToLdmxStdArray(space_point), axial, stereo});
394 }
395 }
396 } else if (!axial_measurements.empty()) {
397 // if there are only axial measurements, take them to be our sensor points
398 for (const auto& axial : axial_measurements) {
399 const auto& [axial_meas, axial_hit, axial_sp] = axial;
400 const Acts::Surface* axial_surface =
401 tg.getSurface(axial_meas.getLayerID());
402
403 Acts::Vector3 axial_meas_hit = axial_surface->localToGlobal(
404 geometryContext(),
405 Acts::Vector2(axial_meas.getLocalPosition()[0], 0.0), dummy);
406
407 points_with_measurement.push_back(
408 {convertToLdmxStdArray(axial_meas_hit), axial, std::nullopt});
409 }
410 } else if (!stereo_measurements.empty()) {
411 for (const auto& stereo : stereo_measurements) {
412 const auto& [stereo_meas, stereo_hit, stereo_sp] = stereo;
413 const Acts::Surface* stereo_surface =
414 tg.getSurface(stereo_meas.getLayerID());
415
416 Acts::Vector3 stereo_meas_hit = stereo_surface->localToGlobal(
417 geometryContext(),
418 Acts::Vector2(stereo_meas.getLocalPosition()[0], 0.0), dummy);
419
420 points_with_measurement.push_back(
421 {convertToLdmxStdArray(stereo_meas_hit), stereo, std::nullopt});
422 }
423 }
424 return points_with_measurement;
425}
426
427// ACTS saves its arrays like (x, y, z)
428std::array<double, 3> LinearSeedFinder::convertToLdmxStdArray(
429 const Acts::Vector3& vec) {
430 return {vec.x(), vec.y(), vec.z()};
431}
432
433// Helper function to calculate unit vector by taking advantage of the
434// localToGlobal transformation
435std::tuple<Acts::Vector3, Acts::Vector3, Acts::Vector3>
436LinearSeedFinder::getSurfaceVectors(const Acts::Surface& surface) {
437 Acts::Vector3 dummy{0., 0., 0.};
438 Acts::Vector3 u =
439 surface.localToGlobal(geometryContext(), Acts::Vector2(1, 0), dummy) -
440 surface.center(geometryContext());
441 Acts::Vector3 v =
442 surface.localToGlobal(geometryContext(), Acts::Vector2(0, 1), dummy) -
443 surface.center(geometryContext());
444 Acts::Vector3 w = u.cross(v).normalized();
445 return {u.normalized(), v.normalized(), w};
446}
447
448// estimate the 3d position of the particle as it passes through a stereo/axial
449// pair currently this uses the sim hits_ from the target and the axial sensor
450// to calculate the angle of the particle, which is needed to project the two
451// sensors to the same z. For real data, we could use the position at the
452// target for the tagger and the axial u position of the measured hit (we only
453// project in x, which, in MC, is identically x) assumptions: axial
454// u-direction is identically global-x (tracking-global y)
455// sensors' w-directions are aligned with beam (global-z,
456// tracking-global x)
457Acts::Vector3 LinearSeedFinder::simple3DHitV2(
458 const ldmx::Measurement& axial, const Acts::Surface& axial_surface,
459 const ldmx::Measurement& stereo, const Acts::Surface& stereo_surface,
460 const ldmx::SimTrackerHit& target_sp,
461 std::vector<ldmx::SimTrackerHit> pair_sim_hits) {
462 Acts::Vector3 dummy{0., 0., 0.};
463 Acts::Vector3 hit_on_target{target_sp.getPosition()[0],
464 target_sp.getPosition()[1],
465 target_sp.getPosition()[2]}; // x,y,z
466
467 Acts::Vector3 axial_true_global{pair_sim_hits[0].getPosition()[0],
468 pair_sim_hits[0].getPosition()[1],
469 pair_sim_hits[0].getPosition()[2]};
470 // stereo_true_global is unused
471 Acts::Vector3 stereo_true_global{pair_sim_hits[1].getPosition()[0],
472 pair_sim_hits[1].getPosition()[1],
473 pair_sim_hits[1].getPosition()[2]};
474
475 Acts::Vector3 simpart_path = axial_true_global - hit_on_target;
476 Acts::Vector3 simpart_unit = simpart_path.normalized();
477
478 // Get global positions for strip origins .... actually these are in
479 // tracking coordinates!
480 Acts::Vector3 axial_origin = axial_surface.center(geometryContext());
481 Acts::Vector3 stereo_origin = stereo_surface.center(geometryContext());
482
483 // the tracking-global vector difference between stereo and axial sensor
484 // centers
485 Acts::Vector3 delta_sensors = stereo_origin - axial_origin;
486
487 // calculate the displacement in tracking global x (need to generalize) by
488 // going from tracking x=axial to x=stereo
489 double dx_proj = (simpart_unit[0] / simpart_unit[2]) *
490 delta_sensors[0]; // this looks weird because simpart_unit
491 // is in global-global and delta sensors
492 // is in tracking-global
493
494 // Compute unit vectors for both hits_
495 auto [axial_u, axial_v, axial_w] = getSurfaceVectors(axial_surface);
496 auto [stereo_u, stereo_v, stereo_w] = getSurfaceVectors(stereo_surface);
497 double salpha = dotProduct(axial_v, stereo_u);
498 double cosalpha = dotProduct(axial_u, stereo_u);
499
500 // Get sensor local measured coordinates
501 // Get local position components
502 auto [axial_u_value, axial_v_value] = axial.getLocalPosition();
503 auto [stereo_u_value, stereo_v_value] = stereo.getLocalPosition();
504
505 // Manual correction, since v should always be 0 (insensitive direction)
506 axial_v_value = 0.0;
507 stereo_v_value = 0.0;
508
509 // use the dx_proj as the displacement in u of the axial measurement
510 // it's axial_u_value - dx_proj because u is in the -x direction
511 // this calculation is in the axial frame
512 double v_intercept_useproj =
513 (stereo_u_value - (axial_u_value - dx_proj) * cosalpha) / salpha;
514 double u_intercept_useproj = axial_u_value - dx_proj;
515
516 // convert to tracking global
517 Acts::Vector3 axst_global_useproj = axial_surface.localToGlobal(
518 geometryContext(),
519 Acts::Vector2(u_intercept_useproj, v_intercept_useproj), dummy);
520 Acts::Vector3 dummy_stereo_proj = stereo_surface.localToGlobal(
521 geometryContext(),
522 Acts::Vector2(u_intercept_useproj, v_intercept_useproj), dummy);
523
524 // we want the reconstructed hit to be at the z of the stereo layer
525 Acts::Vector3 reconstructed_hit{dummy_stereo_proj[0], axst_global_useproj[1],
526 axst_global_useproj[2]};
527
528 ldmx_log(debug) << "The particle projected axst measured position is "
529 "(compare with stereo sim position): "
530 << reconstructed_hit[0] << ", " << reconstructed_hit[1]
531 << ", " << reconstructed_hit[2] << "\n";
532
533 return reconstructed_hit;
534}
535
536double LinearSeedFinder::dotProduct(const Acts::Vector3& v1,
537 const Acts::Vector3& v2) {
538 return v1.dot(v2);
539}
540
541std::tuple<double, double, double, double, std::vector<double>>
542LinearSeedFinder::fit3DLine(const std::array<double, 3>& first_recoil,
543 const std::array<double, 3>& second_recoil,
544 const std::array<double, 3>& ecal) {
545 double z_pos1 = first_recoil[0], x_pos1 = first_recoil[1],
546 y_pos1 = first_recoil[2];
547 double z_pos2 = second_recoil[0], x_pos2 = second_recoil[1],
548 y_pos2 = second_recoil[2];
549 double z_pos3 = ecal[0], x_pos3 = ecal[1], y_pos3 = ecal[2];
550
551 std::array<double, 6> weights = {
552 1 / pow(recoil_uncertainty_[0], 2), 1 / pow(recoil_uncertainty_[1], 2),
553 1 / pow(recoil_uncertainty_[0], 2), 1 / pow(recoil_uncertainty_[1], 2),
554 1 / pow(ecal_uncertainty_, 2), 1 / pow(ecal_uncertainty_, 2)};
555
556 Eigen::Matrix<double, 6, 4> a_mat;
557 Eigen::Matrix<double, 6, 1> d_vec, w_vec;
558
559 // Fill the A matrix (z, 1, 0, 0) for x and (0, 0, z, 1) for y
560 a_mat << z_pos1, 1, 0, 0, 0, 0, z_pos1, 1, z_pos2, 1, 0, 0, 0, 0, z_pos2, 1,
561 z_pos3, 1, 0, 0, 0, 0, z_pos3, 1;
562
563 // Fill the d vector with x and y values
564 d_vec << x_pos1, y_pos1, x_pos2, y_pos2, x_pos3, y_pos3;
565
566 // Fill the weights vector
567 w_vec = Eigen::Matrix<double, 6, 1>(weights.data());
568
569 // Solve the weighted least squares system
570 Eigen::MatrixXd at_w_a = a_mat.transpose() * w_vec.asDiagonal() * a_mat;
571 Eigen::MatrixXd at_w_d = a_mat.transpose() * w_vec.asDiagonal() * d_vec;
572 Eigen::VectorXd param_vec = at_w_a.ldlt().solve(at_w_d);
573
574 Eigen::Matrix4d covariance_matrix = at_w_a.inverse();
575
576 // Store only the upper triangular part of the covariance matrix since it is
577 // symmetric
578 std::vector<double> covariance_vector = {
579 covariance_matrix(0, 0), covariance_matrix(0, 1), covariance_matrix(0, 2),
580 covariance_matrix(0, 3), covariance_matrix(1, 1), covariance_matrix(1, 2),
581 covariance_matrix(1, 3), covariance_matrix(2, 2), covariance_matrix(2, 3),
582 covariance_matrix(3, 3)};
583
584 // return {slope_x, intercept_x, slope_y, intercept_y, covariance}
585 return {param_vec(0), param_vec(1), param_vec(2), param_vec(3),
586 covariance_vector};
587} // fit3DLine
588
589double LinearSeedFinder::calculateDistance(
590 const std::array<double, 3>& point1, const std::array<double, 3>& point2) {
591 return sqrt(pow(point1[1] - point2[1], 2) + pow(point1[2] - point2[2], 2));
592} // calculateDistance in xy
593
594double LinearSeedFinder::globalChiSquare(
595 const std::array<double, 3>& first_sensor,
596 const std::array<double, 3>& second_sensor,
597 const std::array<double, 3>& ecal_hit, double m_x, double m_y, double b_x,
598 double b_y) {
599 double chi2_x = 0, chi2_y = 0;
600 chi2_x += pow(
601 (m_x * first_sensor[0] + b_x - first_sensor[1]) / recoil_uncertainty_[0],
602 2);
603 chi2_y += pow(
604 (m_y * first_sensor[0] + b_y - first_sensor[2]) / recoil_uncertainty_[1],
605 2);
606
607 chi2_x += pow((m_x * second_sensor[0] + b_x - second_sensor[1]) /
608 recoil_uncertainty_[0],
609 2);
610 chi2_y += pow((m_y * second_sensor[0] + b_y - second_sensor[2]) /
611 recoil_uncertainty_[1],
612 2);
613
614 chi2_x += pow((m_x * ecal_hit[0] + b_x - ecal_hit[1]) / ecal_uncertainty_, 2);
615 chi2_y += pow((m_y * ecal_hit[0] + b_y - ecal_hit[2]) / ecal_uncertainty_, 2);
616
617 return chi2_x + chi2_y;
618} // globalChiSquare
619
620int LinearSeedFinder::uniqueLayersHit(
621 const std::vector<ldmx::Measurement>& digi_points) {
622 std::vector<ldmx::Measurement> sorted_points = digi_points;
623
624 // Sort by z position in the Recoil
625 std::sort(sorted_points.begin(), sorted_points.end(),
626 [](const ldmx::Measurement& meas1, const ldmx::Measurement& meas2) {
627 return meas1.getGlobalPosition()[0] <
628 meas2.getGlobalPosition()[0];
629 });
630
631 // Remove duplicates to ensure we only keep unique z positions
632 auto last = std::unique(
633 sorted_points.begin(), sorted_points.end(),
634 [](const ldmx::Measurement& meas1, const ldmx::Measurement& meas2) {
635 return meas1.getGlobalPosition()[0] == meas2.getGlobalPosition()[0];
636 });
637
638 // return the number of unique layer_ hits_
639 return std::distance(sorted_points.begin(), last);
640} // uniqueLayersHit
641
642} // namespace reco
643} // namespace tracking
644
#define DECLARE_PRODUCER(CLASS)
Macro which allows the framework to construct a producer given its name during configuration.
std::string getName() const
Get the processor name.
Implements an event buffer system for storing event data.
Definition Event.h:42
bool exists(const std::string &name, const std::string &passName, bool unique=true) const
Check for the existence of an object or collection with the given name and pass name in the event.
Definition Event.cxx:105
Class which represents the process under execution.
Definition Process.h:37
Class encapsulating parameters for configuring a processor.
Definition Parameters.h:29
const T & get(const std::string &name) const
Retrieve the parameter of the given name.
Definition Parameters.h:78
Stores reconstructed hit information from the ECAL.
Definition EcalHit.h:19
std::array< float, 2 > getLocalPosition() const
Definition Measurement.h:66
Class representing a simulated particle.
Definition SimParticle.h:24
Represents a simulated tracker hit in the simulation.
std::vector< float > getPosition() const
Get the XYZ position of the hit [mm].
std::string out_seed_collection_
The name of the output collection of seeds to be stored.
LinearSeedFinder(const std::string &name, framework::Process &process)
Constructor.
void configure(framework::config::Parameters &parameters) override
Configure the processor using the given user specified parameters.
void produce(framework::Event &event) override
Run the processor and create a collection of results which indicate if a charge particle can be found...
void onProcessEnd() override
Output event statistics.
std::string input_hits_collection_
The name of the input hits collection to use in finding seeds..
void onProcessStart() override
Setup the truth matching.
std::string input_rec_hits_collection_
The name of the tagger Tracks (only for Recoil Seeding)
a helper base class providing some methods to shorten access to common conditions used within the tra...
The measurement calibrator can be a function or a class/struct able to retrieve the sim hits containe...