Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-07-13 08:04:59

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