Back to home page

EIC code displayed by LXR

 
 

    


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