98 ldmx::EcalGeometry::CONDITIONS_OBJECT_NAME);
101 layer_surfaces_.clear();
105 Acts::Vector3 b_field(0., 0., 0.);
106 auto zero_b_field = std::make_shared<Acts::ConstantBField>(b_field);
119 Acts::Vector3 front_acts =
120 tracking::sim::utils::ldmx2Acts(Acts::Vector3(0.0, 0.0, ecal_front_z));
121 Acts::Vector3 back_acts =
122 tracking::sim::utils::ldmx2Acts(Acts::Vector3(0.0, 0.0, ecal_back_z));
127 std::vector<Acts::CuboidVolumeBuilder::LayerConfig> layer_configs;
128 double clearance = 1.0;
130 for (
auto& [layer, surface] : layer_surfaces_) {
131 Acts::CuboidVolumeBuilder::LayerConfig lcfg;
132 lcfg.surfaces = {surface};
133 lcfg.envelopeX = std::array<double, 2>{clearance, clearance};
135 layer_configs.push_back(lcfg);
139 Acts::Vector3 volume_center = 0.5 * (front_acts + back_acts);
140 double x_length = std::abs(back_acts.x() - front_acts.x()) + 20.0;
142 Acts::CuboidVolumeBuilder::VolumeConfig ecal_vol_cfg;
143 ecal_vol_cfg.position = volume_center;
144 ecal_vol_cfg.length = {x_length, 1000.0, 1000.0};
145 ecal_vol_cfg.name =
"EcalVolume";
146 ecal_vol_cfg.layerCfg = layer_configs;
147 ecal_vol_cfg.volumeMaterial =
148 std::make_shared<Acts::HomogeneousVolumeMaterial>(
149 Acts::Material::Vacuum());
152 Acts::CuboidVolumeBuilder cvb;
153 Acts::CuboidVolumeBuilder::Config cvb_cfg;
154 cvb_cfg.position = volume_center;
155 cvb_cfg.length = {x_length + 20.0, 1020.0, 1020.0};
156 cvb_cfg.volumeCfg = {ecal_vol_cfg};
157 cvb.setConfig(cvb_cfg);
159 Acts::TrackingGeometryBuilder::Config tgb_cfg;
160 tgb_cfg.trackingVolumeBuilders.push_back(
161 [=](
const auto& cxt,
const auto& inner,
const auto&) {
162 return cvb.trackingVolume(cxt, inner,
nullptr);
165 Acts::TrackingGeometryBuilder tgb(tgb_cfg);
166 tracking_geometry_ = tgb.trackingGeometry(gctx);
173 layer_geo_ids_.clear();
174 tracking_geometry_->visitSurfaces([&](
const Acts::Surface* surface) {
175 if (!surface)
return;
177 if (surface->geometryId().sensitive() == 0)
return;
182 const_cast<Acts::Surface*
>(surface)->assignIsSensitive(
true);
185 Acts::Vector3 center_ldmx =
186 tracking::sim::utils::acts2Ldmx(surface->center(gctx));
187 double z_ldmx = center_ldmx[2];
189 for (
int layer = 0; layer < geometry_->
getNumLayers(); ++layer) {
191 if (std::abs(z_ldmx - layer_z) < 0.1) {
192 layer_geo_ids_[layer] = surface->geometryId();
193 ldmx_log(debug) <<
"ECAL layer " << layer <<
" -> builder geo_id: vol="
194 << surface->geometryId().volume()
195 <<
" lay=" << surface->geometryId().layer()
196 <<
" sen=" << surface->geometryId().sensitive();
202 ldmx_log(info) <<
"Mapped " << layer_geo_ids_.size()
203 <<
" ECAL layers to builder-assigned geometry IDs";
206 const auto stepper = Acts::EigenStepper<>{zero_b_field};
208 auto acts_logging_level =
209 debug_ ? Acts::Logging::VERBOSE : Acts::Logging::FATAL;
212 Acts::Navigator::Config nav_cfg{tracking_geometry_};
213 nav_cfg.resolveSensitive =
true;
214 nav_cfg.resolvePassive =
false;
215 nav_cfg.resolveMaterial =
false;
216 const Acts::Navigator navigator(
217 nav_cfg, Acts::getDefaultLogger(
"ECAL_NAV", acts_logging_level));
220 propagator_ = std::make_unique<EcalPropagator>(
222 Acts::getDefaultLogger(
"ECAL_PROP", acts_logging_level));
225 ckf_ = std::make_unique<std::decay_t<
decltype(*ckf_)>>(
226 *propagator_, Acts::getDefaultLogger(
"ECAL_CKF", acts_logging_level));
228 ldmx_log(info) <<
"EcalTrackFinderProcessor initialized with "
229 << layer_surfaces_.size() <<
" ECAL layer surfaces";
359 const std::vector<ldmx::Measurement>& measurements) {
360 std::vector<ldmx::Track> seeds;
362 if (measurements.size() <
static_cast<size_t>(min_hits_)) {
363 ldmx_log(debug) <<
"Too few measurements for seed: " << measurements.size()
364 <<
" < " << min_hits_;
369 std::map<int, std::vector<const ldmx::Measurement*>> layer_map;
370 for (
const auto& meas : measurements) {
371 layer_map[meas.getLayerID()].push_back(&meas);
374 ldmx_log(debug) <<
"Measurements span " << layer_map.size() <<
" layers";
377 std::vector<Acts::Vector3> points;
378 std::vector<ldmx::Measurement> seed_measurements;
380 for (
const auto& [layer, meas_vec] : layer_map) {
382 if (!meas_vec.empty()) {
383 const auto* meas = meas_vec[0];
384 auto gpos = meas->getGlobalPosition();
387 Acts::Vector3 pos_acts = tracking::sim::utils::ldmx2Acts(
388 Acts::Vector3(gpos[0], gpos[1], gpos[2]));
389 points.push_back(pos_acts);
390 seed_measurements.push_back(*meas);
394 if (points.size() <
static_cast<size_t>(min_hits_)) {
395 ldmx_log(debug) <<
"Too few layers hit for seed: " << points.size() <<
" < "
403 ldmx_log(debug) <<
"Seed line fit: rms=" << rms <<
" dir=(" << direction.x()
404 <<
"," << direction.y() <<
"," << direction.z() <<
")";
406 if (rms > max_seed_rms_) {
407 ldmx_log(debug) <<
"Seed RMS too large: " << rms <<
" > " << max_seed_rms_
420 auto& seed_surface = layer_surfaces_.begin()->second;
423 Acts::Vector3 ref_normal =
424 seed_surface->localToGlobalTransform(gctx).rotation().col(2);
425 Acts::Vector3 ref_center = seed_surface->center(gctx);
428 (ref_center - position).dot(ref_normal) / direction.dot(ref_normal);
429 Acts::Vector3 seed_pos = position + t * direction;
432 double p_estimate = 200.0;
433 Acts::Vector3 seed_mom = p_estimate * direction;
436 double q = Acts::UnitConstants::e;
439 Acts::FreeVector seed_free =
440 tracking::sim::utils::toFreeParameters(seed_pos, seed_mom, q);
442 auto bound_params_result =
443 Acts::transformFreeToBoundParameters(seed_free, *seed_surface, gctx);
445 if (!bound_params_result.ok()) {
446 ldmx_log(warn) <<
"Failed to create bound parameters for seed";
450 Acts::BoundVector bound_params = bound_params_result.value();
453 Acts::BoundVector stddev;
454 stddev[Acts::eBoundLoc0] = 10.0 * Acts::UnitConstants::mm;
455 stddev[Acts::eBoundLoc1] = 10.0 * Acts::UnitConstants::mm;
456 stddev[Acts::eBoundPhi] = 0.1 * Acts::UnitConstants::rad;
457 stddev[Acts::eBoundTheta] = 0.1 * Acts::UnitConstants::rad;
458 stddev[Acts::eBoundQOverP] = 0.5 / p_estimate;
459 stddev[Acts::eBoundTime] = 10.0 * Acts::UnitConstants::ns;
461 Acts::BoundMatrix bound_cov = stddev.cwiseProduct(stddev).asDiagonal();
467 Acts::Vector3 ref_ldmx = tracking::sim::utils::acts2Ldmx(ref_center);
468 seed.setPerigeeLocation(ref_ldmx[0], ref_ldmx[1], ref_ldmx[2]);
471 seed.setNhits(seed_measurements.size());
473 seed.setNsharedHits(0);
474 seed.setCharge(q > 0 ? 1 : -1);
477 std::vector<double> v_seed_params(bound_params.data(),
478 bound_params.data() + bound_params.size());
479 std::vector<double> v_seed_cov;
480 tracking::sim::utils::flatCov(bound_cov, v_seed_cov);
482 seed.setPerigeeParameters(v_seed_params);
483 seed.setPerigeeCov(v_seed_cov);
485 seeds.push_back(seed);
522 auto start = std::chrono::high_resolution_clock::now();
525 std::vector<ldmx::Track> tracks;
528 if (!event.
exists(rec_coll_name_, rec_pass_name_)) {
529 ldmx_log(debug) <<
"No ECAL RecHits collection found";
530 event.add(out_track_collection_, tracks);
534 const std::vector<ldmx::EcalHit> ecal_hits =
535 event.getCollection<
ldmx::EcalHit>(rec_coll_name_, rec_pass_name_);
537 ldmx_log(debug) <<
"Processing " << ecal_hits.size() <<
" ECAL hits";
540 std::vector<double> measurement_energies;
542 ldmx_log(debug) <<
"Created " << measurements.size() <<
" measurements";
544 if (measurements.empty()) {
545 event.add(out_track_collection_, tracks);
551 ldmx_log(info) <<
"Source link map: " << geo_id_sl_map.size()
552 <<
" entries from " << measurements.size() <<
" measurements";
555 auto seed_tracks =
findSeeds(measurements);
556 ldmx_log(info) <<
"Found " << seed_tracks.size() <<
" seed tracks";
557 nseeds_ += seed_tracks.size();
559 if (seed_tracks.empty()) {
560 event.add(out_track_collection_, tracks);
576 Acts::PropagatorPlainOptions propagator_options(gctx, mctx);
577 propagator_options.pathLimit = std::numeric_limits<double>::max();
578 propagator_options.maxSteps = 1000;
579 propagator_options.stepping.maxStepSize = 100.0 * Acts::UnitConstants::mm;
582 Acts::GainMatrixUpdater kf_updater;
583 Acts::MeasurementSelector::Config meas_sel_cfg = {
584 {Acts::GeometryIdentifier(), {{}, {max_chi2_}, {1u}}}};
585 Acts::MeasurementSelector meas_sel{meas_sel_cfg};
590 struct SourceLinkAccIt {
591 using BaseIt =
decltype(geo_id_sl_map.begin());
594#pragma GCC diagnostic push
595#pragma GCC diagnostic ignored "-Wunused-local-typedefs"
597 using difference_type =
typename BaseIt::difference_type;
598 using iterator_category = std::input_iterator_tag;
599 using value_type = Acts::SourceLink;
600 using pointer = value_type*;
601 using reference = value_type&;
602#pragma GCC diagnostic pop
604 SourceLinkAccIt& operator++() {
608 bool operator==(
const SourceLinkAccIt& other)
const {
609 return it_ == other.it_;
611 bool operator!=(
const SourceLinkAccIt& other)
const {
612 return !(*
this == other);
614 value_type operator*()
const {
return value_type{it_->second}; }
617 auto source_link_accessor = [&](
const Acts::Surface& surface)
618 -> std::pair<SourceLinkAccIt, SourceLinkAccIt> {
619 auto [begin, end] = geo_id_sl_map.equal_range(surface.geometryId());
620 return {SourceLinkAccIt{begin}, SourceLinkAccIt{end}};
624 Acts::TrackStateCreator<SourceLinkAccIt, TrackContainer> track_state_creator;
625 track_state_creator.sourceLinkAccessor
626 .connect<&
decltype(source_link_accessor)::operator(),
627 decltype(source_link_accessor)>(&source_link_accessor);
628 track_state_creator.calibrator
630 Acts::VectorMultiTrajectory>>(&calibrator);
631 track_state_creator.measurementSelector
632 .connect<&Acts::MeasurementSelector::select<Acts::VectorMultiTrajectory>>(
635 Acts::CombinatorialKalmanFilterExtensions<TrackContainer> ckf_extensions;
636 ckf_extensions.updater.connect<
637 &Acts::GainMatrixUpdater::operator()<Acts::VectorMultiTrajectory>>(
639 ckf_extensions.createTrackStates.connect<&Acts::TrackStateCreator<
640 SourceLinkAccIt, TrackContainer>::createTrackStates>(
641 &track_state_creator);
644 Acts::VectorTrackContainer vtc;
645 Acts::VectorMultiTrajectory mtj;
646 Acts::TrackContainer tc{vtc, mtj};
649 for (
size_t seed_idx = 0; seed_idx < seed_tracks.size(); ++seed_idx) {
650 const auto& seed = seed_tracks[seed_idx];
656 Acts::BoundVector param_vec;
657 param_vec << seed.getD0(), seed.getZ0(), seed.getPhi(), seed.getTheta(),
658 seed.getQoP(), seed.getT();
660 ldmx_log(debug) <<
"Seed " << seed_idx <<
": loc0=" << param_vec[0]
661 <<
" loc1=" << param_vec[1] <<
" phi=" << param_vec[2]
662 <<
" theta=" << param_vec[3] <<
" qop=" << param_vec[4];
664 Acts::BoundMatrix cov_mat =
665 tracking::sim::utils::unpackCov(seed.getPerigeeCov());
667 auto part_hypo{Acts::ParticleHypothesis::electron()};
668 auto& layer0_surface = layer_surfaces_.begin()->second;
669 Acts::BoundTrackParameters start_params(layer0_surface, param_vec, cov_mat,
673 const Acts::CombinatorialKalmanFilterOptions<TrackContainer> ckf_options(
674 gctx, mctx, cctx, ckf_extensions, propagator_options);
677 auto results = ckf_->findTracks(start_params, ckf_options, tc);
680 ldmx_log(debug) <<
"CKF failed for seed " << seed_idx <<
": "
681 << results.error().message();
685 auto& tracks_from_seed = results.value();
686 ldmx_log(info) <<
"CKF returned " << tracks_from_seed.size()
687 <<
" tracks from seed " << seed_idx;
688 for (
auto& track : tracks_from_seed) {
690 int n_meas = 0, n_holes = 0, n_outliers = 0, n_total = 0;
691 for (
const auto& ts : track.trackStatesReversed()) {
693 if (ts.typeFlags().isMeasurement()) ++n_meas;
694 if (ts.typeFlags().isHole()) ++n_holes;
695 if (ts.typeFlags().isOutlier()) ++n_outliers;
697 ldmx_log(info) <<
"Track states: total=" << n_total <<
" meas=" << n_meas
698 <<
" holes=" << n_holes <<
" outliers=" << n_outliers;
701 auto smooth_result = Acts::smoothTrack(gctx, track);
702 if (!smooth_result.ok()) {
703 ldmx_log(warn) <<
"smoothTrack failed: "
704 << smooth_result.error().message();
714 Acts::BoundVector smoothed_params;
715 std::shared_ptr<const Acts::Surface> smoothed_surface;
716 bool found_smoothed =
false;
718 for (
const auto& ts : track.trackStatesReversed()) {
719 if (ts.hasSmoothed()) {
720 smoothed_params = ts.smoothed();
721 smoothed_surface = ts.referenceSurface().getSharedPtr();
722 found_smoothed =
true;
727 if (!found_smoothed) {
728 ldmx_log(warn) <<
"No smoothed track state found after smoothing";
732 ldmx_log(debug) <<
"Smoothed params: loc0=" << smoothed_params[0]
733 <<
" loc1=" << smoothed_params[1]
734 <<
" phi=" << smoothed_params[2]
735 <<
" theta=" << smoothed_params[3]
736 <<
" qop=" << smoothed_params[4];
739 Acts::FreeVector free_params = Acts::transformBoundToFreeParameters(
740 *smoothed_surface, gctx, smoothed_params);
743 Acts::Vector3 pos_acts(free_params[Acts::eFreePos0],
744 free_params[Acts::eFreePos1],
745 free_params[Acts::eFreePos2]);
746 Acts::Vector3 mom_acts(free_params[Acts::eFreeDir0],
747 free_params[Acts::eFreeDir1],
748 free_params[Acts::eFreeDir2]);
750 Acts::Vector3 pos_ldmx = tracking::sim::utils::acts2Ldmx(pos_acts);
751 Acts::Vector3 mom_ldmx = tracking::sim::utils::acts2Ldmx(mom_acts);
753 double x = pos_ldmx[0];
754 double y = pos_ldmx[1];
755 double z = pos_ldmx[2];
756 double px = mom_ldmx[0];
757 double py = mom_ldmx[1];
758 double pz = mom_ldmx[2];
760 ldmx_log(debug) <<
"LDMX momentum: px=" << px <<
" py=" << py
764 double pt = std::sqrt(px * px + py * py);
765 double theta = std::atan2(pt, pz);
766 double phi = std::atan2(py, px);
769 double qop = free_params[Acts::eFreeQOverP];
774 double d0 = -(x * std::sin(phi) - y * std::cos(phi));
776 double time = free_params[Acts::eFreeTime];
778 Acts::BoundVector perigee_params;
779 perigee_params << d0, z0, phi, theta, qop, time;
781 ldmx_log(debug) <<
"Perigee params: d0=" << d0 <<
" z0=" << z0
782 <<
" phi=" << phi <<
" theta=" << theta;
785 trk.setPerigeeParameters(
786 tracking::sim::utils::convertActsToLdmxPars(perigee_params));
789 std::vector<double> cov_vec;
790 tracking::sim::utils::flatCov(track.covariance(), cov_vec);
791 trk.setPerigeeCov(cov_vec);
793 Acts::Vector3 ref_loc_ldmx = tracking::sim::utils::acts2Ldmx(
794 layer_surfaces_.begin()->second->center(gctx));
795 trk.setPerigeeLocation(ref_loc_ldmx[0], ref_loc_ldmx[1], ref_loc_ldmx[2]);
797 trk.setChi2(track.chi2());
798 trk.setNhits(track.nMeasurements());
799 trk.setNdf(track.nMeasurements() - 5);
800 trk.setNsharedHits(0);
801 trk.setCharge(qop > 0 ? 1 : -1);
804 for (
const auto ts : track.trackStatesReversed()) {
805 if (ts.typeFlags().isMeasurement() && ts.hasUncalibratedSourceLink()) {
806 Acts::SourceLink usl = ts.getUncalibratedSourceLink();
809 trk.addMeasurementIndex(sl.
index());
814 double track_energy = 0.0;
816 if (use_roc_energy_ && !roc_range_values_.empty()) {
821 double trk_p_mag = 1.0 / std::abs(smoothed_params[Acts::eBoundQOverP]);
822 double trk_theta_deg = theta * 180.0 / M_PI;
825 std::vector<float> ele_radii(roc_range_values_[0].begin() + 4,
826 roc_range_values_[0].end());
827 for (
const auto& row : roc_range_values_) {
828 float theta_min = row[0], theta_max = row[1];
829 float p_min = row[2], p_max = row[3];
831 if (theta_min != -1.0f)
832 inrange = inrange && (trk_theta_deg >= theta_min);
833 if (theta_max != -1.0f)
834 inrange = inrange && (trk_theta_deg < theta_max);
835 if (p_min != -1.0f) inrange = inrange && (trk_p_mag >= p_min);
836 if (p_max != -1.0f) inrange = inrange && (trk_p_mag < p_max);
838 ele_radii.assign(row.begin() + 4, row.end());
844 for (
const auto& hit : ecal_hits) {
845 if (hit.isNoise())
continue;
847 int layer = ecal_id.
layer();
848 if (layer < 0 || layer >=
static_cast<int>(ele_radii.size()))
851 auto [hx, hy, hz] = geometry_->
getPosition(ecal_id);
855 double proj_x = x + (px / pz) * dz;
856 double proj_y = y + (py / pz) * dz;
859 double dx = hx - proj_x;
860 double dy = hy - proj_y;
861 double dist = std::sqrt(dx * dx + dy * dy);
863 if (dist < ele_radii[layer]) {
864 track_energy += hit.getEnergy();
869 for (
const auto ts : track.trackStatesReversed()) {
870 if (ts.typeFlags().isMeasurement() &&
871 ts.hasUncalibratedSourceLink()) {
872 Acts::SourceLink usl = ts.getUncalibratedSourceLink();
875 track_energy += measurement_energies[sl.
index()];
881 double track_p = track_energy;
882 qop = (track_p > 0) ? -1.0 / track_p : 0.0;
883 perigee_params[Acts::eBoundQOverP] = qop;
884 trk.setPerigeeParameters(
885 tracking::sim::utils::convertActsToLdmxPars(perigee_params));
887 ldmx_log(debug) <<
"Track energy from RecHits: " << track_energy
888 <<
" MeV, q/p=" << qop;
890 tracks.push_back(trk);
895 ldmx_log(info) <<
"Found " << tracks.size() <<
" fitted tracks";
898 event.add(out_track_collection_, tracks);
900 auto end = std::chrono::high_resolution_clock::now();
901 auto diff = end - start;
902 processing_time_ += std::chrono::duration<double, std::milli>(diff).count();