Back to home page

sPhenix code displayed by LXR

 
 

    


File indexing completed on 2025-08-06 08:10:10

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