Back to home page

EIC code displayed by LXR

 
 

    


Warning, file /acts/Core/include/Acts/Seeding/SeedFinder.ipp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

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_space_point_t, typename grid_t, typename platform_t>
0019 SeedFinder<external_space_point_t, grid_t, platform_t>::SeedFinder(
0020     const SeedFinderConfig<external_space_point_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_space_point_t, typename grid_t, typename platform_t>
0038 template <typename container_t, GridBinCollection sp_range_t>
0039   requires CollectionStoresSeedsTo<container_t, external_space_point_t, 3ul>
0040 void SeedFinder<external_space_point_t, grid_t, platform_t>::
0041     createSeedsForGroup(const SeedFinderOptions& options, SeedingState& state,
0042                         const grid_t& grid, container_t& outputCollection,
0043                         const sp_range_t& bottomSPsIdx,
0044                         const std::size_t middleSPsIdx,
0045                         const sp_range_t& topSPsIdx,
0046                         const Range1D<float>& rMiddleSPRange) const {
0047   // This is used for seed filtering later
0048   const std::size_t max_num_seeds_per_spm =
0049       m_config.seedFilter->getSeedFilterConfig().maxSeedsPerSpMConf;
0050   const std::size_t max_num_quality_seeds_per_spm =
0051       m_config.seedFilter->getSeedFilterConfig().maxQualitySeedsPerSpMConf;
0052 
0053   state.candidatesCollector.setMaxElements(max_num_seeds_per_spm,
0054                                            max_num_quality_seeds_per_spm);
0055 
0056   // If there are no bottom or top bins, just return and waste no time
0057   if (bottomSPsIdx.size() == 0 || topSPsIdx.size() == 0) {
0058     return;
0059   }
0060 
0061   // Get the middle space point candidates
0062   const std::vector<const external_space_point_t*>& middleSPs =
0063       grid.at(middleSPsIdx);
0064   // Return if somehow there are no middle sp candidates
0065   if (middleSPs.empty()) {
0066     return;
0067   }
0068 
0069   // neighbours
0070   // clear previous results
0071   state.bottomNeighbours.clear();
0072   state.topNeighbours.clear();
0073 
0074   // Fill
0075   // bottoms
0076   for (const std::size_t idx : bottomSPsIdx) {
0077     // Only add an entry if the bin has entries
0078     if (grid.at(idx).size() == 0) {
0079       continue;
0080     }
0081     state.bottomNeighbours.emplace_back(
0082         grid, idx, middleSPs.front()->radius() - m_config.deltaRMaxBottomSP);
0083   }
0084   // if no bottom candidates, then no need to proceed
0085   if (state.bottomNeighbours.size() == 0) {
0086     return;
0087   }
0088 
0089   // tops
0090   for (const std::size_t idx : topSPsIdx) {
0091     // Only add an entry if the bin has entries
0092     if (grid.at(idx).size() == 0) {
0093       continue;
0094     }
0095     state.topNeighbours.emplace_back(
0096         grid, idx, middleSPs.front()->radius() + m_config.deltaRMinTopSP);
0097   }
0098   // if no top candidates, then no need to proceed
0099   if (state.topNeighbours.size() == 0) {
0100     return;
0101   }
0102 
0103   // we compute this here since all middle space point candidates belong to the
0104   // same z-bin
0105   auto [minRadiusRangeForMiddle, maxRadiusRangeForMiddle] =
0106       retrieveRadiusRangeForMiddle(*middleSPs.front(), rMiddleSPRange);
0107   ACTS_VERBOSE("Current global bin: " << middleSPsIdx << ", z value of "
0108                                       << middleSPs.front()->z());
0109   ACTS_VERBOSE("Validity range (radius) for the middle space point is ["
0110                << minRadiusRangeForMiddle << ", " << maxRadiusRangeForMiddle
0111                << "]");
0112 
0113   for (const external_space_point_t* spM : middleSPs) {
0114     const float rM = spM->radius();
0115 
0116     // check if spM is outside our radial region of interest
0117     if (rM < minRadiusRangeForMiddle) {
0118       continue;
0119     }
0120     if (rM > maxRadiusRangeForMiddle) {
0121       // break because SPs are sorted in r
0122       break;
0123     }
0124 
0125     const float zM = spM->z();
0126     const float uIP = -1 / rM;
0127     const float cosPhiM = -spM->x() * uIP;
0128     const float sinPhiM = -spM->y() * uIP;
0129     const float uIP2 = uIP * uIP;
0130 
0131     // Iterate over middle-top dublets
0132     getCompatibleDoublets<SpacePointCandidateType::eTop>(
0133         options, grid, state.spacePointMutableData, state.topNeighbours, *spM,
0134         state.linCircleTop, state.compatTopSP, m_config.deltaRMinTopSP,
0135         m_config.deltaRMaxTopSP, uIP, uIP2, cosPhiM, sinPhiM);
0136 
0137     // no top SP found -> try next spM
0138     if (state.compatTopSP.empty()) {
0139       ACTS_VERBOSE("No compatible Tops, moving to next middle candidate");
0140       continue;
0141     }
0142 
0143     // apply cut on the number of top SP if seedConfirmation is true
0144     SeedFilterState seedFilterState;
0145     if (m_config.seedConfirmation) {
0146       // check if middle SP is in the central or forward region
0147       SeedConfirmationRangeConfig seedConfRange =
0148           (zM > m_config.centralSeedConfirmationRange.zMaxSeedConf ||
0149            zM < m_config.centralSeedConfirmationRange.zMinSeedConf)
0150               ? m_config.forwardSeedConfirmationRange
0151               : m_config.centralSeedConfirmationRange;
0152       // set the minimum number of top SP depending on whether the middle SP is
0153       // in the central or forward region
0154       seedFilterState.nTopSeedConf = rM > seedConfRange.rMaxSeedConf
0155                                          ? seedConfRange.nTopForLargeR
0156                                          : seedConfRange.nTopForSmallR;
0157       // set max bottom radius for seed confirmation
0158       seedFilterState.rMaxSeedConf = seedConfRange.rMaxSeedConf;
0159       // continue if number of top SPs is smaller than minimum
0160       if (state.compatTopSP.size() < seedFilterState.nTopSeedConf) {
0161         ACTS_VERBOSE(
0162             "Number of top SPs is "
0163             << state.compatTopSP.size()
0164             << " and is smaller than minimum, moving to next middle candidate");
0165         continue;
0166       }
0167     }
0168 
0169     // Iterate over middle-bottom dublets
0170     getCompatibleDoublets<SpacePointCandidateType::eBottom>(
0171         options, grid, state.spacePointMutableData, state.bottomNeighbours,
0172         *spM, state.linCircleBottom, state.compatBottomSP,
0173         m_config.deltaRMinBottomSP, m_config.deltaRMaxBottomSP, uIP, uIP2,
0174         cosPhiM, sinPhiM);
0175 
0176     // no bottom SP found -> try next spM
0177     if (state.compatBottomSP.empty()) {
0178       ACTS_VERBOSE("No compatible Bottoms, moving to next middle candidate");
0179       continue;
0180     }
0181 
0182     ACTS_VERBOSE("Candidates: " << state.compatBottomSP.size()
0183                                 << " bottoms and " << state.compatTopSP.size()
0184                                 << " tops for middle candidate indexed "
0185                                 << spM->index());
0186     // filter candidates
0187     if (m_config.useDetailedDoubleMeasurementInfo) {
0188       filterCandidates<DetectorMeasurementInfo::eDetailed>(
0189           *spM, options, seedFilterState, state);
0190     } else {
0191       filterCandidates<DetectorMeasurementInfo::eDefault>(
0192           *spM, options, seedFilterState, state);
0193     }
0194 
0195     m_config.seedFilter->filterSeeds_1SpFixed(state.spacePointMutableData,
0196                                               state.candidatesCollector,
0197                                               outputCollection);
0198 
0199   }  // loop on mediums
0200 }
0201 
0202 template <typename external_space_point_t, typename grid_t, typename platform_t>
0203 template <SpacePointCandidateType candidateType, typename out_range_t>
0204 inline void
0205 SeedFinder<external_space_point_t, grid_t, platform_t>::getCompatibleDoublets(
0206     const SeedFinderOptions& options, const grid_t& grid,
0207     SpacePointMutableData& mutableData,
0208     boost::container::small_vector<
0209         Neighbour<grid_t>, detail::ipow(3, grid_t::DIM)>& otherSPsNeighbours,
0210     const external_space_point_t& mediumSP,
0211     std::vector<LinCircle>& linCircleVec, out_range_t& outVec,
0212     const float deltaRMinSP, const float deltaRMaxSP, const float uIP,
0213     const float uIP2, const float cosPhiM, const float sinPhiM) const {
0214   float impactMax = m_config.impactMax;
0215 
0216   constexpr bool isBottomCandidate =
0217       candidateType == SpacePointCandidateType::eBottom;
0218 
0219   if constexpr (isBottomCandidate) {
0220     impactMax = -impactMax;
0221   }
0222 
0223   outVec.clear();
0224   linCircleVec.clear();
0225 
0226   // get number of neighbour SPs
0227   std::size_t nsp = 0;
0228   for (const auto& otherSPCol : otherSPsNeighbours) {
0229     nsp += grid.at(otherSPCol.index).size();
0230   }
0231 
0232   linCircleVec.reserve(nsp);
0233   outVec.reserve(nsp);
0234 
0235   const float rM = mediumSP.radius();
0236   const float xM = mediumSP.x();
0237   const float yM = mediumSP.y();
0238   const float zM = mediumSP.z();
0239   const float varianceRM = mediumSP.varianceR();
0240   const float varianceZM = mediumSP.varianceZ();
0241 
0242   float vIPAbs = 0;
0243   if (m_config.interactionPointCut) {
0244     // equivalent to m_config.impactMax / (rM * rM);
0245     vIPAbs = impactMax * uIP2;
0246   }
0247 
0248   float deltaR = 0;
0249   float deltaZ = 0;
0250 
0251   const auto outsideRangeCheck = [](const float value, const float min,
0252                                     const float max) -> bool {
0253     // intentionally using `|` after profiling. faster due to better branch
0254     // prediction
0255     return static_cast<bool>(static_cast<int>(value < min) |
0256                              static_cast<int>(value > max));
0257   };
0258 
0259   for (auto& otherSPCol : otherSPsNeighbours) {
0260     const std::vector<const external_space_point_t*>& otherSPs =
0261         grid.at(otherSPCol.index);
0262     if (otherSPs.empty()) {
0263       continue;
0264     }
0265 
0266     // we make a copy of the iterator here since we need it to remain
0267     // the same in the Neighbour object
0268     auto min_itr = otherSPCol.itr;
0269 
0270     // find the first SP inside the radius region of interest and update
0271     // the iterator so we don't need to look at the other SPs again
0272     for (; min_itr != otherSPs.end(); ++min_itr) {
0273       const external_space_point_t* otherSP = *min_itr;
0274       if constexpr (candidateType == SpacePointCandidateType::eBottom) {
0275         // if r-distance is too big, try next SP in bin
0276         if ((rM - otherSP->radius()) <= deltaRMaxSP) {
0277           break;
0278         }
0279       } else {
0280         // if r-distance is too small, try next SP in bin
0281         if ((otherSP->radius() - rM) >= deltaRMinSP) {
0282           break;
0283         }
0284       }
0285     }
0286     // We update the iterator in the Neighbour object
0287     // that mean that we have changed the middle space point
0288     // and the lower bound has moved accordingly
0289     otherSPCol.itr = min_itr;
0290 
0291     for (; min_itr != otherSPs.end(); ++min_itr) {
0292       const external_space_point_t* otherSP = *min_itr;
0293 
0294       if constexpr (isBottomCandidate) {
0295         deltaR = (rM - otherSP->radius());
0296 
0297         // if r-distance is too small, try next SP in bin
0298         if (deltaR < deltaRMinSP) {
0299           break;
0300         }
0301       } else {
0302         deltaR = (otherSP->radius() - rM);
0303 
0304         // if r-distance is too big, try next SP in bin
0305         if (deltaR > deltaRMaxSP) {
0306           break;
0307         }
0308       }
0309 
0310       if constexpr (isBottomCandidate) {
0311         deltaZ = (zM - otherSP->z());
0312       } else {
0313         deltaZ = (otherSP->z() - zM);
0314       }
0315 
0316       // the longitudinal impact parameter zOrigin is defined as (zM - rM *
0317       // cotTheta) where cotTheta is the ratio Z/R (forward angle) of space
0318       // point duplet but instead we calculate (zOrigin * deltaR) and multiply
0319       // collisionRegion by deltaR to avoid divisions
0320       const float zOriginTimesDeltaR = (zM * deltaR - rM * deltaZ);
0321       // check if duplet origin on z axis within collision region
0322       if (outsideRangeCheck(zOriginTimesDeltaR,
0323                             m_config.collisionRegionMin * deltaR,
0324                             m_config.collisionRegionMax * deltaR)) {
0325         continue;
0326       }
0327 
0328       // if interactionPointCut is false we apply z cuts before coordinate
0329       // transformation to avoid unnecessary calculations. If
0330       // interactionPointCut is true we apply the curvature cut first because it
0331       // is more frequent but requires the coordinate transformation
0332       if (!m_config.interactionPointCut) {
0333         // check if duplet cotTheta is within the region of interest
0334         // cotTheta is defined as (deltaZ / deltaR) but instead we multiply
0335         // cotThetaMax by deltaR to avoid division
0336         if (outsideRangeCheck(deltaZ, -m_config.cotThetaMax * deltaR,
0337                               m_config.cotThetaMax * deltaR)) {
0338           continue;
0339         }
0340         // if z-distance between SPs is within max and min values
0341         if (outsideRangeCheck(deltaZ, -m_config.deltaZMax,
0342                               m_config.deltaZMax)) {
0343           continue;
0344         }
0345 
0346         // transform SP coordinates to the u-v reference frame
0347         const float deltaX = otherSP->x() - xM;
0348         const float deltaY = otherSP->y() - yM;
0349 
0350         const float xNewFrame = deltaX * cosPhiM + deltaY * sinPhiM;
0351         const float yNewFrame = deltaY * cosPhiM - deltaX * sinPhiM;
0352 
0353         const float deltaR2 = (deltaX * deltaX + deltaY * deltaY);
0354         const float iDeltaR2 = 1 / deltaR2;
0355 
0356         const float uT = xNewFrame * iDeltaR2;
0357         const float vT = yNewFrame * iDeltaR2;
0358 
0359         const float iDeltaR = std::sqrt(iDeltaR2);
0360         const float cotTheta = deltaZ * iDeltaR;
0361 
0362         const float Er =
0363             ((varianceZM + otherSP->varianceZ()) +
0364              (cotTheta * cotTheta) * (varianceRM + otherSP->varianceR())) *
0365             iDeltaR2;
0366 
0367         // fill output vectors
0368         linCircleVec.emplace_back(cotTheta, iDeltaR, Er, uT, vT, xNewFrame,
0369                                   yNewFrame);
0370 
0371         mutableData.setDeltaR(otherSP->index(),
0372                               std::sqrt(deltaR2 + (deltaZ * deltaZ)));
0373         outVec.push_back(otherSP);
0374         continue;
0375       }
0376 
0377       // transform SP coordinates to the u-v reference frame
0378       const float deltaX = otherSP->x() - xM;
0379       const float deltaY = otherSP->y() - yM;
0380 
0381       const float xNewFrame = deltaX * cosPhiM + deltaY * sinPhiM;
0382       const float yNewFrame = deltaY * cosPhiM - deltaX * sinPhiM;
0383 
0384       const float deltaR2 = deltaX * deltaX + deltaY * deltaY;
0385       const float iDeltaR2 = 1 / deltaR2;
0386 
0387       const float uT = xNewFrame * iDeltaR2;
0388       const float vT = yNewFrame * iDeltaR2;
0389 
0390       // We check the interaction point by evaluating the minimal distance
0391       // between the origin and the straight line connecting the two points in
0392       // the doublets. Using a geometric similarity, the Im is given by
0393       // yNewFrame * rM / deltaR <= m_config.impactMax
0394       // However, we make here an approximation of the impact parameter
0395       // which is valid under the assumption yNewFrame / xNewFrame is small
0396       // The correct computation would be:
0397       // yNewFrame * yNewFrame * rM * rM <= m_config.impactMax *
0398       // m_config.impactMax * deltaR2
0399       if (std::abs(rM * yNewFrame) <= impactMax * xNewFrame) {
0400         // check if duplet cotTheta is within the region of interest
0401         // cotTheta is defined as (deltaZ / deltaR) but instead we multiply
0402         // cotThetaMax by deltaR to avoid division
0403         if (outsideRangeCheck(deltaZ, -m_config.cotThetaMax * deltaR,
0404                               m_config.cotThetaMax * deltaR)) {
0405           continue;
0406         }
0407 
0408         const float iDeltaR = std::sqrt(iDeltaR2);
0409         const float cotTheta = deltaZ * iDeltaR;
0410 
0411         // discard bottom-middle dublets in a certain (r, eta) region according
0412         // to detector specific cuts
0413         if (!m_config.experimentCuts(mediumSP, *otherSP, cotTheta,
0414                                      isBottomCandidate)) {
0415           continue;
0416         }
0417 
0418         const float Er =
0419             ((varianceZM + otherSP->varianceZ()) +
0420              (cotTheta * cotTheta) * (varianceRM + otherSP->varianceR())) *
0421             iDeltaR2;
0422 
0423         // fill output vectors
0424         linCircleVec.emplace_back(cotTheta, iDeltaR, Er, uT, vT, xNewFrame,
0425                                   yNewFrame);
0426         mutableData.setDeltaR(otherSP->index(),
0427                               std::sqrt(deltaR2 + (deltaZ * deltaZ)));
0428         outVec.emplace_back(otherSP);
0429         continue;
0430       }
0431 
0432       // in the rotated frame the interaction point is positioned at x = -rM
0433       // and y ~= impactParam
0434       const float vIP = (yNewFrame > 0) ? -vIPAbs : vIPAbs;
0435 
0436       // we can obtain aCoef as the slope dv/du of the linear function,
0437       // estimated using du and dv between the two SP bCoef is obtained by
0438       // inserting aCoef into the linear equation
0439       const float aCoef = (vT - vIP) / (uT - uIP);
0440       const float bCoef = vIP - aCoef * uIP;
0441       // the distance of the straight line from the origin (radius of the
0442       // circle) is related to aCoef and bCoef by d^2 = bCoef^2 / (1 +
0443       // aCoef^2) = 1 / (radius^2) and we can apply the cut on the curvature
0444       if ((bCoef * bCoef) * options.minHelixDiameter2 > (1 + aCoef * aCoef)) {
0445         continue;
0446       }
0447 
0448       // check if duplet cotTheta is within the region of interest
0449       // cotTheta is defined as (deltaZ / deltaR) but instead we multiply
0450       // cotThetaMax by deltaR to avoid division
0451       if (outsideRangeCheck(deltaZ, -m_config.cotThetaMax * deltaR,
0452                             m_config.cotThetaMax * deltaR)) {
0453         continue;
0454       }
0455 
0456       const float iDeltaR = std::sqrt(iDeltaR2);
0457       const float cotTheta = deltaZ * iDeltaR;
0458 
0459       // discard bottom-middle dublets in a certain (r, eta) region according
0460       // to detector specific cuts
0461       if (!m_config.experimentCuts(mediumSP, *otherSP, cotTheta,
0462                                    isBottomCandidate)) {
0463         continue;
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_space_point_t, typename grid_t, typename platform_t>
0483 template <DetectorMeasurementInfo detailedMeasurement>
0484 inline void
0485 SeedFinder<external_space_point_t, grid_t, platform_t>::filterCandidates(
0486     const external_space_point_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 space point
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 space point
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 space point
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.5f * (cotThetaB + cotThetaT);
0680         cotThetaAvg2 = averageCotTheta * averageCotTheta;
0681       }
0682 
0683       // add errors of spB-spM and spM-spT pairs and add the correlation term
0684       // for errors on spM
0685       float error2 =
0686           lt.Er + ErB +
0687           2 * (cotThetaAvg2 * varianceRM + varianceZM) * iDeltaRB * lt.iDeltaR;
0688 
0689       float deltaCotTheta = cotThetaB - cotThetaT;
0690       float deltaCotTheta2 = deltaCotTheta * deltaCotTheta;
0691 
0692       // Apply a cut on the compatibility between the r-z slope of the two
0693       // seed segments. This is done by comparing the squared difference
0694       // between slopes, and comparing to the squared uncertainty in this
0695       // difference - we keep a seed if the difference is compatible within
0696       // the assumed uncertainties. The uncertainties get contribution from
0697       // the  space-point-related squared error (error2) and a scattering term
0698       // calculated assuming the minimum pt we expect to reconstruct
0699       // (scatteringInRegion2). This assumes gaussian error propagation which
0700       // allows just adding the two errors if they are uncorrelated (which is
0701       // fair for scattering and measurement uncertainties)
0702       if (deltaCotTheta2 > error2 + scatteringInRegion2) {
0703         // skip top SPs based on cotTheta sorting when producing triplets
0704         if constexpr (detailedMeasurement ==
0705                       DetectorMeasurementInfo::eDetailed) {
0706           continue;
0707         }
0708         // break if cotTheta from bottom SP < cotTheta from top SP because
0709         // the SP are sorted by cotTheta
0710         if (cotThetaB - cotThetaT < 0) {
0711           break;
0712         }
0713         t0 = index_t + 1;
0714         continue;
0715       }
0716 
0717       if constexpr (detailedMeasurement == DetectorMeasurementInfo::eDetailed) {
0718         rMxy = std::sqrt(rMTransf[0] * rMTransf[0] + rMTransf[1] * rMTransf[1]);
0719         float irMxy = 1 / rMxy;
0720         float Ax = rMTransf[0] * irMxy;
0721         float Ay = rMTransf[1] * irMxy;
0722 
0723         ub = (xB * Ax + yB * Ay) * iDeltaRB2;
0724         vb = (yB * Ax - xB * Ay) * iDeltaRB2;
0725         ut = (xT * Ax + yT * Ay) * iDeltaRT2;
0726         vt = (yT * Ax - xT * Ay) * iDeltaRT2;
0727       }
0728 
0729       float dU = 0;
0730       float A = 0;
0731       float S2 = 0;
0732       float B = 0;
0733       float B2 = 0;
0734 
0735       if constexpr (detailedMeasurement == DetectorMeasurementInfo::eDetailed) {
0736         dU = ut - ub;
0737         // protects against division by 0
0738         if (dU == 0) {
0739           continue;
0740         }
0741         A = (vt - vb) / dU;
0742         S2 = 1 + A * A;
0743         B = vb - A * ub;
0744         B2 = B * B;
0745       } else {
0746         dU = lt.U - Ub;
0747         // protects against division by 0
0748         if (dU == 0) {
0749           continue;
0750         }
0751         // A and B are evaluated as a function of the circumference parameters
0752         // x_0 and y_0
0753         A = (lt.V - Vb) / dU;
0754         S2 = 1 + A * A;
0755         B = Vb - A * Ub;
0756         B2 = B * B;
0757       }
0758 
0759       // sqrt(S2)/B = 2 * helixradius
0760       // calculated radius must not be smaller than minimum radius
0761       if (S2 < B2 * options.minHelixDiameter2) {
0762         continue;
0763       }
0764 
0765       // refinement of the cut on the compatibility between the r-z slope of
0766       // the two seed segments using a scattering term scaled by the actual
0767       // measured pT (p2scatterSigma)
0768       float iHelixDiameter2 = B2 / S2;
0769       // convert p(T) to p scaling by sin^2(theta) AND scale by 1/sin^4(theta)
0770       // from rad to deltaCotTheta
0771       float p2scatterSigma = iHelixDiameter2 * sigmaSquaredPtDependent;
0772       // if deltaTheta larger than allowed scattering for calculated pT, skip
0773       if (deltaCotTheta2 > error2 + p2scatterSigma) {
0774         if constexpr (detailedMeasurement ==
0775                       DetectorMeasurementInfo::eDetailed) {
0776           continue;
0777         }
0778         if (cotThetaB - cotThetaT < 0) {
0779           break;
0780         }
0781         t0 = index_t;
0782         continue;
0783       }
0784       // A and B allow calculation of impact params in U/V plane with linear
0785       // function
0786       // (in contrast to having to solve a quadratic function in x/y plane)
0787       float Im = 0;
0788       if constexpr (detailedMeasurement == DetectorMeasurementInfo::eDetailed) {
0789         Im = std::abs((A - B * rMxy) * rMxy);
0790       } else {
0791         Im = std::abs((A - B * rM) * rM);
0792       }
0793 
0794       if (Im > m_config.impactMax) {
0795         continue;
0796       }
0797 
0798       state.topSpVec.push_back(state.compatTopSP[t]);
0799       // inverse diameter is signed depending on if the curvature is
0800       // positive/negative in phi
0801       state.curvatures.push_back(B / std::sqrt(S2));
0802       state.impactParameters.push_back(Im);
0803     }  // loop on tops
0804 
0805     // continue if number of top SPs is smaller than minimum required for filter
0806     if (state.topSpVec.size() < minCompatibleTopSPs) {
0807       continue;
0808     }
0809 
0810     seedFilterState.zOrigin = spM.z() - rM * lb.cotTheta;
0811 
0812     m_config.seedFilter->filterSeeds_2SpFixed(
0813         state.spacePointMutableData, *state.compatBottomSP[b], spM,
0814         state.topSpVec, state.curvatures, state.impactParameters,
0815         seedFilterState, state.candidatesCollector);
0816   }  // loop on bottoms
0817 }
0818 
0819 template <typename external_space_point_t, typename grid_t, typename platform_t>
0820 std::pair<float, float> SeedFinder<external_space_point_t, grid_t, platform_t>::
0821     retrieveRadiusRangeForMiddle(const external_space_point_t& spM,
0822                                  const Range1D<float>& rMiddleSPRange) const {
0823   if (m_config.useVariableMiddleSPRange) {
0824     return {rMiddleSPRange.min(), rMiddleSPRange.max()};
0825   }
0826   if (!m_config.rRangeMiddleSP.empty()) {
0827     /// get zBin position of the middle SP
0828     auto pVal = std::lower_bound(m_config.zBinEdges.begin(),
0829                                  m_config.zBinEdges.end(), spM.z());
0830     int zBin = std::distance(m_config.zBinEdges.begin(), pVal);
0831     /// protects against zM at the limit of zBinEdges
0832     zBin == 0 ? zBin : --zBin;
0833     return {m_config.rRangeMiddleSP[zBin][0], m_config.rRangeMiddleSP[zBin][1]};
0834   }
0835   return {m_config.rMinMiddle, m_config.rMaxMiddle};
0836 }
0837 
0838 }  // namespace Acts