Back to home page

EIC code displayed by LXR

 
 

    


Warning, file /EICrecon/src/algorithms/tracking/TrackSeeding.cc was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 // SPDX-License-Identifier: LGPL-3.0-or-later
0002 // Copyright (C) 2023  - 2025 Joe Osborn, Dmitry Romanov, Wouter Deconinck
0003 
0004 #include "TrackSeeding.h"
0005 
0006 #include <Acts/Definitions/Algebra.hpp>
0007 #include <Acts/Definitions/Units.hpp>
0008 #include <Acts/EventData/Seed.hpp>
0009 #include <Acts/EventData/SpacePointProxy.hpp>
0010 #include <Acts/Seeding/SeedConfirmationRangeConfig.hpp>
0011 #include <Acts/Seeding/SeedFilter.hpp>
0012 #include <Acts/Seeding/SeedFilterConfig.hpp>
0013 #include <Acts/Seeding/SeedFinderConfig.hpp>
0014 #include <Acts/Seeding/SeedFinderOrthogonal.hpp>
0015 #include <Acts/Seeding/SeedFinderOrthogonalConfig.hpp>
0016 #include <Acts/Seeding/SeedFinderUtils.hpp>
0017 #include <Acts/Surfaces/PerigeeSurface.hpp>
0018 #include <Acts/Surfaces/Surface.hpp>
0019 #include <Acts/Utilities/KDTree.hpp> // IWYU pragma: keep FIXME KDTree missing in SeedFinderOrthogonal.hpp until Acts v23.0.0
0020 #include <Acts/Utilities/Result.hpp>
0021 #include <edm4eic/Cov6f.h>
0022 #include <edm4eic/EDM4eicVersion.h>
0023 #include <edm4hep/Vector2f.h>
0024 #include <edm4hep/Vector3f.h>
0025 #include <Eigen/Core>
0026 #include <Eigen/Geometry>
0027 #include <array>
0028 #include <cmath>
0029 #include <limits>
0030 #include <tuple>
0031 
0032 namespace eicrecon {
0033 
0034 void TrackSeeding::init() {
0035 
0036   // Filter parameters
0037   m_seedFilterConfig.maxSeedsPerSpM        = m_cfg.maxSeedsPerSpM_filter;
0038   m_seedFilterConfig.deltaRMin             = m_cfg.deltaRMin;
0039   m_seedFilterConfig.seedConfirmation      = m_cfg.seedConfirmation;
0040   m_seedFilterConfig.deltaInvHelixDiameter = m_cfg.deltaInvHelixDiameter;
0041   m_seedFilterConfig.impactWeightFactor    = m_cfg.impactWeightFactor;
0042   m_seedFilterConfig.zOriginWeightFactor   = m_cfg.zOriginWeightFactor;
0043   m_seedFilterConfig.compatSeedWeight      = m_cfg.compatSeedWeight;
0044   m_seedFilterConfig.compatSeedLimit       = m_cfg.compatSeedLimit;
0045   m_seedFilterConfig.seedWeightIncrement   = m_cfg.seedWeightIncrement;
0046 
0047   m_seedFilterConfig.centralSeedConfirmationRange = Acts::SeedConfirmationRangeConfig{
0048       .zMinSeedConf            = m_cfg.zMinSeedConfCentral,
0049       .zMaxSeedConf            = m_cfg.zMaxSeedConfCentral,
0050       .rMaxSeedConf            = m_cfg.rMaxSeedConfCentral,
0051       .nTopForLargeR           = m_cfg.nTopForLargeRCentral,
0052       .nTopForSmallR           = m_cfg.nTopForSmallRCentral,
0053       .seedConfMinBottomRadius = m_cfg.seedConfMinBottomRadiusCentral,
0054       .seedConfMaxZOrigin      = m_cfg.seedConfMaxZOriginCentral,
0055       .minImpactSeedConf       = m_cfg.minImpactSeedConfCentral};
0056 
0057   m_seedFilterConfig.forwardSeedConfirmationRange = Acts::SeedConfirmationRangeConfig{
0058       .zMinSeedConf            = m_cfg.zMinSeedConfForward,
0059       .zMaxSeedConf            = m_cfg.zMaxSeedConfForward,
0060       .rMaxSeedConf            = m_cfg.rMaxSeedConfForward,
0061       .nTopForLargeR           = m_cfg.nTopForLargeRForward,
0062       .nTopForSmallR           = m_cfg.nTopForSmallRForward,
0063       .seedConfMinBottomRadius = m_cfg.seedConfMinBottomRadiusForward,
0064       .seedConfMaxZOrigin      = m_cfg.seedConfMaxZOriginForward,
0065       .minImpactSeedConf       = m_cfg.minImpactSeedConfForward};
0066 
0067   // Finder parameters
0068   m_seedFinderConfig.seedFilter =
0069       std::make_unique<Acts::SeedFilter<proxy_type>>(m_seedFilterConfig);
0070   m_seedFinderConfig.rMax               = m_cfg.rMax;
0071   m_seedFinderConfig.rMin               = m_cfg.rMin;
0072   m_seedFinderConfig.deltaRMinTopSP     = m_cfg.deltaRMinTopSP;
0073   m_seedFinderConfig.deltaRMaxTopSP     = m_cfg.deltaRMaxTopSP;
0074   m_seedFinderConfig.deltaRMinBottomSP  = m_cfg.deltaRMinBottomSP;
0075   m_seedFinderConfig.deltaRMaxBottomSP  = m_cfg.deltaRMaxBottomSP;
0076   m_seedFinderConfig.collisionRegionMin = m_cfg.collisionRegionMin;
0077   m_seedFinderConfig.collisionRegionMax = m_cfg.collisionRegionMax;
0078   m_seedFinderConfig.zMin               = m_cfg.zMin;
0079   m_seedFinderConfig.zMax               = m_cfg.zMax;
0080   m_seedFinderConfig.maxSeedsPerSpM     = m_cfg.maxSeedsPerSpM;
0081   m_seedFinderConfig.cotThetaMax        = m_cfg.cotThetaMax;
0082   m_seedFinderConfig.sigmaScattering    = m_cfg.sigmaScattering;
0083   m_seedFinderConfig.radLengthPerSeed   = m_cfg.radLengthPerSeed;
0084   m_seedFinderConfig.minPt              = m_cfg.minPt;
0085   m_seedFinderConfig.impactMax          = m_cfg.impactMax;
0086   m_seedFinderConfig.rMinMiddle         = m_cfg.rMinMiddle;
0087   m_seedFinderConfig.rMaxMiddle         = m_cfg.rMaxMiddle;
0088   m_seedFinderConfig.deltaPhiMax        = m_cfg.deltaPhiMax;
0089 
0090   m_seedFinderOptions.beamPos   = Acts::Vector2(m_cfg.beamPosX, m_cfg.beamPosY);
0091   m_seedFinderOptions.bFieldInZ = m_cfg.bFieldInZ;
0092 
0093   m_seedFinderConfig  = m_seedFinderConfig.calculateDerivedQuantities();
0094   m_seedFinderOptions = m_seedFinderOptions.calculateDerivedQuantities(m_seedFinderConfig);
0095 }
0096 
0097 void TrackSeeding::process(const Input& input, const Output& output) const {
0098 
0099   const auto [trk_hits]        = input;
0100   auto [trk_seeds, trk_params] = output;
0101 
0102   std::vector<const eicrecon::SpacePoint*> spacePoints = getSpacePoints(*trk_hits);
0103 
0104   Acts::SeedFinderOrthogonal<proxy_type> finder(m_seedFinderConfig); // FIXME move into class scope
0105 
0106   // Config
0107   Acts::SpacePointContainerConfig spConfig;
0108 
0109   // Options
0110   Acts::SpacePointContainerOptions spOptions;
0111   spOptions.beamPos = {0., 0.};
0112 
0113   SpacePointContainerType container(spacePoints);
0114   Acts::SpacePointContainer<decltype(container), Acts::detail::RefHolder> spContainer(
0115       spConfig, spOptions, container);
0116 
0117   std::vector<Acts::Seed<proxy_type>> seeds = finder.createSeeds(m_seedFinderOptions, spContainer);
0118 
0119   // need to convert here from seed of proxies to seed of sps
0120   for (const auto& seed : seeds) {
0121     const auto& sps = seed.sp();
0122 
0123     auto seedToAdd = Acts::Seed<eicrecon::SpacePoint>(*sps[0]->externalSpacePoint(),
0124                                                       *sps[1]->externalSpacePoint(),
0125                                                       *sps[2]->externalSpacePoint());
0126     seedToAdd.setVertexZ(seed.z());
0127     seedToAdd.setQuality(seed.seedQuality());
0128 
0129     // Estimate track parameters
0130     auto trackParams = estimateTrackParamsFromSeed(seedToAdd);
0131     if (!trackParams.has_value()) {
0132       debug("Failed to estimate track parameters from seed");
0133       continue;
0134     }
0135     trk_params->push_back(trackParams.value());
0136 
0137     // Add seed to collection
0138     auto trk_seed = trk_seeds->create();
0139     trk_seed.setPerigee({0.f, 0.f, 0.f});
0140 #if EDM4EIC_VERSION_MAJOR > 8 || (EDM4EIC_VERSION_MAJOR == 8 && EDM4EIC_VERSION_MINOR > 5)
0141     trk_seed.setQuality(seedToAdd.seedQuality());
0142 #endif
0143     trk_seed.setParams(trackParams.value());
0144     trk_seed.addToHits(*sps[0]->externalSpacePoint());
0145     trk_seed.addToHits(*sps[1]->externalSpacePoint());
0146     trk_seed.addToHits(*sps[2]->externalSpacePoint());
0147   }
0148 
0149   for (auto& sp : spacePoints) {
0150     delete sp;
0151   }
0152 }
0153 
0154 std::vector<const eicrecon::SpacePoint*>
0155 TrackSeeding::getSpacePoints(const edm4eic::TrackerHitCollection& trk_hits) {
0156   std::vector<const eicrecon::SpacePoint*> spacepoints;
0157 
0158   for (const auto hit : trk_hits) {
0159     const eicrecon::SpacePoint* sp = new SpacePoint(hit);
0160     spacepoints.push_back(sp);
0161   }
0162 
0163   return spacepoints;
0164 }
0165 
0166 std::optional<edm4eic::MutableTrackParameters>
0167 TrackSeeding::estimateTrackParamsFromSeed(const Acts::Seed<SpacePoint>& seed) const {
0168   std::vector<std::pair<float, float>> xyHitPositions;
0169   std::vector<std::pair<float, float>> rzHitPositions;
0170   for (const auto& spptr : seed.sp()) {
0171     xyHitPositions.emplace_back(spptr->x(), spptr->y());
0172     rzHitPositions.emplace_back(spptr->r(), spptr->z());
0173   }
0174 
0175   auto RX0Y0 = circleFit(xyHitPositions);
0176   float R    = std::get<0>(RX0Y0);
0177   float X0   = std::get<1>(RX0Y0);
0178   float Y0   = std::get<2>(RX0Y0);
0179   if (!(std::isfinite(R) && std::isfinite(std::abs(X0)) && std::isfinite(std::abs(Y0)))) {
0180     // avoid float overflow for hits on a line
0181     return {};
0182   }
0183   if (std::hypot(X0, Y0) < std::numeric_limits<decltype(std::hypot(X0, Y0))>::epsilon() ||
0184       !std::isfinite(std::hypot(X0, Y0))) {
0185     //Avoid center of circle at origin, where there is no point-of-closest approach
0186     //Also, avoid float overflow on circle center
0187     return {};
0188   }
0189 
0190   auto slopeZ0     = lineFit(rzHitPositions);
0191   const auto xypos = findPCA(RX0Y0);
0192 
0193   //Determine charge
0194   int charge = determineCharge(xyHitPositions, xypos, RX0Y0);
0195 
0196   float theta = atan(1. / std::get<0>(slopeZ0));
0197   // normalize to 0<theta<pi
0198   if (theta < 0) {
0199     theta += M_PI;
0200   }
0201   float eta    = -log(tan(theta / 2.));
0202   float pt     = R * m_cfg.bFieldInZ; // pt[GeV] = R[mm] * B[GeV/mm]
0203   float p      = pt * cosh(eta);
0204   float qOverP = charge / p;
0205 
0206   //Calculate phi at xypos
0207   auto xpos = xypos.first;
0208   auto ypos = xypos.second;
0209 
0210   auto vxpos = -1. * charge * (ypos - Y0);
0211   auto vypos = charge * (xpos - X0);
0212 
0213   auto phi = atan2(vypos, vxpos);
0214 
0215   const float z0 = seed.z();
0216   auto perigee   = Acts::Surface::makeShared<Acts::PerigeeSurface>(Acts::Vector3(0, 0, 0));
0217   Acts::Vector3 global(xypos.first, xypos.second, z0);
0218 
0219   //Compute local position at PCA
0220   Acts::Vector2 localpos;
0221   Acts::Vector3 direction(sin(theta) * cos(phi), sin(theta) * sin(phi), cos(theta));
0222 
0223   auto local = perigee->globalToLocal(m_geoSvc->getActsGeometryContext(), global, direction);
0224 
0225   if (!local.ok()) {
0226     return {};
0227   }
0228 
0229   localpos = local.value();
0230 
0231   auto trackparam = edm4eic::MutableTrackParameters();
0232   trackparam.setType(-1); // type --> seed(-1)
0233   trackparam.setLoc(
0234       {static_cast<float>(localpos(0)), static_cast<float>(localpos(1))}); // 2d location on surface
0235   trackparam.setPhi(static_cast<float>(phi));                              // phi [rad]
0236   trackparam.setTheta(theta);                                              //theta [rad]
0237   trackparam.setQOverP(qOverP);                                            // Q/p [e/GeV]
0238   trackparam.setTime(10);                                                  // time in ns
0239   edm4eic::Cov6f cov;
0240   cov(0, 0) = m_cfg.locaError / Acts::UnitConstants::mm;    // loc0
0241   cov(1, 1) = m_cfg.locbError / Acts::UnitConstants::mm;    // loc1
0242   cov(2, 2) = m_cfg.phiError / Acts::UnitConstants::rad;    // phi
0243   cov(3, 3) = m_cfg.thetaError / Acts::UnitConstants::rad;  // theta
0244   cov(4, 4) = m_cfg.qOverPError * Acts::UnitConstants::GeV; // qOverP
0245   cov(5, 5) = m_cfg.timeError / Acts::UnitConstants::ns;    // time
0246   trackparam.setCovariance(cov);
0247 
0248   return trackparam;
0249 }
0250 
0251 std::pair<float, float> TrackSeeding::findPCA(std::tuple<float, float, float>& circleParams) {
0252   const float R  = std::get<0>(circleParams);
0253   const float X0 = std::get<1>(circleParams);
0254   const float Y0 = std::get<2>(circleParams);
0255 
0256   const double R0 = std::hypot(X0, Y0);
0257 
0258   //Calculate point on circle closest to origin
0259   const double xmin = X0 * (1. - R / R0);
0260   const double ymin = Y0 * (1. - R / R0);
0261 
0262   return std::make_pair(xmin, ymin);
0263 }
0264 
0265 int TrackSeeding::determineCharge(std::vector<std::pair<float, float>>& positions,
0266                                   const std::pair<float, float>& PCA,
0267                                   std::tuple<float, float, float>& RX0Y0) {
0268 
0269   const auto& firstpos = positions.at(0);
0270   auto hit_x           = firstpos.first;
0271   auto hit_y           = firstpos.second;
0272 
0273   auto xpos = PCA.first;
0274   auto ypos = PCA.second;
0275 
0276   float X0 = std::get<1>(RX0Y0);
0277   float Y0 = std::get<2>(RX0Y0);
0278 
0279   Acts::Vector3 B_z(0, 0, 1);
0280   Acts::Vector3 radial(X0 - xpos, Y0 - ypos, 0);
0281   Acts::Vector3 hit(hit_x - xpos, hit_y - ypos, 0);
0282 
0283   auto cross = radial.cross(hit);
0284 
0285   float dot = cross.dot(B_z);
0286 
0287   return copysign(1., -dot);
0288 }
0289 
0290 /**
0291    * Circle fit to a given set of data points (in 2D)
0292    * This is an algebraic fit, due to Taubin, based on the journal article
0293    * G. Taubin, "Estimation Of Planar Curves, Surfaces And Nonplanar
0294    * Space Curves Defined By Implicit Equations, With
0295    * Applications To Edge And Range Image Segmentation",
0296    * IEEE Trans. PAMI, Vol. 13, pages 1115-1138, (1991)
0297    * It works well whether data points are sampled along an entire circle or along a small arc.
0298    * It still has a small bias and its statistical accuracy is slightly lower than that of the geometric fit (minimizing geometric distances),
0299    * It provides a very good initial guess for a subsequent geometric fit.
0300    * Nikolai Chernov  (September 2012)
0301    */
0302 std::tuple<float, float, float>
0303 TrackSeeding::circleFit(std::vector<std::pair<float, float>>& positions) {
0304   // Compute x- and y- sample means
0305   double meanX  = 0;
0306   double meanY  = 0;
0307   double weight = 0;
0308 
0309   for (const auto& [x, y] : positions) {
0310     meanX += x;
0311     meanY += y;
0312     ++weight;
0313   }
0314   meanX /= weight;
0315   meanY /= weight;
0316 
0317   //     computing moments
0318 
0319   double Mxx = 0;
0320   double Myy = 0;
0321   double Mxy = 0;
0322   double Mxz = 0;
0323   double Myz = 0;
0324   double Mzz = 0;
0325 
0326   for (auto& [x, y] : positions) {
0327     double Xi = x - meanX; //  centered x-coordinates
0328     double Yi = y - meanY; //  centered y-coordinates
0329     double Zi = std::pow(Xi, 2) + std::pow(Yi, 2);
0330 
0331     Mxy += Xi * Yi;
0332     Mxx += Xi * Xi;
0333     Myy += Yi * Yi;
0334     Mxz += Xi * Zi;
0335     Myz += Yi * Zi;
0336     Mzz += Zi * Zi;
0337   }
0338   Mxx /= weight;
0339   Myy /= weight;
0340   Mxy /= weight;
0341   Mxz /= weight;
0342   Myz /= weight;
0343   Mzz /= weight;
0344 
0345   //  computing coefficients of the characteristic polynomial
0346 
0347   const double Mz     = Mxx + Myy;
0348   const double Cov_xy = Mxx * Myy - Mxy * Mxy;
0349   const double Var_z  = Mzz - Mz * Mz;
0350   const double A3     = 4 * Mz;
0351   const double A2     = -3 * Mz * Mz - Mzz;
0352   const double A1     = Var_z * Mz + 4 * Cov_xy * Mz - Mxz * Mxz - Myz * Myz;
0353   const double A0  = Mxz * (Mxz * Myy - Myz * Mxy) + Myz * (Myz * Mxx - Mxz * Mxy) - Var_z * Cov_xy;
0354   const double A22 = A2 + A2;
0355   const double A33 = A3 + A3 + A3;
0356 
0357   //    finding the root of the characteristic polynomial
0358   //    using Newton's method starting at x=0
0359   //    (it is guaranteed to converge to the right root)
0360   static constexpr int iter_max = 99;
0361   double x                      = 0;
0362   double y                      = A0;
0363 
0364   // usually, 4-6 iterations are enough
0365   for (int iter = 0; iter < iter_max; ++iter) {
0366     const double Dy   = A1 + x * (A22 + A33 * x);
0367     const double xnew = x - y / Dy;
0368     if ((xnew == x) || (!std::isfinite(xnew))) {
0369       break;
0370     }
0371 
0372     const double ynew = A0 + xnew * (A1 + xnew * (A2 + xnew * A3));
0373     if (std::abs(ynew) >= std::abs(y)) {
0374       break;
0375     }
0376 
0377     x = xnew;
0378     y = ynew;
0379   }
0380 
0381   //  computing parameters of the fitting circle
0382   const double DET     = std::pow(x, 2) - x * Mz + Cov_xy;
0383   const double Xcenter = (Mxz * (Myy - x) - Myz * Mxy) / DET / 2;
0384   const double Ycenter = (Myz * (Mxx - x) - Mxz * Mxy) / DET / 2;
0385 
0386   //  assembling the output
0387   float X0 = Xcenter + meanX;
0388   float Y0 = Ycenter + meanY;
0389   float R  = std::sqrt(std::pow(Xcenter, 2) + std::pow(Ycenter, 2) + Mz);
0390   return std::make_tuple(R, X0, Y0);
0391 }
0392 
0393 std::tuple<float, float> TrackSeeding::lineFit(std::vector<std::pair<float, float>>& positions) {
0394   double xsum  = 0;
0395   double x2sum = 0;
0396   double ysum  = 0;
0397   double xysum = 0;
0398   for (const auto& [r, z] : positions) {
0399     xsum  = xsum + r;               //calculate sigma(xi)
0400     ysum  = ysum + z;               //calculate sigma(yi)
0401     x2sum = x2sum + std::pow(r, 2); //calculate sigma(x^2i)
0402     xysum = xysum + r * z;          //calculate sigma(xi*yi)
0403   }
0404 
0405   const auto npts          = positions.size();
0406   const double denominator = (x2sum * npts - std::pow(xsum, 2));
0407   const float a            = (xysum * npts - xsum * ysum) / denominator;  //calculate slope
0408   const float b            = (x2sum * ysum - xsum * xysum) / denominator; //calculate intercept
0409   return std::make_tuple(a, b);
0410 }
0411 
0412 } // namespace eicrecon