File indexing completed on 2025-10-13 08:15:52
0001
0002
0003
0004
0005
0006
0007
0008
0009 #pragma once
0010
0011 #include "Acts/Seeding/CompositeSpacePointLineFitter.hpp"
0012
0013 #include "Acts/Utilities/AlgebraHelpers.hpp"
0014 #include "Acts/Utilities/StringHelpers.hpp"
0015
0016 #include <format>
0017 namespace Acts::Experimental {
0018
0019 template <CompositeSpacePointContainer Cont_t>
0020 std::array<std::size_t, 3> CompositeSpacePointLineFitter::countDoF(
0021 const Cont_t& measurements) const {
0022 return countDoF(measurements, Selector_t<SpacePoint_t<Cont_t>>{});
0023 }
0024
0025 template <CompositeSpacePointContainer Cont_t>
0026 std::array<std::size_t, 3> CompositeSpacePointLineFitter::countDoF(
0027 const Cont_t& measurements,
0028 const Selector_t<SpacePoint_t<Cont_t>>& selector) const {
0029 using enum detail::CompSpacePointAuxiliaries::ResidualIdx;
0030 auto counts = filledArray<std::size_t, 3>(0u);
0031 std::size_t nValid{0};
0032 for (const auto& sp : measurements) {
0033 if (selector.connected() && !selector(*sp)) {
0034 continue;
0035 }
0036 ++nValid;
0037 if (sp->measuresLoc0()) {
0038 ++counts[toUnderlying(nonBending)];
0039 }
0040 if (sp->measuresLoc1()) {
0041 ++counts[toUnderlying(bending)];
0042 }
0043
0044 if (m_cfg.fitT0 && (sp->hasTime() || sp->isStraw())) {
0045 ++counts[toUnderlying(time)];
0046 }
0047 }
0048 ACTS_DEBUG(
0049 __func__ << "() " << __LINE__ << " - " << nValid << "/"
0050 << measurements.size() << " valid measurements passed. Found "
0051 << counts[toUnderlying(nonBending)] << "/"
0052 << counts[toUnderlying(bending)] << "/"
0053 << counts[toUnderlying(time)] << " measuring loc0/loc1/time.");
0054 return counts;
0055 }
0056
0057 template <CompositeSpacePointContainer Cont_t>
0058 CompositeSpacePointLineFitter::FastFitResult
0059 CompositeSpacePointLineFitter::fastPrecFit(
0060 const Cont_t& measurements, const Line_t& initialGuess,
0061 const std::vector<FitParIndex>& parsToUse) const {
0062 if (std::ranges::none_of(parsToUse, [](const FitParIndex idx) {
0063 using enum FitParIndex;
0064 return idx == theta || idx == y0;
0065 })) {
0066 return std::nullopt;
0067 }
0068
0069 std::size_t nStraw = std::ranges::count_if(
0070 measurements, [](const auto& sp) { return sp->isStraw(); });
0071 ACTS_DEBUG(__func__ << "() " << __LINE__ << " - Fetched " << nStraw
0072 << " measurements for fast fit.");
0073 if (nStraw > 2) {
0074 std::vector<int> signs = detail::CompSpacePointAuxiliaries::strawSigns(
0075 initialGuess, measurements);
0076 FastFitResult precResult = m_fastFitter.fit(measurements, signs);
0077
0078 if (precResult && precResult->chi2 / static_cast<double>(precResult->nDoF) >
0079 m_cfg.badFastChi2SignSwap) {
0080
0081 ACTS_DEBUG(__func__ << "() " << __LINE__
0082 << " - The fit result is of poor quality "
0083 << (*precResult) << " attempt with L<->R swapping");
0084 for (auto& s : signs) {
0085 s = -s;
0086 }
0087
0088 FastFitResult swappedPrecResult = m_fastFitter.fit(measurements, signs);
0089 if (swappedPrecResult && swappedPrecResult->chi2 < precResult->chi2) {
0090 ACTS_DEBUG(__func__ << "() " << __LINE__
0091 << " - Swapped fit is of better quality "
0092 << (*swappedPrecResult));
0093 swappedPrecResult->nIter += precResult->nIter;
0094 return swappedPrecResult;
0095 } else {
0096 precResult->nIter += swappedPrecResult->nIter;
0097 ACTS_DEBUG(__func__ << "() " << __LINE__ << " - Fit did not improve "
0098 << (*swappedPrecResult));
0099 return precResult;
0100 }
0101 }
0102 }
0103 using ResidualIdx = detail::CompSpacePointAuxiliaries::ResidualIdx;
0104 return m_fastFitter.fit(measurements, ResidualIdx::bending);
0105 }
0106
0107 template <CompositeSpacePointContainer Cont_t>
0108 CompositeSpacePointLineFitter::FitParameters
0109 CompositeSpacePointLineFitter::fastFit(
0110 const Cont_t& measurements, const Line_t& initialGuess,
0111 const std::vector<FitParIndex>& parsToUse) const {
0112 using namespace Acts::UnitLiterals;
0113 using enum FitParIndex;
0114
0115 FitParameters result{};
0116
0117 const FastFitResult precResult{
0118 fastPrecFit(measurements, initialGuess, parsToUse)};
0119 if (!precResult) {
0120 ACTS_DEBUG(__func__ << "() " << __LINE__ << " - Fast fit failed.");
0121 return result;
0122 }
0123
0124
0125 result.parameters[toUnderlying(y0)] = precResult->y0;
0126 result.parameters[toUnderlying(theta)] = precResult->theta;
0127 result.covariance(toUnderlying(y0), toUnderlying(y0)) =
0128 Acts::square(precResult->dY0);
0129 result.covariance(toUnderlying(theta), toUnderlying(theta)) =
0130 Acts::square(precResult->dTheta);
0131 result.nDoF = static_cast<unsigned>(precResult->nDoF);
0132 result.nIter = static_cast<unsigned>(precResult->nIter);
0133 result.chi2 = precResult->chi2;
0134 result.converged = true;
0135
0136
0137 if (std::ranges::none_of(parsToUse, [](const FitParIndex idx) {
0138 return idx == phi || idx == x0;
0139 })) {
0140 result.parameters[toUnderlying(phi)] = 90._degree;
0141 ACTS_DEBUG(
0142 __func__ << "() " << __LINE__
0143 << " - No measurements in non precision direction parsed.");
0144 return result;
0145 }
0146
0147 ACTS_DEBUG(__func__ << "() " << __LINE__
0148 << " - Start fast non-precision fit.");
0149 using ResidualIdx = detail::CompSpacePointAuxiliaries::ResidualIdx;
0150 const FastFitResult nonPrecResult =
0151 m_fastFitter.fit(measurements, ResidualIdx::nonBending);
0152 if (!nonPrecResult) {
0153 result.parameters[toUnderlying(phi)] = 90._degree;
0154 ACTS_DEBUG(__func__ << "() " << __LINE__
0155 << " - Fast non-precision fit failed.");
0156 return result;
0157 }
0158 result.parameters[toUnderlying(x0)] = nonPrecResult->y0;
0159 result.covariance(toUnderlying(x0), toUnderlying(x0)) =
0160 Acts::square(nonPrecResult->dY0);
0161 result.nDoF += nonPrecResult->nDoF;
0162 result.nIter += nonPrecResult->nIter;
0163
0164
0165 double tanTheta = std::tan(precResult->theta);
0166 double tanPhi = std::tan(nonPrecResult->theta);
0167 auto dir = makeDirectionFromAxisTangents(tanPhi, tanTheta);
0168 result.parameters[toUnderlying(phi)] = VectorHelpers::phi(dir);
0169 result.parameters[toUnderlying(theta)] = VectorHelpers::theta(dir);
0170
0171 Line_t recoLine{result.parameters};
0172 result.chi2 = 0.;
0173 std::ranges::for_each(measurements, [&recoLine, &result](const auto& m) {
0174 result.chi2 += detail::CompSpacePointAuxiliaries::chi2Term(recoLine, *m);
0175 });
0176
0177 ACTS_DEBUG(__func__ << "() " << __LINE__ << ": Fast fit done. Obtained result"
0178 << result);
0179 return result;
0180 }
0181 template <CompositeSpacePointContainer Cont_t,
0182 CompositeSpacePointCalibrator<Cont_t, Cont_t> Calibrator_t>
0183 CompositeSpacePointLineFitter::FitResult<Cont_t>
0184 CompositeSpacePointLineFitter::fit(
0185 FitOptions<Cont_t, Calibrator_t>&& fitOpts) const {
0186 using namespace Acts::UnitLiterals;
0187
0188 if (!fitOpts.calibrator) {
0189 throw std::invalid_argument(
0190 "CompositeSpacePointLineFitter::fit() - Please provide a valid pointer "
0191 "to a calibrator.");
0192 }
0193
0194 FitResult<Cont_t> result{};
0195 result.measurements = std::move(fitOpts.measurements);
0196 result.parameters = std::move(fitOpts.startParameters);
0197
0198
0199 detail::CompSpacePointAuxiliaries::Config resCfg{};
0200 resCfg.localToGlobal = std::move(fitOpts.localToGlobal);
0201 resCfg.useHessian = m_cfg.useHessian;
0202 resCfg.calcAlongStraw = m_cfg.calcAlongStraw;
0203 resCfg.calcAlongStrip = m_cfg.calcAlongStrip;
0204 resCfg.includeToF = m_cfg.includeToF;
0205
0206 resCfg.parsToUse =
0207 extractFitablePars(countDoF(result.measurements, fitOpts.selector));
0208 if (resCfg.parsToUse.empty()) {
0209 ACTS_WARNING(__func__ << "() " << __LINE__
0210 << ": No valid degrees of freedom parsed. Please "
0211 << "check your measurements");
0212 return result;
0213 }
0214
0215 Line_t line{};
0216
0217
0218 if (m_cfg.useFastFitter) {
0219 line.updateParameters(result.parameters);
0220 if (resCfg.parsToUse.back() == FitParIndex::t0) {
0221 ACTS_WARNING(__func__ << "() " << __LINE__
0222 << " - Fit with t0 to be implemented");
0223 } else {
0224 ACTS_DEBUG(__func__ << "() " << __LINE__
0225 << " - Attempt a fast fit, first.");
0226 FitParameters fastResult =
0227 fastFit(result.measurements, line, resCfg.parsToUse);
0228 if (fastResult.converged) {
0229 static_cast<FitParameters&>(result) = std::move(fastResult);
0230 ACTS_DEBUG(__func__ << "() " << __LINE__ << " - Fit converged.");
0231
0232 if (!m_cfg.fastPreFitter) {
0233 line.updateParameters(result.parameters);
0234
0235 result.measurements = fitOpts.calibrator->calibrate(
0236 fitOpts.calibContext, line.position(), line.direction(), 0.,
0237 result.measurements);
0238 return result;
0239 }
0240
0241 result.converged = false;
0242 } else {
0243 ACTS_DEBUG(__func__ << "() " << __LINE__ << " - Fit failed.");
0244 return result;
0245 }
0246 }
0247 }
0248
0249 if (!m_cfg.recalibrate) {
0250 line.updateParameters(result.parameters);
0251 fitOpts.calibrator->updateSigns(line.position(), line.direction(),
0252 result.measurements);
0253 }
0254
0255 ChiSqCache cache{};
0256 detail::CompSpacePointAuxiliaries pullCalculator{resCfg, logger().clone()};
0257 for (; !result.converged && result.nIter < m_cfg.maxIter; ++result.nIter) {
0258 cache.reset();
0259
0260 line.updateParameters(result.parameters);
0261 const double t0 = result.parameters[toUnderlying(FitParIndex::t0)];
0262
0263 ACTS_VERBOSE(__func__ << "() " << __LINE__ << ": Start next iteration "
0264 << result << " --> " << toString(line.position())
0265 << " + " << toString(line.direction()) << ".");
0266
0267 if (m_cfg.recalibrate) {
0268 result.measurements = fitOpts.calibrator->calibrate(
0269 fitOpts.calibContext, line.position(), line.direction(), t0,
0270 result.measurements);
0271
0272 auto parsBkp = std::move(resCfg.parsToUse);
0273 resCfg.parsToUse =
0274 extractFitablePars(countDoF(result.measurements, fitOpts.selector));
0275
0276
0277 if (resCfg.parsToUse.empty()) {
0278 ACTS_WARNING(__func__ << "() " << __LINE__ << ": Line parameters "
0279 << toString(line.position()) << " + "
0280 << toString(line.direction()) << ", t0: " << t0
0281 << " invalidated all measurements");
0282 return result;
0283 }
0284
0285 if (parsBkp != resCfg.parsToUse) {
0286
0287 pullCalculator =
0288 detail::CompSpacePointAuxiliaries{resCfg, logger().clone()};
0289 }
0290
0291 fitOpts.calibrator->updateSigns(line.position(), line.direction(),
0292 result.measurements);
0293 }
0294
0295 for (const auto& spacePoint : result.measurements) {
0296
0297 if (fitOpts.selector.connected() && !fitOpts.selector(*spacePoint)) {
0298 continue;
0299 }
0300 double driftV{0.};
0301 double driftA{0.};
0302
0303 if (resCfg.parsToUse.back() == FitParIndex::t0) {
0304 pullCalculator.updateFullResidual(line, t0, *spacePoint, driftV,
0305 driftA);
0306 } else {
0307 pullCalculator.updateSpatialResidual(line, *spacePoint);
0308 }
0309
0310 pullCalculator.updateChiSq(cache, spacePoint->covariance());
0311 }
0312 pullCalculator.symmetrizeHessian(cache);
0313 result.chi2 = cache.chi2;
0314
0315 UpdateStep update{UpdateStep::goodStep};
0316 switch (resCfg.parsToUse.size()) {
0317
0318 case 2: {
0319 update = updateParameters<2>(resCfg.parsToUse.front(), cache,
0320 result.parameters);
0321 break;
0322 }
0323
0324 case 3: {
0325 update = updateParameters<3>(resCfg.parsToUse.front(), cache,
0326 result.parameters);
0327 break;
0328 }
0329
0330 case 4: {
0331 update = updateParameters<4>(resCfg.parsToUse.front(), cache,
0332 result.parameters);
0333 break;
0334 }
0335
0336 case 5: {
0337 update = updateParameters<5>(resCfg.parsToUse.front(), cache,
0338 result.parameters);
0339 break;
0340 }
0341 default:
0342 ACTS_WARNING(__func__ << "() " << __LINE__
0343 << ": Invalid parameter size "
0344 << resCfg.parsToUse.size());
0345 return result;
0346 }
0347 switch (update) {
0348 using enum UpdateStep;
0349 case converged: {
0350 result.converged = true;
0351 break;
0352 }
0353 case goodStep: {
0354 break;
0355 }
0356 case outOfBounds: {
0357 return result;
0358 }
0359 }
0360 }
0361
0362 if (result.converged) {
0363 const auto doF = countDoF(result.measurements, fitOpts.selector);
0364 result.nDoF = (doF[0] + doF[1] + doF[2]) - resCfg.parsToUse.size();
0365 line.updateParameters(result.parameters);
0366 const double t0 = result.parameters[toUnderlying(FitParIndex::t0)];
0367
0368 result.measurements = fitOpts.calibrator->calibrate(
0369 fitOpts.calibContext, line.position(), line.direction(), t0,
0370 result.measurements);
0371
0372
0373 switch (resCfg.parsToUse.size()) {
0374
0375 case 2: {
0376 fillCovariance<2>(resCfg.parsToUse.front(), cache.hessian,
0377 result.covariance);
0378 break;
0379 }
0380
0381 case 3: {
0382 fillCovariance<3>(resCfg.parsToUse.front(), cache.hessian,
0383 result.covariance);
0384 break;
0385 }
0386
0387 case 4: {
0388 fillCovariance<4>(resCfg.parsToUse.front(), cache.hessian,
0389 result.covariance);
0390 break;
0391 }
0392
0393 case 5: {
0394 fillCovariance<5>(resCfg.parsToUse.front(), cache.hessian,
0395 result.covariance);
0396 break;
0397 }
0398
0399 default:
0400 break;
0401 }
0402 }
0403 return result;
0404 }
0405
0406 template <unsigned N>
0407 CompositeSpacePointLineFitter::UpdateStep
0408 CompositeSpacePointLineFitter::updateParameters(const FitParIndex firstPar,
0409 const ChiSqCache& cache,
0410 ParamVec_t& currentPars) const
0411 requires(N >= 2 && N <= s_nPars)
0412 {
0413 auto firstIdx = toUnderlying(firstPar);
0414 assert(firstIdx + N < s_nPars);
0415
0416 Eigen::Map<ActsVector<N>> miniPars{currentPars.data() + firstIdx};
0417 ACTS_VERBOSE(__func__ << "<" << N << ">() - " << __LINE__
0418 << ": Current parameters " << toString(miniPars)
0419 << " with chi2: " << cache.chi2 << ", gradient: "
0420 << toString(cache.gradient) << ", hessian: \n"
0421 << cache.hessian);
0422
0423
0424 Eigen::Map<const ActsVector<N>> miniGradient{cache.gradient.data() +
0425 firstIdx};
0426
0427 if (miniGradient.norm() < m_cfg.precCutOff) {
0428 ACTS_DEBUG(__func__ << "<" << N << ">() - " << __LINE__
0429 << ": Gradient is small enough");
0430 return UpdateStep::converged;
0431 }
0432
0433 Acts::ActsSquareMatrix<N> miniHessian{
0434 cache.hessian.block<N, N>(firstIdx, firstIdx)};
0435 ACTS_VERBOSE(__func__ << "<" << N << ">() - " << __LINE__
0436 << ": Projected parameters: " << toString(miniPars)
0437 << " gradient: " << toString(miniGradient)
0438 << ", hessian: \n"
0439 << miniHessian
0440 << ", determinant: " << miniHessian.determinant());
0441
0442 auto inverseH = safeInverse(miniHessian);
0443
0444 if (inverseH) {
0445 const ActsVector<N> update{(*inverseH) * miniGradient};
0446
0447 if (update.norm() < m_cfg.precCutOff) {
0448 ACTS_DEBUG(__func__ << "<" << N << ">() - " << __LINE__ << ": Update "
0449 << toString(update) << " is negligible small.");
0450 return UpdateStep::converged;
0451 }
0452 ACTS_VERBOSE(__func__ << "<" << N << ">() - " << __LINE__
0453 << ": Inverted Hessian \n"
0454 << (*inverseH) << "\n-> Update parameters by "
0455 << toString(update));
0456 miniPars -= update;
0457
0458 } else {
0459
0460 const ActsVector<N> update{
0461 std::min(m_cfg.gradientStep, miniGradient.norm()) *
0462 miniGradient.normalized()};
0463
0464 ACTS_VERBOSE(__func__ << "<" << N << ">() - " << __LINE__
0465 << ": Update parameters by " << toString(update));
0466 miniPars -= update;
0467 }
0468
0469
0470 return UpdateStep::goodStep;
0471 }
0472
0473 template <unsigned N>
0474 void CompositeSpacePointLineFitter::fillCovariance(const FitParIndex firstPar,
0475 const CovMat_t& hessian,
0476 CovMat_t& covariance) const
0477 requires(N >= 2 && N <= s_nPars)
0478 {
0479 auto firstIdx = toUnderlying(firstPar);
0480 assert(firstIdx + N < s_nPars);
0481
0482 Acts::ActsSquareMatrix<N> miniHessian{
0483 hessian.block<N, N>(firstIdx, firstIdx)};
0484
0485 auto inverseH = safeInverse(miniHessian);
0486
0487 if (inverseH) {
0488 auto covBlock = covariance.block<N, N>(firstIdx, firstIdx);
0489 covBlock = (*inverseH);
0490 ACTS_DEBUG(__func__ << "<" << N << ">() - " << __LINE__
0491 << ": Evaluated covariance: \n"
0492 << covariance);
0493 }
0494 }
0495
0496 }