Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-01-18 09:11:01

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 #include <algorithm>
0010 #include <cmath>
0011 #include <numeric>
0012 #include <type_traits>
0013 
0014 namespace Acts {
0015 
0016 template <typename external_spacepoint_t, typename grid_t, typename platform_t>
0017 SeedFinder<external_spacepoint_t, grid_t, platform_t>::SeedFinder(
0018     const Acts::SeedFinderConfig<external_spacepoint_t>& config,
0019     std::unique_ptr<const Acts::Logger> logger)
0020     : m_config(config), m_logger(std::move(logger)) {
0021   if (!config.isInInternalUnits) {
0022     throw std::runtime_error(
0023         "SeedFinderConfig not in ACTS internal units in SeedFinder");
0024   }
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_spacepoint_t, typename grid_t, typename platform_t>
0040 template <typename container_t, Acts::GridBinCollection sp_range_t>
0041   requires Acts::CollectionStoresSeedsTo<container_t, external_spacepoint_t,
0042                                          3ul>
0043 void SeedFinder<external_spacepoint_t, grid_t, platform_t>::createSeedsForGroup(
0044     const Acts::SeedFinderOptions& options, SeedingState& state,
0045     const grid_t& grid, container_t& outputCollection,
0046     const sp_range_t& bottomSPsIdx, const std::size_t middleSPsIdx,
0047     const sp_range_t& topSPsIdx,
0048     const Acts::Range1D<float>& rMiddleSPRange) const {
0049   if (!options.isInInternalUnits) {
0050     throw std::runtime_error(
0051         "SeedFinderOptions not in ACTS internal units in SeedFinder");
0052   }
0053 
0054   // This is used for seed filtering later
0055   const std::size_t max_num_seeds_per_spm =
0056       m_config.seedFilter->getSeedFilterConfig().maxSeedsPerSpMConf;
0057   const std::size_t max_num_quality_seeds_per_spm =
0058       m_config.seedFilter->getSeedFilterConfig().maxQualitySeedsPerSpMConf;
0059 
0060   state.candidates_collector.setMaxElements(max_num_seeds_per_spm,
0061                                             max_num_quality_seeds_per_spm);
0062 
0063   // If there are no bottom or top bins, just return and waste no time
0064   if (bottomSPsIdx.size() == 0 || topSPsIdx.size() == 0) {
0065     return;
0066   }
0067 
0068   // Get the middle space point candidates
0069   const std::vector<const external_spacepoint_t*>& middleSPs =
0070       grid.at(middleSPsIdx);
0071   // Return if somehow there are no middle sp candidates
0072   if (middleSPs.empty()) {
0073     return;
0074   }
0075 
0076   // neighbours
0077   // clear previous results
0078   state.bottomNeighbours.clear();
0079   state.topNeighbours.clear();
0080 
0081   // Fill
0082   // bottoms
0083   for (const std::size_t idx : bottomSPsIdx) {
0084     // Only add an entry if the bin has entries
0085     if (grid.at(idx).size() == 0) {
0086       continue;
0087     }
0088     state.bottomNeighbours.emplace_back(
0089         grid, idx, middleSPs.front()->radius() - m_config.deltaRMaxBottomSP);
0090   }
0091   // if no bottom candidates, then no need to proceed
0092   if (state.bottomNeighbours.size() == 0) {
0093     return;
0094   }
0095 
0096   // tops
0097   for (const std::size_t idx : topSPsIdx) {
0098     // Only add an entry if the bin has entries
0099     if (grid.at(idx).size() == 0) {
0100       continue;
0101     }
0102     state.topNeighbours.emplace_back(
0103         grid, idx, middleSPs.front()->radius() + m_config.deltaRMinTopSP);
0104   }
0105   // if no top candidates, then no need to proceed
0106   if (state.topNeighbours.size() == 0) {
0107     return;
0108   }
0109 
0110   // we compute this here since all middle space point candidates belong to the
0111   // same z-bin
0112   auto [minRadiusRangeForMiddle, maxRadiusRangeForMiddle] =
0113       retrieveRadiusRangeForMiddle(*middleSPs.front(), rMiddleSPRange);
0114   ACTS_VERBOSE("Current global bin: " << middleSPsIdx << ", z value of "
0115                                       << middleSPs.front()->z());
0116   ACTS_VERBOSE("Validity range (radius) for the middle space point is ["
0117                << minRadiusRangeForMiddle << ", " << maxRadiusRangeForMiddle
0118                << "]");
0119 
0120   for (const external_spacepoint_t* spM : middleSPs) {
0121     const float rM = spM->radius();
0122 
0123     // check if spM is outside our radial region of interest
0124     if (rM < minRadiusRangeForMiddle) {
0125       continue;
0126     }
0127     if (rM > maxRadiusRangeForMiddle) {
0128       // break because SPs are sorted in r
0129       break;
0130     }
0131 
0132     const float zM = spM->z();
0133     const float uIP = -1. / rM;
0134     const float cosPhiM = -spM->x() * uIP;
0135     const float sinPhiM = -spM->y() * uIP;
0136     const float uIP2 = uIP * uIP;
0137 
0138     // Iterate over middle-top dublets
0139     getCompatibleDoublets<Acts::SpacePointCandidateType::eTop>(
0140         options, grid, state.spacePointMutableData, state.topNeighbours, *spM,
0141         state.linCircleTop, state.compatTopSP, m_config.deltaRMinTopSP,
0142         m_config.deltaRMaxTopSP, uIP, uIP2, cosPhiM, sinPhiM);
0143 
0144     // no top SP found -> try next spM
0145     if (state.compatTopSP.empty()) {
0146       ACTS_VERBOSE("No compatible Tops, moving to next middle candidate");
0147       continue;
0148     }
0149 
0150     // apply cut on the number of top SP if seedConfirmation is true
0151     SeedFilterState seedFilterState;
0152     if (m_config.seedConfirmation) {
0153       // check if middle SP is in the central or forward region
0154       SeedConfirmationRangeConfig seedConfRange =
0155           (zM > m_config.centralSeedConfirmationRange.zMaxSeedConf ||
0156            zM < m_config.centralSeedConfirmationRange.zMinSeedConf)
0157               ? m_config.forwardSeedConfirmationRange
0158               : m_config.centralSeedConfirmationRange;
0159       // set the minimum number of top SP depending on whether the middle SP is
0160       // in the central or forward region
0161       seedFilterState.nTopSeedConf = rM > seedConfRange.rMaxSeedConf
0162                                          ? seedConfRange.nTopForLargeR
0163                                          : seedConfRange.nTopForSmallR;
0164       // set max bottom radius for seed confirmation
0165       seedFilterState.rMaxSeedConf = seedConfRange.rMaxSeedConf;
0166       // continue if number of top SPs is smaller than minimum
0167       if (state.compatTopSP.size() < seedFilterState.nTopSeedConf) {
0168         ACTS_VERBOSE(
0169             "Number of top SPs is "
0170             << state.compatTopSP.size()
0171             << " and is smaller than minimum, moving to next middle candidate");
0172         continue;
0173       }
0174     }
0175 
0176     // Iterate over middle-bottom dublets
0177     getCompatibleDoublets<Acts::SpacePointCandidateType::eBottom>(
0178         options, grid, state.spacePointMutableData, state.bottomNeighbours,
0179         *spM, state.linCircleBottom, state.compatBottomSP,
0180         m_config.deltaRMinBottomSP, m_config.deltaRMaxBottomSP, uIP, uIP2,
0181         cosPhiM, sinPhiM);
0182 
0183     // no bottom SP found -> try next spM
0184     if (state.compatBottomSP.empty()) {
0185       ACTS_VERBOSE("No compatible Bottoms, moving to next middle candidate");
0186       continue;
0187     }
0188 
0189     ACTS_VERBOSE("Candidates: " << state.compatBottomSP.size()
0190                                 << " bottoms and " << state.compatTopSP.size()
0191                                 << " tops for middle candidate indexed "
0192                                 << spM->index());
0193     // filter candidates
0194     if (m_config.useDetailedDoubleMeasurementInfo) {
0195       filterCandidates<Acts::DetectorMeasurementInfo::eDetailed>(
0196           *spM, options, seedFilterState, state);
0197     } else {
0198       filterCandidates<Acts::DetectorMeasurementInfo::eDefault>(
0199           *spM, options, seedFilterState, state);
0200     }
0201 
0202     m_config.seedFilter->filterSeeds_1SpFixed(state.spacePointMutableData,
0203                                               state.candidates_collector,
0204                                               outputCollection);
0205 
0206   }  // loop on mediums
0207 }
0208 
0209 template <typename external_spacepoint_t, typename grid_t, typename platform_t>
0210 template <Acts::SpacePointCandidateType candidateType, typename out_range_t>
0211 inline void
0212 SeedFinder<external_spacepoint_t, grid_t, platform_t>::getCompatibleDoublets(
0213     const Acts::SeedFinderOptions& options, const grid_t& grid,
0214     Acts::SpacePointMutableData& mutableData,
0215     boost::container::small_vector<Neighbour<grid_t>,
0216                                    Acts::detail::ipow(3, grid_t::DIM)>&
0217         otherSPsNeighbours,
0218     const external_spacepoint_t& mediumSP, std::vector<LinCircle>& linCircleVec,
0219     out_range_t& outVec, const float deltaRMinSP, const float deltaRMaxSP,
0220     const float uIP, const float uIP2, const float cosPhiM,
0221     const float sinPhiM) const {
0222   float impactMax = m_config.impactMax;
0223 
0224   constexpr bool isBottomCandidate =
0225       candidateType == Acts::SpacePointCandidateType::eBottom;
0226 
0227   if constexpr (isBottomCandidate) {
0228     impactMax = -impactMax;
0229   }
0230 
0231   outVec.clear();
0232   linCircleVec.clear();
0233 
0234   // get number of neighbour SPs
0235   std::size_t nsp = 0;
0236   for (const auto& otherSPCol : otherSPsNeighbours) {
0237     nsp += grid.at(otherSPCol.index).size();
0238   }
0239 
0240   linCircleVec.reserve(nsp);
0241   outVec.reserve(nsp);
0242 
0243   const float rM = mediumSP.radius();
0244   const float xM = mediumSP.x();
0245   const float yM = mediumSP.y();
0246   const float zM = mediumSP.z();
0247   const float varianceRM = mediumSP.varianceR();
0248   const float varianceZM = mediumSP.varianceZ();
0249 
0250   float vIPAbs = 0;
0251   if (m_config.interactionPointCut) {
0252     // equivalent to m_config.impactMax / (rM * rM);
0253     vIPAbs = impactMax * uIP2;
0254   }
0255 
0256   float deltaR = 0.;
0257   float deltaZ = 0.;
0258 
0259   for (auto& otherSPCol : otherSPsNeighbours) {
0260     const std::vector<const external_spacepoint_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_spacepoint_t* otherSP = *min_itr;
0274       if constexpr (candidateType == Acts::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_spacepoint_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 (zOriginTimesDeltaR < m_config.collisionRegionMin * deltaR ||
0323           zOriginTimesDeltaR > m_config.collisionRegionMax * deltaR) {
0324         continue;
0325       }
0326 
0327       // if interactionPointCut is false we apply z cuts before coordinate
0328       // transformation to avoid unnecessary calculations. If
0329       // interactionPointCut is true we apply the curvature cut first because it
0330       // is more frequent but requires the coordinate transformation
0331       if (!m_config.interactionPointCut) {
0332         // check if duplet cotTheta is within the region of interest
0333         // cotTheta is defined as (deltaZ / deltaR) but instead we multiply
0334         // cotThetaMax by deltaR to avoid division
0335         if (deltaZ > m_config.cotThetaMax * deltaR ||
0336             deltaZ < -m_config.cotThetaMax * deltaR) {
0337           continue;
0338         }
0339         // if z-distance between SPs is within max and min values
0340         if (deltaZ > m_config.deltaZMax || deltaZ < -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 (deltaZ > m_config.cotThetaMax * deltaR ||
0402             deltaZ < -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 (deltaZ > m_config.cotThetaMax * deltaR ||
0451           deltaZ < -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 <Acts::DetectorMeasurementInfo detailedMeasurement>
0484 inline void
0485 SeedFinder<external_spacepoint_t, grid_t, platform_t>::filterCandidates(
0486     const external_spacepoint_t& spM, const Acts::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::size_t> sorted_bottoms(state.linCircleBottom.size());
0498   for (std::size_t i(0); i < sorted_bottoms.size(); ++i) {
0499     sorted_bottoms[i] = i;
0500   }
0501 
0502   std::vector<std::size_t> sorted_tops(state.linCircleTop.size());
0503   for (std::size_t i(0); i < sorted_tops.size(); ++i) {
0504     sorted_tops[i] = i;
0505   }
0506 
0507   if constexpr (detailedMeasurement ==
0508                 Acts::DetectorMeasurementInfo::eDefault) {
0509     std::ranges::sort(sorted_bottoms, {}, [&state](const std::size_t s) {
0510       return state.linCircleBottom[s].cotTheta;
0511     });
0512 
0513     std::ranges::sort(sorted_tops, {}, [&state](const std::size_t s) {
0514       return state.linCircleTop[s].cotTheta;
0515     });
0516   }
0517 
0518   // Reserve enough space, in case current capacity is too little
0519   state.topSpVec.reserve(numTopSP);
0520   state.curvatures.reserve(numTopSP);
0521   state.impactParameters.reserve(numTopSP);
0522 
0523   std::size_t t0 = 0;
0524 
0525   // clear previous results and then loop on bottoms and tops
0526   state.candidates_collector.clear();
0527 
0528   for (const std::size_t b : sorted_bottoms) {
0529     // break if we reached the last top SP
0530     if (t0 == numTopSP) {
0531       break;
0532     }
0533 
0534     auto lb = state.linCircleBottom[b];
0535     float cotThetaB = lb.cotTheta;
0536     float Vb = lb.V;
0537     float Ub = lb.U;
0538     float ErB = lb.Er;
0539     float iDeltaRB = lb.iDeltaR;
0540 
0541     // 1+(cot^2(theta)) = 1/sin^2(theta)
0542     float iSinTheta2 = (1. + cotThetaB * cotThetaB);
0543     float sigmaSquaredPtDependent = iSinTheta2 * options.sigmapT2perRadius;
0544     // calculate max scattering for min momentum at the seed's theta angle
0545     // scaling scatteringAngle^2 by sin^2(theta) to convert pT^2 to p^2
0546     // accurate would be taking 1/atan(thetaBottom)-1/atan(thetaTop) <
0547     // scattering
0548     // but to avoid trig functions we approximate cot by scaling by
0549     // 1/sin^4(theta)
0550     // resolving with pT to p scaling --> only divide by sin^2(theta)
0551     // max approximation error for allowed scattering angles of 0.04 rad at
0552     // eta=infinity: ~8.5%
0553     float scatteringInRegion2 = options.multipleScattering2 * iSinTheta2;
0554 
0555     float sinTheta = 1 / std::sqrt(iSinTheta2);
0556     float cosTheta = cotThetaB * sinTheta;
0557 
0558     // clear all vectors used in each inner for loop
0559     state.topSpVec.clear();
0560     state.curvatures.clear();
0561     state.impactParameters.clear();
0562 
0563     // coordinate transformation and checks for middle spacepoint
0564     // x and y terms for the rotation from UV to XY plane
0565     float rotationTermsUVtoXY[2] = {0, 0};
0566     if constexpr (detailedMeasurement ==
0567                   Acts::DetectorMeasurementInfo::eDetailed) {
0568       rotationTermsUVtoXY[0] = cosPhiM * sinTheta;
0569       rotationTermsUVtoXY[1] = sinPhiM * sinTheta;
0570     }
0571 
0572     // minimum number of compatible top SPs to trigger the filter for a certain
0573     // middle bottom pair if seedConfirmation is false we always ask for at
0574     // least one compatible top to trigger the filter
0575     std::size_t minCompatibleTopSPs = 2;
0576     if (!m_config.seedConfirmation ||
0577         state.compatBottomSP[b]->radius() > seedFilterState.rMaxSeedConf) {
0578       minCompatibleTopSPs = 1;
0579     }
0580     if (m_config.seedConfirmation &&
0581         state.candidates_collector.nHighQualityCandidates()) {
0582       minCompatibleTopSPs++;
0583     }
0584 
0585     for (std::size_t index_t = t0; index_t < numTopSP; index_t++) {
0586       const std::size_t t = sorted_tops[index_t];
0587 
0588       auto lt = state.linCircleTop[t];
0589 
0590       float cotThetaT = lt.cotTheta;
0591       float rMxy = 0.;
0592       float ub = 0.;
0593       float vb = 0.;
0594       float ut = 0.;
0595       float vt = 0.;
0596       double rMTransf[3];
0597       float xB = 0.;
0598       float yB = 0.;
0599       float xT = 0.;
0600       float yT = 0.;
0601       float iDeltaRB2 = 0.;
0602       float iDeltaRT2 = 0.;
0603 
0604       if constexpr (detailedMeasurement ==
0605                     Acts::DetectorMeasurementInfo::eDetailed) {
0606         // protects against division by 0
0607         float dU = lt.U - Ub;
0608         if (dU == 0.) {
0609           continue;
0610         }
0611         // A and B are evaluated as a function of the circumference parameters
0612         // x_0 and y_0
0613         float A0 = (lt.V - Vb) / dU;
0614 
0615         float zPositionMiddle = cosTheta * std::sqrt(1 + A0 * A0);
0616 
0617         // position of Middle SP converted from UV to XY assuming cotTheta
0618         // evaluated from the Bottom and Middle SPs double
0619         double positionMiddle[3] = {
0620             rotationTermsUVtoXY[0] - rotationTermsUVtoXY[1] * A0,
0621             rotationTermsUVtoXY[0] * A0 + rotationTermsUVtoXY[1],
0622             zPositionMiddle};
0623 
0624         if (!xyzCoordinateCheck(m_config, spM, positionMiddle, rMTransf)) {
0625           continue;
0626         }
0627 
0628         // coordinate transformation and checks for bottom spacepoint
0629         float B0 = 2. * (Vb - A0 * Ub);
0630         float Cb = 1. - B0 * lb.y;
0631         float Sb = A0 + B0 * lb.x;
0632         double positionBottom[3] = {
0633             rotationTermsUVtoXY[0] * Cb - rotationTermsUVtoXY[1] * Sb,
0634             rotationTermsUVtoXY[0] * Sb + rotationTermsUVtoXY[1] * Cb,
0635             zPositionMiddle};
0636 
0637         auto spB = state.compatBottomSP[b];
0638         double rBTransf[3];
0639         if (!xyzCoordinateCheck(m_config, *spB, positionBottom, rBTransf)) {
0640           continue;
0641         }
0642 
0643         // coordinate transformation and checks for top spacepoint
0644         float Ct = 1. - B0 * lt.y;
0645         float St = A0 + B0 * lt.x;
0646         double positionTop[3] = {
0647             rotationTermsUVtoXY[0] * Ct - rotationTermsUVtoXY[1] * St,
0648             rotationTermsUVtoXY[0] * St + rotationTermsUVtoXY[1] * Ct,
0649             zPositionMiddle};
0650 
0651         auto spT = state.compatTopSP[t];
0652         double rTTransf[3];
0653         if (!xyzCoordinateCheck(m_config, *spT, positionTop, rTTransf)) {
0654           continue;
0655         }
0656 
0657         // bottom and top coordinates in the spM reference frame
0658         xB = rBTransf[0] - rMTransf[0];
0659         yB = rBTransf[1] - rMTransf[1];
0660         float zB = rBTransf[2] - rMTransf[2];
0661         xT = rTTransf[0] - rMTransf[0];
0662         yT = rTTransf[1] - rMTransf[1];
0663         float zT = rTTransf[2] - rMTransf[2];
0664 
0665         iDeltaRB2 = 1. / (xB * xB + yB * yB);
0666         iDeltaRT2 = 1. / (xT * xT + yT * yT);
0667 
0668         cotThetaB = -zB * std::sqrt(iDeltaRB2);
0669         cotThetaT = zT * std::sqrt(iDeltaRT2);
0670       }
0671 
0672       // use geometric average
0673       float cotThetaAvg2 = cotThetaB * cotThetaT;
0674       if constexpr (detailedMeasurement ==
0675                     Acts::DetectorMeasurementInfo::eDetailed) {
0676         // use arithmetic average
0677         float averageCotTheta = 0.5 * (cotThetaB + cotThetaT);
0678         cotThetaAvg2 = averageCotTheta * averageCotTheta;
0679       } else if (cotThetaAvg2 <= 0) {
0680         continue;
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                       Acts::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 ==
0718                     Acts::DetectorMeasurementInfo::eDetailed) {
0719         rMxy = std::sqrt(rMTransf[0] * rMTransf[0] + rMTransf[1] * rMTransf[1]);
0720         double irMxy = 1 / rMxy;
0721         float Ax = rMTransf[0] * irMxy;
0722         float Ay = rMTransf[1] * irMxy;
0723 
0724         ub = (xB * Ax + yB * Ay) * iDeltaRB2;
0725         vb = (yB * Ax - xB * Ay) * iDeltaRB2;
0726         ut = (xT * Ax + yT * Ay) * iDeltaRT2;
0727         vt = (yT * Ax - xT * Ay) * iDeltaRT2;
0728       }
0729 
0730       float dU = 0;
0731       float A = 0;
0732       float S2 = 0;
0733       float B = 0;
0734       float B2 = 0;
0735 
0736       if constexpr (detailedMeasurement ==
0737                     Acts::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                       Acts::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 ==
0805                     Acts::DetectorMeasurementInfo::eDetailed) {
0806         Im = std::abs((A - B * rMxy) * rMxy);
0807       } else {
0808         Im = std::abs((A - B * rM) * rM);
0809       }
0810 
0811       if (Im > m_config.impactMax) {
0812         continue;
0813       }
0814 
0815       state.topSpVec.push_back(state.compatTopSP[t]);
0816       // inverse diameter is signed depending on if the curvature is
0817       // positive/negative in phi
0818       state.curvatures.push_back(B / std::sqrt(S2));
0819       state.impactParameters.push_back(Im);
0820     }  // loop on tops
0821 
0822     // continue if number of top SPs is smaller than minimum required for filter
0823     if (state.topSpVec.size() < minCompatibleTopSPs) {
0824       continue;
0825     }
0826 
0827     seedFilterState.zOrigin = spM.z() - rM * lb.cotTheta;
0828 
0829     m_config.seedFilter->filterSeeds_2SpFixed(
0830         state.spacePointMutableData, *state.compatBottomSP[b], spM,
0831         state.topSpVec, state.curvatures, state.impactParameters,
0832         seedFilterState, state.candidates_collector);
0833   }  // loop on bottoms
0834 }
0835 
0836 template <typename external_spacepoint_t, typename grid_t, typename platform_t>
0837 std::pair<float, float> SeedFinder<external_spacepoint_t, grid_t, platform_t>::
0838     retrieveRadiusRangeForMiddle(
0839         const external_spacepoint_t& spM,
0840         const Acts::Range1D<float>& rMiddleSPRange) const {
0841   if (m_config.useVariableMiddleSPRange) {
0842     return {rMiddleSPRange.min(), rMiddleSPRange.max()};
0843   }
0844   if (!m_config.rRangeMiddleSP.empty()) {
0845     /// get zBin position of the middle SP
0846     auto pVal = std::lower_bound(m_config.zBinEdges.begin(),
0847                                  m_config.zBinEdges.end(), spM.z());
0848     int zBin = std::distance(m_config.zBinEdges.begin(), pVal);
0849     /// protects against zM at the limit of zBinEdges
0850     zBin == 0 ? zBin : --zBin;
0851     return {m_config.rRangeMiddleSP[zBin][0], m_config.rRangeMiddleSP[zBin][1]};
0852   }
0853   return {m_config.rMinMiddle, m_config.rMaxMiddle};
0854 }
0855 
0856 }  // namespace Acts