Process event to find ECAL tracks.
504 {
505 auto start = std::chrono::high_resolution_clock::now();
506 nevents_++;
507
508 std::vector<ldmx::Track> tracks;
509
510
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);
514 return;
515 }
516
517 const std::vector<ldmx::EcalHit> ecal_hits =
518 event.getCollection<
ldmx::EcalHit>(rec_coll_name_, rec_pass_name_);
519
520 ldmx_log(debug) << "Processing " << ecal_hits.size() << " ECAL hits";
521
522
523 std::vector<double> measurement_energies;
525 ldmx_log(debug) << "Created " << measurements.size() << " measurements";
526
527 if (measurements.empty()) {
528 event.add(out_track_collection_, tracks);
529 return;
530 }
531
532
534 ldmx_log(debug) << "Source link map: " << geo_id_sl_map.size() << " entries";
535
536
537 auto seed_tracks =
findSeeds(measurements);
538 ldmx_log(info) << "Found " << seed_tracks.size() << " seed tracks";
539 nseeds_ += seed_tracks.size();
540
541 if (seed_tracks.empty()) {
542 event.add(out_track_collection_, tracks);
543 return;
544 }
545
546
549 .get();
552 .get();
555 .get();
556
557
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;
562
563
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};
568
570
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>>(
577 &kf_updater);
578 ckf_extensions.measurementSelector
579 .connect<&Acts::MeasurementSelector::select<Acts::VectorMultiTrajectory>>(
580 &meas_sel);
581
582
583 struct SourceLinkAccIt {
584 using BaseIt = decltype(geo_id_sl_map.begin());
585 BaseIt it_;
586
587#pragma GCC diagnostic push
588#pragma GCC diagnostic ignored "-Wunused-local-typedefs"
589
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
596
597 SourceLinkAccIt& operator++() {
598 ++it_;
599 return *this;
600 }
601 bool operator==(const SourceLinkAccIt& other) const {
602 return it_ == other.it_;
603 }
604 bool operator!=(const SourceLinkAccIt& other) const {
605 return !(*this == other);
606 }
607 value_type operator*() const { return value_type{it_->second}; }
608 };
609
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}};
614 };
615
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);
621
622
623 Acts::VectorTrackContainer vtc;
624 Acts::VectorMultiTrajectory mtj;
625 Acts::TrackContainer tc{vtc, mtj};
626
627
628 for (size_t seed_idx = 0; seed_idx < seed_tracks.size(); ++seed_idx) {
629 const auto& seed = seed_tracks[seed_idx];
630
631
632
633
634
635 Acts::BoundVector param_vec;
636 param_vec << seed.getD0(), seed.getZ0(), seed.getPhi(), seed.getTheta(),
637 seed.getQoP(), seed.getT();
638
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];
642
643 Acts::BoundSquareMatrix cov_mat =
644 tracking::sim::utils::unpackCov(seed.getPerigeeCov());
645
646 auto part_hypo{Acts::SinglyChargedParticleHypothesis::electron()};
647 Acts::BoundTrackParameters start_params(reference_surface_, param_vec,
648 cov_mat, part_hypo);
649
650
651 const Acts::CombinatorialKalmanFilterOptions<SourceLinkAccIt,
652 TrackContainer>
653 ckf_options(gctx, mctx, cctx, source_link_accessor_delegate,
654 ckf_extensions, propagator_options);
655
656
657 auto results = ckf_->findTracks(start_params, ckf_options, tc);
658
659 if (!results.ok()) {
660 ldmx_log(debug) << "CKF failed for seed " << seed_idx << ": "
661 << results.error().message();
662 continue;
663 }
664
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) {
669
670 Acts::smoothTrack(gctx, track);
671
672
674
675
676
677
678 Acts::BoundVector smoothed_params;
679 std::shared_ptr<const Acts::Surface> smoothed_surface;
680 bool found_smoothed = false;
681
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;
687 break;
688 }
689 }
690
691 if (!found_smoothed) {
692 ldmx_log(warn) << "No smoothed track state found";
693 continue;
694 }
695
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];
701
702
703 Acts::FreeVector free_params = Acts::transformBoundToFreeParameters(
704 *smoothed_surface, gctx, smoothed_params);
705
706
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]);
713
714 Acts::Vector3 pos_ldmx = tracking::sim::utils::acts2Ldmx(pos_acts);
715 Acts::Vector3 mom_ldmx = tracking::sim::utils::acts2Ldmx(mom_acts);
716
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];
723
724 ldmx_log(debug) << "LDMX momentum: px=" << px << " py=" << py
725 << " pz=" << pz;
726
727
728 double pt = std::sqrt(px * px + py * py);
729 double theta = std::atan2(pt, pz);
730 double phi = std::atan2(py, px);
731 if (phi < 0)
732 phi += 2.0 * M_PI;
733 double qop = free_params[Acts::eFreeQOverP];
734
735
736
737
738 double d0 = -(x * std::sin(phi) - y * std::cos(phi));
739 double z0 = z;
740 double time = free_params[Acts::eFreeTime];
741
742 Acts::BoundVector perigee_params;
743 perigee_params << d0, z0, phi, theta, qop, time;
744
745 ldmx_log(debug) << "Perigee params: d0=" << d0 << " z0=" << z0
746 << " phi=" << phi << " theta=" << theta;
747
748
749 trk.setPerigeeParameters(
750 tracking::sim::utils::convertActsToLdmxPars(perigee_params));
751
752
753 std::vector<double> cov_vec;
754 tracking::sim::utils::flatCov(track.covariance(), cov_vec);
755 trk.setPerigeeCov(cov_vec);
756
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]);
760
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);
766
767
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());
775 }
776 }
777
778
779 double track_energy = 0.0;
780
781 if (use_roc_energy_ && !roc_range_values_.empty()) {
782
783
784
785
786 double trk_p_mag = 1.0 / std::abs(smoothed_params[Acts::eBoundQOverP]);
787 double trk_theta_deg = theta * 180.0 / M_PI;
788
789
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];
795 bool inrange = true;
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);
802 if (inrange) {
803 ele_radii.assign(row.begin() + 4, row.end());
804 }
805 }
806
807
808
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()))
814 continue;
815
816 auto [hx, hy, hz] = geometry_->
getPosition(ecal_id);
817
818
819 double dz = hz - z;
820 double proj_x = x + (px / pz) * dz;
821 double proj_y = y + (py / pz) * dz;
822
823
824 double dx = hx - proj_x;
825 double dy = hy - proj_y;
826 double dist = std::sqrt(dx * dx + dy * dy);
827
828 if (dist < ele_radii[layer]) {
829 track_energy += hit.getEnergy();
830 }
831 }
832 } else {
833
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()];
841 }
842 }
843 }
844
845
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));
851
852 ldmx_log(debug) << "Track energy from RecHits: " << track_energy
853 << " MeV, q/p=" << qop;
854
855 tracks.push_back(trk);
856 ntracks_++;
857 }
858 }
859
860 ldmx_log(info) << "Found " << tracks.size() << " fitted tracks";
861
862
863 event.add(out_track_collection_, tracks);
864
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();
868}
constexpr Index index() const
Access the index_.
std::vector< ldmx::Measurement > createMeasurements(const std::vector< ldmx::EcalHit > &hits, std::vector< double > &energies)
Create ACTS measurement objects from ECAL hits.
std::unordered_multimap< Acts::GeometryIdentifier, acts_examples::IndexSourceLink > makeGeoIdSourceLinkMap(const std::vector< ldmx::Measurement > &measurements)
Create geometry ID to source link map for CKF.
std::vector< ldmx::Track > findSeeds(const std::vector< ldmx::Measurement > &measurements)
Find seed tracks via straight-line fitting.
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.
Stores reconstructed hit information from the ECAL.
static const std::string NAME
Conditions object name.
static const std::string NAME
Conditions object name.
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.