Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-07-09 07:49:44

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 constexpr (isBottomCandidate) {
0412           if (!m_config.experimentCuts(otherSP->radius(), cotTheta)) {
0413             continue;
0414           }
0415         }
0416 
0417         const float Er =
0418             ((varianceZM + otherSP->varianceZ()) +
0419              (cotTheta * cotTheta) * (varianceRM + otherSP->varianceR())) *
0420             iDeltaR2;
0421 
0422         // fill output vectors
0423         linCircleVec.emplace_back(cotTheta, iDeltaR, Er, uT, vT, xNewFrame,
0424                                   yNewFrame);
0425         mutableData.setDeltaR(otherSP->index(),
0426                               std::sqrt(deltaR2 + (deltaZ * deltaZ)));
0427         outVec.emplace_back(otherSP);
0428         continue;
0429       }
0430 
0431       // in the rotated frame the interaction point is positioned at x = -rM
0432       // and y ~= impactParam
0433       const float vIP = (yNewFrame > 0.) ? -vIPAbs : vIPAbs;
0434 
0435       // we can obtain aCoef as the slope dv/du of the linear function,
0436       // estimated using du and dv between the two SP bCoef is obtained by
0437       // inserting aCoef into the linear equation
0438       const float aCoef = (vT - vIP) / (uT - uIP);
0439       const float bCoef = vIP - aCoef * uIP;
0440       // the distance of the straight line from the origin (radius of the
0441       // circle) is related to aCoef and bCoef by d^2 = bCoef^2 / (1 +
0442       // aCoef^2) = 1 / (radius^2) and we can apply the cut on the curvature
0443       if ((bCoef * bCoef) * options.minHelixDiameter2 > (1 + aCoef * aCoef)) {
0444         continue;
0445       }
0446 
0447       // check if duplet cotTheta is within the region of interest
0448       // cotTheta is defined as (deltaZ / deltaR) but instead we multiply
0449       // cotThetaMax by deltaR to avoid division
0450       if (outsideRangeCheck(deltaZ, -m_config.cotThetaMax * deltaR,
0451                             m_config.cotThetaMax * deltaR)) {
0452         continue;
0453       }
0454 
0455       const float iDeltaR = std::sqrt(iDeltaR2);
0456       const float cotTheta = deltaZ * iDeltaR;
0457 
0458       // discard bottom-middle dublets in a certain (r, eta) region according
0459       // to detector specific cuts
0460       if constexpr (isBottomCandidate) {
0461         if (!m_config.experimentCuts(otherSP->radius(), cotTheta)) {
0462           continue;
0463         }
0464       }
0465 
0466       const float Er =
0467           ((varianceZM + otherSP->varianceZ()) +
0468            (cotTheta * cotTheta) * (varianceRM + otherSP->varianceR())) *
0469           iDeltaR2;
0470 
0471       // fill output vectors
0472       linCircleVec.emplace_back(cotTheta, iDeltaR, Er, uT, vT, xNewFrame,
0473                                 yNewFrame);
0474 
0475       mutableData.setDeltaR(otherSP->index(),
0476                             std::sqrt(deltaR2 + (deltaZ * deltaZ)));
0477       outVec.emplace_back(otherSP);
0478     }
0479   }
0480 }
0481 
0482 template <typename external_spacepoint_t, typename grid_t, typename platform_t>
0483 template <DetectorMeasurementInfo detailedMeasurement>
0484 inline void
0485 SeedFinder<external_spacepoint_t, grid_t, platform_t>::filterCandidates(
0486     const external_spacepoint_t& spM, const SeedFinderOptions& options,
0487     SeedFilterState& seedFilterState, SeedingState& state) const {
0488   const float rM = spM.radius();
0489   const float cosPhiM = spM.x() / rM;
0490   const float sinPhiM = spM.y() / rM;
0491   const float varianceRM = spM.varianceR();
0492   const float varianceZM = spM.varianceZ();
0493 
0494   std::size_t numTopSp = state.compatTopSP.size();
0495 
0496   // sort: make index vector
0497   std::vector<std::uint32_t> sortedBottoms(state.compatBottomSP.size());
0498   for (std::uint32_t i = 0; i < sortedBottoms.size(); ++i) {
0499     sortedBottoms[i] = i;
0500   }
0501   std::vector<std::uint32_t> sortedTops(state.linCircleTop.size());
0502   for (std::uint32_t i = 0; i < sortedTops.size(); ++i) {
0503     sortedTops[i] = i;
0504   }
0505 
0506   if constexpr (detailedMeasurement == DetectorMeasurementInfo::eDefault) {
0507     std::vector<float> cotThetaBottoms(state.compatBottomSP.size());
0508     for (std::uint32_t i = 0; i < sortedBottoms.size(); ++i) {
0509       cotThetaBottoms[i] = state.linCircleBottom[i].cotTheta;
0510     }
0511     std::ranges::sort(sortedBottoms, {}, [&](const std::uint32_t s) {
0512       return cotThetaBottoms[s];
0513     });
0514 
0515     std::vector<float> cotThetaTops(state.linCircleTop.size());
0516     for (std::uint32_t i = 0; i < sortedTops.size(); ++i) {
0517       cotThetaTops[i] = state.linCircleTop[i].cotTheta;
0518     }
0519     std::ranges::sort(sortedTops, {},
0520                       [&](const std::uint32_t s) { return cotThetaTops[s]; });
0521   }
0522 
0523   // Reserve enough space, in case current capacity is too little
0524   state.topSpVec.reserve(numTopSp);
0525   state.curvatures.reserve(numTopSp);
0526   state.impactParameters.reserve(numTopSp);
0527 
0528   std::size_t t0 = 0;
0529 
0530   // clear previous results and then loop on bottoms and tops
0531   state.candidatesCollector.clear();
0532 
0533   for (const std::size_t b : sortedBottoms) {
0534     // break if we reached the last top SP
0535     if (t0 == numTopSp) {
0536       break;
0537     }
0538 
0539     auto lb = state.linCircleBottom[b];
0540     float cotThetaB = lb.cotTheta;
0541     float Vb = lb.V;
0542     float Ub = lb.U;
0543     float ErB = lb.Er;
0544     float iDeltaRB = lb.iDeltaR;
0545 
0546     // 1+(cot^2(theta)) = 1/sin^2(theta)
0547     float iSinTheta2 = (1. + cotThetaB * cotThetaB);
0548     float sigmaSquaredPtDependent = iSinTheta2 * options.sigmapT2perRadius;
0549     // calculate max scattering for min momentum at the seed's theta angle
0550     // scaling scatteringAngle^2 by sin^2(theta) to convert pT^2 to p^2
0551     // accurate would be taking 1/atan(thetaBottom)-1/atan(thetaTop) <
0552     // scattering
0553     // but to avoid trig functions we approximate cot by scaling by
0554     // 1/sin^4(theta)
0555     // resolving with pT to p scaling --> only divide by sin^2(theta)
0556     // max approximation error for allowed scattering angles of 0.04 rad at
0557     // eta=infinity: ~8.5%
0558     float scatteringInRegion2 = options.multipleScattering2 * iSinTheta2;
0559 
0560     float sinTheta = 1 / std::sqrt(iSinTheta2);
0561     float cosTheta = cotThetaB * sinTheta;
0562 
0563     // clear all vectors used in each inner for loop
0564     state.topSpVec.clear();
0565     state.curvatures.clear();
0566     state.impactParameters.clear();
0567 
0568     // coordinate transformation and checks for middle spacepoint
0569     // x and y terms for the rotation from UV to XY plane
0570     float rotationTermsUVtoXY[2] = {0, 0};
0571     if constexpr (detailedMeasurement == DetectorMeasurementInfo::eDetailed) {
0572       rotationTermsUVtoXY[0] = cosPhiM * sinTheta;
0573       rotationTermsUVtoXY[1] = sinPhiM * sinTheta;
0574     }
0575 
0576     // minimum number of compatible top SPs to trigger the filter for a certain
0577     // middle bottom pair if seedConfirmation is false we always ask for at
0578     // least one compatible top to trigger the filter
0579     std::size_t minCompatibleTopSPs = 2;
0580     if (!m_config.seedConfirmation ||
0581         state.compatBottomSP[b]->radius() > seedFilterState.rMaxSeedConf) {
0582       minCompatibleTopSPs = 1;
0583     }
0584     if (m_config.seedConfirmation &&
0585         state.candidatesCollector.nHighQualityCandidates()) {
0586       minCompatibleTopSPs++;
0587     }
0588 
0589     for (std::size_t index_t = t0; index_t < numTopSp; index_t++) {
0590       const std::size_t t = sortedTops[index_t];
0591 
0592       auto lt = state.linCircleTop[t];
0593 
0594       float cotThetaT = lt.cotTheta;
0595       float rMxy = 0.;
0596       float ub = 0.;
0597       float vb = 0.;
0598       float ut = 0.;
0599       float vt = 0.;
0600       double rMTransf[3];
0601       float xB = 0.;
0602       float yB = 0.;
0603       float xT = 0.;
0604       float yT = 0.;
0605       float iDeltaRB2 = 0.;
0606       float iDeltaRT2 = 0.;
0607 
0608       if constexpr (detailedMeasurement == DetectorMeasurementInfo::eDetailed) {
0609         // protects against division by 0
0610         float dU = lt.U - Ub;
0611         if (dU == 0.) {
0612           continue;
0613         }
0614         // A and B are evaluated as a function of the circumference parameters
0615         // x_0 and y_0
0616         float A0 = (lt.V - Vb) / dU;
0617 
0618         float zPositionMiddle = cosTheta * std::sqrt(1 + A0 * A0);
0619 
0620         // position of Middle SP converted from UV to XY assuming cotTheta
0621         // evaluated from the Bottom and Middle SPs double
0622         double positionMiddle[3] = {
0623             rotationTermsUVtoXY[0] - rotationTermsUVtoXY[1] * A0,
0624             rotationTermsUVtoXY[0] * A0 + rotationTermsUVtoXY[1],
0625             zPositionMiddle};
0626 
0627         if (!xyzCoordinateCheck(m_config, spM, positionMiddle, rMTransf)) {
0628           continue;
0629         }
0630 
0631         // coordinate transformation and checks for bottom spacepoint
0632         float B0 = 2. * (Vb - A0 * Ub);
0633         float Cb = 1. - B0 * lb.y;
0634         float Sb = A0 + B0 * lb.x;
0635         double positionBottom[3] = {
0636             rotationTermsUVtoXY[0] * Cb - rotationTermsUVtoXY[1] * Sb,
0637             rotationTermsUVtoXY[0] * Sb + rotationTermsUVtoXY[1] * Cb,
0638             zPositionMiddle};
0639 
0640         auto spB = state.compatBottomSP[b];
0641         double rBTransf[3];
0642         if (!xyzCoordinateCheck(m_config, *spB, positionBottom, rBTransf)) {
0643           continue;
0644         }
0645 
0646         // coordinate transformation and checks for top spacepoint
0647         float Ct = 1. - B0 * lt.y;
0648         float St = A0 + B0 * lt.x;
0649         double positionTop[3] = {
0650             rotationTermsUVtoXY[0] * Ct - rotationTermsUVtoXY[1] * St,
0651             rotationTermsUVtoXY[0] * St + rotationTermsUVtoXY[1] * Ct,
0652             zPositionMiddle};
0653 
0654         auto spT = state.compatTopSP[t];
0655         double rTTransf[3];
0656         if (!xyzCoordinateCheck(m_config, *spT, positionTop, rTTransf)) {
0657           continue;
0658         }
0659 
0660         // bottom and top coordinates in the spM reference frame
0661         xB = rBTransf[0] - rMTransf[0];
0662         yB = rBTransf[1] - rMTransf[1];
0663         float zB = rBTransf[2] - rMTransf[2];
0664         xT = rTTransf[0] - rMTransf[0];
0665         yT = rTTransf[1] - rMTransf[1];
0666         float zT = rTTransf[2] - rMTransf[2];
0667 
0668         iDeltaRB2 = 1. / (xB * xB + yB * yB);
0669         iDeltaRT2 = 1. / (xT * xT + yT * yT);
0670 
0671         cotThetaB = -zB * std::sqrt(iDeltaRB2);
0672         cotThetaT = zT * std::sqrt(iDeltaRT2);
0673       }
0674 
0675       // use geometric average
0676       float cotThetaAvg2 = cotThetaB * cotThetaT;
0677       if constexpr (detailedMeasurement == DetectorMeasurementInfo::eDetailed) {
0678         // use arithmetic average
0679         float averageCotTheta = 0.5 * (cotThetaB + cotThetaT);
0680         cotThetaAvg2 = averageCotTheta * averageCotTheta;
0681       } else if (cotThetaAvg2 <= 0) {
0682         continue;
0683       }
0684 
0685       // add errors of spB-spM and spM-spT pairs and add the correlation term
0686       // for errors on spM
0687       float error2 =
0688           lt.Er + ErB +
0689           2 * (cotThetaAvg2 * varianceRM + varianceZM) * iDeltaRB * lt.iDeltaR;
0690 
0691       float deltaCotTheta = cotThetaB - cotThetaT;
0692       float deltaCotTheta2 = deltaCotTheta * deltaCotTheta;
0693 
0694       // Apply a cut on the compatibility between the r-z slope of the two
0695       // seed segments. This is done by comparing the squared difference
0696       // between slopes, and comparing to the squared uncertainty in this
0697       // difference - we keep a seed if the difference is compatible within
0698       // the assumed uncertainties. The uncertainties get contribution from
0699       // the  space-point-related squared error (error2) and a scattering term
0700       // calculated assuming the minimum pt we expect to reconstruct
0701       // (scatteringInRegion2). This assumes gaussian error propagation which
0702       // allows just adding the two errors if they are uncorrelated (which is
0703       // fair for scattering and measurement uncertainties)
0704       if (deltaCotTheta2 > (error2 + scatteringInRegion2)) {
0705         // skip top SPs based on cotTheta sorting when producing triplets
0706         if constexpr (detailedMeasurement ==
0707                       DetectorMeasurementInfo::eDetailed) {
0708           continue;
0709         }
0710         // break if cotTheta from bottom SP < cotTheta from top SP because
0711         // the SP are sorted by cotTheta
0712         if (cotThetaB - cotThetaT < 0) {
0713           break;
0714         }
0715         t0 = index_t + 1;
0716         continue;
0717       }
0718 
0719       if constexpr (detailedMeasurement == DetectorMeasurementInfo::eDetailed) {
0720         rMxy = std::sqrt(rMTransf[0] * rMTransf[0] + rMTransf[1] * rMTransf[1]);
0721         float irMxy = 1 / rMxy;
0722         float Ax = rMTransf[0] * irMxy;
0723         float Ay = rMTransf[1] * irMxy;
0724 
0725         ub = (xB * Ax + yB * Ay) * iDeltaRB2;
0726         vb = (yB * Ax - xB * Ay) * iDeltaRB2;
0727         ut = (xT * Ax + yT * Ay) * iDeltaRT2;
0728         vt = (yT * Ax - xT * Ay) * iDeltaRT2;
0729       }
0730 
0731       float dU = 0;
0732       float A = 0;
0733       float S2 = 0;
0734       float B = 0;
0735       float B2 = 0;
0736 
0737       if constexpr (detailedMeasurement == DetectorMeasurementInfo::eDetailed) {
0738         dU = ut - ub;
0739         // protects against division by 0
0740         if (dU == 0.) {
0741           continue;
0742         }
0743         A = (vt - vb) / dU;
0744         S2 = 1. + A * A;
0745         B = vb - A * ub;
0746         B2 = B * B;
0747       } else {
0748         dU = lt.U - Ub;
0749         // protects against division by 0
0750         if (dU == 0.) {
0751           continue;
0752         }
0753         // A and B are evaluated as a function of the circumference parameters
0754         // x_0 and y_0
0755         A = (lt.V - Vb) / dU;
0756         S2 = 1. + A * A;
0757         B = Vb - A * Ub;
0758         B2 = B * B;
0759       }
0760 
0761       // sqrt(S2)/B = 2 * helixradius
0762       // calculated radius must not be smaller than minimum radius
0763       if (S2 < B2 * options.minHelixDiameter2) {
0764         continue;
0765       }
0766 
0767       // refinement of the cut on the compatibility between the r-z slope of
0768       // the two seed segments using a scattering term scaled by the actual
0769       // measured pT (p2scatterSigma)
0770       float iHelixDiameter2 = B2 / S2;
0771       // convert p(T) to p scaling by sin^2(theta) AND scale by 1/sin^4(theta)
0772       // from rad to deltaCotTheta
0773       float p2scatterSigma = iHelixDiameter2 * sigmaSquaredPtDependent;
0774       if (!std::isinf(m_config.maxPtScattering)) {
0775         // if pT > maxPtScattering, calculate allowed scattering angle using
0776         // maxPtScattering instead of pt.
0777         // To avoid 0-divison the pT check is skipped in case of B2==0, and
0778         // p2scatterSigma is calculated directly from maxPtScattering
0779         if (B2 == 0 || options.pTPerHelixRadius * std::sqrt(S2 / B2) >
0780                            2. * m_config.maxPtScattering) {
0781           float pTscatterSigma =
0782               (m_config.highland / m_config.maxPtScattering) *
0783               m_config.sigmaScattering;
0784           p2scatterSigma = pTscatterSigma * pTscatterSigma * iSinTheta2;
0785         }
0786       }
0787 
0788       // if deltaTheta larger than allowed scattering for calculated pT, skip
0789       if (deltaCotTheta2 > (error2 + p2scatterSigma)) {
0790         if constexpr (detailedMeasurement ==
0791                       DetectorMeasurementInfo::eDetailed) {
0792           continue;
0793         }
0794         if (cotThetaB - cotThetaT < 0) {
0795           break;
0796         }
0797         t0 = index_t;
0798         continue;
0799       }
0800       // A and B allow calculation of impact params in U/V plane with linear
0801       // function
0802       // (in contrast to having to solve a quadratic function in x/y plane)
0803       float Im = 0;
0804       if constexpr (detailedMeasurement == DetectorMeasurementInfo::eDetailed) {
0805         Im = std::abs((A - B * rMxy) * rMxy);
0806       } else {
0807         Im = std::abs((A - B * rM) * rM);
0808       }
0809 
0810       if (Im > m_config.impactMax) {
0811         continue;
0812       }
0813 
0814       state.topSpVec.push_back(state.compatTopSP[t]);
0815       // inverse diameter is signed depending on if the curvature is
0816       // positive/negative in phi
0817       state.curvatures.push_back(B / std::sqrt(S2));
0818       state.impactParameters.push_back(Im);
0819     }  // loop on tops
0820 
0821     // continue if number of top SPs is smaller than minimum required for filter
0822     if (state.topSpVec.size() < minCompatibleTopSPs) {
0823       continue;
0824     }
0825 
0826     seedFilterState.zOrigin = spM.z() - rM * lb.cotTheta;
0827 
0828     m_config.seedFilter->filterSeeds_2SpFixed(
0829         state.spacePointMutableData, *state.compatBottomSP[b], spM,
0830         state.topSpVec, state.curvatures, state.impactParameters,
0831         seedFilterState, state.candidatesCollector);
0832   }  // loop on bottoms
0833 }
0834 
0835 template <typename external_spacepoint_t, typename grid_t, typename platform_t>
0836 std::pair<float, float> SeedFinder<external_spacepoint_t, grid_t, platform_t>::
0837     retrieveRadiusRangeForMiddle(const external_spacepoint_t& spM,
0838                                  const Range1D<float>& rMiddleSPRange) const {
0839   if (m_config.useVariableMiddleSPRange) {
0840     return {rMiddleSPRange.min(), rMiddleSPRange.max()};
0841   }
0842   if (!m_config.rRangeMiddleSP.empty()) {
0843     /// get zBin position of the middle SP
0844     auto pVal = std::lower_bound(m_config.zBinEdges.begin(),
0845                                  m_config.zBinEdges.end(), spM.z());
0846     int zBin = std::distance(m_config.zBinEdges.begin(), pVal);
0847     /// protects against zM at the limit of zBinEdges
0848     zBin == 0 ? zBin : --zBin;
0849     return {m_config.rRangeMiddleSP[zBin][0], m_config.rRangeMiddleSP[zBin][1]};
0850   }
0851   return {m_config.rMinMiddle, m_config.rMaxMiddle};
0852 }
0853 
0854 }  // namespace Acts