Back to home page

EIC code displayed by LXR

 
 

    


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

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