Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-09-06 08:24:36

0001 // SPDX-License-Identifier: LGPL-3.0-or-later
0002 // Copyright (C) 2023 - 2025, Simon Gardner
0003 
0004 #include <DD4hep/VolumeManager.h>
0005 #include <Evaluator/DD4hepUnits.h>
0006 #include <Math/GenVector/Cartesian3D.h>
0007 #include <Math/GenVector/DisplacementVector3D.h>
0008 #include <algorithms/geo.h>
0009 #include <edm4eic/Cov6f.h>
0010 #include <edm4eic/MCRecoTrackParticleAssociationCollection.h>
0011 #include <edm4eic/MCRecoTrackerHitLinkCollection.h>
0012 #include <edm4eic/Measurement2DCollection.h>
0013 #include <edm4eic/RawTrackerHit.h>
0014 #include <edm4eic/TrackCollection.h>
0015 #include <edm4eic/TrackerHit.h>
0016 #include <edm4hep/MCParticle.h>
0017 #include <edm4hep/SimTrackerHit.h>
0018 #include <edm4hep/Vector2f.h>
0019 #include <edm4hep/Vector3d.h>
0020 #include <edm4hep/Vector3f.h>
0021 #include <edm4hep/utils/vector_utils.h>
0022 #include <podio/LinkNavigator.h>
0023 #include <podio/RelationRange.h>
0024 #include <podio/detail/Link.h>
0025 #include <Eigen/Geometry>
0026 #include <Eigen/Householder>
0027 #include <Eigen/Jacobi>
0028 #include <Eigen/SVD>
0029 #include <algorithm>
0030 #include <cmath>
0031 #include <cstddef>
0032 #include <cstdint>
0033 #include <map>
0034 #include <memory>
0035 #include <new>
0036 #include <tuple>
0037 #include <utility>
0038 
0039 #include "FarDetectorLinearTracking.h"
0040 #include "algorithms/fardetectors/FarDetectorLinearTrackingConfig.h"
0041 #include "algorithms/interfaces/CompareObjectID.h"
0042 #include "algorithms/interfaces/LinkTruthUtils.h"
0043 
0044 namespace eicrecon {
0045 
0046 void FarDetectorLinearTracking::init() {
0047 
0048   // For changing how strongly each layer hit is in contributing to the fit
0049   m_layerWeights = Eigen::VectorXd::Constant(m_cfg.n_layer, 1);
0050 
0051   for (std::size_t i = 0; i < std::min(m_cfg.layer_weights.size(), m_cfg.n_layer); i++) {
0052     m_layerWeights(i) = m_cfg.layer_weights[i];
0053   }
0054 
0055   // For checking the direction of the track from theta and phi angles
0056   m_optimumDirection = Eigen::Vector3d::UnitZ();
0057   m_optimumDirection =
0058       Eigen::AngleAxisd(m_cfg.optimum_theta, Eigen::Vector3d::UnitY()) * m_optimumDirection;
0059   m_optimumDirection =
0060       Eigen::AngleAxisd(m_cfg.optimum_phi, Eigen::Vector3d::UnitZ()) * m_optimumDirection;
0061 
0062   m_cellid_converter = algorithms::GeoSvc::instance().cellIDPositionConverter();
0063 }
0064 
0065 void FarDetectorLinearTracking::process(const FarDetectorLinearTracking::Input& input,
0066                                         const FarDetectorLinearTracking::Output& output) const {
0067 
0068   const auto [inputhits, hitLinks, assocHits]  = input;
0069   auto [outputTracks, trackLinks, assocTracks] = output;
0070   (void)assocHits;
0071 
0072   // Check the number of input collections is correct
0073   std::size_t nCollections = inputhits.size();
0074   if (nCollections != m_cfg.n_layer) {
0075     error("Wrong number of input collections passed to algorithm");
0076     return;
0077   }
0078 
0079   // Check if truth associations are possible
0080   const truth::EventLinkNavigator<edm4eic::MCRecoTrackerHitLinkCollection> link_nav(hitLinks);
0081   const bool do_assoc = link_nav.enabled();
0082   if (!do_assoc) {
0083     debug("Provided MCRecoTrackerHitLink collection is empty. No truth associations "
0084           "will be performed.");
0085   }
0086 
0087   std::vector<std::vector<Eigen::Vector3d>> convertedHits;
0088   std::vector<std::vector<edm4hep::MCParticle>> assocParts;
0089   convertedHits.reserve(m_cfg.n_layer);
0090   assocParts.reserve(m_cfg.n_layer);
0091 
0092   // Check there aren't too many hits in any layer to handle
0093   // Temporary limit of number of hits per layer before Kalman filtering/GNN implemented
0094   // TODO - Implement more sensible solution
0095   for (const auto& layerHits : inputhits) {
0096     if ((*layerHits).size() > m_cfg.layer_hits_max) {
0097       info("Too many hits in layer");
0098       return;
0099     }
0100     if ((*layerHits).empty()) {
0101       trace("No hits in layer");
0102       return;
0103     }
0104     ConvertClusters(*layerHits, link_nav, convertedHits, assocParts);
0105   }
0106 
0107   // Create a matrix to store the hit positions
0108   Eigen::MatrixXd hitMatrix(3, m_cfg.n_layer);
0109 
0110   // Create vector to store indexes of hits in the track
0111   std::vector<std::size_t> layerHitIndex(m_cfg.n_layer, 0);
0112 
0113   int layer = 0;
0114 
0115   // Iterate over all combinations of measurements in the layers without recursion
0116   while (true) {
0117     hitMatrix.col(layer) << convertedHits[layer][layerHitIndex[layer]];
0118 
0119     bool isValid = true;
0120     // Check the last two hits are within a certain angle of the optimum direction
0121     if (layer > 0 && m_cfg.restrict_direction) {
0122       isValid = checkHitPair(hitMatrix.col(layer - 1), hitMatrix.col(layer));
0123     }
0124 
0125     // If valid hit combination, move to the next layer or check the combination
0126     if (isValid) {
0127       if (layer == static_cast<long>(m_cfg.n_layer) - 1) {
0128         // Check the combination, if chi2 limit is passed, add the track to the output
0129         checkHitCombination(&hitMatrix, outputTracks, trackLinks, assocTracks, inputhits,
0130                             assocParts, layerHitIndex, do_assoc);
0131       } else {
0132         layer++;
0133         continue;
0134       }
0135     }
0136 
0137     // Iterate current layer
0138     layerHitIndex[layer]++;
0139 
0140     bool doBreak = false;
0141     // Set up next combination to check
0142     while (layerHitIndex[layer] >= convertedHits[layer].size()) {
0143       layerHitIndex[layer] = 0;
0144       if (layer == 0) {
0145         doBreak = true;
0146         break;
0147       }
0148       layer--;
0149       // Iterate previous layer
0150       layerHitIndex[layer]++;
0151     }
0152     if (doBreak) {
0153       break;
0154     }
0155   }
0156 }
0157 
0158 void FarDetectorLinearTracking::checkHitCombination(
0159     Eigen::MatrixXd* hitMatrix, edm4eic::TrackCollection* outputTracks,
0160     edm4eic::MCRecoTrackParticleLinkCollection* trackLinks,
0161     edm4eic::MCRecoTrackParticleAssociationCollection* assocTracks,
0162     const std::vector<gsl::not_null<const edm4eic::Measurement2DCollection*>>& inputHits,
0163     const std::vector<std::vector<edm4hep::MCParticle>>& assocParts,
0164     const std::vector<std::size_t>& layerHitIndex, const bool do_assoc) const {
0165 
0166   Eigen::Vector3d weightedAnchor = (*hitMatrix) * m_layerWeights / (m_layerWeights.sum());
0167 
0168   auto localMatrix = (*hitMatrix).colwise() - weightedAnchor;
0169 
0170   Eigen::BDCSVD<Eigen::MatrixXd> svd(localMatrix.transpose(),
0171                                      Eigen::ComputeThinU | Eigen::ComputeThinV);
0172 
0173   auto V = svd.matrixV();
0174 
0175   // Rotate into principle components and calculate chi2/ndf
0176   auto rotatedMatrix = localMatrix.transpose() * V;
0177   auto residuals     = rotatedMatrix.rightCols(2);
0178   double chi2        = (residuals.array() * residuals.array()).sum() / (2 * m_cfg.n_layer);
0179 
0180   if (chi2 > m_cfg.chi2_max) {
0181     return;
0182   }
0183 
0184   edm4hep::Vector3d outPos = weightedAnchor.data();
0185   edm4hep::Vector3d outVec = V.col(0).data();
0186 
0187   // Make sure fit was pointing in the right direction
0188   if (outVec.z > 0) {
0189     outVec = outVec * -1;
0190   }
0191 
0192   int32_t type{0};                                          // Type of track
0193   edm4hep::Vector3f position(outPos.x, outPos.y, outPos.z); // Position of the trajectory point [mm]
0194   edm4hep::Vector3f momentum(outVec.x, outVec.y, outVec.z); // 3-momentum at the point [GeV]
0195   edm4eic::Cov6f positionMomentumCovariance;                // Error on the position
0196   float time{0};                                            // Time at this point [ns]
0197   float timeError{0};                                       // Error on the time at this point
0198   float charge{-1};                                         // Charge of the particle
0199   int32_t ndf{static_cast<int32_t>(m_cfg.n_layer) - 1};     // Number of degrees of freedom
0200   int32_t pdg{11};                                          // PDG code of the particle
0201 
0202   // Create the track
0203   auto track = (*outputTracks)
0204                    .create(type, position, momentum, positionMomentumCovariance, time, timeError,
0205                            charge, chi2, ndf, pdg);
0206 
0207   // Add Measurement2D relations and count occurrence of particles contributing to the track
0208   std::map<edm4hep::MCParticle, int, CompareObjectID<edm4hep::MCParticle>> particleCount;
0209   for (std::size_t layer = 0; layer < layerHitIndex.size(); layer++) {
0210     track.addToMeasurements((*inputHits[layer])[layerHitIndex[layer]]);
0211     if (do_assoc) {
0212       const auto& assocParticle = assocParts[layer][layerHitIndex[layer]];
0213       if (assocParticle.isAvailable()) {
0214         particleCount[assocParticle]++;
0215       }
0216     }
0217   }
0218 
0219   // Create track associations for each particle
0220   if (do_assoc && trackLinks != nullptr && assocTracks != nullptr) {
0221     for (const auto& [particle, count] : particleCount) {
0222       auto trackLink = trackLinks->create();
0223       trackLink.setFrom(track);
0224       trackLink.setTo(particle);
0225       trackLink.setWeight(count / static_cast<double>(m_cfg.n_layer));
0226       auto trackAssoc = assocTracks->create();
0227       trackAssoc.setRec(track);
0228       trackAssoc.setSim(particle);
0229       trackAssoc.setWeight(count / static_cast<double>(m_cfg.n_layer));
0230     }
0231   }
0232 }
0233 
0234 // Check if a pair of hits lies close to the optimum direction
0235 bool FarDetectorLinearTracking::checkHitPair(const Eigen::Vector3d& hit1,
0236                                              const Eigen::Vector3d& hit2) const {
0237 
0238   Eigen::Vector3d hitDiff = hit2 - hit1;
0239   hitDiff.normalize();
0240 
0241   double angle = std::acos(hitDiff.dot(m_optimumDirection));
0242 
0243   debug("Vector: x={}, y={}, z={}", hitDiff.x(), hitDiff.y(), hitDiff.z());
0244   debug("Optimum: x={}, y={}, z={}", m_optimumDirection.x(), m_optimumDirection.y(),
0245         m_optimumDirection.z());
0246   debug("Angle: {}, Tolerance {}", angle, m_cfg.step_angle_tolerance);
0247 
0248   return angle <= m_cfg.step_angle_tolerance;
0249 }
0250 
0251 // Convert measurements into global coordinates
0252 void FarDetectorLinearTracking::ConvertClusters(
0253     const edm4eic::Measurement2DCollection& clusters,
0254     const truth::EventLinkNavigator<edm4eic::MCRecoTrackerHitLinkCollection>& link_nav,
0255     std::vector<std::vector<Eigen::Vector3d>>& pointPositions,
0256     std::vector<std::vector<edm4hep::MCParticle>>& assoc_parts) const {
0257 
0258   // Get context of first hit
0259   const dd4hep::VolumeManagerContext* context =
0260       m_cellid_converter->findContext(clusters[0].getSurface());
0261 
0262   std::vector<Eigen::Vector3d> layerPositions;
0263   std::vector<edm4hep::MCParticle> assocParticles;
0264 
0265   for (auto cluster : clusters) {
0266 
0267     auto globalPos = context->localToWorld({cluster.getLoc()[0], cluster.getLoc()[1], 0});
0268     layerPositions.emplace_back(globalPos.x() / dd4hep::mm, globalPos.y() / dd4hep::mm,
0269                                 globalPos.z() / dd4hep::mm);
0270 
0271     // Determine the MCParticle associated with this measurement based on the weights
0272     // Get hit in measurement with max weight
0273     float maxWeight      = 0;
0274     std::size_t maxIndex = cluster.getWeights().size();
0275     for (std::size_t i = 0; i < cluster.getWeights().size(); ++i) {
0276       if (cluster.getWeights()[i] > maxWeight) {
0277         maxWeight = cluster.getWeights()[i];
0278         maxIndex  = i;
0279       }
0280     }
0281     if (maxIndex == cluster.getWeights().size()) {
0282       // no maximum found (e.g. all weights zero, cluster size zero)
0283       assocParticles.emplace_back();
0284       continue;
0285     }
0286     auto maxHit = cluster.getHits()[maxIndex];
0287     // Get associated raw hit
0288     auto rawHit = maxHit.getRawHit();
0289 
0290     const auto sim_hits = link_nav.linked(rawHit);
0291     if (!sim_hits.empty()) {
0292       auto particle = sim_hits[0].o.getParticle();
0293       assocParticles.push_back(particle);
0294     } else {
0295       assocParticles.emplace_back();
0296     }
0297   }
0298 
0299   pointPositions.push_back(layerPositions);
0300   assoc_parts.push_back(assocParticles);
0301 }
0302 
0303 } // namespace eicrecon