File indexing completed on 2026-09-22 07:58:28
0001
0002
0003
0004
0005
0006
0007
0008
0009 #include "Acts/Seeding/EstimateTrackParamsFromSeed.hpp"
0010
0011 #include "Acts/Definitions/Algebra.hpp"
0012 #include "Acts/Definitions/TrackParametrization.hpp"
0013 #include "Acts/EventData/TransformationHelpers.hpp"
0014 #include "Acts/Seeding/TrackParamsEstimationError.hpp"
0015 #include "Acts/Seeding/detail/CircleFit.hpp"
0016 #include "Acts/Utilities/MathHelpers.hpp"
0017 #include "Acts/Utilities/TransformHelpers.hpp"
0018
0019 #include <cassert>
0020 #include <cmath>
0021 #include <limits>
0022 #include <numbers>
0023 #include <optional>
0024 #include <span>
0025 #include <vector>
0026
0027 #include <Eigen/Eigenvalues>
0028
0029 namespace Acts {
0030
0031 namespace {
0032
0033 Transform3 estimationFrameLocalToGlobal(const Vector3& sp0, const Vector3& sp1,
0034 const Vector3& bField) {
0035
0036
0037
0038
0039
0040
0041
0042
0043 const Vector3 relVec = sp1 - sp0;
0044 const double bMag = bField.norm();
0045 const Vector3 newZAxis = (bMag > std::numeric_limits<double>::epsilon())
0046 ? Vector3(bField / bMag)
0047 : Vector3(Vector3::UnitZ());
0048 Vector3 newYAxis = newZAxis.cross(relVec);
0049 if (newYAxis.norm() < std::numeric_limits<double>::epsilon()) {
0050 newYAxis = newZAxis.unitOrthogonal();
0051 }
0052 newYAxis.normalize();
0053 const Vector3 newXAxis = newYAxis.cross(newZAxis);
0054 RotationMatrix3 rotation;
0055 rotation.col(0) = newXAxis;
0056 rotation.col(1) = newYAxis;
0057 rotation.col(2) = newZAxis;
0058
0059 return makeTransform3(rotation, sp0);
0060 }
0061
0062 double computeDzDs(double A, double B, const Vector3& local0,
0063 const Vector3& local2) {
0064 const auto computeLocalPhi = [&](const Vector2& local) -> double {
0065
0066 const Vector2 r = 2 * B * local - Vector2(-A, 1);
0067
0068 return std::atan2(r.y(), r.x());
0069 };
0070
0071 const double localPhi0 = computeLocalPhi(local0.head<2>());
0072 const double localPhi2 = computeLocalPhi(local2.head<2>());
0073
0074 const double dZ = local2.z() - local0.z();
0075 const double dPhi = localPhi2 - localPhi0;
0076
0077
0078
0079
0080 const double sincCorrection = sinc(dPhi / 2);
0081
0082 const double dzds =
0083 sincCorrection * dZ / (local2.head<2>() - local0.head<2>()).norm();
0084
0085 return dzds;
0086 }
0087
0088 struct ConformalMappingResult {
0089 Vector2 uv1;
0090 Vector2 uv2;
0091 Vector2 duv;
0092 double A;
0093 double B;
0094 double bOverS;
0095 double dzds;
0096 };
0097
0098 ConformalMappingResult performConformalMapping(const Vector3& local1,
0099 const Vector3& local2) {
0100 ConformalMappingResult r{};
0101 r.uv1 = local1.head<2>() / local1.head<2>().squaredNorm();
0102 r.uv2 = local2.head<2>() / local2.head<2>().squaredNorm();
0103 r.duv = r.uv2 - r.uv1;
0104 r.A = r.duv.y() / r.duv.x();
0105 r.B = r.uv1.y() - r.A * r.uv1.x();
0106 r.bOverS = (r.uv1.y() * r.uv2.x() - r.uv2.y() * r.uv1.x()) / r.duv.norm();
0107 r.dzds = computeDzDs(r.A, r.B, local1, local2);
0108 return r;
0109 }
0110
0111 Vector3 computeLocalTangent(const ConformalMappingResult& cm,
0112 const Vector2& local) {
0113
0114 const Vector2 r = 2 * cm.B * local - Vector2(-cm.A, 1);
0115
0116
0117 const Vector3 t(-r.y(), r.x(), r.norm() * cm.dzds);
0118
0119 return t.normalized();
0120 }
0121
0122 }
0123
0124 }
0125
0126 Acts::FreeVector Acts::estimateTrackParamsFromSeed(
0127 const Vector3& sp0, const double t0, const Vector3& sp1, const Vector3& sp2,
0128 const Vector3& bField, Vector3* tangent0, Vector3* tangent1,
0129 Vector3* tangent2) {
0130 const Transform3 transform = estimationFrameLocalToGlobal(sp0, sp1, bField);
0131
0132
0133 const Vector3 local0 = Vector3::Zero();
0134 const Vector3 local1 = transform.inverse() * sp1;
0135 const Vector3 local2 = transform.inverse() * sp2;
0136
0137
0138 const ConformalMappingResult cm = performConformalMapping(local1, local2);
0139
0140
0141 const Vector3 direction =
0142 transform.linear() * computeLocalTangent(cm, local0.head<2>());
0143
0144
0145 FreeVector params = FreeVector::Zero();
0146
0147
0148 params.segment<3>(eFreePos0) = sp0;
0149
0150
0151 params.segment<3>(eFreeDir0) = direction;
0152
0153
0154
0155 const double qOverPt = 2 * cm.bOverS / bField.norm();
0156
0157 params[eFreeQOverP] = qOverPt / fastHypot(1, cm.dzds);
0158
0159
0160 params[eFreeTime] = t0;
0161
0162 if (tangent0 != nullptr) {
0163 *tangent0 = direction;
0164 }
0165 if (tangent1 != nullptr) {
0166 *tangent1 = transform.linear() * computeLocalTangent(cm, local1.head<2>());
0167 }
0168 if (tangent2 != nullptr) {
0169 *tangent2 = transform.linear() * computeLocalTangent(cm, local2.head<2>());
0170 }
0171
0172 return params;
0173 }
0174
0175 Acts::Result<Acts::BoundVector> Acts::estimateTrackParamsFromSeed(
0176 const GeometryContext& gctx, const Surface& surface, const Vector3& sp0,
0177 const double t0, const Vector3& sp1, const Vector3& sp2,
0178 const Vector3& bField) {
0179 const FreeVector freeParams =
0180 estimateTrackParamsFromSeed(sp0, t0, sp1, sp2, bField);
0181 return transformFreeToBoundParameters(freeParams, surface, gctx);
0182 }
0183
0184 Acts::BoundMatrix Acts::estimateTrackParamCovariance(
0185 const EstimateTrackParamCovarianceConfig& config, const BoundVector& params,
0186 bool hasTime) {
0187 assert((params[eBoundTheta] > 0 && params[eBoundTheta] < std::numbers::pi) &&
0188 "Theta must be in the range (0, pi)");
0189
0190 BoundMatrix result = BoundMatrix::Zero();
0191
0192 for (std::size_t i = eBoundLoc0; i < eBoundSize; ++i) {
0193 double sigma = config.initialSigmas[i];
0194 double variance = sigma * sigma;
0195
0196 if (i == eBoundQOverP) {
0197
0198 double varianceTheta = result(eBoundTheta, eBoundTheta);
0199
0200
0201 variance += std::pow(
0202 config.initialSigmaQoverPt * std::sin(params[eBoundTheta]), 2);
0203
0204
0205 variance += std::pow(config.initialSigmaPtRel * params[eBoundQOverP], 2);
0206
0207
0208 variance +=
0209 varianceTheta *
0210 std::pow(params[eBoundQOverP] / std::tan(params[eBoundTheta]), 2);
0211 }
0212
0213 if (i == eBoundTime && !hasTime) {
0214
0215 variance *= config.noTimeVarInflation;
0216 }
0217
0218
0219 variance *= config.initialVarInflation[i];
0220
0221 result(i, i) = variance;
0222 }
0223
0224 return result;
0225 }
0226
0227 Acts::Result<Acts::FreeVector> Acts::estimateTrackParamsFromSpacePoints(
0228 std::span<const Vector3> spacePoints, const Vector3& bField, double t0,
0229 std::size_t geometricRefineIterations, std::span<const double> weights,
0230 std::size_t referenceIndex) {
0231 if (spacePoints.size() < 3) {
0232 return Result<FreeVector>::failure(
0233 TrackParamsEstimationError::NotEnoughSpacePoints);
0234 }
0235 assert((weights.empty() || weights.size() == spacePoints.size()) &&
0236 "weights must be empty or match the space points");
0237 assert(referenceIndex < spacePoints.size() &&
0238 "reference index must point into the space points");
0239
0240 const auto w = [&](std::size_t i) {
0241 return weights.empty() ? 1 : weights[i];
0242 };
0243
0244 const Vector3& reference = spacePoints[referenceIndex];
0245
0246
0247
0248
0249 const Transform3 transform =
0250 estimationFrameLocalToGlobal(spacePoints.front(), spacePoints[1], bField);
0251 const Transform3 toLocal = transform.inverse();
0252 std::vector<Vector3> local;
0253 local.reserve(spacePoints.size());
0254 for (const Vector3& sp : spacePoints) {
0255 local.push_back(toLocal * sp);
0256 }
0257
0258 FreeVector params = FreeVector::Zero();
0259 params.segment<3>(eFreePos0) = reference;
0260 params[eFreeTime] = t0;
0261
0262 const double bMag = bField.norm();
0263
0264 std::optional<detail::CircleFit> circle =
0265 detail::fitCircleTaubin(local, weights);
0266 if (circle.has_value() && geometricRefineIterations > 0) {
0267 circle = detail::refineCircleGeometric(*circle, local,
0268 geometricRefineIterations, weights);
0269 }
0270
0271 if (!circle.has_value()) {
0272
0273 double sumW = 0;
0274 Vector3 mean = Vector3::Zero();
0275 for (std::size_t i = 0; i < local.size(); ++i) {
0276 sumW += w(i);
0277 mean += w(i) * local[i];
0278 }
0279 if (sumW <= 0) {
0280 return Result<FreeVector>::failure(
0281 TrackParamsEstimationError::DegenerateFit);
0282 }
0283 mean /= sumW;
0284 SquareMatrix3 cov = SquareMatrix3::Zero();
0285 for (std::size_t i = 0; i < local.size(); ++i) {
0286 const Vector3 d = local[i] - mean;
0287 cov += w(i) * d * d.transpose();
0288 }
0289 Eigen::SelfAdjointEigenSolver<SquareMatrix3> solver(cov);
0290
0291 if (solver.info() != Eigen::Success || (solver.eigenvalues()[2] <= 0)) {
0292 return Result<FreeVector>::failure(
0293 TrackParamsEstimationError::DegenerateFit);
0294 }
0295 Vector3 localDir = solver.eigenvectors().col(2);
0296 if (localDir.dot(local.back() - local.front()) < 0) {
0297 localDir = -localDir;
0298 }
0299 localDir.normalize();
0300 params.segment<3>(eFreeDir0) = transform.linear() * localDir;
0301 return Result<FreeVector>::success(params);
0302 }
0303
0304 const Vector2 center = circle->center;
0305 const double radius = circle->radius;
0306
0307 const Vector2 firstXy = local.front().head<2>();
0308 const Vector2 refXy = local[referenceIndex].head<2>();
0309
0310
0311
0312
0313 const Vector2 firstRadial = firstXy - center;
0314 const Vector2 firstCcwTangent(-firstRadial.y(), firstRadial.x());
0315 const double rotSense =
0316 (firstCcwTangent.dot(local[1].head<2>() - firstXy) >= 0) ? 1 : -1;
0317
0318
0319
0320 const Vector2 radial = refXy - center;
0321 const Vector2 tangent =
0322 (rotSense * Vector2(-radial.y(), radial.x())).normalized();
0323
0324
0325 const double toCenterProj = tangent.y() * (center.x() - refXy.x()) -
0326 tangent.x() * (center.y() - refXy.y());
0327 const double qSign = (toCenterProj >= 0) ? 1 : -1;
0328
0329
0330
0331
0332
0333 const double phiFirst =
0334 std::atan2(firstXy.y() - center.y(), firstXy.x() - center.x());
0335 double phiPrev = phiFirst;
0336 double sumW = 0;
0337 double sumS = 0;
0338 double sumZ = 0;
0339 double sumSS = 0;
0340 double sumSZ = 0;
0341 for (std::size_t i = 0; i < local.size(); ++i) {
0342 const Vector3& p = local[i];
0343 double phi = std::atan2(p.y() - center.y(), p.x() - center.x());
0344 while (phi - phiPrev > std::numbers::pi) {
0345 phi -= 2 * std::numbers::pi;
0346 }
0347 while (phi - phiPrev < -std::numbers::pi) {
0348 phi += 2 * std::numbers::pi;
0349 }
0350 phiPrev = phi;
0351 const double wi = w(i);
0352 const double s = rotSense * radius * (phi - phiFirst);
0353 const double z = p.z();
0354 sumW += wi;
0355 sumS += wi * s;
0356 sumZ += wi * z;
0357 sumSS += wi * s * s;
0358 sumSZ += wi * s * z;
0359 }
0360 const double denom = sumW * sumSS - sumS * sumS;
0361 const double lambda =
0362 (std::abs(denom) > std::numeric_limits<double>::epsilon())
0363 ? (sumW * sumSZ - sumS * sumZ) / denom
0364 : 0;
0365
0366
0367
0368 Vector3 localDir(tangent.x(), tangent.y(), lambda);
0369 localDir.normalize();
0370 params.segment<3>(eFreeDir0) = transform.linear() * localDir;
0371
0372
0373
0374 if (bMag > std::numeric_limits<double>::epsilon()) {
0375 const double qOverPt = qSign / (radius * bMag);
0376 params[eFreeQOverP] = qOverPt / fastHypot(1, lambda);
0377 }
0378
0379 return Result<FreeVector>::success(params);
0380 }