LDMX Software
CKFProcessor.cxx
1#include "Tracking/Reco/CKFProcessor.h"
2
3#include "Acts/EventData/TrackContainer.hpp"
4#include "Acts/Utilities/TrackHelpers.hpp"
5#include "SimCore/Event/SimParticle.h"
6#include "Tracking/Event/Track.h"
7#include "Tracking/Reco/TruthMatchingTool.h"
8#include "Tracking/Sim/GeometryContainers.h"
9#include "Tracking/geo/DetectorElement.h"
10
11//--- C++ StdLib ---//
12#include <algorithm> //std::vector reverse
13#include <iostream>
14#include <typeinfo>
15// eN files
16#include <fstream>
17
18namespace tracking {
19namespace reco {
20
21CKFProcessor::CKFProcessor(const std::string& name, framework::Process& process)
22 : TrackingGeometryUser(name, process) {}
23
25 profiling_map_["setup"] = 0.;
26 profiling_map_["hits"] = 0.;
27 profiling_map_["seeds"] = 0.;
28 profiling_map_["ckf_setup"] = 0.;
29 profiling_map_["ckf_run"] = 0.;
30 profiling_map_["result_loop"] = 0.;
31
32 // Initialize counters
33 nseeds_ = 0;
34 ntracks_ = 0;
35 eventnr_ = 0;
36
37 // Generate a constant magnetic field
38 Acts::Vector3 b_field(0., 0., bfield_ * Acts::UnitConstants::T);
39
40 // Setup a constant magnetic field
41 const auto const_b_field = std::make_shared<Acts::ConstantBField>(b_field);
42
43 // Define the target surface - be careful:
44 // x - downstream
45 // y - left (when looking along x)
46 // z - up
47 // Passing identity here means that your target surface is oriented in the
48 // same way
49 surf_rotation_ = Acts::RotationMatrix3::Zero();
50 // u direction along +Y
51 surf_rotation_(1, 0) = 1;
52 // v direction along +Z
53 surf_rotation_(2, 1) = 1;
54 // w direction along +X
55 surf_rotation_(0, 2) = 1;
56
57 Acts::Vector3 target_pos(0., 0., 0.);
58 Acts::Translation3 target_translation(target_pos);
59 Acts::Transform3 target_transform(target_translation * surf_rotation_);
60
61 // Unbounded surface
62 target_surface_ =
63 Acts::Surface::makeShared<Acts::PlaneSurface>(target_transform);
64
65 // Setup a interpolated bfield map
66 if (field_map_.empty())
67 loadBField(map_offset_);
68 else
69 loadBField(field_map_, map_offset_);
70 const auto map =
71 std::static_pointer_cast<InterpolatedMagneticField3>(bField());
72
73 auto acts_logging_level = Acts::Logging::FATAL;
74 if (debug_acts_) acts_logging_level = Acts::Logging::VERBOSE;
75
76 // Setup the steppers
77 const auto stepper = Acts::EigenStepper<>{map};
78 const auto const_stepper = Acts::EigenStepper<>{const_b_field};
79 const auto multi_stepper = Acts::MultiEigenStepperLoop{map};
80
81 // Setup the navigator
82 Acts::Navigator::Config nav_cfg{geometry().getTG()};
83 nav_cfg.resolveMaterial = true;
84 nav_cfg.resolvePassive = true;
85 nav_cfg.resolveSensitive = true;
86 const Acts::Navigator navigator(nav_cfg);
87
88 propagator_ = std::make_unique<CkfPropagator>(
89 stepper, navigator,
90 Acts::getDefaultLogger("CKF_PROP", acts_logging_level));
91
92 // Setup the finder / fitters
93 ckf_ = std::make_unique<std::decay_t<decltype(*ckf_)>>(
94 *propagator_, Acts::getDefaultLogger("CKF", acts_logging_level));
95 // Extrapolation uses VoidNavigator so it can reach surfaces outside the
96 // tracking geometry (e.g. ECAL scoring plane) without being stopped at
97 // volume boundaries.
98 propagator_extrap_ = std::make_unique<ExtrapPropagator>(
99 Acts::EigenStepper<>{map}, Acts::VoidNavigator{});
100 trk_extrap_ = std::make_shared<std::decay_t<decltype(*trk_extrap_)>>(
101 *propagator_extrap_, geometryContext(), magneticFieldContext());
102
103 // Setup zero-B CKF as fallback
104 Acts::ConstantBField zero_b_field(Acts::Vector3(0., 0., 0.));
105 const auto zero_b_stepper = Acts::EigenStepper<>{
106 std::make_shared<Acts::ConstantBField>(zero_b_field)};
107 propagator_zero_b_ =
108 std::make_unique<CkfPropagator>(zero_b_stepper, navigator);
109 ckf_zero_b_ = std::make_unique<std::decay_t<decltype(*ckf_zero_b_)>>(
110 *propagator_zero_b_,
111 Acts::getDefaultLogger("CKF_ZERO_B", acts_logging_level));
112 propagator_extrap_zero_b_ = std::make_unique<ExtrapPropagator>(
113 Acts::EigenStepper<>{
114 std::make_shared<Acts::ConstantBField>(zero_b_field)},
115 Acts::VoidNavigator{});
116 trk_extrap_zero_b_ =
117 std::make_shared<std::decay_t<decltype(*trk_extrap_zero_b_)>>(
118 *propagator_extrap_zero_b_, geometryContext(),
119 magneticFieldContext());
120
121 // Setup const-B (1.5T) CKF as fallback for tagger
122 propagator_const_b_ =
123 std::make_unique<CkfPropagator>(const_stepper, navigator);
124 ckf_const_b_ = std::make_unique<std::decay_t<decltype(*ckf_const_b_)>>(
125 *propagator_const_b_,
126 Acts::getDefaultLogger("CKF_CONST_B", acts_logging_level));
127 propagator_extrap_const_b_ = std::make_unique<ExtrapPropagator>(
128 Acts::EigenStepper<>{const_b_field}, Acts::VoidNavigator{});
129 trk_extrap_const_b_ =
130 std::make_shared<std::decay_t<decltype(*trk_extrap_const_b_)>>(
131 *propagator_extrap_const_b_, geometryContext(),
132 magneticFieldContext());
133} // end of CKFProcessor::onNewRun()
134
136 eventnr_++;
137 // get the tracking geometry from conditions
138 auto tg{geometry()};
139
140 // TODO use global variable instead and call clear;
141
142 std::vector<ldmx::Track> tracks;
143
144 auto start = std::chrono::high_resolution_clock::now();
145
146 nevents_++;
147
148 ACTS_LOCAL_LOGGER(Acts::getDefaultLogger("LDMX Tracking Geometry Maker",
149 Acts::Logging::DEBUG));
150
151 // Move this at the start of the producer
152 Acts::PropagatorOptions<Acts::StepperPlainOptions,
153 Acts::NavigatorPlainOptions, ActionList>
154 propagator_options(geometryContext(), magneticFieldContext());
155
156 propagator_options.pathLimit = std::numeric_limits<double>::max();
157 // Activate loop protection at some pt value
158 propagator_options.loopProtection = false;
159 //(startParameters.transverseMomentum() < cfg.ptLoopers);
160
161 // Switch the material interaction on/off & eventually into logging mode
162 auto& m_interactor =
163 propagator_options.actorList.get<Acts::MaterialInteractor>();
164 m_interactor.multipleScattering = true;
165 m_interactor.energyLoss = true;
166 m_interactor.recordInteractions = false;
167
168 // The logger can be switched to sterile, e.g. for timing logging
169 auto& s_logger =
170 propagator_options.actorList.get<Acts::detail::SteppingLogger>();
171 s_logger.sterile = true;
172 // Set a maximum step size
173 propagator_options.stepping.maxStepSize =
174 propagator_step_size_ * Acts::UnitConstants::mm;
175 propagator_options.maxSteps = propagator_max_steps_;
176
177 // #######################//
178 // Kalman Filter algorithm//
179 // #######################//
180
181 // Step 1 - Form the source links
182
183 // a) Loop over the sim Hits
184
185 auto setup = std::chrono::high_resolution_clock::now();
186 profiling_map_["setup"] +=
187 std::chrono::duration<double, std::milli>(setup - start).count();
188
189 const auto& measurements = event.getCollection<ldmx::Measurement>(
190 measurement_collection_, input_pass_name_);
191
192 // check if SimParticleMap is available for truth matching
193 std::shared_ptr<tracking::sim::TruthMatchingTool> truth_matching_tool =
194 nullptr;
195 std::map<int, ldmx::SimParticle> particle_map;
196
197 if (event.exists(sim_particles_coll_name_, sim_particles_event_passname_)) {
198 ldmx_log(debug) << "Setting up track truth matching tool";
199 particle_map = event.getMap<int, ldmx::SimParticle>(
200 sim_particles_coll_name_, sim_particles_event_passname_);
201 truth_matching_tool = std::make_shared<tracking::sim::TruthMatchingTool>(
202 particle_map, measurements);
203 }
204
205 // The mapping between the geometry identifier
206 // and the IndexsourceLink that points to the hit
207 const auto geo_id_sl_map = makeGeoIdSourceLinkMap(tg, measurements);
208
209 auto hits = std::chrono::high_resolution_clock::now();
210 profiling_map_["hits"] +=
211 std::chrono::duration<double, std::milli>(hits - setup).count();
212
213 // ============ Setup the CKF ============
214
215 // Retrieve the seeds
216 const auto& seed_tracks =
217 event.getCollection<ldmx::Track>(seed_coll_name_, input_pass_name_);
218
219 ldmx_log(info) << "Number of " << seed_coll_name_
220 << " seed tracks = " << seed_tracks.size();
221
222 if (seed_tracks.empty()) {
223 std::vector<ldmx::Track> empty;
224 ldmx_log(warn) << "No seed tracks, returning...";
225 event.add(out_trk_collection_, empty);
226 return;
227 }
228
229 // Run the CKF on each seed and produce a track candidate
230 std::vector<Acts::BoundTrackParameters> start_parameters;
231
232 ldmx_log(debug) << "Transform the seed track to bound parameters";
233 int seed_track_index{0};
234 for (auto& seed : seed_tracks) {
235 // Transform the seed track to bound parameters.
236 // Perigee is stored in LDMX global frame; convert to ACTS frame for
237 // surface.
238 Acts::Vector3 perigee_acts = tracking::sim::utils::ldmx2Acts(Acts::Vector3(
239 seed.getPerigeeX(), seed.getPerigeeY(), seed.getPerigeeZ()));
240 std::shared_ptr<Acts::PerigeeSurface> perigee_surface =
241 Acts::Surface::makeShared<Acts::PerigeeSurface>(perigee_acts);
242
243 Acts::BoundVector param_vec;
244 param_vec << seed.getD0(), seed.getZ0(), seed.getPhi(), seed.getTheta(),
245 seed.getQoP(), seed.getT();
246
247 Acts::BoundMatrix cov_mat =
248 tracking::sim::utils::unpackCov(seed.getPerigeeCov());
249
250 ldmx_log(debug) << " For seed index_ = " << seed_track_index
251 << ": Perigee X / Y / Z = " << seed.getPerigeeX() << " / "
252 << seed.getPerigeeY() << " / " << seed.getPerigeeZ()
253 << ", D0 = " << param_vec[0] << ", Z0 = " << param_vec[1]
254 << ", Phi = " << param_vec[2]
255 << ", Theta = " << param_vec[3]
256 << ", QoP = " << param_vec[4]
257 << ", Time = " << param_vec[5];
258
259 ldmx_log(debug) << " Cov matrix diagonal (" << cov_mat(0, 0) << ", "
260 << cov_mat(1, 1) << ", " << cov_mat(2, 2) << ")";
261
262 // need to set particle hypothesis...set to electron for now...
263 auto part_hypo{Acts::ParticleHypothesis::electron()};
264 start_parameters.push_back(Acts::BoundTrackParameters(
265 perigee_surface, param_vec, cov_mat, part_hypo));
266
267 // This is a global variable for performance checks
268 nseeds_++;
269 // This is just to index_ the seed we are looking at
270 seed_track_index++;
271 } // loop on seeds
272
273 auto seeds = std::chrono::high_resolution_clock::now();
274 profiling_map_["seeds"] +=
275 std::chrono::duration<double, std::milli>(seeds - hits).count();
276
277 Acts::GainMatrixUpdater kf_updater;
278
279 // configuration for the measurement selector. Empty geometry identifier means
280 // applicable to all the detector elements
281
282 Acts::MeasurementSelector::Config measurement_selector_cfg = {
283 // global default: no chi2 cut, only one measurement per surface
284 {Acts::GeometryIdentifier(), {{}, {outlier_pval_}, {1u}}},
285 };
286
287 Acts::MeasurementSelector meas_sel{measurement_selector_cfg};
288
289 tracking::sim::LdmxMeasurementCalibrator calibrator{measurements};
290
291 // Create source link accessor iterator type and lambda
292 struct SourceLinkAccIt {
293 using BaseIt = decltype(geo_id_sl_map.begin());
294 BaseIt it_;
295
296#pragma GCC diagnostic push
297#pragma GCC diagnostic ignored "-Wunused-local-typedefs"
298
299 using difference_type = typename BaseIt::difference_type;
300 using iterator_category = typename BaseIt::iterator_category;
301 using value_type = Acts::SourceLink;
302 using pointer = typename BaseIt::pointer;
303 using reference = value_type&;
304#pragma GCC diagnostic pop
305
306 SourceLinkAccIt& operator++() {
307 ++it_;
308 return *this;
309 }
310 bool operator==(const SourceLinkAccIt& other) const {
311 return it_ == other.it_;
312 }
313 bool operator!=(const SourceLinkAccIt& other) const {
314 return !(*this == other);
315 }
316 value_type operator*() const { return value_type{it_->second}; }
317 };
318
319 auto source_link_accessor = [&](const Acts::Surface& surface)
320 -> std::pair<SourceLinkAccIt, SourceLinkAccIt> {
321 auto [begin, end] = geo_id_sl_map.equal_range(surface.geometryId());
322 return {SourceLinkAccIt{begin}, SourceLinkAccIt{end}};
323 };
324
325 // v46: calibrator and measurementSelector moved to TrackStateCreator
326 Acts::TrackStateCreator<SourceLinkAccIt, TrackContainer> track_state_creator;
327 track_state_creator.sourceLinkAccessor
328 .connect<&decltype(source_link_accessor)::operator(),
329 decltype(source_link_accessor)>(&source_link_accessor);
330 if (use1_dmeasurements_) {
331 track_state_creator.calibrator
333 Acts::VectorMultiTrajectory>>(&calibrator);
334 } else {
335 track_state_creator.calibrator
337 Acts::VectorMultiTrajectory>>(&calibrator);
338 }
339 track_state_creator.measurementSelector
340 .connect<&Acts::MeasurementSelector::select<Acts::VectorMultiTrajectory>>(
341 &meas_sel);
342
343 Acts::CombinatorialKalmanFilterExtensions<TrackContainer> ckf_extensions;
344 ckf_extensions.updater.connect<
345 &Acts::GainMatrixUpdater::operator()<Acts::VectorMultiTrajectory>>(
346 &kf_updater);
347 ckf_extensions.createTrackStates.connect<&Acts::TrackStateCreator<
348 SourceLinkAccIt, TrackContainer>::createTrackStates>(
349 &track_state_creator);
350
351 ldmx_log(debug) << "Setting up surfaces...";
352
353 std::shared_ptr<const Acts::PerigeeSurface> origin_surface =
354 Acts::Surface::makeShared<Acts::PerigeeSurface>(
355 Acts::Vector3(0., 0., 0.));
356
357 ldmx_log(debug) << "About to run CKF...";
358
359 // run the CKF for all initial track states
360 auto ckf_setup = std::chrono::high_resolution_clock::now();
361 profiling_map_["ckf_setup"] +=
362 std::chrono::duration<double, std::milli>(ckf_setup - seeds).count();
363
364 Acts::VectorTrackContainer vtc;
365 Acts::VectorMultiTrajectory mtj;
366 Acts::TrackContainer tc{vtc, mtj};
367
368 // The number of track candidates (i.e. startParameters.size()) is always
369 // the same as the number of seed tracks
370 ldmx_log(debug) << "Loop on the track candidates";
371 for (size_t track_id = 0u; track_id < start_parameters.size(); ++track_id) {
372 ldmx_log(debug) << "---------------------------";
373 ldmx_log(debug) << "Candidate Track ID = " << track_id;
374 // Define the CKF options here:
375 const Acts::CombinatorialKalmanFilterOptions<TrackContainer> ckf_options(
376 TrackingGeometryUser::geometryContext(),
377 TrackingGeometryUser::magneticFieldContext(),
378 TrackingGeometryUser::calibrationContext(), ckf_extensions,
379 static_cast<Acts::PropagatorPlainOptions>(propagator_options),
380 true /* multiple scattering */, false /* energy loss */);
381
382 ldmx_log(debug) << " Checking options: multiple scattering = "
383 << ckf_options.multipleScattering
384 << " energy loss = " << ckf_options.energyLoss;
385
386 // Try field-map CKF first
387 auto results =
388 ckf_->findTracks(start_parameters.at(track_id), ckf_options, tc);
389
390 auto start_params = start_parameters.at(track_id).parameters().transpose();
391
392 // If field-map CKF fails, try appropriate fallback based on tracking system
393 if (!results.ok()) {
394 if (!tagger_tracking_) {
395 // Recoil tracking: try zero-B CKF as fallback
396 n_fieldmap_ckf_failed_recoil_++;
397 ldmx_log(debug)
398 << " Field-map CKF failed, trying zero-B CKF fallback";
399 results = ckf_zero_b_->findTracks(start_parameters.at(track_id),
400 ckf_options, tc);
401 if (results.ok()) {
402 n_zerob_ckf_recovered_recoil_++;
403 ldmx_log(debug) << " Yay! Zero-B CKF succeeded as fallback!";
404 } else {
405 ldmx_log(debug) << " Zero-B CKF also failed!";
406 }
407 } else {
408 // Tagger tracking: try const-B (1.5T) CKF as fallback
409 n_fieldmap_ckf_failed_tagger_++;
410 ldmx_log(debug)
411 << " Field-map CKF failed, trying const-B (1.5T) CKF fallback";
412 results = ckf_const_b_->findTracks(start_parameters.at(track_id),
413 ckf_options, tc);
414 if (results.ok()) {
415 n_constb_ckf_recovered_tagger_++;
416 ldmx_log(debug) << " Yay! Const-B CKF succeeded as fallback!";
417 } else {
418 ldmx_log(debug) << " Const-B CKF also failed!";
419 }
420 }
421 }
422
423 ldmx_log(debug)
424 << " Checking CKF success for track candidate with params: "
425 << " D0 = " << start_params[0] << " Z0 = " << start_params[1]
426 << ", Phi = " << start_params[2] << " Theta = " << start_params[3]
427 << ", QoP = " << start_params[4] << " Time = " << start_params[5];
428 if (not results.ok()) {
429 ldmx_log(debug) << " CKF failed!";
430 continue;
431 } else {
432 ldmx_log(debug) << " CKF succeded!";
433 }
434
435 auto& tracks_from_seed = results.value();
436 if (tracks_from_seed.size() != 1) {
437 ldmx_log(info) << " tracksFromSeed.size = " << tracks_from_seed.size();
438 }
439 // For now it seems this loop is only looping on a single element
440 for (auto& track : tracks_from_seed) {
441 // do the track smoothing...this is not done in the CKF code anymore
442 auto smooth_result = Acts::smoothTrack(geometryContext(), track);
443 if (!smooth_result.ok()) {
444 ldmx_log(warn) << "smoothTrack failed: "
445 << smooth_result.error().message();
446 }
447 // Build the output Track
448 ldmx::Track trk;
449
450 // Extrapolate to the target surface
451 auto opt_target = trk_extrap_->extrapolate(track, target_surface_);
452
453 if (!opt_target) {
454 if (tagger_tracking_) {
455 n_fieldmap_target_extrap_failed_tagger_++;
456 ldmx_log(debug) << " Field-map target extrapolation failed, "
457 "trying const-B (1.5T) fallback";
458 opt_target = trk_extrap_const_b_->extrapolate(track, target_surface_);
459 if (opt_target)
460 n_constb_target_extrap_recovered_tagger_++;
461 else
462 ldmx_log(debug) << " Both field-map and Const-B target "
463 "extrapolation failed!";
464 } else {
465 n_fieldmap_target_extrap_failed_recoil_++;
466 ldmx_log(debug) << " Field-map target extrapolation failed, "
467 "trying zero-B fallback";
468 opt_target = trk_extrap_zero_b_->extrapolate(track, target_surface_);
469 if (opt_target)
470 n_zerob_target_extrap_recovered_recoil_++;
471 else
472 ldmx_log(debug)
473 << " Both field-map and Zero-B target extrapolation failed!";
474 }
475 }
476
477 if (!opt_target) {
478 ldmx_log(debug) << " Could not extrapolate to target! nhits = "
479 << track.nMeasurements() << " Printing track states:";
480 for (const auto ts : track.trackStatesReversed()) {
481 if (ts.hasSmoothed())
482 ldmx_log(debug) << " Parameters: " << ts.smoothed().transpose();
483 else
484 ldmx_log(debug) << " Track state not smoothed!";
485 }
486 ldmx_log(debug) << " ...skipping this track candidate...";
487 continue;
488 }
489
490 ldmx_log(debug) << " Successfully obtained TrackState at target";
491
492 // Build TrackState in LDMX coordinates and add to track
493 auto ts_at_target = tracking::sim::utils::makeTrackState(
494 geometryContext(), *opt_target, ldmx::AtTarget);
495 trk.addTrackState(ts_at_target);
496
497 ldmx_log(debug) << " Position at target (LDMX): ("
498 << ts_at_target.pos_[0] << ", " << ts_at_target.pos_[1]
499 << ", " << ts_at_target.pos_[2] << ") mm"
500 << " Momentum: (" << ts_at_target.mom_[0] << ", "
501 << ts_at_target.mom_[1] << ", " << ts_at_target.mom_[2]
502 << ") GeV";
503
504 // Update ACTS track reference surface (needed for downstream ACTS usage)
505 track.setReferenceSurface(target_surface_);
506 track.parameters() = opt_target->parameters();
507
508 // Store perigee (bound) parameters at the target for convenience
509 trk.setPerigeeParameters(tracking::sim::utils::convertActsToLdmxPars(
510 opt_target->parameters()));
511 if (opt_target->covariance()) {
512 std::vector<double> cov_vec;
513 tracking::sim::utils::flatCov(*(opt_target->covariance()), cov_vec);
514 trk.setPerigeeCov(cov_vec);
515 }
516 // Perigee location: target surface origin rotated to LDMX frame
517 Acts::Vector3 target_loc_ldmx = tracking::sim::utils::acts2Ldmx(
518 target_surface_->localToGlobalTransform(geometryContext())
519 .translation());
520 trk.setPerigeeLocation(target_loc_ldmx[0], target_loc_ldmx[1],
521 target_loc_ldmx[2]);
522
523 trk.setChi2(track.chi2());
524 trk.setNhits(track.nMeasurements());
525 trk.setNdf(track.nMeasurements() - 5);
526 trk.setNsharedHits(track.nSharedHits());
527 trk.setCharge(opt_target->parameters()[Acts::eBoundQOverP] > 0 ? 1 : -1);
528
529 // At least min_hits hits and p > 50 MeV
530 if ((trk.getNhits() <= min_hits_) ||
531 (std::abs(1. / trk.getQoP()) <= 0.05)) {
532 ldmx_log(debug)
533 << " > Track candidate did NOT meet the requirements: Nhits = "
534 << trk.getNhits() << " and p = " << std::abs(1. / trk.getQoP())
535 << " GeV";
536 continue;
537 }
538
539 // Add measurements to the final track
540 ldmx_log(debug) << " Add measurements to the final track from "
541 << track.nTrackStates() << " TrackStates with "
542 << track.nMeasurements() << " measurements";
543
544 int trk_state_index{0};
545 for (const auto ts : track.trackStatesReversed()) {
546 // Check TrackStates Quality
547 ldmx_log(debug) << " Checking Track State index_ = "
548 << trk_state_index << " at location "
549 << ts.referenceSurface()
550 .localToGlobalTransform(geometryContext())
551 .translation()
552 .transpose();
553
554 if (ts.hasSmoothed()) {
555 ldmx_log(debug) << " Smoothed track parameters: "
556 << ts.smoothed().transpose();
557 // ldmx_log(debug) << " Smoothed covariance mtx:\n" <<
558 // ts.smoothedCovariance();
559 }
560
561 // Check if the track state is a measurement
562 auto type_flags = ts.typeFlags();
563
564 if (type_flags.isMeasurement() && ts.hasUncalibratedSourceLink()) {
565 Acts::SourceLink usl = ts.getUncalibratedSourceLink();
568
569 ldmx::Measurement ldmx_meas = measurements.at(sl.index());
570 ldmx_log(debug) << " Adding measurement to ldmx::track with "
571 "source link index_ = "
572 << sl.index();
573 ldmx_log(trace) << " Measurement:\n" << ldmx_meas;
574 trk.addMeasurementIndex(sl.index());
575
576 // Store the smoothed state for algebraic unbiased residuals in the
577 // DQM. The leave-one-out formula (NIM A 262, 444, 1987) removes
578 // this hit's contribution analytically:
579 // r_ubs = V/(V - C) * (m - x_smooth), pull = r_ubs * sqrt(V-C)/V
580 // This works correctly for all layers, including seed layers where
581 // the predicted state would be biased.
582 if (ts.hasSmoothed()) {
583 trk.addSmoothedLoc0(
584 static_cast<float>(ts.smoothed()[Acts::eBoundLoc0]),
585 static_cast<float>(ts.smoothedCovariance()(Acts::eBoundLoc0,
586 Acts::eBoundLoc0)));
587 }
588
589 // Extract path length from the track state based on the angle
590 if (ts.hasSmoothed()) {
591 const auto& meas_surface = ts.referenceSurface();
592 const auto& smoothed_params = ts.smoothed();
593
594 // Get the momentum from the track parameters
595 // momentum = p * direction where direction = (sin(theta)*cos(phi),
596 // sin(theta)*sin(phi), cos(theta))
597 float p_inv = smoothed_params[Acts::eBoundQOverP];
598 float p = 1.0f / std::abs(p_inv);
599 float theta = smoothed_params[Acts::eBoundTheta];
600 float phi = smoothed_params[Acts::eBoundPhi];
601
602 Acts::Vector3 global_momentum(p * std::sin(theta) * std::cos(phi),
603 p * std::sin(theta) * std::sin(phi),
604 p * std::cos(theta));
605
606 // Get the local frame (transform from global to local)
607 auto local_frame_transform =
608 meas_surface.localToGlobalTransform(geometryContext());
609 Acts::Vector3 local_momentum =
610 local_frame_transform.rotation().transpose() * global_momentum;
611
612 // Calculate local angle components (tangent of angles)
613 float phi_u = (local_momentum.z() != 0)
614 ? local_momentum.x() / local_momentum.z()
615 : 0.;
616 float phi_v = (local_momentum.z() != 0)
617 ? local_momentum.y() / local_momentum.z()
618 : 0.;
619
620 // Calculate the total angle from the local angle components
621 // tan(angle) = sqrt(phi_u^2 + phi_v^2)
622 // cos(angle) = 1 / sqrt(1 + tan(angle)^2)
623 // path_length = thickness / cos(angle)
624 float sensor_thickness = 0.0f;
625 if (const auto* placement = meas_surface.surfacePlacement()) {
626 sensor_thickness = static_cast<float>(
627 static_cast<const tracking::geo::DetectorElement*>(placement)
628 ->thickness());
629 } else {
630 ldmx_log(warn) << "No detector element for measurement surface"
631 << " — skipping dE/dx for this hit";
632 continue;
633 }
634 float tan_angle_sq = phi_u * phi_u + phi_v * phi_v;
635 float cos_angle = 1.0f / std::sqrt(1.0f + tan_angle_sq);
636 float path_length = sensor_thickness / cos_angle;
637
638 ldmx_log(debug) << " Local angles: phi_u = " << phi_u
639 << ", phi_v = " << phi_v
640 << "; Path length = " << path_length << " mm";
641
642 // Calculate dE/dx and add to track (in MeV/mm)
643 float edep = ldmx_meas.getEdep();
644 float dedx = edep / path_length;
645 trk.addDedxMeasurement(dedx);
646
647 ldmx_log(debug) << " Edep = " << edep
648 << " MeV, dE/dx = " << dedx << " MeV/mm";
649 }
650 } else {
651 ldmx_log(debug) << " This TrackState is not a measurement";
652 }
653 trk_state_index++;
654 }
655
656 ldmx_log(debug) << " Starting extrapolations";
657 // Extrapolations
658 // To ECAL
659 const double ecal_scoring_plane = 240.5;
660 Acts::Vector3 pos(ecal_scoring_plane, 0., 0.);
661 Acts::Translation3 surf_translation(pos);
662 Acts::Transform3 surf_transform(surf_translation * surf_rotation_);
663 const std::shared_ptr<Acts::PlaneSurface> ecal_surface =
664 Acts::Surface::makeShared<Acts::PlaneSurface>(surf_transform);
665
666 // Beam Origin unbounded surface
667 const std::shared_ptr<Acts::Surface> beam_origin_surface =
668 tracking::sim::utils::unboundSurface(-700);
669
670 if (tagger_tracking_) {
671 ldmx_log(debug) << " Beam Origin Extrapolation";
672 auto opt_beam_origin =
673 trk_extrap_->extrapolate(track, beam_origin_surface);
674 if (opt_beam_origin) {
675 trk.addTrackState(tracking::sim::utils::makeTrackState(
676 geometryContext(), *opt_beam_origin, ldmx::AtBeamOrigin));
677 ldmx_log(debug)
678 << " Successfully obtained TrackState at beam origin";
679 }
680 }
681
682 // Recoil Extrapolation to ECAL only
683 if (!tagger_tracking_) {
684 ldmx_log(debug) << " Ecal Extrapolation";
685 auto opt_ecal = trk_extrap_->extrapolate(track, ecal_surface);
686
687 if (!opt_ecal) {
688 n_fieldmap_ecal_extrap_failed_recoil_++;
689 ldmx_log(debug) << " Field-map ECAL extrapolation failed, trying "
690 "zero-B fallback";
691 opt_ecal = trk_extrap_zero_b_->extrapolate(track, ecal_surface);
692 if (opt_ecal)
693 n_zerob_ecal_extrap_recovered_recoil_++;
694 else
695 ldmx_log(debug)
696 << " Both field-map and Zero-B ECAL extrapolation failed!";
697 }
698
699 if (opt_ecal) {
700 auto ts_at_ecal = tracking::sim::utils::makeTrackState(
701 geometryContext(), *opt_ecal, ldmx::AtECAL);
702 trk.addTrackState(ts_at_ecal);
703 ldmx_log(debug) << " Successfully obtained TrackState at ECAL";
704 ldmx_log(debug) << " Position at ECAL (LDMX): ("
705 << ts_at_ecal.pos_[0] << ", " << ts_at_ecal.pos_[1]
706 << ", " << ts_at_ecal.pos_[2] << ") mm";
707 }
708 }
709
710 // Truth matching
711 if (truth_matching_tool) {
712 auto truth_info = truth_matching_tool->truthMatch(trk);
713 trk.setTrackID(truth_info.track_id_);
714 trk.setPdgID(truth_info.pdg_id_);
715 trk.setTruthProb(truth_info.truth_prob_);
716 }
717
718 // Adding the track candidate to the track collection
719 ldmx_log(debug)
720 << " > Adding the track candidate to the track collection";
721 tracks.push_back(trk);
722 ntracks_++;
723 } // // loop on tracksFromSeed (which usually has 1 element)
724 } // loop seed track parameters (i.e. track candidates)
725
726 ldmx_log(info) << "Number of CKF tracks " << tracks.size();
727
728 auto ckf_run = std::chrono::high_resolution_clock::now();
729 profiling_map_["ckf_run"] +=
730 std::chrono::duration<double, std::milli>(ckf_run - ckf_setup).count();
731
732 // Calculating Shared Hits
733 auto shared_hits = computeSharedHits(
734 tracks, measurements, tg, tracking::sim::utils::sourceLinkHash,
735 tracking::sim::utils::sourceLinkEquality);
736 for (std::size_t i_track = 0; i_track < shared_hits.size(); ++i_track) {
737 tracks[i_track].setNsharedHits(shared_hits[i_track].size());
738 for (auto idx : shared_hits[i_track]) {
739 tracks[i_track].addSharedIndex(idx);
740 }
741 }
742
743 auto result_loop = std::chrono::high_resolution_clock::now();
744 profiling_map_["result_loop"] +=
745 std::chrono::duration<double, std::milli>(result_loop - ckf_run).count();
746
747 // Add the tracks to the event
748 event.add(out_trk_collection_, tracks);
749
750 auto end = std::chrono::high_resolution_clock::now();
751 // long long microseconds =
752 // std::chrono::duration_cast<std::chrono::microseconds>(end-start).count();
753 auto diff = end - start;
754 processing_time_ += std::chrono::duration<double, std::milli>(diff).count();
755} // end of produce()
756
758 if (use1_dmeasurements_)
759 ldmx_log(debug) << "Use1Dmeasurements = " << std::boolalpha
760 << use1_dmeasurements_;
761 if (remove_stereo_)
762 ldmx_log(debug) << "Remove_stereo = " << std::boolalpha << remove_stereo_;
763}
764
766 ldmx_log(info) << "--------------------------------- ";
767 ldmx_log(info) << "Found " << ntracks_ << " tracks / " << nseeds_
768 << " nseeds";
769 ldmx_log(info) << "AVG Time/Event: " << std::fixed << std::setprecision(1)
770 << processing_time_ / nevents_ << " ms";
771 ldmx_log(info) << "Breakdown::";
772 ldmx_log(info) << " setup Avg Time/Event = " << std::fixed
773 << std::setprecision(3) << profiling_map_["setup"] / nevents_
774 << " ms";
775 ldmx_log(info) << " hits Avg Time/Event = " << std::fixed
776 << std::setprecision(2) << profiling_map_["hits"] / nevents_
777 << " ms";
778 ldmx_log(info) << " seeds Avg Time/Event = " << std::fixed
779 << std::setprecision(3) << profiling_map_["seeds"] / nevents_
780 << " ms";
781 ldmx_log(info) << " ckf_setup Avg Time/Event = " << std::fixed
782 << std::setprecision(3)
783 << profiling_map_["ckf_setup"] / nevents_ << " ms";
784 ldmx_log(info) << " ckf_run Avg Time/Event = " << std::fixed
785 << std::setprecision(3) << profiling_map_["ckf_run"] / nevents_
786 << " ms";
787 ldmx_log(info) << " result_loop Avg Time/Event = " << std::fixed
788 << std::setprecision(1)
789 << profiling_map_["result_loop"] / nevents_ << " ms";
790
791 // CKF fallback statistics
792 ldmx_log(info) << "CKF Fallback Statistics::";
793 if (tagger_tracking_) {
794 ldmx_log(info) << " Tagger: Field-map CKF failed "
795 << n_fieldmap_ckf_failed_tagger_
796 << " times, const-B CKF recovered "
797 << n_constb_ckf_recovered_tagger_ << " ("
798 << (n_fieldmap_ckf_failed_tagger_ > 0
799 ? 100.0 * n_constb_ckf_recovered_tagger_ /
800 n_fieldmap_ckf_failed_tagger_
801 : 0.0)
802 << "%)";
803
804 // Extrapolation fallback statistics for tagger
805 ldmx_log(info) << "Extrapolation Fallback Statistics::";
806 ldmx_log(info) << " Tagger Target: Field-map extrap failed "
807 << n_fieldmap_target_extrap_failed_tagger_
808 << " times, const-B extrap recovered "
809 << n_constb_target_extrap_recovered_tagger_ << " ("
810 << (n_fieldmap_target_extrap_failed_tagger_ > 0
811 ? 100.0 * n_constb_target_extrap_recovered_tagger_ /
812 n_fieldmap_target_extrap_failed_tagger_
813 : 0.0)
814 << "%)";
815 }
816
817 if (!tagger_tracking_) {
818 ldmx_log(info) << " Recoil: Field-map CKF failed "
819 << n_fieldmap_ckf_failed_recoil_
820 << " times, zero-B CKF recovered "
821 << n_zerob_ckf_recovered_recoil_ << " ("
822 << (n_fieldmap_ckf_failed_recoil_ > 0
823 ? 100.0 * n_zerob_ckf_recovered_recoil_ /
824 n_fieldmap_ckf_failed_recoil_
825 : 0.0)
826 << "%)";
827
828 // Extrapolation fallback statistics
829 ldmx_log(info) << "Extrapolation Fallback Statistics::";
830 ldmx_log(info) << " Recoil Target: Field-map extrap failed "
831 << n_fieldmap_target_extrap_failed_recoil_
832 << " times, zero-B extrap recovered "
833 << n_zerob_target_extrap_recovered_recoil_ << " ("
834 << (n_fieldmap_target_extrap_failed_recoil_ > 0
835 ? 100.0 * n_zerob_target_extrap_recovered_recoil_ /
836 n_fieldmap_target_extrap_failed_recoil_
837 : 0.0)
838 << "%)";
839 ldmx_log(info) << " Recoil ECAL: Field-map extrap failed "
840 << n_fieldmap_ecal_extrap_failed_recoil_
841 << " times, zero-B extrap recovered "
842 << n_zerob_ecal_extrap_recovered_recoil_ << " ("
843 << (n_fieldmap_ecal_extrap_failed_recoil_ > 0
844 ? 100.0 * n_zerob_ecal_extrap_recovered_recoil_ /
845 n_fieldmap_ecal_extrap_failed_recoil_
846 : 0.0)
847 << "%)";
848 }
849}
850
852 dumpobj_ = parameters.get<bool>("dumpobj", 0);
853 pionstates_ = parameters.get<int>("pionstates", 0);
854
855 bfield_ = parameters.get<double>("bfield", -1.5);
856 const_b_field_ = parameters.get<bool>("const_b_field", false);
857 field_map_ = parameters.get<std::string>("field_map");
858 propagator_step_size_ = parameters.get<double>("propagator_step_size", 200.);
859 propagator_max_steps_ = parameters.get<int>("propagator_max_steps", 10000);
860 measurement_collection_ = parameters.get<std::string>(
861 "measurement_collection", "TaggerMeasurements");
862 outlier_pval_ = parameters.get<double>("outlier_pval_", 3.84);
863
864 debug_acts_ = parameters.get<bool>("debug_acts", false);
865
866 remove_stereo_ = parameters.get<bool>("remove_stereo", false);
867 use1_dmeasurements_ = parameters.get<bool>("use1Dmeasurements", true);
868 min_hits_ = parameters.get<int>("min_hits", 7);
869
870 // Ckf specific options
871 use_extrapolate_location_ =
872 parameters.get<bool>("use_extrapolate_location", true);
873 extrapolate_location_ =
874 parameters.get<std::vector<double>>("extrapolate_location", {0., 0., 0.});
875 use_seed_perigee_ = parameters.get<bool>("use_seed_perigee", false);
876
877 // seeds from the event
878 seed_coll_name_ = parameters.get<std::string>("seed_coll_name", "seedTracks");
879
880 sim_particles_coll_name_ =
881 parameters.get<std::string>("sim_particles_coll_name");
882 sim_particles_event_passname_ =
883 parameters.get<std::string>("sim_particles_event_passname");
884
885 // output track collection
886 out_trk_collection_ =
887 parameters.get<std::string>("out_trk_collection", "Tracks");
888
889 // keep track on which system tracking is running
890 tagger_tracking_ = parameters.get<bool>("tagger_tracking", true);
891
892 // BField Systematics
893 map_offset_ =
894 parameters.get<std::vector<double>>("map_offset_", {0., 0., 0.});
895
896 input_pass_name_ = parameters.get<std::string>("input_pass_name");
897} // end of configure()
898
899auto CKFProcessor::makeGeoIdSourceLinkMap(
901 const std::vector<ldmx::Measurement>& measurements)
902 -> std::unordered_multimap<Acts::GeometryIdentifier,
904 std::unordered_multimap<Acts::GeometryIdentifier,
906 geo_id_sl_map;
907
908 ldmx_log(debug) << "The makeGeoIdSourceLinkMap has " << measurements.size()
909 << " measurements";
910
911 // Check the hits associated to the surfaces
912 for (unsigned int i_meas = 0; i_meas < measurements.size(); i_meas++) {
913 ldmx::Measurement meas = measurements.at(i_meas);
914 unsigned int layerid = meas.getLayerID();
915
916 const Acts::Surface* hit_surface = tg.getSurface(layerid);
917
918 if (hit_surface) {
919 // Transform the ldmx space point from global to local and store the
920 // information
921
922 acts_examples::IndexSourceLink idx_sl(hit_surface->geometryId(), i_meas);
923 // mg aug 2024 ... these don't print statements
924 // don't compile using v36 in Acts...figure out later
925 /*
926 ldmx_log(debug)
927 << "Insert measurement on surface located at::"
928 << hit_surface->transform(geometry_context()).translation();
929 ldmx_log(debug) << "and geoId::" << hit_surface->geometryId();
930
931 ldmx_log(debug) << "Surface info::"
932 << std::tie(*hit_surface, geometry_context());
933 */
934 geo_id_sl_map.insert(std::make_pair(hit_surface->geometryId(), idx_sl));
935
936 } else
937 ldmx_log(debug) << getName() << "::HIT " << i_meas << " at layer_"
938 << (measurements.at(i_meas)).getLayerID()
939 << " is not associated to any surface?!";
940 }
941
942 return geo_id_sl_map;
943}
944
945template <typename geometry_t, typename source_link_hash_t,
946 typename source_link_equality_t>
947std::vector<std::vector<std::size_t>> CKFProcessor::computeSharedHits(
948 std::vector<ldmx::Track> tracks, std::vector<ldmx::Measurement> meas_coll,
949 geometry_t& tg, source_link_hash_t&& sourceLinkHash,
950 source_link_equality_t&& sourceLinkEquality) const {
951 auto measurement_index_map =
952 std::unordered_map<Acts::SourceLink, std::size_t, source_link_hash_t,
953 source_link_equality_t>(0, sourceLinkHash,
954 sourceLinkEquality);
955
956 std::vector<std::vector<std::size_t>> measurements_per_track;
957 boost::container::flat_map<std::size_t,
958 boost::container::flat_set<std::size_t>>
959 tracks_per_measurement;
960 std::vector<std::size_t> shared_measurements_per_track;
961 auto number_of_tracks = 0;
962
963 // Iterate through all input tracks, collect their properties like measurement
964 // count and chi2 and fill the measurement map in order to relate tracks to
965 // each other if they have shared hits.
966 for (const auto& track : tracks) {
967 // Kick out tracks that do not fulfill our initial requirements
968 // if (track.getNhits() < n_measurements_min_) {
969 // continue;
970 // }
971
972 std::vector<std::size_t> measurements;
973 for (auto imeas : track.getMeasurementsIdxs()) {
974 auto meas = meas_coll.at(imeas);
975 const Acts::Surface* hit_surface = tg.getSurface(meas.getLayerID());
976 // Store the index_ source link
977 acts_examples::IndexSourceLink idx_sl(hit_surface->geometryId(), imeas);
978 Acts::SourceLink source_link = Acts::SourceLink(idx_sl);
979
980 auto emplace = measurement_index_map.try_emplace(
981 source_link, measurement_index_map.size());
982 measurements.push_back(emplace.first->second);
983 }
984
985 measurements_per_track.push_back(std::move(measurements));
986
987 ++number_of_tracks;
988 }
989
990 // Now we relate measurements to tracks
991 for (std::size_t i_track = 0; i_track < number_of_tracks; ++i_track) {
992 for (auto i_measurement : measurements_per_track[i_track]) {
993 tracks_per_measurement[i_measurement].insert(i_track);
994 }
995 }
996
997 // Finally, we can accumulate the number of shared measurements per track
998 shared_measurements_per_track = std::vector<std::size_t>(number_of_tracks, 0);
999
1000 std::vector<std::vector<std::size_t>> shared_measurement_idxs_per_track;
1001 for (std::size_t i_track = 0; i_track < number_of_tracks; ++i_track) {
1002 std::vector<std::size_t> shared_measurement_idxs;
1003 for (auto i_measurement : measurements_per_track[i_track]) {
1004 if (tracks_per_measurement[i_measurement].size() > 1) {
1005 ++shared_measurements_per_track[i_track];
1006 shared_measurement_idxs.push_back(i_measurement);
1007 }
1008 }
1009 shared_measurement_idxs_per_track.push_back(shared_measurement_idxs);
1010 }
1011 return shared_measurement_idxs_per_track;
1012}
1013
1014} // namespace reco
1015} // namespace tracking
1016
#define DECLARE_PRODUCER(CLASS)
Macro which allows the framework to construct a producer given its name during configuration.
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
int getLayerID() const
Run-specific configuration and data stored in its own output TTree alongside the event TTree in the o...
Definition RunHeader.h:57
Class representing a simulated particle.
Definition SimParticle.h:24
Implementation of a track object.
Definition Track.h:53
void produce(framework::Event &event) override
Run the processor.
void configure(framework::config::Parameters &parameters) override
Configure the processor using the given user specified parameters.
void onNewRun(const ldmx::RunHeader &rh) override
onNewRun is the first function called for each processor after the conditions are fully configured an...
CKFProcessor(const std::string &name, framework::Process &process)
Constructor.
int nseeds_
n seeds and n tracks
void onProcessStart() override
Callback for the EventProcessor to take any necessary action when the processing of events starts,...
void onProcessEnd() override
Callback for the EventProcessor to take any necessary action when the processing of events finishes,...
a helper base class providing some methods to shorten access to common conditions used within the tra...
std::shared_ptr< Acts::MagneticFieldProvider > bField() const
Return the loaded B-field provider.
void loadBField(const std::string &path, const std::vector< double > &map_offset={0., 0., 0.})
Load the interpolated B-field map from path and cache it.
void calibrate1d(const Acts::GeometryContext &, const Acts::CalibrationContext &, const Acts::SourceLink &genericSourceLink, typename traj_t::TrackStateProxy trackState) const
Find the measurement corresponding to the source link.
void calibrate(const Acts::GeometryContext &, const Acts::CalibrationContext &, const Acts::SourceLink &genericSourceLink, typename traj_t::TrackStateProxy trackState) const
Find the measurement corresponding to the source link.
The measurement calibrator can be a function or a class/struct able to retrieve the sim hits containe...