File indexing completed on 2026-07-16 08:07:35
0001
0002
0003
0004
0005
0006
0007
0008
0009 #pragma once
0010
0011 #include "Acts/Seeding/detail/FastStrawLineFitter.hpp"
0012
0013 #include "Acts/Definitions/Units.hpp"
0014 #include "Acts/Utilities/Enumerate.hpp"
0015
0016 namespace Acts::Experimental::detail {
0017
0018 template <CompositeSpacePointContainer StrawCont_t>
0019 std::optional<FastStrawLineFitter::FitResult> FastStrawLineFitter::fit(
0020 const StrawCont_t& measurements, const std::vector<int>& signs) const {
0021 if (measurements.size() != signs.size()) {
0022 ACTS_WARNING(
0023 __func__ << "() - " << __LINE__
0024 << ": Not all measurements are associated with a drift sign");
0025 return std::nullopt;
0026 }
0027
0028 auto result = fit(fillAuxiliaries(measurements, signs));
0029 if (!result) {
0030 return std::nullopt;
0031 }
0032
0033 calcPostFitChi2(measurements, *result);
0034 return result;
0035 }
0036
0037 template <CompositeSpacePointContainer StrawCont_t>
0038 void FastStrawLineFitter::calcPostFitChi2(const StrawCont_t& measurements,
0039 FitResult& result) const {
0040 const TrigonomHelper angles{result.theta};
0041 result.chi2 = 0.;
0042 for (const auto& strawMeas : measurements) {
0043 result.chi2 += chi2Term(angles, result.y0, *strawMeas);
0044 }
0045 ACTS_DEBUG(__func__ << "() - " << __LINE__ << ": Overall chi2: "
0046 << result.chi2 << ", nDoF: " << result.nDoF
0047 << ", redChi2: " << (result.chi2 / result.nDoF));
0048 }
0049
0050 template <CompositeSpacePoint Point_t>
0051 double FastStrawLineFitter::chi2Term(const TrigonomHelper& angle,
0052 const double y0, const Point_t& strawMeas,
0053 std::optional<double> r) const {
0054 if (!strawMeas.isStraw()) {
0055 return 0.;
0056 }
0057 const double cov = strawMeas.covariance()[s_covIdx];
0058 if (cov < std::numeric_limits<double>::epsilon()) {
0059 return 0.;
0060 }
0061 const Vector& pos = strawMeas.localPosition();
0062 const double y = pos.dot(strawMeas.toNextSensor());
0063 const double z = pos.dot(strawMeas.planeNormal());
0064 const double dist = Acts::abs((y - y0) * angle.cosTheta - z * angle.sinTheta);
0065 ACTS_VERBOSE(__func__ << "() - " << __LINE__ << ": Distance straw (" << y
0066 << ", " << z
0067 << "), r: " << r.value_or(strawMeas.driftRadius())
0068 << " - track: " << dist);
0069 return Acts::pow(dist - r.value_or(strawMeas.driftRadius()), 2) / cov;
0070 }
0071
0072 template <CompositeSpacePointContainer StripCont_t>
0073 std::optional<FastStrawLineFitter::FitResult> FastStrawLineFitter::fit(
0074 const StripCont_t& measurements, const ResidualIdx projection) const {
0075 if (projection == ResidualIdx::time) {
0076 ACTS_WARNING(__func__ << "() - " << __LINE__
0077 << ": Only spatial projections, "
0078 << "i.e. nonBending / bending are sensible");
0079 return std::nullopt;
0080 }
0081
0082 FitAuxiliaries auxVars{};
0083 Vector centerOfGravity{Vector::Zero()};
0084
0085 auto select = [&projection](const auto& strip) -> bool {
0086
0087
0088 if (strip->isStraw()) {
0089 return strip->measuresLoc0() && projection == ResidualIdx::nonBending;
0090 }
0091
0092 return (strip->measuresLoc0() && projection == ResidualIdx::nonBending) ||
0093 (strip->measuresLoc1() && projection == ResidualIdx::bending);
0094 };
0095
0096 auxVars.invCovs.resize(measurements.size());
0097 for (const auto& [sIdx, strip] : enumerate(measurements)) {
0098 if (!select(strip)) {
0099 ACTS_VERBOSE(__func__ << "() - " << __LINE__
0100 << ": Skip strip measurement " << toString(*strip));
0101 continue;
0102 }
0103 const auto& invCov =
0104 (auxVars.invCovs[sIdx] =
0105 1. / strip->covariance()[toUnderlying(projection)]);
0106 auxVars.covNorm += invCov;
0107 centerOfGravity += invCov * strip->localPosition();
0108 ++auxVars.nDoF;
0109 }
0110
0111 if (auxVars.nDoF < 3) {
0112 return std::nullopt;
0113 }
0114
0115
0116 auxVars.nDoF -= 2u;
0117 auxVars.covNorm = 1. / auxVars.covNorm;
0118 centerOfGravity *= auxVars.covNorm;
0119
0120 bool centerSet{false};
0121 for (const auto& [sIdx, strip] : enumerate(measurements)) {
0122 if (!select(strip)) {
0123 continue;
0124 }
0125 const Vector pos = strip->localPosition() - centerOfGravity;
0126 const Vector& measDir{
0127 (projection == ResidualIdx::nonBending && strip->measuresLoc1()) ||
0128 strip->isStraw()
0129 ? strip->sensorDirection()
0130 : strip->toNextSensor()};
0131
0132 if (!centerSet) {
0133 auxVars.centerY = centerOfGravity.dot(measDir);
0134 auxVars.centerZ = centerOfGravity.dot(strip->planeNormal());
0135 centerSet = true;
0136 }
0137
0138 const double y = pos.dot(measDir);
0139 const double z = pos.dot(strip->planeNormal());
0140
0141 const auto& invCov = auxVars.invCovs[sIdx];
0142 auxVars.T_zzyy += invCov * (Acts::square(z) - Acts::square(y));
0143 auxVars.T_yz += invCov * z * y;
0144 }
0145 return fit(auxVars);
0146 }
0147
0148 template <CompositeSpacePointContainer StrawCont_t,
0149 CompositeSpacePointFastCalibrator<
0150 Acts::RemovePointer_t<typename StrawCont_t::value_type>>
0151 Calibrator_t>
0152 void FastStrawLineFitter::calcPostFitChi2(const Acts::CalibrationContext& ctx,
0153 const StrawCont_t& measurements,
0154 const Calibrator_t& calibrator,
0155 FitResultT0& result) const {
0156 const TrigonomHelper angles{result.theta};
0157 result.chi2 = 0.;
0158 for (const auto& strawMeas : measurements) {
0159 result.chi2 += chi2Term(angles, result.y0, *strawMeas,
0160 calibrator.driftRadius(ctx, *strawMeas, result.t0));
0161 }
0162 ACTS_DEBUG(__func__ << "() - " << __LINE__ << ": Overall chi2: "
0163 << result.chi2 << ", nDoF: " << result.nDoF
0164 << ", redChi2: " << (result.chi2 / result.nDoF));
0165 }
0166
0167 template <CompositeSpacePointContainer StrawCont_t>
0168 FastStrawLineFitter::FitAuxiliaries FastStrawLineFitter::fillAuxiliaries(
0169 const StrawCont_t& measurements, const std::vector<int>& signs) const {
0170 FitAuxiliaries auxVars{};
0171 auxVars.invCovs.resize(signs.size(), -1.);
0172 Vector centerOfGravity{Vector::Zero()};
0173
0174
0175 for (const auto& [sIdx, strawMeas] : enumerate(measurements)) {
0176 if (!strawMeas->isStraw()) {
0177 ACTS_DEBUG(__func__ << "() - " << __LINE__ << ": The measurement "
0178 << toString(*strawMeas) << " is not a straw");
0179 continue;
0180 }
0181 const double cov = strawMeas->covariance()[s_covIdx];
0182 if (cov < std::numeric_limits<double>::epsilon()) {
0183 ACTS_WARNING(__func__ << "() - " << __LINE__ << ": The covariance ("
0184 << cov << ") of the measurement "
0185 << toString(*strawMeas) << " is invalid.");
0186 continue;
0187 }
0188 ACTS_VERBOSE(__func__ << "() - " << __LINE__ << ": Fill "
0189 << toString(*strawMeas) << ".");
0190
0191 auto& invCov = (auxVars.invCovs[sIdx] = 1. / cov);
0192 auxVars.covNorm += invCov;
0193 centerOfGravity += invCov * strawMeas->localPosition();
0194 ++auxVars.nDoF;
0195 }
0196 if (auxVars.nDoF < 3) {
0197 std::stringstream sstr{};
0198 for (const auto& [sIdx, strawMeas] : enumerate(measurements)) {
0199 sstr << " --- " << (sIdx + 1) << ") " << toString(*strawMeas)
0200 << ", weight: " << auxVars.invCovs[sIdx] << std::endl;
0201 }
0202 ACTS_WARNING(__func__ << "() - " << __LINE__
0203 << ": At least 3 measurements are required to "
0204 "perform the straw line fit\n"
0205 << sstr.str());
0206 auxVars.nDoF = 0u;
0207 return auxVars;
0208 }
0209
0210
0211 auxVars.nDoF -= 2u;
0212 auxVars.covNorm = 1. / auxVars.covNorm;
0213 centerOfGravity *= auxVars.covNorm;
0214
0215
0216 bool centerSet{false};
0217 for (const auto& [sIdx, strawMeas] : enumerate(measurements)) {
0218 const auto& invCov = auxVars.invCovs[sIdx];
0219
0220 if (invCov < 0.) {
0221 continue;
0222 }
0223 if (!centerSet) {
0224 auxVars.centerY = centerOfGravity.dot(strawMeas->toNextSensor());
0225 auxVars.centerZ = centerOfGravity.dot(strawMeas->planeNormal());
0226 centerSet = true;
0227 }
0228 const Vector pos = strawMeas->localPosition() - centerOfGravity;
0229 const double y = pos.dot(strawMeas->toNextSensor());
0230 const double z = pos.dot(strawMeas->planeNormal());
0231 const double r = strawMeas->driftRadius();
0232
0233 auxVars.T_zzyy += invCov * (Acts::square(z) - Acts::square(y));
0234 auxVars.T_yz += invCov * z * y;
0235 const double sInvCov = -invCov * signs[sIdx];
0236 auxVars.T_rz += sInvCov * z * r;
0237 auxVars.T_ry += sInvCov * y * r;
0238 auxVars.fitY0 += sInvCov * r;
0239 }
0240 auxVars.fitY0 *= auxVars.covNorm;
0241
0242 return auxVars;
0243 }
0244
0245 template <CompositeSpacePointContainer StrawCont_t,
0246 CompositeSpacePointFastCalibrator<
0247 Acts::RemovePointer_t<typename StrawCont_t::value_type>>
0248 Calibrator_t>
0249 std::optional<FastStrawLineFitter::FitResultT0> FastStrawLineFitter::fit(
0250 const Acts::CalibrationContext& ctx, const Calibrator_t& calibrator,
0251 const StrawCont_t& measurements, const std::vector<int>& signs,
0252 std::optional<double> startT0) const {
0253 using namespace Acts::UnitLiterals;
0254 if (measurements.size() != signs.size()) {
0255 ACTS_WARNING(
0256 __func__ << "() - " << __LINE__
0257 << ": Not all measurements are associated with a drift sign");
0258 return std::nullopt;
0259 }
0260
0261 FitResultT0 result{};
0262 result.t0 = startT0.value_or(0.);
0263
0264 FitAuxiliariesWithT0 fitPars{
0265 fillAuxiliaries(ctx, calibrator, measurements, signs, result.t0)};
0266 result.theta = startTheta(fitPars);
0267 result.nDoF = fitPars.nDoF;
0268 ACTS_DEBUG(__func__ << "() - " << __LINE__
0269 << ": Initial fit parameters: " << result);
0270 UpdateStatus iterStatus{UpdateStatus::GoodStep};
0271
0272 while ((iterStatus = updateIteration(fitPars, result)) !=
0273 UpdateStatus::Exceeded) {
0274 if (iterStatus == UpdateStatus::Converged) {
0275 calcPostFitChi2(ctx, measurements, calibrator, result);
0276 return result;
0277 }
0278 fitPars = fillAuxiliaries(ctx, calibrator, measurements, signs, result.t0);
0279 }
0280 if (logger().doPrint(Logging::VERBOSE)) {
0281 ACTS_VERBOSE("Fit failed, printing all measurements:");
0282 for (const auto& meas : measurements) {
0283 ACTS_VERBOSE(toString(*meas)
0284 << ", t0: " << result.t0 / 1._ns
0285 << ", truthR, RecoR: " << meas->driftRadius() << ", "
0286 << calibrator.driftRadius(ctx, *meas, result.t0)
0287 << ", velocity: "
0288 << calibrator.driftVelocity(ctx, *meas, result.t0) * 1._ns
0289 << ", acceleration: "
0290 << calibrator.driftAcceleration(ctx, *meas, result.t0) *
0291 Acts::square(1._ns));
0292 }
0293 ACTS_VERBOSE("Result: " << result);
0294 }
0295 return std::nullopt;
0296 }
0297
0298 template <CompositeSpacePointContainer StrawCont_t,
0299 CompositeSpacePointFastCalibrator<
0300 Acts::RemovePointer_t<typename StrawCont_t::value_type>>
0301 Calibrator_t>
0302 FastStrawLineFitter::FitAuxiliariesWithT0 FastStrawLineFitter::fillAuxiliaries(
0303 const CalibrationContext& ctx, const Calibrator_t& calibrator,
0304 const StrawCont_t& measurements, const std::vector<int>& signs,
0305 const double t0) const {
0306 using namespace Acts::UnitLiterals;
0307 FitAuxiliariesWithT0 auxVars{fillAuxiliaries(measurements, signs)};
0308 if (auxVars.nDoF <= 1) {
0309 auxVars.nDoF = 0;
0310 return auxVars;
0311 }
0312
0313 --auxVars.nDoF;
0314
0315 auxVars.T_rz = 0.;
0316 auxVars.T_ry = 0.;
0317 auxVars.fitY0 = 0.;
0318 for (const auto& [spIdx, strawMeas] : enumerate(measurements)) {
0319 const double& invCov = auxVars.invCovs[spIdx];
0320
0321 if (invCov < 0.) {
0322 continue;
0323 }
0324 const double sInvCov = -invCov * signs[spIdx];
0325 const double r = calibrator.driftRadius(ctx, *strawMeas, t0);
0326 const double v = calibrator.driftVelocity(ctx, *strawMeas, t0);
0327 const double a = calibrator.driftAcceleration(ctx, *strawMeas, t0);
0328 const double y = strawMeas->localPosition().dot(strawMeas->toNextSensor()) -
0329 auxVars.centerY;
0330 const double z = strawMeas->localPosition().dot(strawMeas->planeNormal()) -
0331 auxVars.centerZ;
0332
0333 ACTS_VERBOSE(__func__ << "() - " << __LINE__ << ": # " << (spIdx + 1)
0334 << ") " << toString(*strawMeas) << ", t0: "
0335 << t0 / 1._ns << " r: " << r << ", v: " << v * 1._ns
0336 << ", a: " << a * Acts::square(1._ns));
0337 auxVars.fitY0 += sInvCov * r;
0338 auxVars.R_v += sInvCov * v;
0339 auxVars.R_a += sInvCov * a;
0340
0341 auxVars.T_rz += sInvCov * z * r;
0342 auxVars.T_ry += sInvCov * y * r;
0343
0344 auxVars.T_vy += sInvCov * v * y;
0345 auxVars.T_vz += sInvCov * v * z;
0346
0347 auxVars.R_vr += invCov * r * v;
0348 auxVars.R_vv += invCov * v * v;
0349
0350 auxVars.T_ay += sInvCov * a * y;
0351 auxVars.T_az += sInvCov * a * z;
0352
0353 auxVars.R_ar += invCov * a * r;
0354 }
0355 auxVars.fitY0 *= auxVars.covNorm;
0356 ACTS_DEBUG(__func__ << "() - " << __LINE__ << " Fit constants calculated \n"
0357 << auxVars);
0358 return auxVars;
0359 }
0360
0361 }