Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-09-01 08:38:57

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