97 ldmx::EcalGeometry::CONDITIONS_OBJECT_NAME);
100 layer_surfaces_.clear();
104 Acts::Vector3 b_field(0., 0., 0.);
105 auto zero_b_field = std::make_shared<Acts::ConstantBField>(b_field);
118 Acts::Vector3 front_acts =
119 tracking::sim::utils::ldmx2Acts(Acts::Vector3(0.0, 0.0, ecal_front_z));
120 Acts::Vector3 back_acts =
121 tracking::sim::utils::ldmx2Acts(Acts::Vector3(0.0, 0.0, ecal_back_z));
126 std::vector<Acts::CuboidVolumeBuilder::LayerConfig> layer_configs;
127 double clearance = 1.0;
129 for (
auto& [layer, surface] : layer_surfaces_) {
130 Acts::CuboidVolumeBuilder::LayerConfig lcfg;
131 lcfg.surfaces = {surface};
132 lcfg.envelopeX = std::array<double, 2>{clearance, clearance};
134 layer_configs.push_back(lcfg);
138 Acts::Vector3 volume_center = 0.5 * (front_acts + back_acts);
139 double x_length = std::abs(back_acts.x() - front_acts.x()) + 20.0;
141 Acts::CuboidVolumeBuilder::VolumeConfig ecal_vol_cfg;
142 ecal_vol_cfg.position = volume_center;
143 ecal_vol_cfg.length = {x_length, 1000.0, 1000.0};
144 ecal_vol_cfg.name =
"EcalVolume";
145 ecal_vol_cfg.layerCfg = layer_configs;
146 ecal_vol_cfg.volumeMaterial =
147 std::make_shared<Acts::HomogeneousVolumeMaterial>(
148 Acts::Material::Vacuum());
151 Acts::CuboidVolumeBuilder cvb;
152 Acts::CuboidVolumeBuilder::Config cvb_cfg;
153 cvb_cfg.position = volume_center;
154 cvb_cfg.length = {x_length + 20.0, 1020.0, 1020.0};
155 cvb_cfg.volumeCfg = {ecal_vol_cfg};
156 cvb.setConfig(cvb_cfg);
158 Acts::TrackingGeometryBuilder::Config tgb_cfg;
159 tgb_cfg.trackingVolumeBuilders.push_back(
160 [=](
const auto& cxt,
const auto& inner,
const auto&) {
161 return cvb.trackingVolume(cxt, inner,
nullptr);
164 Acts::TrackingGeometryBuilder tgb(tgb_cfg);
165 tracking_geometry_ = tgb.trackingGeometry(gctx);
172 layer_geo_ids_.clear();
173 tracking_geometry_->visitSurfaces([&](
const Acts::Surface* surface) {
174 if (!surface)
return;
176 if (surface->geometryId().sensitive() == 0)
return;
181 const_cast<Acts::Surface*
>(surface)->assignIsSensitive(
true);
184 Acts::Vector3 center_ldmx =
185 tracking::sim::utils::acts2Ldmx(surface->center(gctx));
186 double z_ldmx = center_ldmx[2];
188 for (
int layer = 0; layer < geometry_->
getNumLayers(); ++layer) {
190 if (std::abs(z_ldmx - layer_z) < 0.1) {
191 layer_geo_ids_[layer] = surface->geometryId();
192 ldmx_log(debug) <<
"ECAL layer " << layer <<
" -> builder geo_id: vol="
193 << surface->geometryId().volume()
194 <<
" lay=" << surface->geometryId().layer()
195 <<
" sen=" << surface->geometryId().sensitive();
201 ldmx_log(info) <<
"Mapped " << layer_geo_ids_.size()
202 <<
" ECAL layers to builder-assigned geometry IDs";
205 const auto stepper = Acts::EigenStepper<>{zero_b_field};
207 auto acts_logging_level =
208 debug_ ? Acts::Logging::VERBOSE : Acts::Logging::FATAL;
211 Acts::Navigator::Config nav_cfg{tracking_geometry_};
212 nav_cfg.resolveSensitive =
true;
213 nav_cfg.resolvePassive =
false;
214 nav_cfg.resolveMaterial =
false;
215 const Acts::Navigator navigator(
217 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 = Acts::transformFreeToBoundParameters(
443 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()
556 auto seed_tracks =
findSeeds(measurements);
557 ldmx_log(info) <<
"Found " << seed_tracks.size() <<
" seed tracks";
558 nseeds_ += seed_tracks.size();
560 if (seed_tracks.empty()) {
561 event.add(out_track_collection_, tracks);
577 Acts::PropagatorPlainOptions propagator_options(gctx, mctx);
578 propagator_options.pathLimit = std::numeric_limits<double>::max();
579 propagator_options.maxSteps = 1000;
580 propagator_options.stepping.maxStepSize = 100.0 * Acts::UnitConstants::mm;
583 Acts::GainMatrixUpdater kf_updater;
584 Acts::MeasurementSelector::Config meas_sel_cfg = {
585 {Acts::GeometryIdentifier(), {{}, {max_chi2_}, {1u}}}};
586 Acts::MeasurementSelector meas_sel{meas_sel_cfg};
591 struct SourceLinkAccIt {
592 using BaseIt =
decltype(geo_id_sl_map.begin());
595#pragma GCC diagnostic push
596#pragma GCC diagnostic ignored "-Wunused-local-typedefs"
598 using difference_type =
typename BaseIt::difference_type;
599 using iterator_category = std::input_iterator_tag;
600 using value_type = Acts::SourceLink;
601 using pointer = value_type*;
602 using reference = value_type&;
603#pragma GCC diagnostic pop
605 SourceLinkAccIt& operator++() {
609 bool operator==(
const SourceLinkAccIt& other)
const {
610 return it_ == other.it_;
612 bool operator!=(
const SourceLinkAccIt& other)
const {
613 return !(*
this == other);
615 value_type operator*()
const {
return value_type{it_->second}; }
618 auto source_link_accessor = [&](
const Acts::Surface& surface)
619 -> std::pair<SourceLinkAccIt, SourceLinkAccIt> {
620 auto [begin, end] = geo_id_sl_map.equal_range(surface.geometryId());
621 return {SourceLinkAccIt{begin}, SourceLinkAccIt{end}};
625 Acts::TrackStateCreator<SourceLinkAccIt, TrackContainer> track_state_creator;
626 track_state_creator.sourceLinkAccessor
627 .connect<&
decltype(source_link_accessor)::operator(),
628 decltype(source_link_accessor)>(&source_link_accessor);
629 track_state_creator.calibrator
631 Acts::VectorMultiTrajectory>>(&calibrator);
632 track_state_creator.measurementSelector
633 .connect<&Acts::MeasurementSelector::select<Acts::VectorMultiTrajectory>>(
636 Acts::CombinatorialKalmanFilterExtensions<TrackContainer> ckf_extensions;
637 ckf_extensions.updater.connect<
638 &Acts::GainMatrixUpdater::operator()<Acts::VectorMultiTrajectory>>(
640 ckf_extensions.createTrackStates.connect<&Acts::TrackStateCreator<
641 SourceLinkAccIt, TrackContainer>::createTrackStates>(
642 &track_state_creator);
645 Acts::VectorTrackContainer vtc;
646 Acts::VectorMultiTrajectory mtj;
647 Acts::TrackContainer tc{vtc, mtj};
650 for (
size_t seed_idx = 0; seed_idx < seed_tracks.size(); ++seed_idx) {
651 const auto& seed = seed_tracks[seed_idx];
657 Acts::BoundVector param_vec;
658 param_vec << seed.getD0(), seed.getZ0(), seed.getPhi(), seed.getTheta(),
659 seed.getQoP(), seed.getT();
661 ldmx_log(debug) <<
"Seed " << seed_idx <<
": loc0=" << param_vec[0]
662 <<
" loc1=" << param_vec[1] <<
" phi=" << param_vec[2]
663 <<
" theta=" << param_vec[3] <<
" qop=" << param_vec[4];
665 Acts::BoundMatrix cov_mat =
666 tracking::sim::utils::unpackCov(seed.getPerigeeCov());
668 auto part_hypo{Acts::ParticleHypothesis::electron()};
669 auto& layer0_surface = layer_surfaces_.begin()->second;
670 Acts::BoundTrackParameters start_params(layer0_surface, param_vec,
674 const Acts::CombinatorialKalmanFilterOptions<TrackContainer> ckf_options(
675 gctx, mctx, cctx, ckf_extensions, propagator_options);
678 auto results = ckf_->findTracks(start_params, ckf_options, tc);
681 ldmx_log(debug) <<
"CKF failed for seed " << seed_idx <<
": "
682 << results.error().message();
686 auto& tracks_from_seed = results.value();
687 ldmx_log(info) <<
"CKF returned " << tracks_from_seed.size()
688 <<
" tracks from seed " << seed_idx;
689 for (
auto& track : tracks_from_seed) {
691 int n_meas = 0, n_holes = 0, n_outliers = 0, n_total = 0;
692 for (
const auto& ts : track.trackStatesReversed()) {
694 if (ts.typeFlags().isMeasurement()) ++n_meas;
695 if (ts.typeFlags().isHole()) ++n_holes;
696 if (ts.typeFlags().isOutlier()) ++n_outliers;
698 ldmx_log(info) <<
"Track states: total=" << n_total
699 <<
" meas=" << n_meas <<
" holes=" << n_holes
700 <<
" outliers=" << n_outliers;
703 auto smooth_result = Acts::smoothTrack(gctx, track);
704 if (!smooth_result.ok()) {
705 ldmx_log(warn) <<
"smoothTrack failed: "
706 << smooth_result.error().message();
716 Acts::BoundVector smoothed_params;
717 std::shared_ptr<const Acts::Surface> smoothed_surface;
718 bool found_smoothed =
false;
720 for (
const auto& ts : track.trackStatesReversed()) {
721 if (ts.hasSmoothed()) {
722 smoothed_params = ts.smoothed();
723 smoothed_surface = ts.referenceSurface().getSharedPtr();
724 found_smoothed =
true;
729 if (!found_smoothed) {
730 ldmx_log(warn) <<
"No smoothed track state found after smoothing";
734 ldmx_log(debug) <<
"Smoothed params: loc0=" << smoothed_params[0]
735 <<
" loc1=" << smoothed_params[1]
736 <<
" phi=" << smoothed_params[2]
737 <<
" theta=" << smoothed_params[3]
738 <<
" qop=" << smoothed_params[4];
741 Acts::FreeVector free_params = Acts::transformBoundToFreeParameters(
742 *smoothed_surface, gctx, smoothed_params);
745 Acts::Vector3 pos_acts(free_params[Acts::eFreePos0],
746 free_params[Acts::eFreePos1],
747 free_params[Acts::eFreePos2]);
748 Acts::Vector3 mom_acts(free_params[Acts::eFreeDir0],
749 free_params[Acts::eFreeDir1],
750 free_params[Acts::eFreeDir2]);
752 Acts::Vector3 pos_ldmx = tracking::sim::utils::acts2Ldmx(pos_acts);
753 Acts::Vector3 mom_ldmx = tracking::sim::utils::acts2Ldmx(mom_acts);
755 double x = pos_ldmx[0];
756 double y = pos_ldmx[1];
757 double z = pos_ldmx[2];
758 double px = mom_ldmx[0];
759 double py = mom_ldmx[1];
760 double pz = mom_ldmx[2];
762 ldmx_log(debug) <<
"LDMX momentum: px=" << px <<
" py=" << py
766 double pt = std::sqrt(px * px + py * py);
767 double theta = std::atan2(pt, pz);
768 double phi = std::atan2(py, px);
771 double qop = free_params[Acts::eFreeQOverP];
776 double d0 = -(x * std::sin(phi) - y * std::cos(phi));
778 double time = free_params[Acts::eFreeTime];
780 Acts::BoundVector perigee_params;
781 perigee_params << d0, z0, phi, theta, qop, time;
783 ldmx_log(debug) <<
"Perigee params: d0=" << d0 <<
" z0=" << z0
784 <<
" phi=" << phi <<
" theta=" << theta;
787 trk.setPerigeeParameters(
788 tracking::sim::utils::convertActsToLdmxPars(perigee_params));
791 std::vector<double> cov_vec;
792 tracking::sim::utils::flatCov(track.covariance(), cov_vec);
793 trk.setPerigeeCov(cov_vec);
795 Acts::Vector3 ref_loc_ldmx =
796 tracking::sim::utils::acts2Ldmx(layer_surfaces_.begin()->second->center(gctx));
797 trk.setPerigeeLocation(ref_loc_ldmx[0], ref_loc_ldmx[1], ref_loc_ldmx[2]);
799 trk.setChi2(track.chi2());
800 trk.setNhits(track.nMeasurements());
801 trk.setNdf(track.nMeasurements() - 5);
802 trk.setNsharedHits(0);
803 trk.setCharge(qop > 0 ? 1 : -1);
806 for (
const auto ts : track.trackStatesReversed()) {
807 if (ts.typeFlags().isMeasurement() && ts.hasUncalibratedSourceLink()) {
808 Acts::SourceLink usl = ts.getUncalibratedSourceLink();
811 trk.addMeasurementIndex(sl.
index());
816 double track_energy = 0.0;
818 if (use_roc_energy_ && !roc_range_values_.empty()) {
823 double trk_p_mag = 1.0 / std::abs(smoothed_params[Acts::eBoundQOverP]);
824 double trk_theta_deg = theta * 180.0 / M_PI;
827 std::vector<float> ele_radii(roc_range_values_[0].begin() + 4,
828 roc_range_values_[0].end());
829 for (
const auto& row : roc_range_values_) {
830 float theta_min = row[0], theta_max = row[1];
831 float p_min = row[2], p_max = row[3];
833 if (theta_min != -1.0f)
834 inrange = inrange && (trk_theta_deg >= theta_min);
835 if (theta_max != -1.0f)
836 inrange = inrange && (trk_theta_deg < theta_max);
837 if (p_min != -1.0f) inrange = inrange && (trk_p_mag >= p_min);
838 if (p_max != -1.0f) inrange = inrange && (trk_p_mag < p_max);
840 ele_radii.assign(row.begin() + 4, row.end());
846 for (
const auto& hit : ecal_hits) {
847 if (hit.isNoise())
continue;
849 int layer = ecal_id.
layer();
850 if (layer < 0 || layer >=
static_cast<int>(ele_radii.size()))
853 auto [hx, hy, hz] = geometry_->
getPosition(ecal_id);
857 double proj_x = x + (px / pz) * dz;
858 double proj_y = y + (py / pz) * dz;
861 double dx = hx - proj_x;
862 double dy = hy - proj_y;
863 double dist = std::sqrt(dx * dx + dy * dy);
865 if (dist < ele_radii[layer]) {
866 track_energy += hit.getEnergy();
871 for (
const auto ts : track.trackStatesReversed()) {
872 if (ts.typeFlags().isMeasurement() &&
873 ts.hasUncalibratedSourceLink()) {
874 Acts::SourceLink usl = ts.getUncalibratedSourceLink();
877 track_energy += measurement_energies[sl.
index()];
883 double track_p = track_energy;
884 qop = (track_p > 0) ? -1.0 / track_p : 0.0;
885 perigee_params[Acts::eBoundQOverP] = qop;
886 trk.setPerigeeParameters(
887 tracking::sim::utils::convertActsToLdmxPars(perigee_params));
889 ldmx_log(debug) <<
"Track energy from RecHits: " << track_energy
890 <<
" MeV, q/p=" << qop;
892 tracks.push_back(trk);
897 ldmx_log(info) <<
"Found " << tracks.size() <<
" fitted tracks";
900 event.add(out_track_collection_, tracks);
902 auto end = std::chrono::high_resolution_clock::now();
903 auto diff = end - start;
904 processing_time_ += std::chrono::duration<double, std::milli>(diff).count();