Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-07-11 07:49:56

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