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