Back to home page

EIC code displayed by LXR

 
 

    


Warning, file /include/Acts/Seeding/SeedFinderOrthogonal.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) 2023 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 "Acts/Geometry/Extent.hpp"
0010 #include "Acts/Seeding/SeedFilter.hpp"
0011 #include "Acts/Seeding/SeedFinder.hpp"
0012 #include "Acts/Seeding/SeedFinderOrthogonalConfig.hpp"
0013 #include "Acts/Seeding/SeedFinderUtils.hpp"
0014 #include "Acts/Utilities/BinningType.hpp"
0015 
0016 #include <cmath>
0017 #include <functional>
0018 #include <numeric>
0019 #include <type_traits>
0020 
0021 namespace Acts {
0022 template <typename external_spacepoint_t>
0023 auto SeedFinderOrthogonal<external_spacepoint_t>::validTupleOrthoRangeLH(
0024     const internal_sp_t &low) const -> typename tree_t::range_t {
0025   float colMin = m_config.collisionRegionMin;
0026   float colMax = m_config.collisionRegionMax;
0027   float pL = low.phi();
0028   float rL = low.radius();
0029   float zL = low.z();
0030 
0031   typename tree_t::range_t res;
0032 
0033   /*
0034    * Cut: Ensure that we search only in φ_min ≤ φ ≤ φ_max, as defined by the
0035    * seeding configuration.
0036    */
0037   res[DimPhi].shrinkMin(m_config.phiMin);
0038   res[DimPhi].shrinkMax(m_config.phiMax);
0039 
0040   /*
0041    * Cut: Ensure that we search only in r ≤ r_max, as defined by the seeding
0042    * configuration.
0043    */
0044   res[DimR].shrinkMax(m_config.rMax);
0045 
0046   /*
0047    * Cut: Ensure that we search only in z_min ≤ z ≤ z_max, as defined by the
0048    * seeding configuration.
0049    */
0050   res[DimZ].shrinkMin(m_config.zMin);
0051   res[DimZ].shrinkMax(m_config.zMax);
0052 
0053   /*
0054    * Cut: Ensure that we search only in Δr_min ≤ r - r_L ≤ Δr_max, as defined
0055    * by the seeding configuration and the given lower spacepoint.
0056    */
0057   res[DimR].shrinkMin(rL + m_config.deltaRMinTopSP);
0058   res[DimR].shrinkMax(rL + m_config.deltaRMaxTopSP);
0059 
0060   /*
0061    * Cut: Now that we have constrained r, we can use that new r range to
0062    * further constrain z.
0063    */
0064   float zMax = (res[DimR].max() / rL) * (zL - colMin) + colMin;
0065   float zMin = colMax - (res[DimR].max() / rL) * (colMax - zL);
0066 
0067   /*
0068    * This cut only works if z_low is outside the collision region for z.
0069    */
0070   if (zL > colMin) {
0071     res[DimZ].shrinkMax(zMax);
0072   } else if (zL < colMax) {
0073     res[DimZ].shrinkMin(zMin);
0074   }
0075 
0076   /*
0077    * Cut: Shrink the z-range using the maximum cot(θ).
0078    */
0079   res[DimZ].shrinkMin(zL - m_config.cotThetaMax * (res[DimR].max() - rL));
0080   res[DimZ].shrinkMax(zL + m_config.cotThetaMax * (res[DimR].max() - rL));
0081 
0082   /*
0083    * Cut: Shrink the φ range, such that Δφ_min ≤ φ - φ_L ≤ Δφ_max
0084    */
0085   res[DimPhi].shrinkMin(pL - m_config.deltaPhiMax);
0086   res[DimPhi].shrinkMax(pL + m_config.deltaPhiMax);
0087 
0088   // Cut: Ensure that z-distance between SPs is within max and min values.
0089   res[DimZ].shrinkMin(zL - m_config.deltaZMax);
0090   res[DimZ].shrinkMax(zL + m_config.deltaZMax);
0091 
0092   return res;
0093 }
0094 
0095 template <typename external_spacepoint_t>
0096 auto SeedFinderOrthogonal<external_spacepoint_t>::validTupleOrthoRangeHL(
0097     const internal_sp_t &high) const -> typename tree_t::range_t {
0098   float pM = high.phi();
0099   float rM = high.radius();
0100   float zM = high.z();
0101 
0102   typename tree_t::range_t res;
0103 
0104   /*
0105    * Cut: Ensure that we search only in φ_min ≤ φ ≤ φ_max, as defined by the
0106    * seeding configuration.
0107    */
0108   res[DimPhi].shrinkMin(m_config.phiMin);
0109   res[DimPhi].shrinkMax(m_config.phiMax);
0110 
0111   /*
0112    * Cut: Ensure that we search only in r ≤ r_max, as defined by the seeding
0113    * configuration.
0114    */
0115   res[DimR].shrinkMax(m_config.rMax);
0116 
0117   /*
0118    * Cut: Ensure that we search only in z_min ≤ z ≤ z_max, as defined by the
0119    * seeding configuration.
0120    */
0121   res[DimZ].shrinkMin(m_config.zMin);
0122   res[DimZ].shrinkMax(m_config.zMax);
0123 
0124   /*
0125    * Cut: Ensure that we search only in Δr_min ≤ r_H - r ≤ Δr_max, as defined
0126    * by the seeding configuration and the given higher spacepoint.
0127    */
0128   res[DimR].shrinkMin(rM - m_config.deltaRMaxBottomSP);
0129   res[DimR].shrinkMax(rM - m_config.deltaRMinBottomSP);
0130 
0131   /*
0132    * Cut: Now that we have constrained r, we can use that new r range to
0133    * further constrain z.
0134    */
0135   float fracR = res[DimR].min() / rM;
0136 
0137   float zMin =
0138       (zM - m_config.collisionRegionMin) * fracR + m_config.collisionRegionMin;
0139   float zMax =
0140       (zM - m_config.collisionRegionMax) * fracR + m_config.collisionRegionMax;
0141 
0142   res[DimZ].shrinkMin(std::min(zMin, zM));
0143   res[DimZ].shrinkMax(std::max(zMax, zM));
0144 
0145   /*
0146    * Cut: Shrink the φ range, such that Δφ_min ≤ φ - φ_H ≤ Δφ_max
0147    */
0148   res[DimPhi].shrinkMin(pM - m_config.deltaPhiMax);
0149   res[DimPhi].shrinkMax(pM + m_config.deltaPhiMax);
0150 
0151   // Cut: Ensure that z-distance between SPs is within max and min values.
0152   res[DimZ].shrinkMin(zM - m_config.deltaZMax);
0153   res[DimZ].shrinkMax(zM + m_config.deltaZMax);
0154 
0155   return res;
0156 }
0157 
0158 template <typename external_spacepoint_t>
0159 bool SeedFinderOrthogonal<external_spacepoint_t>::validTuple(
0160     const SeedFinderOptions &options, const internal_sp_t &low,
0161     const internal_sp_t &high, bool isMiddleInverted) const {
0162   float rL = low.radius();
0163   float rH = high.radius();
0164 
0165   float zL = low.z();
0166   float zH = high.z();
0167 
0168   float deltaR = rH - rL;
0169 
0170   float deltaZ = (zH - zL);
0171   float cotTheta = deltaZ / deltaR;
0172   /*
0173    * Cut: Ensure that the origin of the dublet (the intersection of the line
0174    * between them with the z axis) lies within the collision region.
0175    */
0176   float zOrigin = zL - rL * cotTheta;
0177   if (zOrigin < m_config.collisionRegionMin ||
0178       zOrigin > m_config.collisionRegionMax) {
0179     return false;
0180   }
0181 
0182   // cut on the max curvature calculated from dublets
0183   // first transform the space point coordinates into a frame such that the
0184   // central space point SPm is in the origin of the frame and the x axis
0185   // points away from the interaction point in addition to a translation
0186   // transformation we also perform a rotation in order to keep the
0187   // curvature of the circle tangent to the x axis
0188   if (m_config.interactionPointCut) {
0189     float xVal = (high.x() - low.x()) * (low.x() / rL) +
0190                  (high.y() - low.y()) * (low.y() / rL);
0191     float yVal = (high.y() - low.y()) * (low.x() / rL) -
0192                  (high.x() - low.x()) * (low.y() / rL);
0193 
0194     const int sign = isMiddleInverted ? -1 : 1;
0195 
0196     if (std::abs(rL * yVal) > sign * m_config.impactMax * xVal) {
0197       // conformal transformation u=x/(x²+y²) v=y/(x²+y²) transform the
0198       // circle into straight lines in the u/v plane the line equation can
0199       // be described in terms of aCoef and bCoef, where v = aCoef * u +
0200       // bCoef
0201       float uT = xVal / (xVal * xVal + yVal * yVal);
0202       float vT = yVal / (xVal * xVal + yVal * yVal);
0203       // in the rotated frame the interaction point is positioned at x = -rM
0204       // and y ~= impactParam
0205       float uIP = -1. / rL;
0206       float vIP = m_config.impactMax / (rL * rL);
0207       if (sign * yVal > 0.) {
0208         vIP = -vIP;
0209       }
0210       // we can obtain aCoef as the slope dv/du of the linear function,
0211       // estimated using du and dv between the two SP bCoef is obtained by
0212       // inserting aCoef into the linear equation
0213       float aCoef = (vT - vIP) / (uT - uIP);
0214       float bCoef = vIP - aCoef * uIP;
0215       // the distance of the straight line from the origin (radius of the
0216       // circle) is related to aCoef and bCoef by d^2 = bCoef^2 / (1 +
0217       // aCoef^2) = 1 / (radius^2) and we can apply the cut on the curvature
0218       if ((bCoef * bCoef) > (1 + aCoef * aCoef) / options.minHelixDiameter2) {
0219         return false;
0220       }
0221     }
0222   }
0223 
0224   /*
0225    * Cut: Ensure that the forward angle (z / r) lies within reasonable bounds,
0226    * which is to say the absolute value must be smaller than the max cot(θ).
0227    */
0228   if (std::fabs(cotTheta) > m_config.cotThetaMax) {
0229     return false;
0230   }
0231 
0232   /*
0233    * Cut: Ensure that inner-middle dublet is in a certain (r, eta) region of the
0234    * detector according to detector specific cuts.
0235    */
0236   const float rInner = (isMiddleInverted) ? rH : rL;
0237   if (!m_config.experimentCuts(rInner, cotTheta)) {
0238     return false;
0239   }
0240 
0241   return true;
0242 }
0243 
0244 template <typename external_spacepoint_t>
0245 SeedFinderOrthogonal<external_spacepoint_t>::SeedFinderOrthogonal(
0246     const SeedFinderOrthogonalConfig<external_spacepoint_t> &config)
0247     : m_config(config) {
0248   if (!config.isInInternalUnits) {
0249     throw std::runtime_error(
0250         "SeedFinderOrthogonalConfig not in ACTS internal units in "
0251         "SeedFinderOrthogonal");
0252   }
0253 }
0254 
0255 template <typename external_spacepoint_t>
0256 void SeedFinderOrthogonal<external_spacepoint_t>::filterCandidates(
0257     const SeedFinderOptions &options, internal_sp_t &middle,
0258     std::vector<internal_sp_t *> &bottom, std::vector<internal_sp_t *> &top,
0259     SeedFilterState seedFilterState,
0260     CandidatesForMiddleSp<const InternalSpacePoint<external_spacepoint_t>>
0261         &candidates_collector,
0262     Acts::SpacePointData &spacePointData) const {
0263   float rM = middle.radius();
0264   float zM = middle.z();
0265   float varianceRM = middle.varianceR();
0266   float varianceZM = middle.varianceZ();
0267 
0268   // apply cut on the number of top SP if seedConfirmation is true
0269   if (m_config.seedConfirmation == true) {
0270     // check if middle SP is in the central or forward region
0271     SeedConfirmationRangeConfig seedConfRange =
0272         (zM > m_config.centralSeedConfirmationRange.zMaxSeedConf ||
0273          zM < m_config.centralSeedConfirmationRange.zMinSeedConf)
0274             ? m_config.forwardSeedConfirmationRange
0275             : m_config.centralSeedConfirmationRange;
0276     // set the minimum number of top SP depending on whether the middle SP is
0277     // in the central or forward region
0278     seedFilterState.nTopSeedConf = rM > seedConfRange.rMaxSeedConf
0279                                        ? seedConfRange.nTopForLargeR
0280                                        : seedConfRange.nTopForSmallR;
0281     // set max bottom radius for seed confirmation
0282     seedFilterState.rMaxSeedConf = seedConfRange.rMaxSeedConf;
0283     // continue if number of top SPs is smaller than minimum
0284     if (top.size() < seedFilterState.nTopSeedConf) {
0285       return;
0286     }
0287   }
0288 
0289   std::vector<const internal_sp_t *> top_valid;
0290   std::vector<float> curvatures;
0291   std::vector<float> impactParameters;
0292 
0293   // contains parameters required to calculate circle with linear equation
0294   // ...for bottom-middle
0295   std::vector<LinCircle> linCircleBottom;
0296   // ...for middle-top
0297   std::vector<LinCircle> linCircleTop;
0298 
0299   // transform coordinates
0300   transformCoordinates(spacePointData, bottom, middle, true, linCircleBottom);
0301   transformCoordinates(spacePointData, top, middle, false, linCircleTop);
0302 
0303   // sort: make index vector
0304   std::vector<std::size_t> sorted_bottoms(linCircleBottom.size());
0305   for (std::size_t i(0); i < sorted_bottoms.size(); ++i) {
0306     sorted_bottoms[i] = i;
0307   }
0308 
0309   std::vector<std::size_t> sorted_tops(linCircleTop.size());
0310   for (std::size_t i(0); i < sorted_tops.size(); ++i) {
0311     sorted_tops[i] = i;
0312   }
0313 
0314   std::sort(
0315       sorted_bottoms.begin(), sorted_bottoms.end(),
0316       [&linCircleBottom](const std::size_t a, const std::size_t b) -> bool {
0317         return linCircleBottom[a].cotTheta < linCircleBottom[b].cotTheta;
0318       });
0319 
0320   std::sort(sorted_tops.begin(), sorted_tops.end(),
0321             [&linCircleTop](const std::size_t a, const std::size_t b) -> bool {
0322               return linCircleTop[a].cotTheta < linCircleTop[b].cotTheta;
0323             });
0324 
0325   std::vector<float> tanMT;
0326   tanMT.reserve(top.size());
0327 
0328   std::size_t numTopSP = top.size();
0329   for (std::size_t t = 0; t < numTopSP; t++) {
0330     tanMT.push_back(
0331         std::atan2(top[t]->radius() - middle.radius(), top[t]->z() - zM));
0332   }
0333 
0334   std::size_t t0 = 0;
0335 
0336   for (const std::size_t b : sorted_bottoms) {
0337     // break if we reached the last top SP
0338     if (t0 == numTopSP) {
0339       break;
0340     }
0341 
0342     auto lb = linCircleBottom[b];
0343 
0344     // 1+(cot^2(theta)) = 1/sin^2(theta)
0345     float iSinTheta2 = (1. + lb.cotTheta * lb.cotTheta);
0346     float sigmaSquaredPtDependent = iSinTheta2 * options.sigmapT2perRadius;
0347     // calculate max scattering for min momentum at the seed's theta angle
0348     // scaling scatteringAngle^2 by sin^2(theta) to convert pT^2 to p^2
0349     // accurate would be taking 1/atan(thetaBottom)-1/atan(thetaTop) <
0350     // scattering
0351     // but to avoid trig functions we approximate cot by scaling by
0352     // 1/sin^4(theta)
0353     // resolving with pT to p scaling --> only divide by sin^2(theta)
0354     // max approximation error for allowed scattering angles of 0.04 rad at
0355     // eta=infinity: ~8.5%
0356     float scatteringInRegion2 = options.multipleScattering2 * iSinTheta2;
0357 
0358     // minimum number of compatible top SPs to trigger the filter for a certain
0359     // middle bottom pair if seedConfirmation is false we always ask for at
0360     // least one compatible top to trigger the filter
0361     std::size_t minCompatibleTopSPs = 2;
0362     if (!m_config.seedConfirmation ||
0363         bottom[b]->radius() > seedFilterState.rMaxSeedConf) {
0364       minCompatibleTopSPs = 1;
0365     }
0366     if (m_config.seedConfirmation && seedFilterState.numQualitySeeds) {
0367       minCompatibleTopSPs++;
0368     }
0369 
0370     // clear all vectors used in each inner for loop
0371     top_valid.clear();
0372     curvatures.clear();
0373     impactParameters.clear();
0374 
0375     float tanLM = std::atan2(rM - bottom[b]->radius(), zM - bottom[b]->z());
0376 
0377     for (std::size_t index_t = t0; index_t < numTopSP; index_t++) {
0378       const std::size_t t = sorted_tops[index_t];
0379       auto lt = linCircleTop[t];
0380 
0381       if (std::abs(tanLM - tanMT[t]) > 0.005) {
0382         continue;
0383       }
0384 
0385       // add errors of spB-spM and spM-spT pairs and add the correlation term
0386       // for errors on spM
0387       float error2 = lt.Er + lb.Er +
0388                      2 * (lb.cotTheta * lt.cotTheta * varianceRM + varianceZM) *
0389                          lb.iDeltaR * lt.iDeltaR;
0390 
0391       float deltaCotTheta = lb.cotTheta - lt.cotTheta;
0392       float deltaCotTheta2 = deltaCotTheta * deltaCotTheta;
0393 
0394       // Apply a cut on the compatibility between the r-z slope of the two
0395       // seed segments. This is done by comparing the squared difference
0396       // between slopes, and comparing to the squared uncertainty in this
0397       // difference - we keep a seed if the difference is compatible within
0398       // the assumed uncertainties. The uncertainties get contribution from
0399       // the  space-point-related squared error (error2) and a scattering term
0400       // calculated assuming the minimum pt we expect to reconstruct
0401       // (scatteringInRegion2). This assumes gaussian error propagation which
0402       // allows just adding the two errors if they are uncorrelated (which is
0403       // fair for scattering and measurement uncertainties)
0404       if (deltaCotTheta2 > (error2 + scatteringInRegion2)) {
0405         // skip top SPs based on cotTheta sorting when producing triplets
0406         // break if cotTheta from bottom SP < cotTheta from top SP because
0407         // the SP are sorted by cotTheta
0408         if (deltaCotTheta < 0) {
0409           break;
0410         }
0411         t0 = index_t + 1;
0412         continue;
0413       }
0414 
0415       float dU = lt.U - lb.U;
0416 
0417       // A and B are evaluated as a function of the circumference parameters
0418       // x_0 and y_0
0419       float A = (lt.V - lb.V) / dU;
0420       float S2 = 1. + A * A;
0421       float B = lb.V - A * lb.U;
0422       float B2 = B * B;
0423       // sqrt(S2)/B = 2 * helixradius
0424       // calculated radius must not be smaller than minimum radius
0425       if (S2 < B2 * options.minHelixDiameter2) {
0426         continue;
0427       }
0428 
0429       // 1/helixradius: (B/sqrt(S2))*2 (we leave everything squared)
0430       // convert p(T) to p scaling by sin^2(theta) AND scale by 1/sin^4(theta)
0431       // from rad to deltaCotTheta
0432       float p2scatterSigma = B2 / S2 * sigmaSquaredPtDependent;
0433       if (!std::isinf(m_config.maxPtScattering)) {
0434         // if pT > maxPtScattering, calculate allowed scattering angle using
0435         // maxPtScattering instead of pt.
0436         if (B2 == 0 || options.pTPerHelixRadius * std::sqrt(S2 / B2) >
0437                            2. * m_config.maxPtScattering) {
0438           float pTscatterSigma =
0439               (m_config.highland / m_config.maxPtScattering) *
0440               m_config.sigmaScattering;
0441           p2scatterSigma = pTscatterSigma * pTscatterSigma * iSinTheta2;
0442         }
0443       }
0444       // if deltaTheta larger than allowed scattering for calculated pT, skip
0445       if (deltaCotTheta2 > (error2 + p2scatterSigma)) {
0446         if (deltaCotTheta < 0) {
0447           break;
0448         }
0449         t0 = index_t;
0450         continue;
0451       }
0452 
0453       // A and B allow calculation of impact params in U/V plane with linear
0454       // function
0455       // (in contrast to having to solve a quadratic function in x/y plane)
0456       float Im = std::abs((A - B * rM) * rM);
0457 
0458       if (Im <= m_config.impactMax) {
0459         top_valid.push_back(top[t]);
0460         // inverse diameter is signed depending on if the curvature is
0461         // positive/negative in phi
0462         curvatures.push_back(B / std::sqrt(S2));
0463         impactParameters.push_back(Im);
0464       }
0465     }
0466 
0467     // continue if number of top SPs is smaller than minimum required for filter
0468     if (top_valid.size() < minCompatibleTopSPs) {
0469       continue;
0470     }
0471 
0472     seedFilterState.zOrigin = zM - rM * lb.cotTheta;
0473 
0474     m_config.seedFilter->filterSeeds_2SpFixed(
0475         spacePointData, *bottom[b], middle, top_valid, curvatures,
0476         impactParameters, seedFilterState, candidates_collector);
0477 
0478   }  // loop on bottoms
0479 }
0480 
0481 template <typename external_spacepoint_t>
0482 template <typename output_container_t>
0483 void SeedFinderOrthogonal<external_spacepoint_t>::processFromMiddleSP(
0484     const SeedFinderOptions &options, const tree_t &tree,
0485     output_container_t &out_cont, const typename tree_t::pair_t &middle_p,
0486     Acts::SpacePointData &spacePointData) const {
0487   using range_t = typename tree_t::range_t;
0488   internal_sp_t &middle = *middle_p.second;
0489 
0490   /*
0491    * Prepare four output vectors for seed candidates:
0492    *
0493    * bottom_lh_v denotes the candidates bottom seed points, assuming that the
0494    * track has monotonically _increasing_ z position. bottom_hl_v denotes the
0495    * candidate bottom points assuming that the track has monotonically
0496    * _decreasing_ z position. top_lh_v are the candidate top points for an
0497    * increasing z track, and top_hl_v are the candidate top points for a
0498    * decreasing z track.
0499    */
0500   std::vector<internal_sp_t *> bottom_lh_v, bottom_hl_v, top_lh_v, top_hl_v;
0501 
0502   /*
0503    * Storage for seed candidates
0504    */
0505   std::size_t max_num_quality_seeds_per_spm =
0506       m_config.seedFilter->getSeedFilterConfig().maxQualitySeedsPerSpMConf;
0507   std::size_t max_num_seeds_per_spm =
0508       m_config.seedFilter->getSeedFilterConfig().maxSeedsPerSpMConf;
0509 
0510   CandidatesForMiddleSp<const InternalSpacePoint<external_spacepoint_t>>
0511       candidates_collector;
0512   candidates_collector.setMaxElements(max_num_seeds_per_spm,
0513                                       max_num_quality_seeds_per_spm);
0514 
0515   /*
0516    * Calculate the search ranges for bottom and top candidates for this middle
0517    * space point.
0518    */
0519   range_t bottom_r = validTupleOrthoRangeHL(middle);
0520   range_t top_r = validTupleOrthoRangeLH(middle);
0521 
0522   /*
0523    * Calculate the value of cot(θ) for this middle spacepoint.
0524    */
0525   float myCotTheta =
0526       std::max(std::abs(middle.z() / middle.radius()), m_config.cotThetaMax);
0527 
0528   /*
0529    * Calculate the maximum Δr, given that we have already constrained our
0530    * search space.
0531    */
0532   float deltaRMaxTop = top_r[DimR].max() - middle.radius();
0533   float deltaRMaxBottom = middle.radius() - bottom_r[DimR].min();
0534 
0535   /*
0536    * Create the search range for the bottom spacepoint assuming a
0537    * monotonically increasing z track, by calculating the minimum z value from
0538    * the cot(θ), and by setting the maximum to the z position of the middle
0539    * spacepoint - if the z position is higher than the middle point, then it
0540    * would be a decreasing z track!
0541    */
0542   range_t bottom_lh_r = bottom_r;
0543   bottom_lh_r[DimZ].shrink(middle.z() - myCotTheta * deltaRMaxBottom,
0544                            middle.z());
0545 
0546   /*
0547    * Calculate the search ranges for the other four sets of points in a
0548    * similar fashion.
0549    */
0550   range_t top_lh_r = top_r;
0551   top_lh_r[DimZ].shrink(middle.z(), middle.z() + myCotTheta * deltaRMaxTop);
0552 
0553   range_t bottom_hl_r = bottom_r;
0554   bottom_hl_r[DimZ].shrink(middle.z(),
0555                            middle.z() + myCotTheta * deltaRMaxBottom);
0556   range_t top_hl_r = top_r;
0557   top_hl_r[DimZ].shrink(middle.z() - myCotTheta * deltaRMaxTop, middle.z());
0558 
0559   /*
0560    * Make sure the candidate vectors are clear, in case we've used them
0561    * before.
0562    */
0563   bottom_lh_v.clear();
0564   bottom_hl_v.clear();
0565   top_lh_v.clear();
0566   top_hl_v.clear();
0567 
0568   /*
0569    * Now, we will actually search for the spaces. Remembering that we combine
0570    * bottom and top candidates for increasing and decreasing tracks
0571    * separately, we will first check whether both the search ranges for
0572    * increasing tracks are not degenerate - if they are, we will never find
0573    * any seeds and we do not need to bother doing the search.
0574    */
0575   if (!bottom_lh_r.degenerate() && !top_lh_r.degenerate()) {
0576     /*
0577      * Search the trees for points that lie in the given search range.
0578      */
0579     tree.rangeSearchMapDiscard(top_lh_r,
0580                                [this, &options, &middle, &top_lh_v](
0581                                    const typename tree_t::coordinate_t &,
0582                                    const typename tree_t::value_t &top) {
0583                                  if (validTuple(options, *top, middle, true)) {
0584                                    top_lh_v.push_back(top);
0585                                  }
0586                                });
0587   }
0588 
0589   /*
0590    * Perform the same search for candidate bottom spacepoints, but for
0591    * monotonically decreasing z tracks.
0592    */
0593   if (!bottom_hl_r.degenerate() && !top_hl_r.degenerate()) {
0594     tree.rangeSearchMapDiscard(top_hl_r,
0595                                [this, &options, &middle, &top_hl_v](
0596                                    const typename tree_t::coordinate_t &,
0597                                    const typename tree_t::value_t &top) {
0598                                  if (validTuple(options, middle, *top, false)) {
0599                                    top_hl_v.push_back(top);
0600                                  }
0601                                });
0602   }
0603 
0604   // apply cut on the number of top SP if seedConfirmation is true
0605   SeedFilterState seedFilterState;
0606   bool search_bot_hl = true;
0607   bool search_bot_lh = true;
0608   if (m_config.seedConfirmation) {
0609     // check if middle SP is in the central or forward region
0610     SeedConfirmationRangeConfig seedConfRange =
0611         (middle.z() > m_config.centralSeedConfirmationRange.zMaxSeedConf ||
0612          middle.z() < m_config.centralSeedConfirmationRange.zMinSeedConf)
0613             ? m_config.forwardSeedConfirmationRange
0614             : m_config.centralSeedConfirmationRange;
0615     // set the minimum number of top SP depending on whether the middle SP is
0616     // in the central or forward region
0617     seedFilterState.nTopSeedConf = middle.radius() > seedConfRange.rMaxSeedConf
0618                                        ? seedConfRange.nTopForLargeR
0619                                        : seedConfRange.nTopForSmallR;
0620     // set max bottom radius for seed confirmation
0621     seedFilterState.rMaxSeedConf = seedConfRange.rMaxSeedConf;
0622     // continue if number of top SPs is smaller than minimum
0623     if (top_lh_v.size() < seedFilterState.nTopSeedConf) {
0624       search_bot_lh = false;
0625     }
0626     if (top_hl_v.size() < seedFilterState.nTopSeedConf) {
0627       search_bot_hl = false;
0628     }
0629   }
0630 
0631   /*
0632    * Next, we perform a search for bottom candidates in increasing z tracks,
0633    * which only makes sense if we found any bottom candidates.
0634    */
0635   if (!top_lh_v.empty() && search_bot_lh) {
0636     tree.rangeSearchMapDiscard(
0637         bottom_lh_r, [this, &options, &middle, &bottom_lh_v](
0638                          const typename tree_t::coordinate_t &,
0639                          const typename tree_t::value_t &bottom) {
0640           if (validTuple(options, *bottom, middle, false)) {
0641             bottom_lh_v.push_back(bottom);
0642           }
0643         });
0644   }
0645 
0646   /*
0647    * And repeat for the top spacepoints for decreasing z tracks!
0648    */
0649   if (!top_hl_v.empty() && search_bot_hl) {
0650     tree.rangeSearchMapDiscard(
0651         bottom_hl_r, [this, &options, &middle, &bottom_hl_v](
0652                          const typename tree_t::coordinate_t &,
0653                          const typename tree_t::value_t &bottom) {
0654           if (validTuple(options, middle, *bottom, true)) {
0655             bottom_hl_v.push_back(bottom);
0656           }
0657         });
0658   }
0659 
0660   /*
0661    * If we have candidates for increasing z tracks, we try to combine them.
0662    */
0663   if (!bottom_lh_v.empty() && !top_lh_v.empty()) {
0664     filterCandidates(options, middle, bottom_lh_v, top_lh_v, seedFilterState,
0665                      candidates_collector, spacePointData);
0666   }
0667   /*
0668    * Try to combine candidates for decreasing z tracks.
0669    */
0670   if (!bottom_hl_v.empty() && !top_hl_v.empty()) {
0671     filterCandidates(options, middle, bottom_hl_v, top_hl_v, seedFilterState,
0672                      candidates_collector, spacePointData);
0673   }
0674   /*
0675    * Run a seed filter, just like in other seeding algorithms.
0676    */
0677   if ((!bottom_lh_v.empty() && !top_lh_v.empty()) ||
0678       (!bottom_hl_v.empty() && !top_hl_v.empty())) {
0679     m_config.seedFilter->filterSeeds_1SpFixed(
0680         spacePointData, candidates_collector, seedFilterState.numQualitySeeds,
0681         std::back_inserter(out_cont));
0682   }
0683 }
0684 
0685 template <typename external_spacepoint_t>
0686 auto SeedFinderOrthogonal<external_spacepoint_t>::createTree(
0687     const std::vector<internal_sp_t *> &spacePoints) const -> tree_t {
0688   std::vector<typename tree_t::pair_t> points;
0689 
0690   /*
0691    * For every input point, we create a coordinate-pointer pair, which we then
0692    * linearly pass to the k-d tree constructor. That constructor will take
0693    * care of sorting the pairs and splitting the space.
0694    */
0695   for (internal_sp_t *sp : spacePoints) {
0696     typename tree_t::coordinate_t point;
0697 
0698     point[DimPhi] = sp->phi();
0699     point[DimR] = sp->radius();
0700     point[DimZ] = sp->z();
0701 
0702     points.emplace_back(point, sp);
0703   }
0704 
0705   return tree_t(std::move(points));
0706 }
0707 
0708 template <typename external_spacepoint_t>
0709 template <typename input_container_t, typename output_container_t,
0710           typename callable_t>
0711 void SeedFinderOrthogonal<external_spacepoint_t>::createSeeds(
0712     const Acts::SeedFinderOptions &options,
0713     const input_container_t &spacePoints, output_container_t &out_cont,
0714     callable_t &&extract_coordinates) const {
0715   if (!options.isInInternalUnits) {
0716     throw std::runtime_error(
0717         "SeedFinderOptions not in ACTS internal units in "
0718         "SeedFinderOrthogonal");
0719   }
0720   /*
0721    * The template parameters we accept are a little too generic, so we want to
0722    * run some basic checks to make sure the containers have the correct value
0723    * types.
0724    */
0725   static_assert(std::is_same_v<typename output_container_t::value_type,
0726                                Seed<external_spacepoint_t>>,
0727                 "Output iterator container type must accept seeds.");
0728   static_assert(std::is_same_v<typename input_container_t::value_type,
0729                                const external_spacepoint_t *>,
0730                 "Input container must contain external spacepoints.");
0731 
0732   /*
0733    * Sadly, for the time being, we will need to construct our internal space
0734    * points on the heap. This adds some additional overhead and work. Here we
0735    * take each external spacepoint, allocate a corresponding internal space
0736    * point, and save it in a vector.
0737    */
0738   Acts::Extent rRangeSPExtent;
0739   std::size_t counter = 0;
0740   std::vector<internal_sp_t *> internalSpacePoints;
0741   Acts::SpacePointData spacePointData;
0742   spacePointData.resize(spacePoints.size());
0743 
0744   for (const external_spacepoint_t *p : spacePoints) {
0745     auto [position, variance, time] = extract_coordinates(p);
0746     internalSpacePoints.push_back(new InternalSpacePoint<external_spacepoint_t>(
0747         counter++, *p, position, options.beamPos, variance, time));
0748     // store x,y,z values in extent
0749     rRangeSPExtent.extend(position);
0750   }
0751   // variable middle SP radial region of interest
0752   const Acts::Range1D<float> rMiddleSPRange(
0753       std::floor(rRangeSPExtent.min(Acts::binR) / 2) * 2 +
0754           m_config.deltaRMiddleMinSPRange,
0755       std::floor(rRangeSPExtent.max(Acts::binR) / 2) * 2 -
0756           m_config.deltaRMiddleMaxSPRange);
0757 
0758   /*
0759    * Construct the k-d tree from these points. Note that this not consume or
0760    * take ownership of the points.
0761    */
0762   tree_t tree = createTree(internalSpacePoints);
0763   /*
0764    * Run the seeding algorithm by iterating over all the points in the tree
0765    * and seeing what happens if we take them to be our middle spacepoint.
0766    */
0767   for (const typename tree_t::pair_t &middle_p : tree) {
0768     internal_sp_t &middle = *middle_p.second;
0769     auto rM = middle.radius();
0770 
0771     /*
0772      * Cut: Ensure that the middle spacepoint lies within a valid r-region for
0773      * middle points.
0774      */
0775     if (m_config.useVariableMiddleSPRange) {
0776       if (rM < rMiddleSPRange.min() || rM > rMiddleSPRange.max()) {
0777         continue;
0778       }
0779     } else {
0780       if (rM > m_config.rMaxMiddle || rM < m_config.rMinMiddle) {
0781         continue;
0782       }
0783     }
0784 
0785     // remove all middle SPs outside phi and z region of interest
0786     if (middle.z() < m_config.zOutermostLayers.first ||
0787         middle.z() > m_config.zOutermostLayers.second) {
0788       continue;
0789     }
0790     float spPhi = middle.phi();
0791     if (spPhi > m_config.phiMax || spPhi < m_config.phiMin) {
0792       continue;
0793     }
0794 
0795     processFromMiddleSP(options, tree, out_cont, middle_p, spacePointData);
0796   }
0797 
0798   /*
0799    * Don't forget to get rid of all the spacepoints we just allocated!
0800    */
0801   for (const internal_sp_t *p : internalSpacePoints) {
0802     delete p;
0803   }
0804 }
0805 
0806 template <typename external_spacepoint_t>
0807 template <typename input_container_t, typename callable_t>
0808 std::vector<Seed<external_spacepoint_t>>
0809 SeedFinderOrthogonal<external_spacepoint_t>::createSeeds(
0810     const Acts::SeedFinderOptions &options,
0811     const input_container_t &spacePoints,
0812     callable_t &&extract_coordinates) const {
0813   std::vector<seed_t> r;
0814   createSeeds(options, spacePoints, r,
0815               std::forward<callable_t>(extract_coordinates));
0816   return r;
0817 }
0818 
0819 }  // namespace Acts