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