File indexing completed on 2026-08-31 08:21:26
0001 #include "TpcTrackHelixFitter.h"
0002
0003 #include <Eigen/Dense>
0004
0005 #include <algorithm>
0006 #include <cmath>
0007 #include <limits>
0008 #include <numeric>
0009 #include <tuple>
0010
0011 namespace
0012 {
0013 constexpr double kPi = 3.14159265358979323846;
0014 constexpr double kCurvatureRefineMinRadiusCm = 150.0;
0015 constexpr double kCurvatureRefineMaxThetaSpan = 1.0;
0016 constexpr double kMinTurnDzForBranchingCm = 0.5;
0017 constexpr double kAnchorResidualSlackCm = 5.0;
0018 constexpr double kMaximumSearchTurnFraction = 0.95;
0019 constexpr int kMaxMeasurementTurns = 32;
0020
0021 template <class T>
0022 constexpr T square(const T &value)
0023 {
0024 return value * value;
0025 }
0026
0027 double quiet_nan()
0028 {
0029 return std::numeric_limits<double>::quiet_NaN();
0030 }
0031
0032 double unwrap_to_previous(double theta, const double previous)
0033 {
0034 while (theta - previous > kPi)
0035 {
0036 theta -= 2.0 * kPi;
0037 }
0038 while (theta - previous < -kPi)
0039 {
0040 theta += 2.0 * kPi;
0041 }
0042 return theta;
0043 }
0044
0045 bool solve3x3(double a[3][3], double b[3], double x[3])
0046 {
0047 for (int i = 0; i < 3; ++i)
0048 {
0049 int pivot = i;
0050 double pivot_abs = std::abs(a[i][i]);
0051 for (int row = i + 1; row < 3; ++row)
0052 {
0053 const double value_abs = std::abs(a[row][i]);
0054 if (value_abs > pivot_abs)
0055 {
0056 pivot = row;
0057 pivot_abs = value_abs;
0058 }
0059 }
0060
0061 if (pivot_abs < 1e-16)
0062 {
0063 return false;
0064 }
0065
0066 if (pivot != i)
0067 {
0068 for (int col = i; col < 3; ++col)
0069 {
0070 std::swap(a[i][col], a[pivot][col]);
0071 }
0072 std::swap(b[i], b[pivot]);
0073 }
0074
0075 const double diag = a[i][i];
0076 for (int col = i; col < 3; ++col)
0077 {
0078 a[i][col] /= diag;
0079 }
0080 b[i] /= diag;
0081
0082 for (int row = 0; row < 3; ++row)
0083 {
0084 if (row == i)
0085 {
0086 continue;
0087 }
0088 const double factor = a[row][i];
0089 if (std::abs(factor) < 1e-20)
0090 {
0091 continue;
0092 }
0093 for (int col = i; col < 3; ++col)
0094 {
0095 a[row][col] -= factor * a[i][col];
0096 }
0097 b[row] -= factor * b[i];
0098 }
0099 }
0100
0101 x[0] = b[0];
0102 x[1] = b[1];
0103 x[2] = b[2];
0104 return true;
0105 }
0106
0107 bool theta_span_for_circle(const std::vector<TpcTrackPoint> &points,
0108 const std::size_t nfit,
0109 const double cx,
0110 const double cy,
0111 double &theta_span)
0112 {
0113 theta_span = std::numeric_limits<double>::quiet_NaN();
0114 if (nfit < 2 || points.size() < nfit)
0115 {
0116 return false;
0117 }
0118
0119 std::vector<double> theta_values;
0120 theta_values.reserve(nfit);
0121 for (std::size_t i = 0; i < nfit; ++i)
0122 {
0123 const auto &pos = points[i].position;
0124 if (!std::isfinite(pos.x) || !std::isfinite(pos.y))
0125 {
0126 return false;
0127 }
0128 double theta = std::atan2(pos.y - cy, pos.x - cx);
0129 if (!theta_values.empty())
0130 {
0131 theta = unwrap_to_previous(theta, theta_values.back());
0132 }
0133 theta_values.push_back(theta);
0134 }
0135
0136 const auto minmax = std::minmax_element(theta_values.begin(), theta_values.end());
0137 if (minmax.first == theta_values.end())
0138 {
0139 return false;
0140 }
0141
0142 theta_span = *minmax.second - *minmax.first;
0143 return std::isfinite(theta_span) && theta_span >= 0.0;
0144 }
0145
0146 bool prefer_curvature_refined_circle(const std::vector<TpcTrackPoint> &points,
0147 const std::size_t nfit,
0148 const double cx,
0149 const double cy,
0150 const double radius)
0151 {
0152 if (nfit < 4 || !std::isfinite(radius) || radius < kCurvatureRefineMinRadiusCm)
0153 {
0154 return false;
0155 }
0156
0157 double theta_span = std::numeric_limits<double>::quiet_NaN();
0158 if (!theta_span_for_circle(points, nfit, cx, cy, theta_span))
0159 {
0160 return false;
0161 }
0162
0163 return theta_span < kCurvatureRefineMaxThetaSpan;
0164 }
0165 }
0166
0167 bool TpcTrackHelixFitter::parse_point_order(const std::string &mode, TpcTrackPointOrder &order)
0168 {
0169 if (mode == "path")
0170 {
0171 order = TpcTrackPointOrder::Path;
0172 return true;
0173 }
0174 if (mode == "input")
0175 {
0176 order = TpcTrackPointOrder::Input;
0177 return true;
0178 }
0179 if (mode == "radius" || mode == "r")
0180 {
0181 order = TpcTrackPointOrder::Radius;
0182 return true;
0183 }
0184 if (mode == "theta-z" || mode == "thetaz" || mode == "theta_z")
0185 {
0186 order = TpcTrackPointOrder::ThetaZ;
0187 return true;
0188 }
0189 if (mode == "auto")
0190 {
0191 order = TpcTrackPointOrder::Auto;
0192 return true;
0193 }
0194 return false;
0195 }
0196
0197 void TpcTrackHelixFitter::order_points(std::vector<TpcTrackPoint> &points,
0198 const TpcTrackPointOrder order)
0199 {
0200 if (points.size() <= 2 || order == TpcTrackPointOrder::Input)
0201 {
0202 return;
0203 }
0204
0205 auto apply_order = [&points](const std::vector<std::size_t> &indices)
0206 {
0207 std::vector<TpcTrackPoint> ordered;
0208 ordered.reserve(indices.size());
0209 for (const auto index : indices)
0210 {
0211 ordered.push_back(points[index]);
0212 }
0213 points = std::move(ordered);
0214 };
0215
0216 auto path_indices = [&points]()
0217 {
0218 std::vector<std::size_t> indices(points.size());
0219 std::iota(indices.begin(), indices.end(), 0);
0220 std::stable_sort(indices.begin(), indices.end(),
0221 [&points](const auto lhs, const auto rhs)
0222 { return points[lhs].path < points[rhs].path; });
0223 return indices;
0224 };
0225
0226 auto radius_indices = [&points]()
0227 {
0228 std::vector<std::size_t> indices(points.size());
0229 std::iota(indices.begin(), indices.end(), 0);
0230 std::stable_sort(indices.begin(), indices.end(),
0231 [&points](const auto lhs, const auto rhs)
0232 {
0233 const double lhs_r = pt(points[lhs].position);
0234 const double rhs_r = pt(points[rhs].position);
0235 if (std::abs(lhs_r - rhs_r) > 1.0e-6)
0236 {
0237 return lhs_r < rhs_r;
0238 }
0239 return points[lhs].layer < points[rhs].layer;
0240 });
0241 return indices;
0242 };
0243
0244 auto inner_first = [&points](std::vector<std::size_t> indices)
0245 {
0246 if (indices.size() >= 2)
0247 {
0248 const double first_r = pt(points[indices.front()].position);
0249 const double last_r = pt(points[indices.back()].position);
0250 if (first_r > last_r)
0251 {
0252 std::reverse(indices.begin(), indices.end());
0253 }
0254 }
0255 return indices;
0256 };
0257
0258 auto theta_z_indices = [&points, &path_indices, &inner_first]()
0259 {
0260 double cx = 0.0;
0261 double cy = 0.0;
0262 double radius = 0.0;
0263 if (!fit_circle_least_squares(points, points.size(), cx, cy, radius))
0264 {
0265 return path_indices();
0266 }
0267
0268 std::vector<double> raw_theta(points.size(), 0.0);
0269 double z_min = points.front().position.z;
0270 double z_max = points.front().position.z;
0271 for (std::size_t i = 0; i < points.size(); ++i)
0272 {
0273 raw_theta[i] = std::atan2(points[i].position.y - cy, points[i].position.x - cx);
0274 z_min = std::min(z_min, points[i].position.z);
0275 z_max = std::max(z_max, points[i].position.z);
0276 }
0277
0278 std::vector<std::size_t> indices(points.size());
0279 std::iota(indices.begin(), indices.end(), 0);
0280 if (z_max - z_min > 1.0e-3)
0281 {
0282 std::vector<std::size_t> z_order = indices;
0283 std::stable_sort(z_order.begin(), z_order.end(),
0284 [&points](const auto lhs, const auto rhs)
0285 { return points[lhs].position.z < points[rhs].position.z; });
0286
0287 std::vector<double> unwrapped_theta = raw_theta;
0288 bool have_previous = false;
0289 double previous = 0.0;
0290 for (const auto index : z_order)
0291 {
0292 double theta = raw_theta[index];
0293 if (have_previous)
0294 {
0295 theta = unwrap_to_previous(theta, previous);
0296 }
0297 unwrapped_theta[index] = theta;
0298 previous = theta;
0299 have_previous = true;
0300 }
0301
0302 std::stable_sort(indices.begin(), indices.end(),
0303 [&unwrapped_theta](const auto lhs, const auto rhs)
0304 { return unwrapped_theta[lhs] < unwrapped_theta[rhs]; });
0305 return inner_first(indices);
0306 }
0307
0308 std::stable_sort(indices.begin(), indices.end(),
0309 [&raw_theta](const auto lhs, const auto rhs)
0310 { return raw_theta[lhs] < raw_theta[rhs]; });
0311 return inner_first(indices);
0312 };
0313
0314 auto looks_like_looper = [&points, &path_indices]()
0315 {
0316 std::vector<int> layers;
0317 layers.reserve(points.size());
0318 for (const auto &point : points)
0319 {
0320 layers.push_back(point.layer);
0321 }
0322 std::sort(layers.begin(), layers.end());
0323 if (std::adjacent_find(layers.begin(), layers.end()) != layers.end())
0324 {
0325 return true;
0326 }
0327
0328 const auto sequence = path_indices();
0329 std::vector<double> dr_values;
0330 dr_values.reserve(sequence.size());
0331 for (std::size_t i = 1; i < sequence.size(); ++i)
0332 {
0333 const double dr = pt(points[sequence[i]].position) - pt(points[sequence[i - 1]].position);
0334 if (std::abs(dr) > 0.5)
0335 {
0336 dr_values.push_back(dr);
0337 }
0338 }
0339 for (std::size_t i = 1; i < dr_values.size(); ++i)
0340 {
0341 if (dr_values[i] * dr_values[i - 1] < 0.0)
0342 {
0343 return true;
0344 }
0345 }
0346
0347 double cx = 0.0;
0348 double cy = 0.0;
0349 double radius = 0.0;
0350 if (!fit_circle_least_squares(points, points.size(), cx, cy, radius) ||
0351 points.size() < 4)
0352 {
0353 return false;
0354 }
0355
0356 double z_min = points.front().position.z;
0357 double z_max = points.front().position.z;
0358 std::vector<std::size_t> z_order(points.size());
0359 std::iota(z_order.begin(), z_order.end(), 0);
0360 for (const auto &point : points)
0361 {
0362 z_min = std::min(z_min, point.position.z);
0363 z_max = std::max(z_max, point.position.z);
0364 }
0365 if (z_max - z_min <= 1.0e-3)
0366 {
0367 return false;
0368 }
0369
0370 std::stable_sort(z_order.begin(), z_order.end(),
0371 [&points](const auto lhs, const auto rhs)
0372 { return points[lhs].position.z < points[rhs].position.z; });
0373
0374 std::vector<double> theta_values;
0375 theta_values.reserve(points.size());
0376 for (const auto index : z_order)
0377 {
0378 double theta = std::atan2(points[index].position.y - cy, points[index].position.x - cx);
0379 if (!theta_values.empty())
0380 {
0381 theta = unwrap_to_previous(theta, theta_values.back());
0382 }
0383 theta_values.push_back(theta);
0384 }
0385
0386 const auto minmax_theta = std::minmax_element(theta_values.begin(), theta_values.end());
0387 return minmax_theta.first != theta_values.end() &&
0388 *minmax_theta.second - *minmax_theta.first > 1.5 * kPi;
0389 };
0390
0391 switch (order)
0392 {
0393 case TpcTrackPointOrder::Path:
0394 apply_order(path_indices());
0395 break;
0396 case TpcTrackPointOrder::Radius:
0397 apply_order(radius_indices());
0398 break;
0399 case TpcTrackPointOrder::ThetaZ:
0400 apply_order(theta_z_indices());
0401 break;
0402 case TpcTrackPointOrder::Auto:
0403 apply_order(looks_like_looper() ? theta_z_indices() : radius_indices());
0404 break;
0405 case TpcTrackPointOrder::Input:
0406 break;
0407 }
0408 }
0409
0410 bool TpcTrackHelixFitter::fit_circle_least_squares(const std::vector<TpcTrackPoint> &points,
0411 const std::size_t nfit,
0412 double &cx,
0413 double &cy,
0414 double &radius)
0415 {
0416 if (nfit < 3 || points.size() < nfit)
0417 {
0418 return false;
0419 }
0420
0421 Eigen::MatrixXd matrix(nfit, 3);
0422 Eigen::VectorXd rhs(nfit);
0423 for (std::size_t i = 0; i < nfit; ++i)
0424 {
0425 const auto &pos = points[i].position;
0426 matrix(static_cast<int>(i), 0) = pos.x;
0427 matrix(static_cast<int>(i), 1) = pos.y;
0428 matrix(static_cast<int>(i), 2) = 1.0;
0429 rhs(static_cast<int>(i)) = -(square(pos.x) + square(pos.y));
0430 }
0431
0432 const Eigen::Vector3d solution = matrix.colPivHouseholderQr().solve(rhs);
0433 cx = -0.5 * solution(0);
0434 cy = -0.5 * solution(1);
0435 const double radius2 = square(cx) + square(cy) - solution(2);
0436 if (radius2 <= 0.0 || !std::isfinite(radius2))
0437 {
0438 return false;
0439 }
0440
0441 radius = std::sqrt(radius2);
0442 return radius > 0.0 && std::isfinite(radius);
0443 }
0444
0445 bool TpcTrackHelixFitter::fit_circle_curvature_refined(const std::vector<TpcTrackPoint> &points,
0446 const std::size_t nfit,
0447 double &cx,
0448 double &cy,
0449 double &radius)
0450 {
0451 cx = quiet_nan();
0452 cy = quiet_nan();
0453 radius = quiet_nan();
0454
0455 if (nfit < 4 || points.size() < nfit)
0456 {
0457 return false;
0458 }
0459
0460 std::vector<TpcTrackVec3> positions;
0461 positions.reserve(nfit);
0462 for (std::size_t i = 0; i < nfit; ++i)
0463 {
0464 const auto &pos = points[i].position;
0465 if (!finite(pos))
0466 {
0467 continue;
0468 }
0469 positions.push_back(pos);
0470 }
0471
0472 if (positions.size() < 4)
0473 {
0474 return false;
0475 }
0476
0477 const double npoints = static_cast<double>(positions.size());
0478
0479 double mean_x = 0.0;
0480 double mean_y = 0.0;
0481 for (const auto &pos : positions)
0482 {
0483 mean_x += pos.x;
0484 mean_y += pos.y;
0485 }
0486 mean_x /= npoints;
0487 mean_y /= npoints;
0488
0489 double cuu = 0.0;
0490 double cvv = 0.0;
0491 double cuv = 0.0;
0492 for (const auto &pos : positions)
0493 {
0494 const double u = pos.x - mean_x;
0495 const double v = pos.y - mean_y;
0496 cuu += u * u;
0497 cvv += v * v;
0498 cuv += u * v;
0499 }
0500 const double phi = 0.5 * std::atan2(2.0 * cuv, cuu - cvv);
0501 const double cphi = std::cos(phi);
0502 const double sphi = std::sin(phi);
0503
0504 double sum_s = 0.0;
0505 double sum_s2 = 0.0;
0506 double sum_s3 = 0.0;
0507 double sum_s4 = 0.0;
0508 double sum_n = 0.0;
0509 double sum_sn = 0.0;
0510 double sum_s2n = 0.0;
0511 for (const auto &pos : positions)
0512 {
0513 const double u = pos.x - mean_x;
0514 const double v = pos.y - mean_y;
0515 const double s = cphi * u + sphi * v;
0516 const double n = -sphi * u + cphi * v;
0517 sum_s += s;
0518 sum_s2 += s * s;
0519 sum_s3 += s * s * s;
0520 sum_s4 += s * s * s * s;
0521 sum_n += n;
0522 sum_sn += s * n;
0523 sum_s2n += s * s * n;
0524 }
0525
0526 double seed_matrix[3][3] = {
0527 {npoints, sum_s, sum_s2},
0528 {sum_s, sum_s2, sum_s3},
0529 {sum_s2, sum_s3, sum_s4}};
0530 double seed_rhs[3] = {sum_n, sum_sn, sum_s2n};
0531 double seed_coeffs[3] = {0.0, 0.0, 0.0};
0532 if (!solve3x3(seed_matrix, seed_rhs, seed_coeffs))
0533 {
0534 return false;
0535 }
0536
0537 const double a = seed_coeffs[0];
0538 const double b = seed_coeffs[1];
0539 const double c = seed_coeffs[2];
0540 const double denom = std::pow(1.0 + b * b, 1.5);
0541 if (!(denom > 0.0) || !std::isfinite(denom))
0542 {
0543 return false;
0544 }
0545
0546 const double kappa = 2.0 * c / denom;
0547 if (!std::isfinite(kappa) || !(std::abs(kappa) > 1e-14))
0548 {
0549 return false;
0550 }
0551
0552 auto circle_from_local = [&](const double a_par,
0553 const double b_par,
0554 const double kappa_par,
0555 double &cx_out,
0556 double &cy_out,
0557 double &radius_out) -> bool
0558 {
0559 if (!std::isfinite(a_par) || !std::isfinite(b_par) || !std::isfinite(kappa_par))
0560 {
0561 return false;
0562 }
0563 if (!(std::abs(kappa_par) > 1e-18))
0564 {
0565 return false;
0566 }
0567
0568 const double slope_norm = std::sqrt(1.0 + b_par * b_par);
0569 if (!(slope_norm > 0.0) || !std::isfinite(slope_norm))
0570 {
0571 return false;
0572 }
0573
0574 const double center_s = -b_par / (slope_norm * kappa_par);
0575 const double center_n = a_par + 1.0 / (slope_norm * kappa_par);
0576 const double radius_local = std::abs(1.0 / kappa_par);
0577 if (!(radius_local > 0.0) || !std::isfinite(radius_local))
0578 {
0579 return false;
0580 }
0581
0582 cx_out = mean_x + center_s * cphi - center_n * sphi;
0583 cy_out = mean_y + center_s * sphi + center_n * cphi;
0584 radius_out = radius_local;
0585 return std::isfinite(cx_out) && std::isfinite(cy_out);
0586 };
0587
0588 auto residuals_from_local = [&](const double a_par,
0589 const double b_par,
0590 const double kappa_par,
0591 std::vector<double> &residuals,
0592 double &chi2,
0593 double *cx_out = nullptr,
0594 double *cy_out = nullptr,
0595 double *radius_out = nullptr) -> bool
0596 {
0597 double cx_fit = 0.0;
0598 double cy_fit = 0.0;
0599 double radius_fit = 0.0;
0600 if (!circle_from_local(a_par, b_par, kappa_par, cx_fit, cy_fit, radius_fit))
0601 {
0602 return false;
0603 }
0604
0605 residuals.resize(positions.size());
0606 chi2 = 0.0;
0607 for (std::size_t i = 0; i < positions.size(); ++i)
0608 {
0609 const double dx = positions[i].x - cx_fit;
0610 const double dy = positions[i].y - cy_fit;
0611 const double dist = std::sqrt(dx * dx + dy * dy);
0612 if (!std::isfinite(dist))
0613 {
0614 return false;
0615 }
0616 const double resid = dist - radius_fit;
0617 residuals[i] = resid;
0618 chi2 += resid * resid;
0619 }
0620
0621 if (cx_out)
0622 {
0623 *cx_out = cx_fit;
0624 }
0625 if (cy_out)
0626 {
0627 *cy_out = cy_fit;
0628 }
0629 if (radius_out)
0630 {
0631 *radius_out = radius_fit;
0632 }
0633 return std::isfinite(chi2);
0634 };
0635
0636 double a_cur = a;
0637 double b_cur = b;
0638 double kappa_cur = kappa;
0639 double cx_cur = 0.0;
0640 double cy_cur = 0.0;
0641 double radius_cur = 0.0;
0642 std::vector<double> residuals_cur;
0643 double chi2_cur = 0.0;
0644 if (!residuals_from_local(a_cur, b_cur, kappa_cur, residuals_cur, chi2_cur,
0645 &cx_cur, &cy_cur, &radius_cur))
0646 {
0647 return false;
0648 }
0649
0650 double lambda = 1e-3;
0651 for (int iter = 0; iter < 30; ++iter)
0652 {
0653 const double step_a = std::max(1e-8, 1e-6 * std::max({1.0, std::abs(a_cur), 0.01 * radius_cur}));
0654 const double step_b = std::max(1e-8, 1e-6 * std::max(1.0, std::abs(b_cur)));
0655 const double step_kappa = std::max(1e-12, 1e-6 * std::max(std::abs(kappa_cur), 1.0 / std::max(radius_cur, 1.0)));
0656 const double steps[3] = {step_a, step_b, step_kappa};
0657
0658 std::vector<double> jac_cols[3];
0659 bool jacobian_ok = true;
0660 for (int ipar = 0; ipar < 3; ++ipar)
0661 {
0662 jac_cols[ipar].assign(positions.size(), 0.0);
0663
0664 double a_plus = a_cur;
0665 double b_plus = b_cur;
0666 double kappa_plus = kappa_cur;
0667 double a_minus = a_cur;
0668 double b_minus = b_cur;
0669 double kappa_minus = kappa_cur;
0670 if (ipar == 0)
0671 {
0672 a_plus += steps[ipar];
0673 a_minus -= steps[ipar];
0674 }
0675 else if (ipar == 1)
0676 {
0677 b_plus += steps[ipar];
0678 b_minus -= steps[ipar];
0679 }
0680 else
0681 {
0682 kappa_plus += steps[ipar];
0683 kappa_minus -= steps[ipar];
0684 }
0685
0686 std::vector<double> residuals_plus;
0687 std::vector<double> residuals_minus;
0688 double chi2_dummy = 0.0;
0689 const bool have_plus = residuals_from_local(a_plus, b_plus, kappa_plus, residuals_plus, chi2_dummy);
0690 const bool have_minus = residuals_from_local(a_minus, b_minus, kappa_minus, residuals_minus, chi2_dummy);
0691 if (!have_plus && !have_minus)
0692 {
0693 jacobian_ok = false;
0694 break;
0695 }
0696
0697 for (std::size_t ipoint = 0; ipoint < positions.size(); ++ipoint)
0698 {
0699 if (have_plus && have_minus)
0700 {
0701 jac_cols[ipar][ipoint] = (residuals_plus[ipoint] - residuals_minus[ipoint]) / (2.0 * steps[ipar]);
0702 }
0703 else if (have_plus)
0704 {
0705 jac_cols[ipar][ipoint] = (residuals_plus[ipoint] - residuals_cur[ipoint]) / steps[ipar];
0706 }
0707 else
0708 {
0709 jac_cols[ipar][ipoint] = (residuals_cur[ipoint] - residuals_minus[ipoint]) / steps[ipar];
0710 }
0711 }
0712 }
0713 if (!jacobian_ok)
0714 {
0715 break;
0716 }
0717
0718 double jtj[3][3] = {};
0719 double jtr[3] = {};
0720 for (std::size_t ipoint = 0; ipoint < positions.size(); ++ipoint)
0721 {
0722 for (int i = 0; i < 3; ++i)
0723 {
0724 const double ji = jac_cols[i][ipoint];
0725 jtr[i] += ji * residuals_cur[ipoint];
0726 for (int j = 0; j < 3; ++j)
0727 {
0728 jtj[i][j] += ji * jac_cols[j][ipoint];
0729 }
0730 }
0731 }
0732
0733 bool accepted = false;
0734 double delta[3] = {0.0, 0.0, 0.0};
0735 for (int trial = 0; trial < 8; ++trial)
0736 {
0737 double system[3][3] = {
0738 {jtj[0][0], jtj[0][1], jtj[0][2]},
0739 {jtj[1][0], jtj[1][1], jtj[1][2]},
0740 {jtj[2][0], jtj[2][1], jtj[2][2]}};
0741 for (int idiag = 0; idiag < 3; ++idiag)
0742 {
0743 system[idiag][idiag] += lambda * std::max(jtj[idiag][idiag], 1.0);
0744 }
0745
0746 double neg_jtr[3] = {-jtr[0], -jtr[1], -jtr[2]};
0747 if (!solve3x3(system, neg_jtr, delta))
0748 {
0749 lambda *= 10.0;
0750 continue;
0751 }
0752
0753 const double a_try = a_cur + delta[0];
0754 const double b_try = b_cur + delta[1];
0755 const double kappa_try = kappa_cur + delta[2];
0756 std::vector<double> residuals_try;
0757 double chi2_try = 0.0;
0758 double cx_try = 0.0;
0759 double cy_try = 0.0;
0760 double radius_try = 0.0;
0761 if (!residuals_from_local(a_try, b_try, kappa_try, residuals_try, chi2_try,
0762 &cx_try, &cy_try, &radius_try) ||
0763 !(chi2_try < chi2_cur))
0764 {
0765 lambda *= 10.0;
0766 continue;
0767 }
0768
0769 a_cur = a_try;
0770 b_cur = b_try;
0771 kappa_cur = kappa_try;
0772 residuals_cur.swap(residuals_try);
0773 chi2_cur = chi2_try;
0774 cx_cur = cx_try;
0775 cy_cur = cy_try;
0776 radius_cur = radius_try;
0777 lambda = std::max(1e-12, lambda * 0.3);
0778 accepted = true;
0779 break;
0780 }
0781
0782 if (!accepted)
0783 {
0784 break;
0785 }
0786
0787 const double rel_step = std::sqrt(
0788 std::pow(delta[0] / std::max({1.0, std::abs(a_cur), 0.01 * radius_cur}), 2) +
0789 std::pow(delta[1] / std::max(1.0, std::abs(b_cur)), 2) +
0790 std::pow(delta[2] / std::max(std::abs(kappa_cur), 1e-12), 2));
0791 if (rel_step < 1e-10)
0792 {
0793 break;
0794 }
0795 }
0796
0797 cx = cx_cur;
0798 cy = cy_cur;
0799 radius = radius_cur;
0800 return std::isfinite(cx) && std::isfinite(cy) &&
0801 std::isfinite(radius) && radius > 0.0;
0802 }
0803
0804 bool TpcTrackHelixFitter::fit(const std::vector<TpcTrackPoint> &points,
0805 const int fit_first_points,
0806 const double bfield_t,
0807 TpcTrackHelix &helix)
0808 {
0809 const std::size_t nfit =
0810 (fit_first_points > 0) ? std::min(points.size(), static_cast<std::size_t>(fit_first_points)) : points.size();
0811 if (nfit < 3)
0812 {
0813 return false;
0814 }
0815
0816 double cx = 0.0;
0817 double cy = 0.0;
0818 double radius = 0.0;
0819 bool have_circle = fit_circle_least_squares(points, nfit, cx, cy, radius);
0820 if (have_circle && prefer_curvature_refined_circle(points, nfit, cx, cy, radius))
0821 {
0822 double refined_cx = 0.0;
0823 double refined_cy = 0.0;
0824 double refined_radius = 0.0;
0825 if (fit_circle_curvature_refined(points, nfit, refined_cx, refined_cy, refined_radius))
0826 {
0827 cx = refined_cx;
0828 cy = refined_cy;
0829 radius = refined_radius;
0830 }
0831 }
0832 else if (!have_circle)
0833 {
0834 have_circle = fit_circle_curvature_refined(points, nfit, cx, cy, radius);
0835 }
0836
0837 if (!have_circle)
0838 {
0839 return false;
0840 }
0841
0842 Eigen::MatrixXd z_matrix(nfit, 2);
0843 Eigen::VectorXd z_rhs(nfit);
0844 std::vector<double> theta_values;
0845 theta_values.reserve(nfit);
0846 for (std::size_t i = 0; i < nfit; ++i)
0847 {
0848 double theta = std::atan2(points[i].position.y - cy, points[i].position.x - cx);
0849 if (!theta_values.empty())
0850 {
0851 theta = unwrap_to_previous(theta, theta_values.back());
0852 }
0853 theta_values.push_back(theta);
0854 z_matrix(static_cast<int>(i), 0) = theta;
0855 z_matrix(static_cast<int>(i), 1) = 1.0;
0856 z_rhs(static_cast<int>(i)) = points[i].position.z;
0857 }
0858
0859 const auto minmax_theta = std::minmax_element(theta_values.begin(), theta_values.end());
0860 if (minmax_theta.first == theta_values.end() ||
0861 *minmax_theta.second - *minmax_theta.first < 1e-4)
0862 {
0863 return false;
0864 }
0865
0866 const Eigen::Vector2d z_solution = z_matrix.colPivHouseholderQr().solve(z_rhs);
0867 double direction = (theta_values.back() > theta_values.front()) ? 1.0 : -1.0;
0868 if (std::abs(theta_values.back() - theta_values.front()) <= 0.0)
0869 {
0870 direction = 1.0;
0871 }
0872
0873 helix.cx = cx;
0874 helix.cy = cy;
0875 helix.radius = radius;
0876 helix.z0 = z_solution(1);
0877 helix.pitch = z_solution(0);
0878 helix.theta_first = theta_values.front();
0879 helix.theta_last = theta_values.back();
0880 helix.theta_min = *minmax_theta.first;
0881 helix.theta_max = *minmax_theta.second;
0882 helix.direction = direction;
0883 helix.bfield_t = bfield_t;
0884 return true;
0885 }
0886
0887 bool TpcTrackHelixFitter::from_state(const TpcTrackVec3 &position,
0888 const TpcTrackVec3 &momentum_value,
0889 const int charge,
0890 const double bfield_t,
0891 TpcTrackHelix &helix)
0892 {
0893 if (charge == 0 || bfield_t == 0.0 || !finite(position) || !finite(momentum_value))
0894 {
0895 return false;
0896 }
0897
0898 const double p_t = pt(momentum_value);
0899 if (p_t <= 0.0 || !std::isfinite(p_t))
0900 {
0901 return false;
0902 }
0903
0904 const double radius = p_t / (0.003 * std::abs(bfield_t));
0905 if (radius <= 0.0 || !std::isfinite(radius))
0906 {
0907 return false;
0908 }
0909
0910 const double direction = -static_cast<double>(charge) * ((bfield_t > 0.0) ? 1.0 : -1.0);
0911 const double radial_x = direction * momentum_value.y / p_t;
0912 const double radial_y = -direction * momentum_value.x / p_t;
0913 const double cx = position.x - radius * radial_x;
0914 const double cy = position.y - radius * radial_y;
0915 const double theta = std::atan2(position.y - cy, position.x - cx);
0916 const double pitch = momentum_value.z * radius / (direction * p_t);
0917 const double z0 = position.z - pitch * theta;
0918
0919 helix.cx = cx;
0920 helix.cy = cy;
0921 helix.radius = radius;
0922 helix.z0 = z0;
0923 helix.pitch = pitch;
0924 helix.theta_first = theta;
0925 helix.theta_last = theta;
0926 helix.theta_min = theta;
0927 helix.theta_max = theta;
0928 helix.direction = direction;
0929 helix.bfield_t = bfield_t;
0930 return finite(point(helix, theta)) && finite(momentum(helix, theta));
0931 }
0932
0933 bool TpcTrackHelixFitter::orient_to_charge(TpcTrackHelix &helix, const int charge)
0934 {
0935 if (charge == 0 || helix.bfield_t == 0.0 || !std::isfinite(helix.bfield_t))
0936 {
0937 return false;
0938 }
0939
0940 const double charge_sign = static_cast<double>((charge > 0) ? 1 : -1);
0941 const double bfield_sign = (helix.bfield_t > 0.0) ? 1.0 : -1.0;
0942 const double physical_direction = -charge_sign * bfield_sign;
0943
0944
0945
0946 if (helix.direction * physical_direction < 0.0)
0947 {
0948 std::swap(helix.theta_first, helix.theta_last);
0949 }
0950 helix.direction = physical_direction;
0951 return true;
0952 }
0953
0954 TpcTrackVec3 TpcTrackHelixFitter::point(const TpcTrackHelix &helix, const double theta)
0955 {
0956 return {
0957 helix.cx + helix.radius * std::cos(theta),
0958 helix.cy + helix.radius * std::sin(theta),
0959 helix.z0 + helix.pitch * theta};
0960 }
0961
0962 TpcTrackVec3 TpcTrackHelixFitter::tangent(const TpcTrackHelix &helix, const double theta)
0963 {
0964 return {
0965 -helix.radius * std::sin(theta),
0966 helix.radius * std::cos(theta),
0967 helix.pitch};
0968 }
0969
0970 TpcTrackVec3 TpcTrackHelixFitter::momentum(const TpcTrackHelix &helix, const double theta)
0971 {
0972 double p_t = 0.3 * std::abs(helix.bfield_t) * (helix.radius / 100.0);
0973 if (p_t <= 0.0 || !std::isfinite(p_t))
0974 {
0975 p_t = 1.0;
0976 }
0977
0978 return {
0979 helix.direction * p_t * (-std::sin(theta)),
0980 helix.direction * p_t * std::cos(theta),
0981 helix.direction * p_t * helix.pitch / helix.radius};
0982 }
0983
0984 std::pair<double, double> TpcTrackHelixFitter::theta_search_range(const TpcTrackHelix &helix,
0985 const double theta_extension,
0986 const double downstream_margin)
0987 {
0988 const double upstream = helix.theta_first - helix.direction * theta_extension;
0989 const double downstream = helix.theta_first + helix.direction * downstream_margin;
0990 return {std::min(upstream, downstream), std::max(upstream, downstream)};
0991 }
0992
0993 bool TpcTrackHelixFitter::measurement_anchored_search_range(
0994 const TpcTrackHelix &helix,
0995 const std::vector<TpcTrackPoint> &points,
0996 const double max_upstream_cm,
0997 const double downstream_margin_cm,
0998 TpcTrackHelixSearchRange &range)
0999 {
1000 range = {};
1001 if (points.empty() || !(helix.radius > 0.0) ||
1002 !std::isfinite(helix.radius) || !std::isfinite(helix.theta_first) ||
1003 !std::isfinite(helix.direction) || std::abs(helix.direction) < 0.5)
1004 {
1005 return false;
1006 }
1007
1008 const double direction = helix.direction > 0.0 ? 1.0 : -1.0;
1009 const double reference_theta = helix.theta_first;
1010 const double circumference_cm = 2.0 * kPi * helix.radius;
1011 if (!(circumference_cm > 0.0) || !std::isfinite(circumference_cm))
1012 {
1013 return false;
1014 }
1015
1016 struct Projection
1017 {
1018 int point_index{-1};
1019 double theta{0.0};
1020 double path_cm{0.0};
1021 double residual_cm{0.0};
1022 };
1023
1024 std::vector<Projection> projections;
1025 projections.reserve(points.size());
1026 double minimum_residual_cm = std::numeric_limits<double>::infinity();
1027 const double turn_dz_cm = 2.0 * kPi * helix.pitch * direction;
1028
1029 for (std::size_t index = 0; index < points.size(); ++index)
1030 {
1031 const auto &measurement = points[index].position;
1032 if (!finite(measurement))
1033 {
1034 continue;
1035 }
1036
1037 const double raw_theta = std::atan2(measurement.y - helix.cy,
1038 measurement.x - helix.cx);
1039
1040
1041
1042 const double base_phase = std::remainder(
1043 direction * (raw_theta - reference_theta), 2.0 * kPi);
1044 const double base_theta = reference_theta + direction * base_phase;
1045 const TpcTrackVec3 base_position = point(helix, base_theta);
1046
1047 int turn_guess = 0;
1048 if (std::abs(turn_dz_cm) >= kMinTurnDzForBranchingCm)
1049 {
1050 turn_guess = static_cast<int>(std::llround(
1051 (measurement.z - base_position.z) / turn_dz_cm));
1052 turn_guess = std::clamp(turn_guess,
1053 -kMaxMeasurementTurns,
1054 kMaxMeasurementTurns);
1055 }
1056
1057 const int first_turn = std::max(-kMaxMeasurementTurns, turn_guess - 2);
1058 const int last_turn = std::min(kMaxMeasurementTurns, turn_guess + 2);
1059 Projection best;
1060 best.point_index = static_cast<int>(index);
1061 best.residual_cm = std::numeric_limits<double>::infinity();
1062 int best_turn = -1;
1063 for (int turn = first_turn; turn <= last_turn; ++turn)
1064 {
1065 const double theta = base_theta + direction * 2.0 * kPi * turn;
1066 const TpcTrackVec3 projected = point(helix, theta);
1067 const double residual_cm = distance(projected, measurement);
1068 if (!std::isfinite(residual_cm))
1069 {
1070 continue;
1071 }
1072
1073 if (residual_cm < best.residual_cm - 1.0e-12 ||
1074 (std::abs(residual_cm - best.residual_cm) <= 1.0e-12 &&
1075 (best_turn < 0 || turn < best_turn)))
1076 {
1077 best.theta = theta;
1078 best.path_cm = helix.radius * (base_phase + 2.0 * kPi * turn);
1079 best.residual_cm = residual_cm;
1080 best_turn = turn;
1081 }
1082 }
1083
1084 if (!std::isfinite(best.residual_cm))
1085 {
1086 continue;
1087 }
1088 projections.push_back(best);
1089 minimum_residual_cm = std::min(minimum_residual_cm, best.residual_cm);
1090 }
1091
1092 if (projections.empty() || !std::isfinite(minimum_residual_cm))
1093 {
1094 return false;
1095 }
1096
1097 const double residual_limit_cm = minimum_residual_cm + kAnchorResidualSlackCm;
1098 auto anchor_iter = projections.end();
1099 for (auto iter = projections.begin(); iter != projections.end(); ++iter)
1100 {
1101 if (iter->residual_cm > residual_limit_cm)
1102 {
1103 continue;
1104 }
1105 if (anchor_iter == projections.end() ||
1106 iter->path_cm < anchor_iter->path_cm - 1.0e-9 ||
1107 (std::abs(iter->path_cm - anchor_iter->path_cm) <= 1.0e-9 &&
1108 iter->residual_cm < anchor_iter->residual_cm))
1109 {
1110 anchor_iter = iter;
1111 }
1112 }
1113 if (anchor_iter == projections.end())
1114 {
1115 return false;
1116 }
1117
1118 const double requested_upstream_cm = std::max(0.0, max_upstream_cm);
1119 const double requested_downstream_cm = std::max(0.0, downstream_margin_cm);
1120
1121
1122
1123 const double maximum_span_cm =
1124 kMaximumSearchTurnFraction * circumference_cm;
1125 const double downstream_cm =
1126 std::min(requested_downstream_cm, maximum_span_cm);
1127 const double upstream_cm = std::min(
1128 requested_upstream_cm, std::max(0.0, maximum_span_cm - downstream_cm));
1129 if (!(upstream_cm + downstream_cm > 0.0))
1130 {
1131 return false;
1132 }
1133
1134 const double lower_path_cm = anchor_iter->path_cm - upstream_cm;
1135 const double upper_path_cm = anchor_iter->path_cm + downstream_cm;
1136 const double theta_a = reference_theta + direction * lower_path_cm / helix.radius;
1137 const double theta_b = reference_theta + direction * upper_path_cm / helix.radius;
1138
1139 range.valid = true;
1140 range.anchor_point_index = anchor_iter->point_index;
1141 range.anchor_theta = anchor_iter->theta;
1142 range.anchor_path_cm = anchor_iter->path_cm;
1143 range.anchor_residual_cm = anchor_iter->residual_cm;
1144 range.theta_min = std::min(theta_a, theta_b);
1145 range.theta_max = std::max(theta_a, theta_b);
1146 range.upstream_cm = upstream_cm;
1147 range.downstream_cm = downstream_cm;
1148 return std::isfinite(range.theta_min) && std::isfinite(range.theta_max) &&
1149 range.theta_max > range.theta_min;
1150 }
1151
1152 bool TpcTrackHelixFitter::line_line_pca(const TpcTrackVec3 &pos1,
1153 const TpcTrackVec3 &dir1,
1154 const TpcTrackVec3 &pos2,
1155 const TpcTrackVec3 &dir2,
1156 TpcTrackLinePca &pca,
1157 const bool normalize_dirs)
1158 {
1159 TpcTrackVec3 u1 = normalize_dirs ? unit(dir1) : dir1;
1160 TpcTrackVec3 u2 = normalize_dirs ? unit(dir2) : dir2;
1161 if (!finite(u1) || !finite(u2))
1162 {
1163 return false;
1164 }
1165
1166 const TpcTrackVec3 w0 = subtract(pos1, pos2);
1167 const double a = dot(u1, u1);
1168 const double b = dot(u1, u2);
1169 const double c = dot(u2, u2);
1170 const double d = dot(u1, w0);
1171 const double e = dot(u2, w0);
1172 const double denom = a * c - b * b;
1173 if (std::abs(denom) < 1e-12)
1174 {
1175 return false;
1176 }
1177
1178 const double s = (b * e - c * d) / denom;
1179 const double t = (a * e - b * d) / denom;
1180 pca.pca1 = add(pos1, scale(u1, s));
1181 pca.pca2 = add(pos2, scale(u2, t));
1182 pca.dca = distance(pca.pca1, pca.pca2);
1183 pca.step1 = s;
1184 pca.step2 = t;
1185 return true;
1186 }
1187
1188 TpcTrackHelixPca TpcTrackHelixFitter::refine_pair(const TpcTrackHelix &helix1,
1189 const TpcTrackHelix &helix2,
1190 double theta1,
1191 double theta2,
1192 const double min1,
1193 const double max1,
1194 const double min2,
1195 const double max2,
1196 double max_step)
1197 {
1198 double best_dca2 = square(distance(point(helix1, theta1), point(helix2, theta2)));
1199
1200 for (int iter = 0; iter < 30; ++iter)
1201 {
1202 TpcTrackLinePca line_pca;
1203 if (!line_line_pca(point(helix1, theta1), tangent(helix1, theta1),
1204 point(helix2, theta2), tangent(helix2, theta2),
1205 line_pca, false))
1206 {
1207 break;
1208 }
1209
1210 const double step1 = std::clamp(line_pca.step1, -max_step, max_step);
1211 const double step2 = std::clamp(line_pca.step2, -max_step, max_step);
1212 if (std::abs(step1) < 1e-5 && std::abs(step2) < 1e-5)
1213 {
1214 break;
1215 }
1216
1217 const double candidate_theta1 = std::clamp(theta1 + step1, min1, max1);
1218 const double candidate_theta2 = std::clamp(theta2 + step2, min2, max2);
1219 const double candidate_dca2 = square(distance(point(helix1, candidate_theta1),
1220 point(helix2, candidate_theta2)));
1221 if (candidate_dca2 < best_dca2)
1222 {
1223 theta1 = candidate_theta1;
1224 theta2 = candidate_theta2;
1225 best_dca2 = candidate_dca2;
1226 }
1227 else
1228 {
1229 max_step *= 0.5;
1230 if (max_step < 1e-4)
1231 {
1232 break;
1233 }
1234 }
1235 }
1236
1237 TpcTrackHelixPca output;
1238 output.theta1 = theta1;
1239 output.theta2 = theta2;
1240 output.pca1 = point(helix1, theta1);
1241 output.pca2 = point(helix2, theta2);
1242 output.dca = distance(output.pca1, output.pca2);
1243 return output;
1244 }
1245
1246 std::vector<TpcTrackHelixPca> TpcTrackHelixFitter::pca_candidates(
1247 const TpcTrackHelix &helix1,
1248 const TpcTrackHelix &helix2,
1249 const double theta_extension,
1250 const int coarse_steps,
1251 const double downstream_margin,
1252 const int max_candidates)
1253 {
1254 TpcTrackHelixSearchRange range1;
1255 const auto legacy_range1 = theta_search_range(helix1, theta_extension, downstream_margin);
1256 range1.valid = true;
1257 range1.theta_min = legacy_range1.first;
1258 range1.theta_max = legacy_range1.second;
1259 TpcTrackHelixSearchRange range2;
1260 const auto legacy_range2 = theta_search_range(helix2, theta_extension, downstream_margin);
1261 range2.valid = true;
1262 range2.theta_min = legacy_range2.first;
1263 range2.theta_max = legacy_range2.second;
1264 return pca_candidates_in_ranges(helix1, helix2, range1, range2,
1265 coarse_steps, max_candidates);
1266 }
1267
1268 std::vector<TpcTrackHelixPca> TpcTrackHelixFitter::pca_candidates_in_ranges(
1269 const TpcTrackHelix &helix1,
1270 const TpcTrackHelix &helix2,
1271 const TpcTrackHelixSearchRange &range1,
1272 const TpcTrackHelixSearchRange &range2,
1273 const int coarse_steps,
1274 const int max_candidates)
1275 {
1276 if (!range1.valid || !range2.valid ||
1277 !std::isfinite(range1.theta_min) || !std::isfinite(range1.theta_max) ||
1278 !std::isfinite(range2.theta_min) || !std::isfinite(range2.theta_max) ||
1279 !(range1.theta_max > range1.theta_min) ||
1280 !(range2.theta_max > range2.theta_min))
1281 {
1282 return {};
1283 }
1284
1285 const int n_steps = std::max(8, coarse_steps);
1286
1287 std::vector<double> theta1_values;
1288 std::vector<double> theta2_values;
1289 theta1_values.reserve(n_steps);
1290 theta2_values.reserve(n_steps);
1291 for (int i = 0; i < n_steps; ++i)
1292 {
1293 const double fraction = (n_steps == 1) ? 0.0 : static_cast<double>(i) / static_cast<double>(n_steps - 1);
1294 theta1_values.push_back(range1.theta_min + fraction * (range1.theta_max - range1.theta_min));
1295 theta2_values.push_back(range2.theta_min + fraction * (range2.theta_max - range2.theta_min));
1296 }
1297
1298 std::vector<std::tuple<double, int, int>> coarse;
1299 coarse.reserve(static_cast<std::size_t>(n_steps) * static_cast<std::size_t>(n_steps));
1300 for (int i = 0; i < n_steps; ++i)
1301 {
1302 const TpcTrackVec3 point1 = point(helix1, theta1_values[i]);
1303 for (int j = 0; j < n_steps; ++j)
1304 {
1305 const TpcTrackVec3 point2 = point(helix2, theta2_values[j]);
1306 coarse.emplace_back(square(distance(point1, point2)), i, j);
1307 }
1308 }
1309
1310 std::sort(coarse.begin(), coarse.end(),
1311 [](const auto &lhs, const auto &rhs)
1312 { return std::get<0>(lhs) < std::get<0>(rhs); });
1313
1314 const double max_step = std::max(range1.theta_max - range1.theta_min,
1315 range2.theta_max - range2.theta_min) /
1316 static_cast<double>(n_steps);
1317 const int n_candidates = std::min({std::max(1, max_candidates), static_cast<int>(coarse.size())});
1318
1319 std::vector<TpcTrackHelixPca> candidates;
1320 candidates.reserve(n_candidates);
1321 for (int index = 0; index < n_candidates; ++index)
1322 {
1323 const int i = std::get<1>(coarse[index]);
1324 const int j = std::get<2>(coarse[index]);
1325 candidates.push_back(refine_pair(
1326 helix1, helix2, theta1_values[i], theta2_values[j],
1327 range1.theta_min, range1.theta_max,
1328 range2.theta_min, range2.theta_max, max_step));
1329 }
1330
1331 std::sort(candidates.begin(), candidates.end(),
1332 [](const TpcTrackHelixPca &lhs, const TpcTrackHelixPca &rhs)
1333 { return lhs.dca < rhs.dca; });
1334 return candidates;
1335 }
1336
1337 std::pair<double, double> TpcTrackHelixFitter::helix_dca_to_vertex(const TpcTrackHelix &helix,
1338 const TpcTrackVec3 &vertex)
1339 {
1340 const double vx = vertex.x - helix.cx;
1341 const double vy = vertex.y - helix.cy;
1342 const double distance_to_center = std::sqrt(square(vx) + square(vy));
1343
1344 double theta_raw = helix.theta_first;
1345 double dca_xy = helix.radius;
1346 if (distance_to_center > 0.0)
1347 {
1348 theta_raw = std::atan2(vy, vx);
1349 dca_xy = std::abs(distance_to_center - helix.radius);
1350 }
1351
1352 const double wraps = std::round((helix.theta_first - theta_raw) / (2.0 * kPi));
1353 const double theta = theta_raw + 2.0 * kPi * wraps;
1354 const TpcTrackVec3 closest = point(helix, theta);
1355 return {dca_xy, std::abs(closest.z - vertex.z)};
1356 }
1357
1358 std::pair<double, double> TpcTrackHelixFitter::line_dca_to_vertex(const TpcTrackVec3 &pos,
1359 const TpcTrackVec3 &mom,
1360 const TpcTrackVec3 &vertex)
1361 {
1362 const TpcTrackVec3 rel = subtract(pos, vertex);
1363 const double pt2 = square(mom.x) + square(mom.y);
1364 if (pt2 <= 0.0)
1365 {
1366 return {quiet_nan(), quiet_nan()};
1367 }
1368 const double dca_xy = std::abs(rel.x * mom.y - rel.y * mom.x) / std::sqrt(pt2);
1369 const double sxy = -(rel.x * mom.x + rel.y * mom.y) / pt2;
1370 const TpcTrackVec3 closest = add(pos, scale(mom, sxy));
1371 return {dca_xy, std::abs(closest.z - vertex.z)};
1372 }
1373
1374 bool TpcTrackHelixFitter::finite(const TpcTrackVec3 &value)
1375 {
1376 return std::isfinite(value.x) && std::isfinite(value.y) && std::isfinite(value.z);
1377 }
1378
1379 TpcTrackVec3 TpcTrackHelixFitter::add(const TpcTrackVec3 &lhs, const TpcTrackVec3 &rhs)
1380 {
1381 return {lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z};
1382 }
1383
1384 TpcTrackVec3 TpcTrackHelixFitter::subtract(const TpcTrackVec3 &lhs, const TpcTrackVec3 &rhs)
1385 {
1386 return {lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z};
1387 }
1388
1389 TpcTrackVec3 TpcTrackHelixFitter::scale(const TpcTrackVec3 &value, const double factor)
1390 {
1391 return {value.x * factor, value.y * factor, value.z * factor};
1392 }
1393
1394 double TpcTrackHelixFitter::dot(const TpcTrackVec3 &lhs, const TpcTrackVec3 &rhs)
1395 {
1396 return lhs.x * rhs.x + lhs.y * rhs.y + lhs.z * rhs.z;
1397 }
1398
1399 double TpcTrackHelixFitter::norm(const TpcTrackVec3 &value)
1400 {
1401 return std::sqrt(dot(value, value));
1402 }
1403
1404 TpcTrackVec3 TpcTrackHelixFitter::unit(const TpcTrackVec3 &value)
1405 {
1406 const double length = norm(value);
1407 if (length <= 0.0 || !std::isfinite(length))
1408 {
1409 return {quiet_nan(), quiet_nan(), quiet_nan()};
1410 }
1411 return scale(value, 1.0 / length);
1412 }
1413
1414 double TpcTrackHelixFitter::pt(const TpcTrackVec3 &value)
1415 {
1416 return std::sqrt(square(value.x) + square(value.y));
1417 }
1418
1419 double TpcTrackHelixFitter::distance(const TpcTrackVec3 &lhs, const TpcTrackVec3 &rhs)
1420 {
1421 return norm(subtract(lhs, rhs));
1422 }
1423
1424 double TpcTrackHelixFitter::vector_cosine(const TpcTrackVec3 &lhs, const TpcTrackVec3 &rhs)
1425 {
1426 const double denom = norm(lhs) * norm(rhs);
1427 if (denom <= 0.0 || !std::isfinite(denom))
1428 {
1429 return quiet_nan();
1430 }
1431 return dot(lhs, rhs) / denom;
1432 }