File indexing completed on 2026-09-06 08:24:36
0001
0002
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
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
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
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
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
0093
0094
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
0108 Eigen::MatrixXd hitMatrix(3, m_cfg.n_layer);
0109
0110
0111 std::vector<std::size_t> layerHitIndex(m_cfg.n_layer, 0);
0112
0113 int layer = 0;
0114
0115
0116 while (true) {
0117 hitMatrix.col(layer) << convertedHits[layer][layerHitIndex[layer]];
0118
0119 bool isValid = true;
0120
0121 if (layer > 0 && m_cfg.restrict_direction) {
0122 isValid = checkHitPair(hitMatrix.col(layer - 1), hitMatrix.col(layer));
0123 }
0124
0125
0126 if (isValid) {
0127 if (layer == static_cast<long>(m_cfg.n_layer) - 1) {
0128
0129 checkHitCombination(&hitMatrix, outputTracks, trackLinks, assocTracks, inputhits,
0130 assocParts, layerHitIndex, do_assoc);
0131 } else {
0132 layer++;
0133 continue;
0134 }
0135 }
0136
0137
0138 layerHitIndex[layer]++;
0139
0140 bool doBreak = false;
0141
0142 while (layerHitIndex[layer] >= convertedHits[layer].size()) {
0143 layerHitIndex[layer] = 0;
0144 if (layer == 0) {
0145 doBreak = true;
0146 break;
0147 }
0148 layer--;
0149
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
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
0188 if (outVec.z > 0) {
0189 outVec = outVec * -1;
0190 }
0191
0192 int32_t type{0};
0193 edm4hep::Vector3f position(outPos.x, outPos.y, outPos.z);
0194 edm4hep::Vector3f momentum(outVec.x, outVec.y, outVec.z);
0195 edm4eic::Cov6f positionMomentumCovariance;
0196 float time{0};
0197 float timeError{0};
0198 float charge{-1};
0199 int32_t ndf{static_cast<int32_t>(m_cfg.n_layer) - 1};
0200 int32_t pdg{11};
0201
0202
0203 auto track = (*outputTracks)
0204 .create(type, position, momentum, positionMomentumCovariance, time, timeError,
0205 charge, chi2, ndf, pdg);
0206
0207
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
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
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
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
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
0272
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
0283 assocParticles.emplace_back();
0284 continue;
0285 }
0286 auto maxHit = cluster.getHits()[maxIndex];
0287
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 }