Back to home page

sPhenix code displayed by LXR

 
 

    


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 }  // namespace
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   // The geometric fit may be ordered opposite to physical time.  Keep the
0945   // fitted helix, but make theta_first the physical initial branch.
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     // Path is signed relative to the transverse perigee. Measurements may be
1040     // either before or after that point in physical time, so start from the
1041     // nearest angular branch and let z resolve any additional full turns.
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   // Leave a finite gap between the two ends of the search domain. A cap that
1121   // differs from one turn only by machine epsilon still permits duplicate
1122   // geometric intersections and rounds back to 2*pi in float QA branches.
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 }