Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-12-16 09:22:39

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/CompositeSpacePointLineSeeder.hpp"
0012 
0013 #include "Acts/Definitions/Tolerance.hpp"
0014 #include "Acts/Surfaces/detail/LineHelper.hpp"
0015 #include "Acts/Utilities/Helpers.hpp"
0016 
0017 #include <algorithm>
0018 #include <cassert>
0019 
0020 namespace Acts::Experimental {
0021 
0022 template <CompositeSpacePoint Sp_t>
0023 CompositeSpacePointLineSeeder::TwoCircleTangentPars
0024 CompositeSpacePointLineSeeder::constructTangentLine(const Sp_t& topHit,
0025                                                     const Sp_t& bottomHit,
0026                                                     const TangentAmbi ambi) {
0027   using ResidualIdx = detail::CompSpacePointAuxiliaries::ResidualIdx;
0028   using namespace Acts::UnitLiterals;
0029   using namespace Acts::detail;
0030   TwoCircleTangentPars result{};
0031   result.ambi = ambi;
0032   const auto& [signTop, signBot] = s_signCombo[toUnderlying(ambi)];
0033 
0034   const Vector& bottomPos{bottomHit.localPosition()};
0035   const Vector& topPos{topHit.localPosition()};
0036   const Vector& eY{bottomHit.toNextSensor()};
0037   const Vector& eZ{bottomHit.planeNormal()};
0038   const Vector D = topPos - bottomPos;
0039 
0040   assert(Acts::abs(eY.dot(eZ)) < s_epsilon);
0041   assert(Acts::abs(bottomHit.sensorDirection().dot(eY)) < s_epsilon);
0042   assert(Acts::abs(bottomHit.sensorDirection().dot(eZ)) < s_epsilon);
0043   assert(topHit.isStraw() && bottomHit.isStraw());
0044 
0045   const double dY = D.dot(eY);
0046   const double dZ = D.dot(eZ);
0047 
0048   const double thetaTubes = std::atan2(dY, dZ);
0049   const double distTubes = Acts::fastHypot(dY, dZ);
0050   assert(distTubes > 1._mm);
0051   constexpr auto covIdx = Acts::toUnderlying(ResidualIdx::bending);
0052   const double combDriftUncert{topHit.covariance()[covIdx] +
0053                                bottomHit.covariance()[covIdx]};
0054   const double R =
0055       -signBot * bottomHit.driftRadius() + signTop * topHit.driftRadius();
0056   result.theta = thetaTubes - std::asin(std::clamp(R / distTubes, -1., 1.));
0057 
0058   const double cosTheta = std::cos(result.theta);
0059   const double sinTheta = std::sin(result.theta);
0060 
0061   result.y0 = bottomPos.dot(eY) * cosTheta - bottomPos.dot(eZ) * sinTheta -
0062               signBot * bottomHit.driftRadius();
0063   assert(Acts::abs(topPos.dot(eY) * cosTheta - topPos.dot(eZ) * sinTheta -
0064                    signTop * topHit.driftRadius() - result.y0) <
0065          std::numeric_limits<float>::epsilon());
0066   result.y0 /= cosTheta;
0067   const double denomSquare = 1. - Acts::pow(R / distTubes, 2);
0068   if (denomSquare < s_epsilon) {
0069     return result;
0070   }
0071   result.dTheta = combDriftUncert / std::sqrt(denomSquare) / distTubes;
0072   result.dY0 =
0073       Acts::fastHypot(
0074           bottomPos.dot(eY) * sinTheta + bottomPos.dot(eZ) * cosTheta, 1.) *
0075       result.dTheta;
0076 
0077   return result;
0078 }
0079 
0080 template <CompositeSpacePoint Sp_t>
0081 CompositeSpacePointLineSeeder::Vector
0082 CompositeSpacePointLineSeeder::makeDirection(const Sp_t& refHit,
0083                                              const double tanAngle) {
0084   const Vector& eY{refHit.toNextSensor()};
0085   const Vector& eZ{refHit.planeNormal()};
0086   const double cosTheta = std::cos(tanAngle);
0087   const double sinTheta = std::sin(tanAngle);
0088   return copySign<Vector, double>(sinTheta * eY + cosTheta * eZ, sinTheta);
0089 }
0090 
0091 template <CompositeSpacePointContainer Cont_t>
0092 std::size_t CompositeSpacePointLineSeeder::countHits(
0093     const Cont_t& container, const Selector_t<Cont_t>& selector) const {
0094   if (m_cfg.busyLimitCountGood) {
0095     return std::ranges::count_if(
0096         container, [&](const auto& hit) { return selector(*hit); });
0097   }
0098   return container.size();
0099 }
0100 
0101 /// ##########################################################################
0102 ///                CompositeSpacePointLineSeeder::SeedSolution
0103 /// ##########################################################################
0104 template <CompositeSpacePointContainer UnCalibCont_t,
0105           detail::CompositeSpacePointSorter<UnCalibCont_t> Splitter_t>
0106 CompositeSpacePointLineSeeder::SeedSolution<UnCalibCont_t,
0107                                             Splitter_t>::SpacePoint_t
0108 CompositeSpacePointLineSeeder::SeedSolution<UnCalibCont_t, Splitter_t>::getHit(
0109     const std::size_t idx) const {
0110   const auto& [layIdx, hitIdx] = m_seedHits.at(idx);
0111   const UnCalibCont_t& strawLayer = m_splitter.strawHits().at(layIdx);
0112   return *strawLayer.at(hitIdx);
0113 }
0114 template <CompositeSpacePointContainer UnCalibCont_t,
0115           detail::CompositeSpacePointSorter<UnCalibCont_t> Splitter_t>
0116 std::vector<int> CompositeSpacePointLineSeeder::SeedSolution<
0117     UnCalibCont_t, Splitter_t>::leftRightAmbiguity(const Vector& seedPos,
0118                                                    const Vector& seedDir)
0119     const {
0120   std::vector<int> result{};
0121   result.reserve(size());
0122   std::ranges::transform(
0123       m_seedHits, std::back_inserter(result),
0124       [&](const std::pair<std::size_t, std::size_t>& indices) {
0125         const auto& [layIdx, hitIdx] = indices;
0126         const UnCalibCont_t& strawLayer = m_splitter.strawHits().at(layIdx);
0127         return detail::CompSpacePointAuxiliaries::strawSign(
0128             seedPos, seedDir, *strawLayer.at(hitIdx));
0129       });
0130   return result;
0131 }
0132 template <CompositeSpacePointContainer UnCalibCont_t,
0133           detail::CompositeSpacePointSorter<UnCalibCont_t> Splitter_t>
0134 void CompositeSpacePointLineSeeder::SeedSolution<
0135     UnCalibCont_t, Splitter_t>::append(const std::size_t layIdx,
0136                                        const std::size_t hitIdx) {
0137   assert(!rangeContainsValue(m_seedHits, std::make_pair(layIdx, hitIdx)));
0138   m_seedHits.emplace_back(layIdx, hitIdx);
0139 }
0140 template <CompositeSpacePointContainer UnCalibCont_t,
0141           detail::CompositeSpacePointSorter<UnCalibCont_t> Splitter_t>
0142 void CompositeSpacePointLineSeeder::SeedSolution<
0143     UnCalibCont_t, Splitter_t>::print(std::ostream& ostr) const {
0144   TwoCircleTangentPars::print(ostr);
0145   const std::size_t N = size();
0146   ostr << ", associated hits: " << N << std::endl;
0147   for (std::size_t h = 0; h < N; ++h) {
0148     ostr << "    **** " << (h + 1ul) << ") " << Acts::toString(getHit(h))
0149          << std::endl;
0150   }
0151 }
0152 
0153 /// ##########################################################################
0154 ///                CompositeSpacePointLineSeeder::SeedingState
0155 /// ##########################################################################
0156 template <
0157     CompositeSpacePointContainer UncalibCont_t,
0158     CompositeSpacePointContainer CalibCont_t,
0159     detail::CompSpacePointSeederDelegate<UncalibCont_t, CalibCont_t> Delegate_t>
0160 void CompositeSpacePointLineSeeder::SeedingState<
0161     UncalibCont_t, CalibCont_t, Delegate_t>::print(std::ostream& ostr) const {
0162   const std::size_t nStraw = this->strawHits().size();
0163 
0164   ostr << "Seed state:\n";
0165   ostr << "N strawLayers: " << nStraw
0166        << " N strip layers: " << this->stripHits().size() << "\n";
0167   ostr << "upperLayer " << m_upperLayer.value_or(nStraw - 1ul) << " lowerLayer "
0168        << m_lowerLayer.value_or(0u) << " upperHitIndex " << m_upperHitIndex
0169        << " lower layer hit index " << m_lowerHitIndex << " sign combo index "
0170        << toString(encodeAmbiguity(s_signCombo[m_signComboIndex][0],
0171                                    s_signCombo[m_signComboIndex][1]))
0172        << "\n";
0173   ostr << " start with pattern " << startWithPattern << " nGenSeeds "
0174        << nGenSeeds() << " nStrawCut " << m_nStrawCut << "\n";
0175   if (nGenSeeds() > 0ul) {
0176     for (const auto& seen : m_seenSolutions) {
0177       ostr << "################################################" << std::endl;
0178       ostr << seen << std::endl;
0179       ostr << "################################################" << std::endl;
0180     }
0181   }
0182 }
0183 
0184 template <CompositeSpacePointContainer UnCalibCont_t>
0185 bool CompositeSpacePointLineSeeder::moveToNextHit(
0186     const UnCalibCont_t& hitVec, const Selector_t<UnCalibCont_t>& selector,
0187     std::size_t& hitIdx) const {
0188   ACTS_VERBOSE(__func__ << "() " << __LINE__
0189                         << " - Moving to next good hit from index " << hitIdx
0190                         << " in straw layer with " << hitVec.size()
0191                         << " hits.");
0192   while (++hitIdx < hitVec.size()) {
0193     if (selector(*hitVec[hitIdx])) {
0194       ACTS_VERBOSE(__func__ << "() " << __LINE__ << " - Moved towards index "
0195                             << hitIdx);
0196       return true;
0197     }
0198   }
0199   ACTS_VERBOSE(__func__ << "() " << __LINE__ << " - No good hit found.");
0200   return false;
0201 }
0202 
0203 template <CompositeSpacePointContainer UnCalibCont_t>
0204 bool CompositeSpacePointLineSeeder::firstGoodHit(
0205     const UnCalibCont_t& hitVec, const Selector_t<UnCalibCont_t>& selector,
0206     std::size_t& hitIdx) const {
0207   hitIdx = 0;
0208   if (hitVec.empty()) {
0209     ACTS_VERBOSE(__func__ << "() " << __LINE__ << " - Layer is empty.");
0210     return false;
0211   }
0212   return selector(*hitVec[hitIdx]) || moveToNextHit(hitVec, selector, hitIdx);
0213 }
0214 template <CompositeSpacePointContainer UnCalibCont_t>
0215 bool CompositeSpacePointLineSeeder::nextLayer(
0216     const StrawLayers_t<UnCalibCont_t>& strawLayers,
0217     const Selector_t<UnCalibCont_t>& selector, const std::size_t boundary,
0218     std::optional<std::size_t>& layerIndex, std::size_t& hitIdx,
0219     bool moveForward) const {
0220   if (strawLayers.empty()) {
0221     ACTS_VERBOSE(__func__ << "() " << __LINE__ << " - No straw layers.");
0222     return false;
0223   }
0224   /// The layer index is not yet instantiated.
0225   if (!layerIndex.has_value()) {
0226     layerIndex = moveForward ? 0u : strawLayers.size() - 1u;
0227     const UnCalibCont_t& hitVec{strawLayers.at(layerIndex.value())};
0228     if (hitVec.size() <= m_cfg.busyLayerLimit &&
0229         firstGoodHit(hitVec, selector, hitIdx)) {
0230       ACTS_VERBOSE(__func__ << "() " << __LINE__ << " - Instantiated "
0231                             << (moveForward ? "lower" : "upper") << " layer to "
0232                             << layerIndex.value() << ".");
0233       return true;
0234     }
0235   }
0236   ACTS_VERBOSE(__func__ << "() " << __LINE__ << " - Move "
0237                         << (moveForward ? "lower" : "upper") << " layer "
0238                         << layerIndex.value() << " to next value.");
0239   /// Increment or decrement the layer index
0240   while ((moveForward ? (++layerIndex.value()) : (--layerIndex.value())) <
0241          strawLayers.size()) {
0242     const UnCalibCont_t& hitVec{strawLayers.at(layerIndex.value())};
0243     /// Check whether the layer index is still witihin the allow boundaries
0244     if ((moveForward && layerIndex.value() >= boundary) ||
0245         (!moveForward && layerIndex.value() <= boundary)) {
0246       ACTS_VERBOSE(__func__ << "() " << __LINE__ << " - The "
0247                             << (moveForward ? "lower" : "upper") << " index "
0248                             << layerIndex.value()
0249                             << " exceeds the boundary: " << boundary << ".");
0250       return false;
0251     }
0252 
0253     if (const std::size_t nHits = countHits(hitVec, selector);
0254         nHits > m_cfg.busyLayerLimit) {
0255       ACTS_VERBOSE(__func__ << "() " << __LINE__ << " - The layer "
0256                             << layerIndex.value()
0257                             << " is too busy for seeding: " << nHits
0258                             << ". Limit: " << m_cfg.busyLayerLimit << ".");
0259       continue;
0260     }
0261 
0262     /// Check whether a good hit can be detected inside the layer
0263     if (firstGoodHit(strawLayers.at(layerIndex.value()), selector, hitIdx)) {
0264       ACTS_VERBOSE(__func__
0265                    << "() " << __LINE__ << " - Loop over all hits in the  "
0266                    << layerIndex.value() << (moveForward ? "lower" : "upper")
0267                    << " layer.");
0268       return true;
0269     }
0270   }
0271   return false;
0272 }
0273 
0274 template <
0275     CompositeSpacePointContainer UncalibCont_t,
0276     CompositeSpacePointContainer CalibCont_t,
0277     detail::CompSpacePointSeederDelegate<UncalibCont_t, CalibCont_t> Delegate_t>
0278 std::optional<CompositeSpacePointLineSeeder::SegmentSeed<CalibCont_t>>
0279 CompositeSpacePointLineSeeder::nextSeed(
0280     const CalibrationContext& cctx,
0281     SeedingState<UncalibCont_t, CalibCont_t, Delegate_t>& state) const {
0282   const StrawLayers_t<UncalibCont_t>& strawLayers{state.strawHits()};
0283 
0284   Selector_t<UncalibCont_t> selector{};
0285   selector.template connect<&Delegate_t::goodCandidate>(&state);
0286   /// The layer hast not yet been initialized
0287   if (!state.m_upperLayer || !state.m_lowerLayer) {
0288     state.m_nStrawCut = m_cfg.nStrawHitCut;
0289     /// Check whether the seeding can start with the external pattern
0290     /// parameters
0291     if (state.startWithPattern &&
0292         std::ranges::any_of(strawLayers, [&](const UncalibCont_t& layerHits) {
0293           return countHits(layerHits, selector) > m_cfg.busyLayerLimit;
0294         })) {
0295       state.startWithPattern = false;
0296     }
0297     if (state.startWithPattern) {
0298       SegmentSeed<CalibCont_t> patternSeed{state.patternParams,
0299                                            state.newContainer(cctx)};
0300 
0301       const auto [pos, dir] = makeLine(state.patternParams);
0302       const double t0 = patternSeed.parameters[toUnderlying(ParIdx::t0)];
0303 
0304       auto append = [&](const StrawLayers_t<UncalibCont_t>& hitLayers) {
0305         for (const auto& layer : hitLayers) {
0306           for (const auto& hit : layer) {
0307             state.append(cctx, pos, dir, t0, *hit, patternSeed.hits);
0308           }
0309         }
0310       };
0311       append(strawLayers);
0312       append(state.stripHits());
0313       state.startWithPattern = false;
0314       return patternSeed;
0315     }
0316     ACTS_DEBUG(__func__ << "() " << __LINE__ << " - Instantiate layers. ");
0317     /// No valid seed can be found
0318     if (!nextLayer(strawLayers, selector, strawLayers.size(),
0319                    state.m_lowerLayer, state.m_lowerHitIndex, true) ||
0320         !nextLayer(strawLayers, selector, state.m_lowerLayer.value(),
0321                    state.m_upperLayer, state.m_upperHitIndex, false)) {
0322       ACTS_DEBUG(__func__ << "() " << __LINE__
0323                           << " - No valid seed can be constructed. ");
0324       return std::nullopt;
0325     }
0326   }
0327 
0328   while (state.m_lowerLayer.value() < state.m_upperLayer.value()) {
0329     auto seed = buildSeed(cctx, selector, state);
0330     moveToNextCandidate(selector, state);
0331     if (seed) {
0332       return seed;
0333     }
0334   }
0335 
0336   return std::nullopt;
0337 }
0338 template <
0339     CompositeSpacePointContainer UncalibCont_t,
0340     CompositeSpacePointContainer CalibCont_t,
0341     detail::CompSpacePointSeederDelegate<UncalibCont_t, CalibCont_t> Delegate_t>
0342 void CompositeSpacePointLineSeeder::moveToNextCandidate(
0343     const Selector_t<UncalibCont_t>& selector,
0344     SeedingState<UncalibCont_t, CalibCont_t, Delegate_t>& state) const {
0345   // Vary the left right solutions
0346   ++state.m_signComboIndex;
0347   if (state.m_signComboIndex < s_signCombo.size()) {
0348     return;
0349   }
0350   // All sign combos tested. Let's reset the signs combo and move on
0351   /// to the next hit inside the layer
0352   state.m_signComboIndex = 0;
0353 
0354   const StrawLayers_t<UncalibCont_t>& strawLayers{state.strawHits()};
0355 
0356   const UncalibCont_t& lower = strawLayers[state.m_lowerLayer.value()];
0357   const UncalibCont_t& upper = strawLayers[state.m_upperLayer.value()];
0358 
0359   /// Next good hit in the lower layer found
0360   ACTS_VERBOSE(__func__ << "() " << __LINE__ << " - Move to next lower hit.");
0361   if (moveToNextHit(lower, selector, state.m_lowerHitIndex)) {
0362     return;
0363   }
0364 
0365   /// Reset the hit in the lower layer && move to the next hit
0366   /// in the upper layer
0367   ACTS_VERBOSE(__func__ << "() " << __LINE__
0368                         << " - All lower hits were tried increment upper hit.");
0369   if (firstGoodHit(lower, selector, state.m_lowerHitIndex) &&
0370       moveToNextHit(upper, selector, state.m_upperHitIndex)) {
0371     return;
0372   }
0373   /// All hit combinations in the two layers are tried -> move to next layer
0374   auto& layerToStay{state.m_moveUpLayer ? state.m_lowerLayer
0375                                         : state.m_upperLayer};
0376   auto& layerToMove{state.m_moveUpLayer ? state.m_upperLayer
0377                                         : state.m_lowerLayer};
0378   auto& hitToStay{state.m_moveUpLayer ? state.m_lowerHitIndex
0379                                       : state.m_upperHitIndex};
0380   auto& hitToMove{state.m_moveUpLayer ? state.m_upperHitIndex
0381                                       : state.m_lowerHitIndex};
0382   ACTS_VERBOSE(__func__ << "() " << __LINE__ << " - Move towards the next "
0383                         << (state.m_moveUpLayer ? "upper" : "lower")
0384                         << " layer. Current state: " << layerToMove.value());
0385   /// Reset the hits in the layer that remains and go to the next layer on the
0386   /// other side. Next time the otherside will stay and the former will move.
0387   if (firstGoodHit(strawLayers[layerToStay.value()], selector, hitToStay) &&
0388       nextLayer(strawLayers, selector, layerToStay.value(), layerToMove,
0389                 hitToMove, !state.m_moveUpLayer)) {
0390     state.m_moveUpLayer = !state.m_moveUpLayer;
0391     if (state.stopSeeding(state.m_lowerLayer.value(),
0392                           state.m_upperLayer.value())) {
0393       state.m_lowerLayer.value() = state.m_upperLayer.value() + 1ul;
0394     }
0395     return;
0396   }
0397 }
0398 
0399 template <
0400     CompositeSpacePointContainer UncalibCont_t,
0401     CompositeSpacePointContainer CalibCont_t,
0402     detail::CompSpacePointSeederDelegate<UncalibCont_t, CalibCont_t> Delegate_t>
0403 bool CompositeSpacePointLineSeeder::passSeedCuts(
0404     const Line_t& tangentSeed,
0405     SeedSolution<UncalibCont_t, Delegate_t>& newSolution,
0406     SeedingState<UncalibCont_t, CalibCont_t, Delegate_t>& state) const {
0407   // check if we collected enough straw hits
0408   const auto& [seedPos, seedDir] = tangentSeed;
0409   const double hitCut =
0410       std::max(1.0 * state.m_nStrawCut,
0411                m_cfg.nStrawLayHitCut * state.strawHits().size());
0412   ACTS_VERBOSE(__func__ << "() " << __LINE__ << " - Found "
0413                         << newSolution.nStrawHits
0414                         << " compatible straw hits. Hit cut is " << hitCut);
0415   if (newSolution.nStrawHits < hitCut) {
0416     return false;
0417   }
0418   if (!m_cfg.overlapCorridor) {
0419     return true;
0420   }
0421   newSolution.solutionSigns = newSolution.leftRightAmbiguity(seedPos, seedDir);
0422   for (std::size_t a = 0ul; a < state.nGenSeeds(); ++a) {
0423     const auto& acceptedSol = state.m_seenSolutions[a];
0424     std::size_t nOverlap{0};
0425     const std::vector<int> corridor =
0426         acceptedSol.leftRightAmbiguity(seedPos, seedDir);
0427     for (std::size_t l = 0; l < acceptedSol.size(); ++l) {
0428       nOverlap += (corridor[l] == acceptedSol.solutionSigns[l]);
0429     }
0430     if (nOverlap == corridor.size() &&
0431         acceptedSol.size() >= newSolution.size()) {
0432       return false;
0433     }
0434   }
0435   if (m_cfg.tightenHitCut) {
0436     state.m_nStrawCut = std::max(state.m_nStrawCut, newSolution.nStrawHits);
0437   }
0438   return true;
0439 }
0440 
0441 template <
0442     CompositeSpacePointContainer UncalibCont_t,
0443     CompositeSpacePointContainer CalibCont_t,
0444     detail::CompSpacePointSeederDelegate<UncalibCont_t, CalibCont_t> Delegate_t>
0445 std::optional<CompositeSpacePointLineSeeder::SegmentSeed<CalibCont_t>>
0446 CompositeSpacePointLineSeeder::buildSeed(
0447     const CalibrationContext& cctx, const Selector_t<UncalibCont_t>& selector,
0448     SeedingState<UncalibCont_t, CalibCont_t, Delegate_t>& state) const {
0449   const StrawLayers_t<UncalibCont_t>& strawLayers{state.strawHits()};
0450 
0451   ACTS_DEBUG(__func__ << "() " << __LINE__ << " - Try to draw new seed from \n"
0452                       << state << ".");
0453   if (state.strawRadius <= Acts::s_epsilon) {
0454     throw std::invalid_argument(
0455         "buildSeed() - Please configure the state's strawRadius");
0456   }
0457   const auto& upperHit =
0458       *strawLayers.at(state.m_upperLayer.value()).at(state.m_upperHitIndex);
0459   const auto& lowerHit =
0460       *strawLayers.at(state.m_lowerLayer.value()).at(state.m_lowerHitIndex);
0461   const auto ambi{static_cast<TangentAmbi>(state.m_signComboIndex)};
0462   ACTS_VERBOSE(__func__ << "() " << __LINE__ << " - " << toString(ambi)
0463                         << "\n   Top seed hit: " << Acts::toString(upperHit)
0464                         << "\n   Bottom seed hit:" << Acts::toString(lowerHit)
0465                         << ".");
0466 
0467   const TwoCircleTangentPars seedPars =
0468       constructTangentLine(lowerHit, upperHit, ambi);
0469   ACTS_VERBOSE(__func__ << "() " << __LINE__
0470                         << " - Tangential parameters: " << seedPars);
0471   if (!isValidLine(seedPars)) {
0472     ACTS_VERBOSE(__func__ << "() " << __LINE__ << " - Reject seed.");
0473     return std::nullopt;
0474   }
0475   // check if we have already seen this solution
0476   if (std::ranges::any_of(state.m_seenSolutions, [&seedPars](const auto& seen) {
0477         const double deltaY = Acts::abs(seen.y0 - seedPars.y0);
0478         const double limitY = Acts::fastHypot(seen.dY0, seedPars.dY0);
0479         const double deltaTheta = Acts::abs(seen.theta - seedPars.theta);
0480         const double limitTheta = Acts::fastHypot(seen.dTheta, seedPars.dTheta);
0481         return deltaY < limitY && deltaTheta < limitTheta;
0482       })) {
0483     ACTS_VERBOSE(__func__ << "() " << __LINE__ << " - Reject seed.");
0484     return std::nullopt;
0485   }
0486   /// Continue to construct a new solution
0487   const double t0 = state.patternParams[toUnderlying(ParIdx::t0)];
0488   SeedSolution<UncalibCont_t, Delegate_t> newSolution{seedPars, state};
0489 
0490   ACTS_DEBUG(__func__ << "() " << __LINE__
0491                       << " - Start looking for compatible hits");
0492 
0493   const Line_t tangentSeed{seedPars.y0 * Vector3::UnitY(),
0494                            makeDirection(lowerHit, seedPars.theta)};
0495   const auto& [seedPos, seedDir] = tangentSeed;
0496   for (const auto& [layerNr, hitsInLayer] :
0497        Acts::enumerate(state.strawHits())) {
0498     bool hadGoodHit{false};
0499     for (const auto& [hitNr, testMe] : Acts::enumerate(hitsInLayer)) {
0500       using namespace Acts::detail::LineHelper;
0501       const double distance = Acts::abs(
0502           signedDistance(testMe->localPosition(), testMe->sensorDirection(),
0503                          seedPos, seedDir));
0504       // the hits are ordered in the layer so we assume that once we found good
0505       // hits we are moving away from the seed line so we can abort the hit
0506       // association
0507       if (distance < state.strawRadius &&
0508           state.candidatePull(cctx, seedPos, seedDir, t0, *testMe) <
0509               Acts::pow(m_cfg.hitPullCut, 2u)) {
0510         ACTS_VERBOSE(__func__ << "() " << __LINE__ << " - layer,hit = ("
0511                               << layerNr << "," << hitNr
0512                               << ") -> spacePoint: " << Acts::toString(*testMe)
0513                               << " is close enough: " << distance);
0514         hadGoodHit = true;
0515         newSolution.append(layerNr, hitNr);
0516         newSolution.nStrawHits += selector(*testMe);
0517         continue;
0518       }
0519       if (hadGoodHit) {
0520         break;
0521       }
0522     }
0523   }
0524   if (!passSeedCuts(tangentSeed, newSolution, state)) {
0525     return std::nullopt;
0526   }
0527   return consructSegmentSeed(cctx, tangentSeed, state, std::move(newSolution));
0528 }
0529 
0530 template <
0531     CompositeSpacePointContainer UncalibCont_t,
0532     CompositeSpacePointContainer CalibCont_t,
0533     detail::CompSpacePointSeederDelegate<UncalibCont_t, CalibCont_t> Delegate_t>
0534 CompositeSpacePointLineSeeder::SegmentSeed<CalibCont_t>
0535 CompositeSpacePointLineSeeder::consructSegmentSeed(
0536     const CalibrationContext& cctx, const Line_t& tangentSeed,
0537     SeedingState<UncalibCont_t, CalibCont_t, Delegate_t>& state,
0538     SeedSolution<UncalibCont_t, Delegate_t>&& newSolution) const {
0539   ACTS_DEBUG(__func__ << "() " << __LINE__ << " Construct new seed from \n"
0540                       << newSolution);
0541   SegmentSeed<CalibCont_t> finalSeed{
0542       combineWithPattern(tangentSeed, state.patternParams),
0543       state.newContainer(cctx)};
0544   const auto [seedPos, seedDir] = makeLine(finalSeed.parameters);
0545   /// Append the collected straws to the seed
0546   const double t0 = finalSeed.parameters[toUnderlying(ParIdx::t0)];
0547   for (std::size_t s = 0; s < newSolution.size(); ++s) {
0548     state.append(cctx, seedPos, seedDir, t0, newSolution.getHit(s),
0549                  finalSeed.hits);
0550   }
0551   /// The solution object is no longer needed for this seed.
0552   state.m_seenSolutions.push_back(std::move(newSolution));
0553 
0554   ACTS_DEBUG(__func__ << "() " << __LINE__
0555                       << " - Associate the strip hits to the seed");
0556   for (const auto& stripLayerHits : state.stripHits()) {
0557     double bestChi2Loc0{m_cfg.hitPullCut};
0558     double bestChi2Loc1{m_cfg.hitPullCut};
0559     std::size_t bestIdxLoc0{stripLayerHits.size()};
0560     std::size_t bestIdxLoc1{stripLayerHits.size()};
0561     /// Find the hit with the lowest pull
0562     for (const auto& [hitIdx, testMe] : Acts::enumerate(stripLayerHits)) {
0563       const double chi2 =
0564           state.candidatePull(cctx, seedPos, seedDir, t0, *testMe);
0565       if (testMe->measuresLoc0() && chi2 < bestChi2Loc0) {
0566         bestChi2Loc0 = chi2;
0567         bestIdxLoc0 = hitIdx;
0568       }
0569       if (testMe->measuresLoc1() && chi2 < bestChi2Loc1) {
0570         bestChi2Loc1 = chi2;
0571         bestIdxLoc1 = hitIdx;
0572       }
0573     }
0574     if (bestIdxLoc0 < stripLayerHits.size()) {
0575       ACTS_VERBOSE(__func__ << "() " << __LINE__ << " - Append loc0 strip hit "
0576                             << Acts::toString(*stripLayerHits.at(bestIdxLoc0))
0577                             << ".");
0578       state.append(cctx, seedPos, seedDir, t0, *stripLayerHits.at(bestIdxLoc0),
0579                    finalSeed.hits);
0580     }
0581     if (bestIdxLoc1 != bestIdxLoc0 && bestIdxLoc1 < stripLayerHits.size()) {
0582       ACTS_VERBOSE(__func__ << "() " << __LINE__ << " - Append loc1 strip hit "
0583                             << Acts::toString(*stripLayerHits.at(bestIdxLoc1))
0584                             << ".");
0585       state.append(cctx, seedPos, seedDir, t0, *stripLayerHits.at(bestIdxLoc1),
0586                    finalSeed.hits);
0587     }
0588   }
0589   return finalSeed;
0590 }
0591 }  // namespace Acts::Experimental