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));
125 std::vector<Acts::CuboidVolumeBuilder::LayerConfig> layer_configs;
126 double clearance = 1.0;
128 for (
auto& [layer, surface] : layer_surfaces_) {
129 Acts::CuboidVolumeBuilder::LayerConfig lcfg;
130 lcfg.surfaces = {surface};
131 lcfg.envelopeX = std::array<double, 2>{clearance, clearance};
133 layer_configs.push_back(lcfg);
137 Acts::Vector3 volume_center = 0.5 * (front_acts + back_acts);
138 double x_length = std::abs(back_acts.x() - front_acts.x()) + 20.0;
140 Acts::CuboidVolumeBuilder::VolumeConfig ecal_vol_cfg;
141 ecal_vol_cfg.position = volume_center;
142 ecal_vol_cfg.length = {x_length, 1000.0, 1000.0};
143 ecal_vol_cfg.name =
"EcalVolume";
144 ecal_vol_cfg.layerCfg = layer_configs;
145 ecal_vol_cfg.volumeMaterial =
146 std::make_shared<Acts::HomogeneousVolumeMaterial>(Acts::Material());
149 Acts::CuboidVolumeBuilder cvb;
150 Acts::CuboidVolumeBuilder::Config cvb_cfg;
151 cvb_cfg.position = volume_center;
152 cvb_cfg.length = {x_length + 20.0, 1020.0, 1020.0};
153 cvb_cfg.volumeCfg = {ecal_vol_cfg};
154 cvb.setConfig(cvb_cfg);
156 Acts::TrackingGeometryBuilder::Config tgb_cfg;
157 tgb_cfg.trackingVolumeBuilders.push_back(
158 [=](
const auto& cxt,
const auto& inner,
const auto&) {
159 return cvb.trackingVolume(cxt, inner,
nullptr);
162 Acts::TrackingGeometryBuilder tgb(tgb_cfg);
163 tracking_geometry_ = tgb.trackingGeometry(gctx);
170 layer_geo_ids_.clear();
171 tracking_geometry_->visitSurfaces([&](
const Acts::Surface* surface) {
172 if (!surface)
return;
174 if (surface->geometryId().sensitive() == 0)
return;
177 Acts::Vector3 center_ldmx =
178 tracking::sim::utils::acts2Ldmx(surface->center(gctx));
179 double z_ldmx = center_ldmx[2];
181 for (
int layer = 0; layer < geometry_->
getNumLayers(); ++layer) {
183 if (std::abs(z_ldmx - layer_z) < 0.1) {
184 layer_geo_ids_[layer] = surface->geometryId();
185 ldmx_log(debug) <<
"ECAL layer " << layer <<
" -> builder geo_id: vol="
186 << surface->geometryId().volume()
187 <<
" lay=" << surface->geometryId().layer()
188 <<
" sen=" << surface->geometryId().sensitive();
194 ldmx_log(info) <<
"Mapped " << layer_geo_ids_.size()
195 <<
" ECAL layers to builder-assigned geometry IDs";
198 const auto stepper = Acts::EigenStepper<>{zero_b_field};
201 Acts::Navigator::Config nav_cfg{tracking_geometry_};
202 nav_cfg.resolveSensitive =
true;
203 nav_cfg.resolvePassive =
false;
204 nav_cfg.resolveMaterial =
false;
205 const Acts::Navigator navigator(nav_cfg);
208 auto acts_logging_level =
209 debug_ ? Acts::Logging::VERBOSE : Acts::Logging::WARNING;
210 propagator_ = std::make_unique<EcalPropagator>(
212 Acts::getDefaultLogger(
"ECAL_PROP", acts_logging_level));
215 ckf_ = std::make_unique<std::decay_t<
decltype(*ckf_)>>(
216 *propagator_, Acts::getDefaultLogger(
"ECAL_CKF", acts_logging_level));
218 ldmx_log(info) <<
"EcalTrackFinderProcessor initialized with "
219 << layer_surfaces_.size() <<
" ECAL layer surfaces";
345 const std::vector<ldmx::Measurement>& measurements) {
346 std::vector<ldmx::Track> seeds;
348 if (measurements.size() <
static_cast<size_t>(min_hits_)) {
349 ldmx_log(debug) <<
"Too few measurements for seed: " << measurements.size()
350 <<
" < " << min_hits_;
355 std::map<int, std::vector<const ldmx::Measurement*>> layer_map;
356 for (
const auto& meas : measurements) {
357 layer_map[meas.getLayerID()].push_back(&meas);
360 ldmx_log(debug) <<
"Measurements span " << layer_map.size() <<
" layers";
363 std::vector<Acts::Vector3> points;
364 std::vector<ldmx::Measurement> seed_measurements;
366 for (
const auto& [layer, meas_vec] : layer_map) {
368 if (!meas_vec.empty()) {
369 const auto* meas = meas_vec[0];
370 auto gpos = meas->getGlobalPosition();
373 Acts::Vector3 pos_acts = tracking::sim::utils::ldmx2Acts(
374 Acts::Vector3(gpos[0], gpos[1], gpos[2]));
375 points.push_back(pos_acts);
376 seed_measurements.push_back(*meas);
380 if (points.size() <
static_cast<size_t>(min_hits_)) {
381 ldmx_log(debug) <<
"Too few layers hit for seed: " << points.size() <<
" < "
389 ldmx_log(debug) <<
"Seed line fit: rms=" << rms <<
" dir=(" << direction.x()
390 <<
"," << direction.y() <<
"," << direction.z() <<
")";
392 if (rms > max_seed_rms_) {
393 ldmx_log(debug) <<
"Seed RMS too large: " << rms <<
" > " << max_seed_rms_
406 Acts::Vector3 ref_normal =
407 reference_surface_->transform(gctx).rotation().col(2);
408 Acts::Vector3 ref_center = reference_surface_->center(gctx);
411 (ref_center - position).dot(ref_normal) / direction.dot(ref_normal);
412 Acts::Vector3 seed_pos = position + t * direction;
415 double p_estimate = 200.0;
416 Acts::Vector3 seed_mom = p_estimate * direction;
419 Acts::ActsScalar q = Acts::UnitConstants::e;
422 Acts::FreeVector seed_free =
423 tracking::sim::utils::toFreeParameters(seed_pos, seed_mom, q);
425 auto bound_params_result = Acts::transformFreeToBoundParameters(
426 seed_free, *reference_surface_, gctx);
428 if (!bound_params_result.ok()) {
429 ldmx_log(warn) <<
"Failed to create bound parameters for seed";
433 Acts::BoundVector bound_params = bound_params_result.value();
436 Acts::BoundVector stddev;
437 stddev[Acts::eBoundLoc0] = 10.0 * Acts::UnitConstants::mm;
438 stddev[Acts::eBoundLoc1] = 10.0 * Acts::UnitConstants::mm;
439 stddev[Acts::eBoundPhi] = 0.1 * Acts::UnitConstants::rad;
440 stddev[Acts::eBoundTheta] = 0.1 * Acts::UnitConstants::rad;
441 stddev[Acts::eBoundQOverP] = 0.5 / p_estimate;
442 stddev[Acts::eBoundTime] = 10.0 * Acts::UnitConstants::ns;
444 Acts::BoundSquareMatrix bound_cov = stddev.cwiseProduct(stddev).asDiagonal();
450 Acts::Vector3 ref_ldmx = tracking::sim::utils::acts2Ldmx(ref_center);
451 seed.setPerigeeLocation(ref_ldmx[0], ref_ldmx[1], ref_ldmx[2]);
454 seed.setNhits(seed_measurements.size());
456 seed.setNsharedHits(0);
457 seed.setCharge(q > 0 ? 1 : -1);
460 std::vector<double> v_seed_params(bound_params.data(),
461 bound_params.data() + bound_params.size());
462 std::vector<double> v_seed_cov;
463 tracking::sim::utils::flatCov(bound_cov, v_seed_cov);
465 seed.setPerigeeParameters(v_seed_params);
466 seed.setPerigeeCov(v_seed_cov);
468 seeds.push_back(seed);
505 auto start = std::chrono::high_resolution_clock::now();
508 std::vector<ldmx::Track> tracks;
511 if (!event.
exists(rec_coll_name_, rec_pass_name_)) {
512 ldmx_log(debug) <<
"No ECAL RecHits collection found";
513 event.add(out_track_collection_, tracks);
517 const std::vector<ldmx::EcalHit> ecal_hits =
518 event.getCollection<
ldmx::EcalHit>(rec_coll_name_, rec_pass_name_);
520 ldmx_log(debug) <<
"Processing " << ecal_hits.size() <<
" ECAL hits";
523 std::vector<double> measurement_energies;
525 ldmx_log(debug) <<
"Created " << measurements.size() <<
" measurements";
527 if (measurements.empty()) {
528 event.add(out_track_collection_, tracks);
534 ldmx_log(debug) <<
"Source link map: " << geo_id_sl_map.size() <<
" entries";
537 auto seed_tracks =
findSeeds(measurements);
538 ldmx_log(info) <<
"Found " << seed_tracks.size() <<
" seed tracks";
539 nseeds_ += seed_tracks.size();
541 if (seed_tracks.empty()) {
542 event.add(out_track_collection_, tracks);
558 Acts::PropagatorPlainOptions propagator_options(gctx, mctx);
559 propagator_options.pathLimit = std::numeric_limits<double>::max();
560 propagator_options.maxSteps = 1000;
561 propagator_options.stepping.maxStepSize = 100.0 * Acts::UnitConstants::mm;
564 Acts::GainMatrixUpdater kf_updater;
565 Acts::MeasurementSelector::Config meas_sel_cfg = {
566 {Acts::GeometryIdentifier(), {{}, {max_chi2_}, {1u}}}};
567 Acts::MeasurementSelector meas_sel{meas_sel_cfg};
571 Acts::CombinatorialKalmanFilterExtensions<TrackContainer> ckf_extensions;
572 ckf_extensions.calibrator
574 Acts::VectorMultiTrajectory>>(&calibrator);
575 ckf_extensions.updater.connect<
576 &Acts::GainMatrixUpdater::operator()<Acts::VectorMultiTrajectory>>(
578 ckf_extensions.measurementSelector
579 .connect<&Acts::MeasurementSelector::select<Acts::VectorMultiTrajectory>>(
583 struct SourceLinkAccIt {
584 using BaseIt =
decltype(geo_id_sl_map.begin());
587#pragma GCC diagnostic push
588#pragma GCC diagnostic ignored "-Wunused-local-typedefs"
590 using difference_type =
typename BaseIt::difference_type;
591 using iterator_category = std::input_iterator_tag;
592 using value_type = Acts::SourceLink;
593 using pointer = value_type*;
594 using reference = value_type&;
595#pragma GCC diagnostic pop
597 SourceLinkAccIt& operator++() {
601 bool operator==(
const SourceLinkAccIt& other)
const {
602 return it_ == other.it_;
604 bool operator!=(
const SourceLinkAccIt& other)
const {
605 return !(*
this == other);
607 value_type operator*()
const {
return value_type{it_->second}; }
610 auto source_link_accessor = [&](
const Acts::Surface& surface)
611 -> std::pair<SourceLinkAccIt, SourceLinkAccIt> {
612 auto [begin, end] = geo_id_sl_map.equal_range(surface.geometryId());
613 return {SourceLinkAccIt{begin}, SourceLinkAccIt{end}};
616 Acts::SourceLinkAccessorDelegate<SourceLinkAccIt>
617 source_link_accessor_delegate;
618 source_link_accessor_delegate
619 .connect<&
decltype(source_link_accessor)::operator(),
620 decltype(source_link_accessor)>(&source_link_accessor);
623 Acts::VectorTrackContainer vtc;
624 Acts::VectorMultiTrajectory mtj;
625 Acts::TrackContainer tc{vtc, mtj};
628 for (
size_t seed_idx = 0; seed_idx < seed_tracks.size(); ++seed_idx) {
629 const auto& seed = seed_tracks[seed_idx];
635 Acts::BoundVector param_vec;
636 param_vec << seed.getD0(), seed.getZ0(), seed.getPhi(), seed.getTheta(),
637 seed.getQoP(), seed.getT();
639 ldmx_log(debug) <<
"Seed " << seed_idx <<
": loc0=" << param_vec[0]
640 <<
" loc1=" << param_vec[1] <<
" phi=" << param_vec[2]
641 <<
" theta=" << param_vec[3] <<
" qop=" << param_vec[4];
643 Acts::BoundSquareMatrix cov_mat =
644 tracking::sim::utils::unpackCov(seed.getPerigeeCov());
646 auto part_hypo{Acts::SinglyChargedParticleHypothesis::electron()};
647 Acts::BoundTrackParameters start_params(reference_surface_, param_vec,
651 const Acts::CombinatorialKalmanFilterOptions<SourceLinkAccIt,
653 ckf_options(gctx, mctx, cctx, source_link_accessor_delegate,
654 ckf_extensions, propagator_options);
657 auto results = ckf_->findTracks(start_params, ckf_options, tc);
660 ldmx_log(debug) <<
"CKF failed for seed " << seed_idx <<
": "
661 << results.error().message();
665 auto& tracks_from_seed = results.value();
666 ldmx_log(debug) <<
"CKF returned " << tracks_from_seed.size()
667 <<
" tracks from seed " << seed_idx;
668 for (
auto& track : tracks_from_seed) {
670 Acts::smoothTrack(gctx, track);
678 Acts::BoundVector smoothed_params;
679 std::shared_ptr<const Acts::Surface> smoothed_surface;
680 bool found_smoothed =
false;
682 for (
const auto& ts : track.trackStatesReversed()) {
683 if (ts.hasSmoothed()) {
684 smoothed_params = ts.smoothed();
685 smoothed_surface = ts.referenceSurface().getSharedPtr();
686 found_smoothed =
true;
691 if (!found_smoothed) {
692 ldmx_log(warn) <<
"No smoothed track state found";
696 ldmx_log(debug) <<
"Smoothed params: loc0=" << smoothed_params[0]
697 <<
" loc1=" << smoothed_params[1]
698 <<
" phi=" << smoothed_params[2]
699 <<
" theta=" << smoothed_params[3]
700 <<
" qop=" << smoothed_params[4];
703 Acts::FreeVector free_params = Acts::transformBoundToFreeParameters(
704 *smoothed_surface, gctx, smoothed_params);
707 Acts::Vector3 pos_acts(free_params[Acts::eFreePos0],
708 free_params[Acts::eFreePos1],
709 free_params[Acts::eFreePos2]);
710 Acts::Vector3 mom_acts(free_params[Acts::eFreeDir0],
711 free_params[Acts::eFreeDir1],
712 free_params[Acts::eFreeDir2]);
714 Acts::Vector3 pos_ldmx = tracking::sim::utils::acts2Ldmx(pos_acts);
715 Acts::Vector3 mom_ldmx = tracking::sim::utils::acts2Ldmx(mom_acts);
717 double x = pos_ldmx[0];
718 double y = pos_ldmx[1];
719 double z = pos_ldmx[2];
720 double px = mom_ldmx[0];
721 double py = mom_ldmx[1];
722 double pz = mom_ldmx[2];
724 ldmx_log(debug) <<
"LDMX momentum: px=" << px <<
" py=" << py
728 double pt = std::sqrt(px * px + py * py);
729 double theta = std::atan2(pt, pz);
730 double phi = std::atan2(py, px);
733 double qop = free_params[Acts::eFreeQOverP];
738 double d0 = -(x * std::sin(phi) - y * std::cos(phi));
740 double time = free_params[Acts::eFreeTime];
742 Acts::BoundVector perigee_params;
743 perigee_params << d0, z0, phi, theta, qop, time;
745 ldmx_log(debug) <<
"Perigee params: d0=" << d0 <<
" z0=" << z0
746 <<
" phi=" << phi <<
" theta=" << theta;
749 trk.setPerigeeParameters(
750 tracking::sim::utils::convertActsToLdmxPars(perigee_params));
753 std::vector<double> cov_vec;
754 tracking::sim::utils::flatCov(track.covariance(), cov_vec);
755 trk.setPerigeeCov(cov_vec);
757 Acts::Vector3 ref_loc_ldmx =
758 tracking::sim::utils::acts2Ldmx(reference_surface_->center(gctx));
759 trk.setPerigeeLocation(ref_loc_ldmx[0], ref_loc_ldmx[1], ref_loc_ldmx[2]);
761 trk.setChi2(track.chi2());
762 trk.setNhits(track.nMeasurements());
763 trk.setNdf(track.nMeasurements() - 5);
764 trk.setNsharedHits(0);
765 trk.setCharge(qop > 0 ? 1 : -1);
768 for (
const auto ts : track.trackStatesReversed()) {
769 if (ts.typeFlags().test(Acts::TrackStateFlag::MeasurementFlag) &&
770 ts.hasUncalibratedSourceLink()) {
772 ts.getUncalibratedSourceLink()
773 .template get<acts_examples::IndexSourceLink>();
774 trk.addMeasurementIndex(sl.
index());
779 double track_energy = 0.0;
781 if (use_roc_energy_ && !roc_range_values_.empty()) {
786 double trk_p_mag = 1.0 / std::abs(smoothed_params[Acts::eBoundQOverP]);
787 double trk_theta_deg = theta * 180.0 / M_PI;
790 std::vector<float> ele_radii(roc_range_values_[0].begin() + 4,
791 roc_range_values_[0].end());
792 for (
const auto& row : roc_range_values_) {
793 float theta_min = row[0], theta_max = row[1];
794 float p_min = row[2], p_max = row[3];
796 if (theta_min != -1.0f)
797 inrange = inrange && (trk_theta_deg >= theta_min);
798 if (theta_max != -1.0f)
799 inrange = inrange && (trk_theta_deg < theta_max);
800 if (p_min != -1.0f) inrange = inrange && (trk_p_mag >= p_min);
801 if (p_max != -1.0f) inrange = inrange && (trk_p_mag < p_max);
803 ele_radii.assign(row.begin() + 4, row.end());
809 for (
const auto& hit : ecal_hits) {
810 if (hit.isNoise())
continue;
812 int layer = ecal_id.
layer();
813 if (layer < 0 || layer >=
static_cast<int>(ele_radii.size()))
816 auto [hx, hy, hz] = geometry_->
getPosition(ecal_id);
820 double proj_x = x + (px / pz) * dz;
821 double proj_y = y + (py / pz) * dz;
824 double dx = hx - proj_x;
825 double dy = hy - proj_y;
826 double dist = std::sqrt(dx * dx + dy * dy);
828 if (dist < ele_radii[layer]) {
829 track_energy += hit.getEnergy();
834 for (
const auto ts : track.trackStatesReversed()) {
835 if (ts.typeFlags().test(Acts::TrackStateFlag::MeasurementFlag) &&
836 ts.hasUncalibratedSourceLink()) {
838 ts.getUncalibratedSourceLink()
839 .template get<acts_examples::IndexSourceLink>();
840 track_energy += measurement_energies[sl.
index()];
846 double track_p = track_energy;
847 qop = (track_p > 0) ? -1.0 / track_p : 0.0;
848 perigee_params[Acts::eBoundQOverP] = qop;
849 trk.setPerigeeParameters(
850 tracking::sim::utils::convertActsToLdmxPars(perigee_params));
852 ldmx_log(debug) <<
"Track energy from RecHits: " << track_energy
853 <<
" MeV, q/p=" << qop;
855 tracks.push_back(trk);
860 ldmx_log(info) <<
"Found " << tracks.size() <<
" fitted tracks";
863 event.add(out_track_collection_, tracks);
865 auto end = std::chrono::high_resolution_clock::now();
866 auto diff = end - start;
867 processing_time_ += std::chrono::duration<double, std::milli>(diff).count();