Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-12-16 09:23:19

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/Seeding2/TripletSeedFinder.hpp"
0010 
0011 #include "Acts/EventData/SpacePointContainer2.hpp"
0012 #include "Acts/Utilities/MathHelpers.hpp"
0013 #include "Acts/Utilities/Zip.hpp"
0014 
0015 #include <ranges>
0016 
0017 #include <Eigen/Dense>
0018 #include <boost/mp11.hpp>
0019 #include <boost/mp11/algorithm.hpp>
0020 
0021 namespace Acts {
0022 
0023 namespace {
0024 
0025 /// Check the compatibility of strip space point coordinates in xyz assuming
0026 /// the Bottom-Middle direction with the strip measurement details
0027 bool stripCoordinateCheck(float tolerance, const ConstSpacePointProxy2& sp,
0028                           const std::array<float, 3>& spacePointPosition,
0029                           std::array<float, 3>& outputCoordinates) {
0030   const std::array<float, 3>& topStripVector = sp.topStripVector();
0031   const std::array<float, 3>& bottomStripVector = sp.bottomStripVector();
0032   const std::array<float, 3>& stripCenterDistance = sp.stripCenterDistance();
0033 
0034   // cross product between top strip vector and spacePointPosition
0035   const std::array<float, 3> d1 = {
0036       topStripVector[1] * spacePointPosition[2] -
0037           topStripVector[2] * spacePointPosition[1],
0038       topStripVector[2] * spacePointPosition[0] -
0039           topStripVector[0] * spacePointPosition[2],
0040       topStripVector[0] * spacePointPosition[1] -
0041           topStripVector[1] * spacePointPosition[0]};
0042 
0043   // scalar product between bottom strip vector and d1
0044   const float bd1 = bottomStripVector[0] * d1[0] +
0045                     bottomStripVector[1] * d1[1] + bottomStripVector[2] * d1[2];
0046 
0047   // compatibility check using distance between strips to evaluate if
0048   // spacepointPosition is inside the bottom detector element
0049   const float s1 = stripCenterDistance[0] * d1[0] +
0050                    stripCenterDistance[1] * d1[1] +
0051                    stripCenterDistance[2] * d1[2];
0052   if (std::abs(s1) > std::abs(bd1) * tolerance) {
0053     return false;
0054   }
0055 
0056   // cross product between bottom strip vector and spacePointPosition
0057   const std::array<float, 3> d0 = {
0058       bottomStripVector[1] * spacePointPosition[2] -
0059           bottomStripVector[2] * spacePointPosition[1],
0060       bottomStripVector[2] * spacePointPosition[0] -
0061           bottomStripVector[0] * spacePointPosition[2],
0062       bottomStripVector[0] * spacePointPosition[1] -
0063           bottomStripVector[1] * spacePointPosition[0]};
0064 
0065   // compatibility check using distance between strips to evaluate if
0066   // spacePointPosition is inside the top detector element
0067   float s0 = stripCenterDistance[0] * d0[0] + stripCenterDistance[1] * d0[1] +
0068              stripCenterDistance[2] * d0[2];
0069   if (std::abs(s0) > std::abs(bd1) * tolerance) {
0070     return false;
0071   }
0072 
0073   // if arrive here spacePointPosition is compatible with strip directions and
0074   // detector elements
0075 
0076   const std::array<float, 3>& topStripCenter = sp.topStripCenter();
0077 
0078   // spacePointPosition corrected with respect to the top strip position and
0079   // direction and the distance between the strips
0080   s0 = s0 / bd1;
0081   outputCoordinates[0] = topStripCenter[0] + topStripVector[0] * s0;
0082   outputCoordinates[1] = topStripCenter[1] + topStripVector[1] * s0;
0083   outputCoordinates[2] = topStripCenter[2] + topStripVector[2] * s0;
0084   return true;
0085 }
0086 
0087 template <bool useStripInfo, bool sortedByCotTheta>
0088 class Impl final : public TripletSeedFinder {
0089  public:
0090   explicit Impl(const DerivedConfig& config) : m_cfg(config) {}
0091 
0092   const DerivedConfig& config() const override { return m_cfg; }
0093 
0094   template <typename TopDoublets>
0095   void createPixelTripletTopCandidates(
0096       const ConstSpacePointProxy2& spM,
0097       const DoubletsForMiddleSp::Proxy& bottomDoublet, TopDoublets& topDoublets,
0098       TripletTopCandidates& tripletTopCandidates) const {
0099     const float rM = spM.zr()[1];
0100     const float varianceZM = spM.varianceZ();
0101     const float varianceRM = spM.varianceR();
0102 
0103     // Reserve enough space, in case current capacity is too little
0104     tripletTopCandidates.reserve(tripletTopCandidates.size() +
0105                                  topDoublets.size());
0106 
0107     const float cotThetaB = bottomDoublet.cotTheta();
0108     const float erB = bottomDoublet.er();
0109     const float iDeltaRB = bottomDoublet.iDeltaR();
0110     const float Ub = bottomDoublet.u();
0111     const float Vb = bottomDoublet.v();
0112 
0113     // 1+(cot^2(theta)) = 1/sin^2(theta)
0114     const float iSinTheta2 = 1 + cotThetaB * cotThetaB;
0115     const float sigmaSquaredPtDependent = iSinTheta2 * m_cfg.sigmapT2perRadius;
0116     // calculate max scattering for min momentum at the seed's theta angle
0117     // scaling scatteringAngle^2 by sin^2(theta) to convert pT^2 to p^2
0118     // accurate would be taking 1/atan(thetaBottom)-1/atan(thetaTop) <
0119     // scattering
0120     // but to avoid trig functions we approximate cot by scaling by
0121     // 1/sin^4(theta)
0122     // resolving with pT to p scaling --> only divide by sin^2(theta)
0123     // max approximation error for allowed scattering angles of 0.04 rad at
0124     // eta=infinity: ~8.5%
0125     const float scatteringInRegion2 = m_cfg.multipleScattering2 * iSinTheta2;
0126 
0127     std::size_t topDoubletOffset = 0;
0128     for (auto [topDoublet, topDoubletIndex] :
0129          zip(topDoublets, std::ranges::iota_view<std::size_t, std::size_t>(
0130                               0, topDoublets.size()))) {
0131       const SpacePointIndex2 spT = topDoublet.spacePointIndex();
0132       const float cotThetaT = topDoublet.cotTheta();
0133 
0134       // use geometric average
0135       const float cotThetaAvg2 = cotThetaB * cotThetaT;
0136 
0137       // add errors of spB-spM and spM-spT pairs and add the correlation term
0138       // for errors on spM
0139       const float error2 = topDoublet.er() + erB +
0140                            2 * (cotThetaAvg2 * varianceRM + varianceZM) *
0141                                iDeltaRB * topDoublet.iDeltaR();
0142 
0143       const float deltaCotTheta = cotThetaB - cotThetaT;
0144       const float deltaCotTheta2 = deltaCotTheta * deltaCotTheta;
0145 
0146       // Apply a cut on the compatibility between the r-z slope of the two
0147       // seed segments. This is done by comparing the squared difference
0148       // between slopes, and comparing to the squared uncertainty in this
0149       // difference - we keep a seed if the difference is compatible within
0150       // the assumed uncertainties. The uncertainties get contribution from
0151       // the  space-point-related squared error (error2) and a scattering term
0152       // calculated assuming the minimum pt we expect to reconstruct
0153       // (scatteringInRegion2). This assumes gaussian error propagation which
0154       // allows just adding the two errors if they are uncorrelated (which is
0155       // fair for scattering and measurement uncertainties)
0156       if (deltaCotTheta2 > error2 + scatteringInRegion2) {
0157         if constexpr (sortedByCotTheta) {
0158           // skip top SPs based on cotTheta sorting when producing triplets
0159           // break if cotTheta from bottom SP < cotTheta from top SP because
0160           // the SP are sorted by cotTheta
0161           if (cotThetaB < cotThetaT) {
0162             break;
0163           }
0164           topDoubletOffset = topDoubletIndex + 1;
0165         }
0166         continue;
0167       }
0168 
0169       const float dU = topDoublet.u() - Ub;
0170       // protects against division by 0
0171       if (dU == 0) {
0172         continue;
0173       }
0174       // A and B are evaluated as a function of the circumference parameters
0175       // x_0 and y_0
0176       const float A = (topDoublet.v() - Vb) / dU;
0177       const float S2 = 1 + A * A;
0178       const float B = Vb - A * Ub;
0179       const float B2 = B * B;
0180 
0181       // sqrt(S2)/B = 2 * helixradius
0182       // calculated radius must not be smaller than minimum radius
0183       if (S2 < B2 * m_cfg.minHelixDiameter2) {
0184         continue;
0185       }
0186 
0187       // refinement of the cut on the compatibility between the r-z slope of
0188       // the two seed segments using a scattering term scaled by the actual
0189       // measured pT (p2scatterSigma)
0190       const float iHelixDiameter2 = B2 / S2;
0191       // convert p(T) to p scaling by sin^2(theta) AND scale by 1/sin^4(theta)
0192       // from rad to deltaCotTheta
0193       const float p2scatterSigma = iHelixDiameter2 * sigmaSquaredPtDependent;
0194       // if deltaTheta larger than allowed scattering for calculated pT, skip
0195       if (deltaCotTheta2 > error2 + p2scatterSigma) {
0196         if constexpr (sortedByCotTheta) {
0197           if (cotThetaB < cotThetaT) {
0198             break;
0199           }
0200           topDoubletOffset = topDoubletIndex;
0201         }
0202         continue;
0203       }
0204 
0205       // A and B allow calculation of impact params in U/V plane with linear
0206       // function
0207       // (in contrast to having to solve a quadratic function in x/y plane)
0208       const float im = std::abs((A - B * rM) * rM);
0209       if (im > m_cfg.impactMax) {
0210         continue;
0211       }
0212 
0213       // inverse diameter is signed depending on if the curvature is
0214       // positive/negative in phi
0215       tripletTopCandidates.emplace_back(spT, B / std::sqrt(S2), im);
0216     }  // loop on tops
0217 
0218     if constexpr (sortedByCotTheta) {
0219       // remove the top doublets that were skipped due to cotTheta sorting
0220       topDoublets = topDoublets.subrange(topDoubletOffset);
0221     }
0222   }
0223 
0224   template <typename TopDoublets>
0225   void createStripTripletTopCandidates(
0226       const SpacePointContainer2& spacePoints, const ConstSpacePointProxy2& spM,
0227       const DoubletsForMiddleSp::Proxy& bottomDoublet,
0228       const TopDoublets& topDoublets,
0229       TripletTopCandidates& tripletTopCandidates) const {
0230     const float rM = spM.zr()[1];
0231     const float cosPhiM = spM.xy()[0] / rM;
0232     const float sinPhiM = spM.xy()[1] / rM;
0233     const float varianceZM = spM.varianceZ();
0234     const float varianceRM = spM.varianceR();
0235 
0236     // Reserve enough space, in case current capacity is too little
0237     tripletTopCandidates.reserve(tripletTopCandidates.size() +
0238                                  topDoublets.size());
0239 
0240     float cotThetaB = bottomDoublet.cotTheta();
0241     const float erB = bottomDoublet.er();
0242     const float iDeltaRB = bottomDoublet.iDeltaR();
0243     const float Vb = bottomDoublet.v();
0244     const float Ub = bottomDoublet.u();
0245 
0246     // 1+(cot^2(theta)) = 1/sin^2(theta)
0247     const float iSinTheta2 = 1 + cotThetaB * cotThetaB;
0248     const float sigmaSquaredPtDependent = iSinTheta2 * m_cfg.sigmapT2perRadius;
0249     // calculate max scattering for min momentum at the seed's theta angle
0250     // scaling scatteringAngle^2 by sin^2(theta) to convert pT^2 to p^2
0251     // accurate would be taking 1/atan(thetaBottom)-1/atan(thetaTop) <
0252     // scattering
0253     // but to avoid trig functions we approximate cot by scaling by
0254     // 1/sin^4(theta)
0255     // resolving with pT to p scaling --> only divide by sin^2(theta)
0256     // max approximation error for allowed scattering angles of 0.04 rad at
0257     // eta=infinity: ~8.5%
0258     const float scatteringInRegion2 = m_cfg.multipleScattering2 * iSinTheta2;
0259 
0260     const float sinTheta = 1 / std::sqrt(iSinTheta2);
0261     const float cosTheta = cotThetaB * sinTheta;
0262 
0263     // coordinate transformation and checks for middle spacepoint
0264     // x and y terms for the rotation from UV to XY plane
0265     const std::array<float, 2> rotationTermsUVtoXY = {cosPhiM * sinTheta,
0266                                                       sinPhiM * sinTheta};
0267 
0268     for (auto topDoublet : topDoublets) {
0269       // protects against division by 0
0270       float dU = topDoublet.u() - Ub;
0271       if (dU == 0) {
0272         continue;
0273       }
0274       // A and B are evaluated as a function of the circumference parameters
0275       // x_0 and y_0
0276       const float A0 = (topDoublet.v() - Vb) / dU;
0277 
0278       const float zPositionMiddle = cosTheta * std::sqrt(1 + A0 * A0);
0279 
0280       // position of Middle SP converted from UV to XY assuming cotTheta
0281       // evaluated from the Bottom and Middle SPs double
0282       const std::array<float, 3> positionMiddle = {
0283           rotationTermsUVtoXY[0] - rotationTermsUVtoXY[1] * A0,
0284           rotationTermsUVtoXY[0] * A0 + rotationTermsUVtoXY[1],
0285           zPositionMiddle};
0286 
0287       std::array<float, 3> rMTransf{};
0288       if (!stripCoordinateCheck(m_cfg.toleranceParam, spM, positionMiddle,
0289                                 rMTransf)) {
0290         continue;
0291       }
0292 
0293       // coordinate transformation and checks for bottom spacepoint
0294       const float B0 = 2 * (Vb - A0 * Ub);
0295       const float Cb = 1 - B0 * bottomDoublet.y();
0296       const float Sb = A0 + B0 * bottomDoublet.x();
0297       const std::array<float, 3> positionBottom = {
0298           rotationTermsUVtoXY[0] * Cb - rotationTermsUVtoXY[1] * Sb,
0299           rotationTermsUVtoXY[0] * Sb + rotationTermsUVtoXY[1] * Cb,
0300           zPositionMiddle};
0301 
0302       const ConstSpacePointProxy2 spB =
0303           spacePoints[bottomDoublet.spacePointIndex()];
0304       std::array<float, 3> rBTransf{};
0305       if (!stripCoordinateCheck(m_cfg.toleranceParam, spB, positionBottom,
0306                                 rBTransf)) {
0307         continue;
0308       }
0309 
0310       // coordinate transformation and checks for top spacepoint
0311       const float Ct = 1 - B0 * topDoublet.y();
0312       const float St = A0 + B0 * topDoublet.x();
0313       const std::array<float, 3> positionTop = {
0314           rotationTermsUVtoXY[0] * Ct - rotationTermsUVtoXY[1] * St,
0315           rotationTermsUVtoXY[0] * St + rotationTermsUVtoXY[1] * Ct,
0316           zPositionMiddle};
0317 
0318       const ConstSpacePointProxy2 spT =
0319           spacePoints[topDoublet.spacePointIndex()];
0320       std::array<float, 3> rTTransf{};
0321       if (!stripCoordinateCheck(m_cfg.toleranceParam, spT, positionTop,
0322                                 rTTransf)) {
0323         continue;
0324       }
0325 
0326       // bottom and top coordinates in the spM reference frame
0327       const float xB = rBTransf[0] - rMTransf[0];
0328       const float yB = rBTransf[1] - rMTransf[1];
0329       const float zB = rBTransf[2] - rMTransf[2];
0330       const float xT = rTTransf[0] - rMTransf[0];
0331       const float yT = rTTransf[1] - rMTransf[1];
0332       const float zT = rTTransf[2] - rMTransf[2];
0333 
0334       const float iDeltaRB2 = 1 / (xB * xB + yB * yB);
0335       const float iDeltaRT2 = 1 / (xT * xT + yT * yT);
0336 
0337       cotThetaB = -zB * std::sqrt(iDeltaRB2);
0338       const float cotThetaT = zT * std::sqrt(iDeltaRT2);
0339 
0340       // use arithmetic average
0341       const float averageCotTheta = 0.5f * (cotThetaB + cotThetaT);
0342       const float cotThetaAvg2 = averageCotTheta * averageCotTheta;
0343 
0344       // add errors of spB-spM and spM-spT pairs and add the correlation term
0345       // for errors on spM
0346       const float error2 = topDoublet.er() + erB +
0347                            2 * (cotThetaAvg2 * varianceRM + varianceZM) *
0348                                iDeltaRB * topDoublet.iDeltaR();
0349 
0350       const float deltaCotTheta = cotThetaB - cotThetaT;
0351       const float deltaCotTheta2 = deltaCotTheta * deltaCotTheta;
0352 
0353       // Apply a cut on the compatibility between the r-z slope of the two
0354       // seed segments. This is done by comparing the squared difference
0355       // between slopes, and comparing to the squared uncertainty in this
0356       // difference - we keep a seed if the difference is compatible within
0357       // the assumed uncertainties. The uncertainties get contribution from
0358       // the  space-point-related squared error (error2) and a scattering term
0359       // calculated assuming the minimum pt we expect to reconstruct
0360       // (scatteringInRegion2). This assumes gaussian error propagation which
0361       // allows just adding the two errors if they are uncorrelated (which is
0362       // fair for scattering and measurement uncertainties)
0363       if (deltaCotTheta2 > error2 + scatteringInRegion2) {
0364         // skip top SPs based on cotTheta sorting when producing triplets
0365         continue;
0366       }
0367 
0368       const float rMxy =
0369           std::sqrt(rMTransf[0] * rMTransf[0] + rMTransf[1] * rMTransf[1]);
0370       const float irMxy = 1 / rMxy;
0371       const float Ax = rMTransf[0] * irMxy;
0372       const float Ay = rMTransf[1] * irMxy;
0373 
0374       const float ub = (xB * Ax + yB * Ay) * iDeltaRB2;
0375       const float vb = (yB * Ax - xB * Ay) * iDeltaRB2;
0376       const float ut = (xT * Ax + yT * Ay) * iDeltaRT2;
0377       const float vt = (yT * Ax - xT * Ay) * iDeltaRT2;
0378 
0379       dU = ut - ub;
0380       // protects against division by 0
0381       if (dU == 0) {
0382         continue;
0383       }
0384       const float A = (vt - vb) / dU;
0385       const float S2 = 1 + A * A;
0386       const float B = vb - A * ub;
0387       const float B2 = B * B;
0388 
0389       // sqrt(S2)/B = 2 * helixradius
0390       // calculated radius must not be smaller than minimum radius
0391       if (S2 < B2 * m_cfg.minHelixDiameter2) {
0392         continue;
0393       }
0394 
0395       // refinement of the cut on the compatibility between the r-z slope of
0396       // the two seed segments using a scattering term scaled by the actual
0397       // measured pT (p2scatterSigma)
0398       const float iHelixDiameter2 = B2 / S2;
0399       // convert p(T) to p scaling by sin^2(theta) AND scale by 1/sin^4(theta)
0400       // from rad to deltaCotTheta
0401       const float p2scatterSigma = iHelixDiameter2 * sigmaSquaredPtDependent;
0402       // if deltaTheta larger than allowed scattering for calculated pT, skip
0403       if (deltaCotTheta2 > error2 + p2scatterSigma) {
0404         continue;
0405       }
0406 
0407       // A and B allow calculation of impact params in U/V plane with linear
0408       // function
0409       // (in contrast to having to solve a quadratic function in x/y plane)
0410       const float im = std::abs((A - B * rMxy) * rMxy);
0411       if (im > m_cfg.impactMax) {
0412         continue;
0413       }
0414 
0415       // inverse diameter is signed depending on if the curvature is
0416       // positive/negative in phi
0417       tripletTopCandidates.emplace_back(spT.index(), B / std::sqrt(S2), im);
0418     }  // loop on tops
0419   }
0420 
0421   void createTripletTopCandidates(
0422       const SpacePointContainer2& spacePoints, const ConstSpacePointProxy2& spM,
0423       const DoubletsForMiddleSp::Proxy& bottomDoublet,
0424       DoubletsForMiddleSp::Range& topDoublets,
0425       TripletTopCandidates& tripletTopCandidates) const override {
0426     if constexpr (useStripInfo) {
0427       createStripTripletTopCandidates(spacePoints, spM, bottomDoublet,
0428                                       topDoublets, tripletTopCandidates);
0429     } else {
0430       createPixelTripletTopCandidates(spM, bottomDoublet, topDoublets,
0431                                       tripletTopCandidates);
0432     }
0433   }
0434 
0435   void createTripletTopCandidates(
0436       const SpacePointContainer2& spacePoints, const ConstSpacePointProxy2& spM,
0437       const DoubletsForMiddleSp::Proxy& bottomDoublet,
0438       DoubletsForMiddleSp::Subset& topDoublets,
0439       TripletTopCandidates& tripletTopCandidates) const override {
0440     if constexpr (useStripInfo) {
0441       createStripTripletTopCandidates(spacePoints, spM, bottomDoublet,
0442                                       topDoublets, tripletTopCandidates);
0443     } else {
0444       createPixelTripletTopCandidates(spM, bottomDoublet, topDoublets,
0445                                       tripletTopCandidates);
0446     }
0447   }
0448 
0449   void createTripletTopCandidates(
0450       const SpacePointContainer2& spacePoints, const ConstSpacePointProxy2& spM,
0451       const DoubletsForMiddleSp::Proxy& bottomDoublet,
0452       DoubletsForMiddleSp::Subset2& topDoublets,
0453       TripletTopCandidates& tripletTopCandidates) const override {
0454     if constexpr (useStripInfo) {
0455       createStripTripletTopCandidates(spacePoints, spM, bottomDoublet,
0456                                       topDoublets, tripletTopCandidates);
0457     } else {
0458       createPixelTripletTopCandidates(spM, bottomDoublet, topDoublets,
0459                                       tripletTopCandidates);
0460     }
0461   }
0462 
0463  private:
0464   DerivedConfig m_cfg;
0465 };
0466 
0467 }  // namespace
0468 
0469 TripletSeedFinder::DerivedConfig::DerivedConfig(const Config& config,
0470                                                 float bFieldInZ_)
0471     : Config(config), bFieldInZ(bFieldInZ_) {
0472   using namespace Acts::UnitLiterals;
0473 
0474   // similar to `theta0Highland` in `Core/src/Material/Interactions.cpp`
0475   {
0476     const double xOverX0 = radLengthPerSeed;
0477     const double q2OverBeta2 = 1;  // q^2=1, beta^2~1
0478     // RPP2018 eq. 33.15 (treats beta and q² consistently)
0479     const double t = std::sqrt(xOverX0 * q2OverBeta2);
0480     // log((x/X0) * (q²/beta²)) = log((sqrt(x/X0) * (q/beta))²)
0481     //                          = 2 * log(sqrt(x/X0) * (q/beta))
0482     highland =
0483         static_cast<float>(13.6_MeV * t * (1.0 + 0.038 * 2 * std::log(t)));
0484   }
0485 
0486   const float maxScatteringAngle = highland / minPt;
0487   const float maxScatteringAngle2 = maxScatteringAngle * maxScatteringAngle;
0488 
0489   // bFieldInZ is in (pT/radius) natively, no need for conversion
0490   pTPerHelixRadius = bFieldInZ;
0491   minHelixDiameter2 = square(minPt * 2 / pTPerHelixRadius) * helixCutTolerance;
0492   const float pT2perRadius = square(highland / pTPerHelixRadius);
0493   sigmapT2perRadius = pT2perRadius * square(2 * sigmaScattering);
0494   multipleScattering2 = maxScatteringAngle2 * square(sigmaScattering);
0495 }
0496 
0497 std::unique_ptr<TripletSeedFinder> TripletSeedFinder::create(
0498     const DerivedConfig& config) {
0499   using BooleanOptions =
0500       boost::mp11::mp_list<std::bool_constant<false>, std::bool_constant<true>>;
0501 
0502   using UseStripInfoOptions = BooleanOptions;
0503   using SortedByCotThetaOptions = BooleanOptions;
0504 
0505   using TripletOptions =
0506       boost::mp11::mp_product<boost::mp11::mp_list, UseStripInfoOptions,
0507                               SortedByCotThetaOptions>;
0508 
0509   std::unique_ptr<TripletSeedFinder> result;
0510   boost::mp11::mp_for_each<TripletOptions>([&](auto option) {
0511     using OptionType = decltype(option);
0512 
0513     using UseStripInfo = boost::mp11::mp_at_c<OptionType, 0>;
0514     using SortedByCotTheta = boost::mp11::mp_at_c<OptionType, 1>;
0515 
0516     if (config.useStripInfo != UseStripInfo::value ||
0517         config.sortedByCotTheta != SortedByCotTheta::value) {
0518       return;  // skip if the configuration does not match
0519     }
0520 
0521     // check if we already have an implementation for this configuration
0522     if (result != nullptr) {
0523       throw std::runtime_error(
0524           "TripletSeedFinder: Multiple implementations found for one "
0525           "configuration");
0526     }
0527 
0528     // create the implementation for the given configuration
0529     result =
0530         std::make_unique<Impl<UseStripInfo::value, SortedByCotTheta::value>>(
0531             config);
0532   });
0533   if (result == nullptr) {
0534     throw std::runtime_error(
0535         "TripletSeedFinder: No implementation found for the given "
0536         "configuration");
0537   }
0538   return result;
0539 }
0540 
0541 }  // namespace Acts