Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-09-17 08:01:49

0001 // This file is part of the ACTS project.
0002 //
0003 // Copyright (C) 2016 CERN for the benefit of the ACTS project
0004 //
0005 // This Source Code Form is subject to the terms of the Mozilla Public
0006 // License, v. 2.0. If a copy of the MPL was not distributed with this
0007 // file, You can obtain one at https://mozilla.org/MPL/2.0/.
0008 
0009 #pragma once
0010 
0011 #include "Acts/Seeding/SeedFinder.hpp"
0012 
0013 #include <algorithm>
0014 #include <cmath>
0015 
0016 namespace Acts {
0017 
0018 template <typename external_spacepoint_t, typename grid_t, typename platform_t>
0019 SeedFinder<external_spacepoint_t, grid_t, platform_t>::SeedFinder(
0020     const SeedFinderConfig<external_spacepoint_t>& config,
0021     std::unique_ptr<const Logger> logger)
0022     : m_config(config), m_logger(std::move(logger)) {
0023   if (std::isnan(config.deltaRMaxTopSP)) {
0024     throw std::runtime_error("Value of deltaRMaxTopSP was not initialised");
0025   }
0026   if (std::isnan(config.deltaRMinTopSP)) {
0027     throw std::runtime_error("Value of deltaRMinTopSP was not initialised");
0028   }
0029   if (std::isnan(config.deltaRMaxBottomSP)) {
0030     throw std::runtime_error("Value of deltaRMaxBottomSP was not initialised");
0031   }
0032   if (std::isnan(config.deltaRMinBottomSP)) {
0033     throw std::runtime_error("Value of deltaRMinBottomSP was not initialised");
0034   }
0035 }
0036 
0037 template <typename external_spacepoint_t, typename grid_t, typename platform_t>
0038 template <typename container_t, GridBinCollection sp_range_t>
0039   requires CollectionStoresSeedsTo<container_t, external_spacepoint_t, 3ul>
0040 void SeedFinder<external_spacepoint_t, grid_t, platform_t>::createSeedsForGroup(
0041     const SeedFinderOptions& options, SeedingState& state, const grid_t& grid,
0042     container_t& outputCollection, const sp_range_t& bottomSPsIdx,
0043     const std::size_t middleSPsIdx, const sp_range_t& topSPsIdx,
0044     const Range1D<float>& rMiddleSPRange) const {
0045   // This is used for seed filtering later
0046   const std::size_t max_num_seeds_per_spm =
0047       m_config.seedFilter->getSeedFilterConfig().maxSeedsPerSpMConf;
0048   const std::size_t max_num_quality_seeds_per_spm =
0049       m_config.seedFilter->getSeedFilterConfig().maxQualitySeedsPerSpMConf;
0050 
0051   state.candidatesCollector.setMaxElements(max_num_seeds_per_spm,
0052                                            max_num_quality_seeds_per_spm);
0053 
0054   // If there are no bottom or top bins, just return and waste no time
0055   if (bottomSPsIdx.size() == 0 || topSPsIdx.size() == 0) {
0056     return;
0057   }
0058 
0059   // Get the middle space point candidates
0060   const std::vector<const external_spacepoint_t*>& middleSPs =
0061       grid.at(middleSPsIdx);
0062   // Return if somehow there are no middle sp candidates
0063   if (middleSPs.empty()) {
0064     return;
0065   }
0066 
0067   // neighbours
0068   // clear previous results
0069   state.bottomNeighbours.clear();
0070   state.topNeighbours.clear();
0071 
0072   // Fill
0073   // bottoms
0074   for (const std::size_t idx : bottomSPsIdx) {
0075     // Only add an entry if the bin has entries
0076     if (grid.at(idx).size() == 0) {
0077       continue;
0078     }
0079     state.bottomNeighbours.emplace_back(
0080         grid, idx, middleSPs.front()->radius() - m_config.deltaRMaxBottomSP);
0081   }
0082   // if no bottom candidates, then no need to proceed
0083   if (state.bottomNeighbours.size() == 0) {
0084     return;
0085   }
0086 
0087   // tops
0088   for (const std::size_t idx : topSPsIdx) {
0089     // Only add an entry if the bin has entries
0090     if (grid.at(idx).size() == 0) {
0091       continue;
0092     }
0093     state.topNeighbours.emplace_back(
0094         grid, idx, middleSPs.front()->radius() + m_config.deltaRMinTopSP);
0095   }
0096   // if no top candidates, then no need to proceed
0097   if (state.topNeighbours.size() == 0) {
0098     return;
0099   }
0100 
0101   // we compute this here since all middle space point candidates belong to the
0102   // same z-bin
0103   auto [minRadiusRangeForMiddle, maxRadiusRangeForMiddle] =
0104       retrieveRadiusRangeForMiddle(*middleSPs.front(), rMiddleSPRange);
0105   ACTS_VERBOSE("Current global bin: " << middleSPsIdx << ", z value of "
0106                                       << middleSPs.front()->z());
0107   ACTS_VERBOSE("Validity range (radius) for the middle space point is ["
0108                << minRadiusRangeForMiddle << ", " << maxRadiusRangeForMiddle
0109                << "]");
0110 
0111   for (const external_spacepoint_t* spM : middleSPs) {
0112     const float rM = spM->radius();
0113 
0114     // check if spM is outside our radial region of interest
0115     if (rM < minRadiusRangeForMiddle) {
0116       continue;
0117     }
0118     if (rM > maxRadiusRangeForMiddle) {
0119       // break because SPs are sorted in r
0120       break;
0121     }
0122 
0123     const float zM = spM->z();
0124     const float uIP = -1 / rM;
0125     const float cosPhiM = -spM->x() * uIP;
0126     const float sinPhiM = -spM->y() * uIP;
0127     const float uIP2 = uIP * uIP;
0128 
0129     // Iterate over middle-top dublets
0130     getCompatibleDoublets<SpacePointCandidateType::eTop>(
0131         options, grid, state.spacePointMutableData, state.topNeighbours, *spM,
0132         state.linCircleTop, state.compatTopSP, m_config.deltaRMinTopSP,
0133         m_config.deltaRMaxTopSP, uIP, uIP2, cosPhiM, sinPhiM);
0134 
0135     // no top SP found -> try next spM
0136     if (state.compatTopSP.empty()) {
0137       ACTS_VERBOSE("No compatible Tops, moving to next middle candidate");
0138       continue;
0139     }
0140 
0141     // apply cut on the number of top SP if seedConfirmation is true
0142     SeedFilterState seedFilterState;
0143     if (m_config.seedConfirmation) {
0144       // check if middle SP is in the central or forward region
0145       SeedConfirmationRangeConfig seedConfRange =
0146           (zM > m_config.centralSeedConfirmationRange.zMaxSeedConf ||
0147            zM < m_config.centralSeedConfirmationRange.zMinSeedConf)
0148               ? m_config.forwardSeedConfirmationRange
0149               : m_config.centralSeedConfirmationRange;
0150       // set the minimum number of top SP depending on whether the middle SP is
0151       // in the central or forward region
0152       seedFilterState.nTopSeedConf = rM > seedConfRange.rMaxSeedConf
0153                                          ? seedConfRange.nTopForLargeR
0154                                          : seedConfRange.nTopForSmallR;
0155       // set max bottom radius for seed confirmation
0156       seedFilterState.rMaxSeedConf = seedConfRange.rMaxSeedConf;
0157       // continue if number of top SPs is smaller than minimum
0158       if (state.compatTopSP.size() < seedFilterState.nTopSeedConf) {
0159         ACTS_VERBOSE(
0160             "Number of top SPs is "
0161             << state.compatTopSP.size()
0162             << " and is smaller than minimum, moving to next middle candidate");
0163         continue;
0164       }
0165     }
0166 
0167     // Iterate over middle-bottom dublets
0168     getCompatibleDoublets<SpacePointCandidateType::eBottom>(
0169         options, grid, state.spacePointMutableData, state.bottomNeighbours,
0170         *spM, state.linCircleBottom, state.compatBottomSP,
0171         m_config.deltaRMinBottomSP, m_config.deltaRMaxBottomSP, uIP, uIP2,
0172         cosPhiM, sinPhiM);
0173 
0174     // no bottom SP found -> try next spM
0175     if (state.compatBottomSP.empty()) {
0176       ACTS_VERBOSE("No compatible Bottoms, moving to next middle candidate");
0177       continue;
0178     }
0179 
0180     ACTS_VERBOSE("Candidates: " << state.compatBottomSP.size()
0181                                 << " bottoms and " << state.compatTopSP.size()
0182                                 << " tops for middle candidate indexed "
0183                                 << spM->index());
0184     // filter candidates
0185     if (m_config.useDetailedDoubleMeasurementInfo) {
0186       filterCandidates<DetectorMeasurementInfo::eDetailed>(
0187           *spM, options, seedFilterState, state);
0188     } else {
0189       filterCandidates<DetectorMeasurementInfo::eDefault>(
0190           *spM, options, seedFilterState, state);
0191     }
0192 
0193     m_config.seedFilter->filterSeeds_1SpFixed(state.spacePointMutableData,
0194                                               state.candidatesCollector,
0195                                               outputCollection);
0196 
0197   }  // loop on mediums
0198 }
0199 
0200 template <typename external_spacepoint_t, typename grid_t, typename platform_t>
0201 template <SpacePointCandidateType candidateType, typename out_range_t>
0202 inline void
0203 SeedFinder<external_spacepoint_t, grid_t, platform_t>::getCompatibleDoublets(
0204     const SeedFinderOptions& options, const grid_t& grid,
0205     SpacePointMutableData& mutableData,
0206     boost::container::small_vector<
0207         Neighbour<grid_t>, detail::ipow(3, grid_t::DIM)>& otherSPsNeighbours,
0208     const external_spacepoint_t& mediumSP, std::vector<LinCircle>& linCircleVec,
0209     out_range_t& outVec, const float deltaRMinSP, const float deltaRMaxSP,
0210     const float uIP, const float uIP2, const float cosPhiM,
0211     const float sinPhiM) const {
0212   float impactMax = m_config.impactMax;
0213 
0214   constexpr bool isBottomCandidate =
0215       candidateType == SpacePointCandidateType::eBottom;
0216 
0217   if constexpr (isBottomCandidate) {
0218     impactMax = -impactMax;
0219   }
0220 
0221   outVec.clear();
0222   linCircleVec.clear();
0223 
0224   // get number of neighbour SPs
0225   std::size_t nsp = 0;
0226   for (const auto& otherSPCol : otherSPsNeighbours) {
0227     nsp += grid.at(otherSPCol.index).size();
0228   }
0229 
0230   linCircleVec.reserve(nsp);
0231   outVec.reserve(nsp);
0232 
0233   const float rM = mediumSP.radius();
0234   const float xM = mediumSP.x();
0235   const float yM = mediumSP.y();
0236   const float zM = mediumSP.z();
0237   const float varianceRM = mediumSP.varianceR();
0238   const float varianceZM = mediumSP.varianceZ();
0239 
0240   float vIPAbs = 0;
0241   if (m_config.interactionPointCut) {
0242     // equivalent to m_config.impactMax / (rM * rM);
0243     vIPAbs = impactMax * uIP2;
0244   }
0245 
0246   float deltaR = 0;
0247   float deltaZ = 0;
0248 
0249   const auto outsideRangeCheck = [](const float value, const float min,
0250                                     const float max) -> bool {
0251     // intentionally using `|` after profiling. faster due to better branch
0252     // prediction
0253     return static_cast<bool>(static_cast<int>(value < min) |
0254                              static_cast<int>(value > max));
0255   };
0256 
0257   for (auto& otherSPCol : otherSPsNeighbours) {
0258     const std::vector<const external_spacepoint_t*>& otherSPs =
0259         grid.at(otherSPCol.index);
0260     if (otherSPs.empty()) {
0261       continue;
0262     }
0263 
0264     // we make a copy of the iterator here since we need it to remain
0265     // the same in the Neighbour object
0266     auto min_itr = otherSPCol.itr;
0267 
0268     // find the first SP inside the radius region of interest and update
0269     // the iterator so we don't need to look at the other SPs again
0270     for (; min_itr != otherSPs.end(); ++min_itr) {
0271       const external_spacepoint_t* otherSP = *min_itr;
0272       if constexpr (candidateType == SpacePointCandidateType::eBottom) {
0273         // if r-distance is too big, try next SP in bin
0274         if ((rM - otherSP->radius()) <= deltaRMaxSP) {
0275           break;
0276         }
0277       } else {
0278         // if r-distance is too small, try next SP in bin
0279         if ((otherSP->radius() - rM) >= deltaRMinSP) {
0280           break;
0281         }
0282       }
0283     }
0284     // We update the iterator in the Neighbour object
0285     // that mean that we have changed the middle space point
0286     // and the lower bound has moved accordingly
0287     otherSPCol.itr = min_itr;
0288 
0289     for (; min_itr != otherSPs.end(); ++min_itr) {
0290       const external_spacepoint_t* otherSP = *min_itr;
0291 
0292       if constexpr (isBottomCandidate) {
0293         deltaR = (rM - otherSP->radius());
0294 
0295         // if r-distance is too small, try next SP in bin
0296         if (deltaR < deltaRMinSP) {
0297           break;
0298         }
0299       } else {
0300         deltaR = (otherSP->radius() - rM);
0301 
0302         // if r-distance is too big, try next SP in bin
0303         if (deltaR > deltaRMaxSP) {
0304           break;
0305         }
0306       }
0307 
0308       if constexpr (isBottomCandidate) {
0309         deltaZ = (zM - otherSP->z());
0310       } else {
0311         deltaZ = (otherSP->z() - zM);
0312       }
0313 
0314       // the longitudinal impact parameter zOrigin is defined as (zM - rM *
0315       // cotTheta) where cotTheta is the ratio Z/R (forward angle) of space
0316       // point duplet but instead we calculate (zOrigin * deltaR) and multiply
0317       // collisionRegion by deltaR to avoid divisions
0318       const float zOriginTimesDeltaR = (zM * deltaR - rM * deltaZ);
0319       // check if duplet origin on z axis within collision region
0320       if (outsideRangeCheck(zOriginTimesDeltaR,
0321                             m_config.collisionRegionMin * deltaR,
0322                             m_config.collisionRegionMax * deltaR)) {
0323         continue;
0324       }
0325 
0326       // if interactionPointCut is false we apply z cuts before coordinate
0327       // transformation to avoid unnecessary calculations. If
0328       // interactionPointCut is true we apply the curvature cut first because it
0329       // is more frequent but requires the coordinate transformation
0330       if (!m_config.interactionPointCut) {
0331         // check if duplet cotTheta is within the region of interest
0332         // cotTheta is defined as (deltaZ / deltaR) but instead we multiply
0333         // cotThetaMax by deltaR to avoid division
0334         if (outsideRangeCheck(deltaZ, -m_config.cotThetaMax * deltaR,
0335                               m_config.cotThetaMax * deltaR)) {
0336           continue;
0337         }
0338         // if z-distance between SPs is within max and min values
0339         if (outsideRangeCheck(deltaZ, -m_config.deltaZMax,
0340                               m_config.deltaZMax)) {
0341           continue;
0342         }
0343 
0344         // transform SP coordinates to the u-v reference frame
0345         const float deltaX = otherSP->x() - xM;
0346         const float deltaY = otherSP->y() - yM;
0347 
0348         const float xNewFrame = deltaX * cosPhiM + deltaY * sinPhiM;
0349         const float yNewFrame = deltaY * cosPhiM - deltaX * sinPhiM;
0350 
0351         const float deltaR2 = (deltaX * deltaX + deltaY * deltaY);
0352         const float iDeltaR2 = 1 / deltaR2;
0353 
0354         const float uT = xNewFrame * iDeltaR2;
0355         const float vT = yNewFrame * iDeltaR2;
0356 
0357         const float iDeltaR = std::sqrt(iDeltaR2);
0358         const float cotTheta = deltaZ * iDeltaR;
0359 
0360         const float Er =
0361             ((varianceZM + otherSP->varianceZ()) +
0362              (cotTheta * cotTheta) * (varianceRM + otherSP->varianceR())) *
0363             iDeltaR2;
0364 
0365         // fill output vectors
0366         linCircleVec.emplace_back(cotTheta, iDeltaR, Er, uT, vT, xNewFrame,
0367                                   yNewFrame);
0368 
0369         mutableData.setDeltaR(otherSP->index(),
0370                               std::sqrt(deltaR2 + (deltaZ * deltaZ)));
0371         outVec.push_back(otherSP);
0372         continue;
0373       }
0374 
0375       // transform SP coordinates to the u-v reference frame
0376       const float deltaX = otherSP->x() - xM;
0377       const float deltaY = otherSP->y() - yM;
0378 
0379       const float xNewFrame = deltaX * cosPhiM + deltaY * sinPhiM;
0380       const float yNewFrame = deltaY * cosPhiM - deltaX * sinPhiM;
0381 
0382       const float deltaR2 = deltaX * deltaX + deltaY * deltaY;
0383       const float iDeltaR2 = 1 / deltaR2;
0384 
0385       const float uT = xNewFrame * iDeltaR2;
0386       const float vT = yNewFrame * iDeltaR2;
0387 
0388       // We check the interaction point by evaluating the minimal distance
0389       // between the origin and the straight line connecting the two points in
0390       // the doublets. Using a geometric similarity, the Im is given by
0391       // yNewFrame * rM / deltaR <= m_config.impactMax
0392       // However, we make here an approximation of the impact parameter
0393       // which is valid under the assumption yNewFrame / xNewFrame is small
0394       // The correct computation would be:
0395       // yNewFrame * yNewFrame * rM * rM <= m_config.impactMax *
0396       // m_config.impactMax * deltaR2
0397       if (std::abs(rM * yNewFrame) <= impactMax * xNewFrame) {
0398         // check if duplet cotTheta is within the region of interest
0399         // cotTheta is defined as (deltaZ / deltaR) but instead we multiply
0400         // cotThetaMax by deltaR to avoid division
0401         if (outsideRangeCheck(deltaZ, -m_config.cotThetaMax * deltaR,
0402                               m_config.cotThetaMax * deltaR)) {
0403           continue;
0404         }
0405 
0406         const float iDeltaR = std::sqrt(iDeltaR2);
0407         const float cotTheta = deltaZ * iDeltaR;
0408 
0409         // discard bottom-middle dublets in a certain (r, eta) region according
0410         // to detector specific cuts
0411         if (!m_config.experimentCuts(mediumSP, *otherSP, cotTheta,
0412                                      isBottomCandidate)) {
0413           continue;
0414         }
0415 
0416         const float Er =
0417             ((varianceZM + otherSP->varianceZ()) +
0418              (cotTheta * cotTheta) * (varianceRM + otherSP->varianceR())) *
0419             iDeltaR2;
0420 
0421         // fill output vectors
0422         linCircleVec.emplace_back(cotTheta, iDeltaR, Er, uT, vT, xNewFrame,
0423                                   yNewFrame);
0424         mutableData.setDeltaR(otherSP->index(),
0425                               std::sqrt(deltaR2 + (deltaZ * deltaZ)));
0426         outVec.emplace_back(otherSP);
0427         continue;
0428       }
0429 
0430       // in the rotated frame the interaction point is positioned at x = -rM
0431       // and y ~= impactParam
0432       const float vIP = (yNewFrame > 0) ? -vIPAbs : vIPAbs;
0433 
0434       // we can obtain aCoef as the slope dv/du of the linear function,
0435       // estimated using du and dv between the two SP bCoef is obtained by
0436       // inserting aCoef into the linear equation
0437       const float aCoef = (vT - vIP) / (uT - uIP);
0438       const float bCoef = vIP - aCoef * uIP;
0439       // the distance of the straight line from the origin (radius of the
0440       // circle) is related to aCoef and bCoef by d^2 = bCoef^2 / (1 +
0441       // aCoef^2) = 1 / (radius^2) and we can apply the cut on the curvature
0442       if ((bCoef * bCoef) * options.minHelixDiameter2 > (1 + aCoef * aCoef)) {
0443         continue;
0444       }
0445 
0446       // check if duplet cotTheta is within the region of interest
0447       // cotTheta is defined as (deltaZ / deltaR) but instead we multiply
0448       // cotThetaMax by deltaR to avoid division
0449       if (outsideRangeCheck(deltaZ, -m_config.cotThetaMax * deltaR,
0450                             m_config.cotThetaMax * deltaR)) {
0451         continue;
0452       }
0453 
0454       const float iDeltaR = std::sqrt(iDeltaR2);
0455       const float cotTheta = deltaZ * iDeltaR;
0456 
0457       // discard bottom-middle dublets in a certain (r, eta) region according
0458       // to detector specific cuts
0459       if (!m_config.experimentCuts(mediumSP, *otherSP, cotTheta,
0460                                    isBottomCandidate)) {
0461         continue;
0462       }
0463 
0464       const float Er =
0465           ((varianceZM + otherSP->varianceZ()) +
0466            (cotTheta * cotTheta) * (varianceRM + otherSP->varianceR())) *
0467           iDeltaR2;
0468 
0469       // fill output vectors
0470       linCircleVec.emplace_back(cotTheta, iDeltaR, Er, uT, vT, xNewFrame,
0471                                 yNewFrame);
0472 
0473       mutableData.setDeltaR(otherSP->index(),
0474                             std::sqrt(deltaR2 + (deltaZ * deltaZ)));
0475       outVec.emplace_back(otherSP);
0476     }
0477   }
0478 }
0479 
0480 template <typename external_spacepoint_t, typename grid_t, typename platform_t>
0481 template <DetectorMeasurementInfo detailedMeasurement>
0482 inline void
0483 SeedFinder<external_spacepoint_t, grid_t, platform_t>::filterCandidates(
0484     const external_spacepoint_t& spM, const SeedFinderOptions& options,
0485     SeedFilterState& seedFilterState, SeedingState& state) const {
0486   const float rM = spM.radius();
0487   const float cosPhiM = spM.x() / rM;
0488   const float sinPhiM = spM.y() / rM;
0489   const float varianceRM = spM.varianceR();
0490   const float varianceZM = spM.varianceZ();
0491 
0492   std::size_t numTopSp = state.compatTopSP.size();
0493 
0494   // sort: make index vector
0495   std::vector<std::uint32_t> sortedBottoms(state.compatBottomSP.size());
0496   for (std::uint32_t i = 0; i < sortedBottoms.size(); ++i) {
0497     sortedBottoms[i] = i;
0498   }
0499   std::vector<std::uint32_t> sortedTops(state.linCircleTop.size());
0500   for (std::uint32_t i = 0; i < sortedTops.size(); ++i) {
0501     sortedTops[i] = i;
0502   }
0503 
0504   if constexpr (detailedMeasurement == DetectorMeasurementInfo::eDefault) {
0505     std::vector<float> cotThetaBottoms(state.compatBottomSP.size());
0506     for (std::uint32_t i = 0; i < sortedBottoms.size(); ++i) {
0507       cotThetaBottoms[i] = state.linCircleBottom[i].cotTheta;
0508     }
0509     std::ranges::sort(sortedBottoms, {}, [&](const std::uint32_t s) {
0510       return cotThetaBottoms[s];
0511     });
0512 
0513     std::vector<float> cotThetaTops(state.linCircleTop.size());
0514     for (std::uint32_t i = 0; i < sortedTops.size(); ++i) {
0515       cotThetaTops[i] = state.linCircleTop[i].cotTheta;
0516     }
0517     std::ranges::sort(sortedTops, {},
0518                       [&](const std::uint32_t s) { return cotThetaTops[s]; });
0519   }
0520 
0521   // Reserve enough space, in case current capacity is too little
0522   state.topSpVec.reserve(numTopSp);
0523   state.curvatures.reserve(numTopSp);
0524   state.impactParameters.reserve(numTopSp);
0525 
0526   std::size_t t0 = 0;
0527 
0528   // clear previous results and then loop on bottoms and tops
0529   state.candidatesCollector.clear();
0530 
0531   for (const std::size_t b : sortedBottoms) {
0532     // break if we reached the last top SP
0533     if (t0 == numTopSp) {
0534       break;
0535     }
0536 
0537     auto lb = state.linCircleBottom[b];
0538     float cotThetaB = lb.cotTheta;
0539     float Vb = lb.V;
0540     float Ub = lb.U;
0541     float ErB = lb.Er;
0542     float iDeltaRB = lb.iDeltaR;
0543 
0544     // 1+(cot^2(theta)) = 1/sin^2(theta)
0545     float iSinTheta2 = 1 + cotThetaB * cotThetaB;
0546     float sigmaSquaredPtDependent = iSinTheta2 * options.sigmapT2perRadius;
0547     // calculate max scattering for min momentum at the seed's theta angle
0548     // scaling scatteringAngle^2 by sin^2(theta) to convert pT^2 to p^2
0549     // accurate would be taking 1/atan(thetaBottom)-1/atan(thetaTop) <
0550     // scattering
0551     // but to avoid trig functions we approximate cot by scaling by
0552     // 1/sin^4(theta)
0553     // resolving with pT to p scaling --> only divide by sin^2(theta)
0554     // max approximation error for allowed scattering angles of 0.04 rad at
0555     // eta=infinity: ~8.5%
0556     float scatteringInRegion2 = options.multipleScattering2 * iSinTheta2;
0557 
0558     float sinTheta = 1 / std::sqrt(iSinTheta2);
0559     float cosTheta = cotThetaB * sinTheta;
0560 
0561     // clear all vectors used in each inner for loop
0562     state.topSpVec.clear();
0563     state.curvatures.clear();
0564     state.impactParameters.clear();
0565 
0566     // coordinate transformation and checks for middle spacepoint
0567     // x and y terms for the rotation from UV to XY plane
0568     float rotationTermsUVtoXY[2] = {0, 0};
0569     if constexpr (detailedMeasurement == DetectorMeasurementInfo::eDetailed) {
0570       rotationTermsUVtoXY[0] = cosPhiM * sinTheta;
0571       rotationTermsUVtoXY[1] = sinPhiM * sinTheta;
0572     }
0573 
0574     // minimum number of compatible top SPs to trigger the filter for a certain
0575     // middle bottom pair if seedConfirmation is false we always ask for at
0576     // least one compatible top to trigger the filter
0577     std::size_t minCompatibleTopSPs = 2;
0578     if (!m_config.seedConfirmation ||
0579         state.compatBottomSP[b]->radius() > seedFilterState.rMaxSeedConf) {
0580       minCompatibleTopSPs = 1;
0581     }
0582     if (m_config.seedConfirmation &&
0583         state.candidatesCollector.nHighQualityCandidates()) {
0584       minCompatibleTopSPs++;
0585     }
0586 
0587     for (std::size_t index_t = t0; index_t < numTopSp; index_t++) {
0588       const std::size_t t = sortedTops[index_t];
0589 
0590       auto lt = state.linCircleTop[t];
0591 
0592       float cotThetaT = lt.cotTheta;
0593       float rMxy = 0;
0594       float ub = 0;
0595       float vb = 0;
0596       float ut = 0;
0597       float vt = 0;
0598       double rMTransf[3];
0599       float xB = 0;
0600       float yB = 0;
0601       float xT = 0;
0602       float yT = 0;
0603       float iDeltaRB2 = 0;
0604       float iDeltaRT2 = 0;
0605 
0606       if constexpr (detailedMeasurement == DetectorMeasurementInfo::eDetailed) {
0607         // protects against division by 0
0608         float dU = lt.U - Ub;
0609         if (dU == 0) {
0610           continue;
0611         }
0612         // A and B are evaluated as a function of the circumference parameters
0613         // x_0 and y_0
0614         float A0 = (lt.V - Vb) / dU;
0615 
0616         float zPositionMiddle = cosTheta * std::sqrt(1 + A0 * A0);
0617 
0618         // position of Middle SP converted from UV to XY assuming cotTheta
0619         // evaluated from the Bottom and Middle SPs double
0620         double positionMiddle[3] = {
0621             rotationTermsUVtoXY[0] - rotationTermsUVtoXY[1] * A0,
0622             rotationTermsUVtoXY[0] * A0 + rotationTermsUVtoXY[1],
0623             zPositionMiddle};
0624 
0625         if (!xyzCoordinateCheck(m_config, spM, positionMiddle, rMTransf)) {
0626           continue;
0627         }
0628 
0629         // coordinate transformation and checks for bottom spacepoint
0630         float B0 = 2 * (Vb - A0 * Ub);
0631         float Cb = 1 - B0 * lb.y;
0632         float Sb = A0 + B0 * lb.x;
0633         double positionBottom[3] = {
0634             rotationTermsUVtoXY[0] * Cb - rotationTermsUVtoXY[1] * Sb,
0635             rotationTermsUVtoXY[0] * Sb + rotationTermsUVtoXY[1] * Cb,
0636             zPositionMiddle};
0637 
0638         auto spB = state.compatBottomSP[b];
0639         double rBTransf[3];
0640         if (!xyzCoordinateCheck(m_config, *spB, positionBottom, rBTransf)) {
0641           continue;
0642         }
0643 
0644         // coordinate transformation and checks for top spacepoint
0645         float Ct = 1 - B0 * lt.y;
0646         float St = A0 + B0 * lt.x;
0647         double positionTop[3] = {
0648             rotationTermsUVtoXY[0] * Ct - rotationTermsUVtoXY[1] * St,
0649             rotationTermsUVtoXY[0] * St + rotationTermsUVtoXY[1] * Ct,
0650             zPositionMiddle};
0651 
0652         auto spT = state.compatTopSP[t];
0653         double rTTransf[3];
0654         if (!xyzCoordinateCheck(m_config, *spT, positionTop, rTTransf)) {
0655           continue;
0656         }
0657 
0658         // bottom and top coordinates in the spM reference frame
0659         xB = rBTransf[0] - rMTransf[0];
0660         yB = rBTransf[1] - rMTransf[1];
0661         float zB = rBTransf[2] - rMTransf[2];
0662         xT = rTTransf[0] - rMTransf[0];
0663         yT = rTTransf[1] - rMTransf[1];
0664         float zT = rTTransf[2] - rMTransf[2];
0665 
0666         iDeltaRB2 = 1 / (xB * xB + yB * yB);
0667         iDeltaRT2 = 1 / (xT * xT + yT * yT);
0668 
0669         cotThetaB = -zB * std::sqrt(iDeltaRB2);
0670         cotThetaT = zT * std::sqrt(iDeltaRT2);
0671       }
0672 
0673       // use geometric average
0674       float cotThetaAvg2 = cotThetaB * cotThetaT;
0675       if constexpr (detailedMeasurement == DetectorMeasurementInfo::eDetailed) {
0676         // use arithmetic average
0677         float averageCotTheta = 0.5f * (cotThetaB + cotThetaT);
0678         cotThetaAvg2 = averageCotTheta * averageCotTheta;
0679       }
0680 
0681       // add errors of spB-spM and spM-spT pairs and add the correlation term
0682       // for errors on spM
0683       float error2 =
0684           lt.Er + ErB +
0685           2 * (cotThetaAvg2 * varianceRM + varianceZM) * iDeltaRB * lt.iDeltaR;
0686 
0687       float deltaCotTheta = cotThetaB - cotThetaT;
0688       float deltaCotTheta2 = deltaCotTheta * deltaCotTheta;
0689 
0690       // Apply a cut on the compatibility between the r-z slope of the two
0691       // seed segments. This is done by comparing the squared difference
0692       // between slopes, and comparing to the squared uncertainty in this
0693       // difference - we keep a seed if the difference is compatible within
0694       // the assumed uncertainties. The uncertainties get contribution from
0695       // the  space-point-related squared error (error2) and a scattering term
0696       // calculated assuming the minimum pt we expect to reconstruct
0697       // (scatteringInRegion2). This assumes gaussian error propagation which
0698       // allows just adding the two errors if they are uncorrelated (which is
0699       // fair for scattering and measurement uncertainties)
0700       if (deltaCotTheta2 > error2 + scatteringInRegion2) {
0701         // skip top SPs based on cotTheta sorting when producing triplets
0702         if constexpr (detailedMeasurement ==
0703                       DetectorMeasurementInfo::eDetailed) {
0704           continue;
0705         }
0706         // break if cotTheta from bottom SP < cotTheta from top SP because
0707         // the SP are sorted by cotTheta
0708         if (cotThetaB - cotThetaT < 0) {
0709           break;
0710         }
0711         t0 = index_t + 1;
0712         continue;
0713       }
0714 
0715       if constexpr (detailedMeasurement == DetectorMeasurementInfo::eDetailed) {
0716         rMxy = std::sqrt(rMTransf[0] * rMTransf[0] + rMTransf[1] * rMTransf[1]);
0717         float irMxy = 1 / rMxy;
0718         float Ax = rMTransf[0] * irMxy;
0719         float Ay = rMTransf[1] * irMxy;
0720 
0721         ub = (xB * Ax + yB * Ay) * iDeltaRB2;
0722         vb = (yB * Ax - xB * Ay) * iDeltaRB2;
0723         ut = (xT * Ax + yT * Ay) * iDeltaRT2;
0724         vt = (yT * Ax - xT * Ay) * iDeltaRT2;
0725       }
0726 
0727       float dU = 0;
0728       float A = 0;
0729       float S2 = 0;
0730       float B = 0;
0731       float B2 = 0;
0732 
0733       if constexpr (detailedMeasurement == DetectorMeasurementInfo::eDetailed) {
0734         dU = ut - ub;
0735         // protects against division by 0
0736         if (dU == 0) {
0737           continue;
0738         }
0739         A = (vt - vb) / dU;
0740         S2 = 1 + A * A;
0741         B = vb - A * ub;
0742         B2 = B * B;
0743       } else {
0744         dU = lt.U - Ub;
0745         // protects against division by 0
0746         if (dU == 0) {
0747           continue;
0748         }
0749         // A and B are evaluated as a function of the circumference parameters
0750         // x_0 and y_0
0751         A = (lt.V - Vb) / dU;
0752         S2 = 1 + A * A;
0753         B = Vb - A * Ub;
0754         B2 = B * B;
0755       }
0756 
0757       // sqrt(S2)/B = 2 * helixradius
0758       // calculated radius must not be smaller than minimum radius
0759       if (S2 < B2 * options.minHelixDiameter2) {
0760         continue;
0761       }
0762 
0763       // refinement of the cut on the compatibility between the r-z slope of
0764       // the two seed segments using a scattering term scaled by the actual
0765       // measured pT (p2scatterSigma)
0766       float iHelixDiameter2 = B2 / S2;
0767       // convert p(T) to p scaling by sin^2(theta) AND scale by 1/sin^4(theta)
0768       // from rad to deltaCotTheta
0769       float p2scatterSigma = iHelixDiameter2 * sigmaSquaredPtDependent;
0770       // if deltaTheta larger than allowed scattering for calculated pT, skip
0771       if (deltaCotTheta2 > error2 + p2scatterSigma) {
0772         if constexpr (detailedMeasurement ==
0773                       DetectorMeasurementInfo::eDetailed) {
0774           continue;
0775         }
0776         if (cotThetaB - cotThetaT < 0) {
0777           break;
0778         }
0779         t0 = index_t;
0780         continue;
0781       }
0782       // A and B allow calculation of impact params in U/V plane with linear
0783       // function
0784       // (in contrast to having to solve a quadratic function in x/y plane)
0785       float Im = 0;
0786       if constexpr (detailedMeasurement == DetectorMeasurementInfo::eDetailed) {
0787         Im = std::abs((A - B * rMxy) * rMxy);
0788       } else {
0789         Im = std::abs((A - B * rM) * rM);
0790       }
0791 
0792       if (Im > m_config.impactMax) {
0793         continue;
0794       }
0795 
0796       state.topSpVec.push_back(state.compatTopSP[t]);
0797       // inverse diameter is signed depending on if the curvature is
0798       // positive/negative in phi
0799       state.curvatures.push_back(B / std::sqrt(S2));
0800       state.impactParameters.push_back(Im);
0801     }  // loop on tops
0802 
0803     // continue if number of top SPs is smaller than minimum required for filter
0804     if (state.topSpVec.size() < minCompatibleTopSPs) {
0805       continue;
0806     }
0807 
0808     seedFilterState.zOrigin = spM.z() - rM * lb.cotTheta;
0809 
0810     m_config.seedFilter->filterSeeds_2SpFixed(
0811         state.spacePointMutableData, *state.compatBottomSP[b], spM,
0812         state.topSpVec, state.curvatures, state.impactParameters,
0813         seedFilterState, state.candidatesCollector);
0814   }  // loop on bottoms
0815 }
0816 
0817 template <typename external_spacepoint_t, typename grid_t, typename platform_t>
0818 std::pair<float, float> SeedFinder<external_spacepoint_t, grid_t, platform_t>::
0819     retrieveRadiusRangeForMiddle(const external_spacepoint_t& spM,
0820                                  const Range1D<float>& rMiddleSPRange) const {
0821   if (m_config.useVariableMiddleSPRange) {
0822     return {rMiddleSPRange.min(), rMiddleSPRange.max()};
0823   }
0824   if (!m_config.rRangeMiddleSP.empty()) {
0825     /// get zBin position of the middle SP
0826     auto pVal = std::lower_bound(m_config.zBinEdges.begin(),
0827                                  m_config.zBinEdges.end(), spM.z());
0828     int zBin = std::distance(m_config.zBinEdges.begin(), pVal);
0829     /// protects against zM at the limit of zBinEdges
0830     zBin == 0 ? zBin : --zBin;
0831     return {m_config.rRangeMiddleSP[zBin][0], m_config.rRangeMiddleSP[zBin][1]};
0832   }
0833   return {m_config.rMinMiddle, m_config.rMaxMiddle};
0834 }
0835 
0836 }  // namespace Acts