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