File indexing completed on 2026-08-31 08:21:22
0001 #include "Tpc_FittingTools.h"
0002
0003 #include <algorithm>
0004 #include <cmath>
0005 #include <vector>
0006
0007 namespace
0008 {
0009 double wrap_pi(double phi)
0010 {
0011 while (phi > M_PI)
0012 {
0013 phi -= 2.0 * M_PI;
0014 }
0015 while (phi <= -M_PI)
0016 {
0017 phi += 2.0 * M_PI;
0018 }
0019 return phi;
0020 }
0021
0022 double unwrap_near(double phi, const double ref)
0023 {
0024 while (phi - ref > M_PI)
0025 {
0026 phi -= 2.0 * M_PI;
0027 }
0028 while (phi - ref < -M_PI)
0029 {
0030 phi += 2.0 * M_PI;
0031 }
0032 return phi;
0033 }
0034
0035 bool solve_3x3(double A[3][3], double b[3], double x[3])
0036 {
0037 double M[3][4] = {
0038 {A[0][0], A[0][1], A[0][2], b[0]},
0039 {A[1][0], A[1][1], A[1][2], b[1]},
0040 {A[2][0], A[2][1], A[2][2], b[2]}};
0041 for (int col = 0; col < 3; ++col)
0042 {
0043 int pivot = col;
0044 for (int row = col + 1; row < 3; ++row)
0045 {
0046 if (std::fabs(M[row][col]) > std::fabs(M[pivot][col]))
0047 {
0048 pivot = row;
0049 }
0050 }
0051 if (std::fabs(M[pivot][col]) < 1.0e-20)
0052 {
0053 return false;
0054 }
0055 if (pivot != col)
0056 {
0057 for (int k = col; k < 4; ++k)
0058 {
0059 std::swap(M[col][k], M[pivot][k]);
0060 }
0061 }
0062 const double div = M[col][col];
0063 for (int k = col; k < 4; ++k)
0064 {
0065 M[col][k] /= div;
0066 }
0067 for (int row = 0; row < 3; ++row)
0068 {
0069 if (row == col)
0070 {
0071 continue;
0072 }
0073 const double factor = M[row][col];
0074 for (int k = col; k < 4; ++k)
0075 {
0076 M[row][k] -= factor * M[col][k];
0077 }
0078 }
0079 }
0080 x[0] = M[0][3];
0081 x[1] = M[1][3];
0082 x[2] = M[2][3];
0083 return true;
0084 }
0085
0086 bool line_fit(const std::vector<double>& x, const std::vector<double>& y,
0087 double& slope, double& intercept, double& chi2, int& ndof)
0088 {
0089 if (x.size() < 2 || x.size() != y.size())
0090 {
0091 return false;
0092 }
0093 double S = 0.0;
0094 double Sx = 0.0;
0095 double Sy = 0.0;
0096 double Sxx = 0.0;
0097 double Sxy = 0.0;
0098 for (unsigned int i = 0; i < x.size(); ++i)
0099 {
0100 S += 1.0;
0101 Sx += x[i];
0102 Sy += y[i];
0103 Sxx += x[i] * x[i];
0104 Sxy += x[i] * y[i];
0105 }
0106 const double den = S * Sxx - Sx * Sx;
0107 if (std::fabs(den) < 1.0e-20)
0108 {
0109 return false;
0110 }
0111 slope = (S * Sxy - Sx * Sy) / den;
0112 intercept = (Sy - slope * Sx) / S;
0113 chi2 = 0.0;
0114 for (unsigned int i = 0; i < x.size(); ++i)
0115 {
0116 const double r = y[i] - (slope * x[i] + intercept);
0117 chi2 += r * r;
0118 }
0119 ndof = static_cast<int>(x.size()) - 2;
0120 return true;
0121 }
0122 }
0123
0124 Tpc_FittingTools::FitPoint::FitPoint()
0125 : x(0.0)
0126 , y(0.0)
0127 , w(1.0)
0128 {
0129 }
0130
0131 Tpc_FittingTools::FitPoint::FitPoint(double x_, double y_, double w_)
0132 : x(x_)
0133 , y(y_)
0134 , w(w_)
0135 {
0136 }
0137
0138 Tpc_FittingTools::LineFit::LineFit()
0139 : ok(false)
0140 , slope(0.0)
0141 , intercept(0.0)
0142 , chi2(0.0)
0143 , ndof(0)
0144 {
0145 }
0146
0147 Tpc_FittingTools::SagittaFit::SagittaFit()
0148 : ok(false)
0149 , S(0.0)
0150 , x0(0.0)
0151 , invR(0.0)
0152 , theta(0.0)
0153 , b(0.0)
0154 , chi2(0.0)
0155 , ndof(0)
0156 {
0157 }
0158
0159 double Tpc_FittingTools::adcWeight(double adc, double maxadc, double power, double floor_frac)
0160 {
0161 if (adc <= 0.0)
0162 {
0163 return 0.0;
0164 }
0165 if (maxadc <= 0.0)
0166 {
0167 return 1.0;
0168 }
0169
0170 double w = std::pow(adc / maxadc, power);
0171 w = std::max(w, floor_frac);
0172 return w;
0173 }
0174
0175 bool Tpc_FittingTools::weightedLineFit(const std::vector<double>& x,
0176 const std::vector<double>& y,
0177 const std::vector<double>& w,
0178 double& m,
0179 double& b,
0180 double& chi2,
0181 int& ndof)
0182 {
0183 if (x.size() < 2 || x.size() != y.size() || x.size() != w.size())
0184 {
0185 return false;
0186 }
0187
0188 double S = 0.0;
0189 double Sx = 0.0;
0190 double Sy = 0.0;
0191 double Sxx = 0.0;
0192 double Sxy = 0.0;
0193
0194 for (unsigned int i = 0; i < x.size(); ++i)
0195 {
0196 const double wi = w[i] > 0.0 ? w[i] : 1.0;
0197 S += wi;
0198 Sx += wi * x[i];
0199 Sy += wi * y[i];
0200 Sxx += wi * x[i] * x[i];
0201 Sxy += wi * x[i] * y[i];
0202 }
0203
0204 const double den = S * Sxx - Sx * Sx;
0205 if (std::fabs(den) < 1.0e-12)
0206 {
0207 return false;
0208 }
0209
0210 m = (S * Sxy - Sx * Sy) / den;
0211 b = (Sy - m * Sx) / S;
0212
0213 chi2 = 0.0;
0214 for (unsigned int i = 0; i < x.size(); ++i)
0215 {
0216 const double wi = w[i] > 0.0 ? w[i] : 1.0;
0217 const double r = y[i] - (m * x[i] + b);
0218 chi2 += wi * r * r;
0219 }
0220
0221 ndof = static_cast<int>(x.size()) - 2;
0222 return true;
0223 }
0224
0225 Tpc_FittingTools::LineFit Tpc_FittingTools::fitLine(const std::vector<FitPoint>& points)
0226 {
0227 LineFit fit;
0228 std::vector<double> x;
0229 std::vector<double> y;
0230 std::vector<double> w;
0231 x.reserve(points.size());
0232 y.reserve(points.size());
0233 w.reserve(points.size());
0234
0235 for (const auto& point : points)
0236 {
0237 x.push_back(point.x);
0238 y.push_back(point.y);
0239 w.push_back(point.w);
0240 }
0241
0242 fit.ok = weightedLineFit(x, y, w, fit.slope, fit.intercept, fit.chi2, fit.ndof);
0243 return fit;
0244 }
0245
0246 double Tpc_FittingTools::sagittaModel(const double xrot,
0247 const double S,
0248 const double x0,
0249 const double invR)
0250 {
0251 const double dx = xrot - x0;
0252 const double invR2 = invR * invR;
0253 const double dx2 = dx * dx;
0254
0255 return S - 0.5 * invR * dx2 - 0.125 * invR * invR2 * dx2 * dx2 - 0.0625 * invR * invR2 * invR2 * dx2 * dx2 * dx2;
0256 }
0257
0258 bool Tpc_FittingTools::weightedSagittaFit(const std::vector<double>& local_x,
0259 const std::vector<double>& local_y,
0260 const std::vector<double>& w,
0261 double& S,
0262 double& x0,
0263 double& invR,
0264 double& theta,
0265 double& bline,
0266 double& chi2,
0267 int& ndof)
0268 {
0269 if (local_x.size() < 3 || local_x.size() != local_y.size() || local_x.size() != w.size())
0270 {
0271 return false;
0272 }
0273
0274 double mline = 0.0;
0275 double line_chi2 = 0.0;
0276 int line_ndof = 0;
0277 if (!weightedLineFit(local_x, local_y, w, mline, bline, line_chi2, line_ndof))
0278 {
0279 return false;
0280 }
0281
0282 theta = std::atan(mline);
0283 const double c = std::cos(theta);
0284 const double s = std::sin(theta);
0285
0286 std::vector<double> xrot;
0287 std::vector<double> yrot;
0288 xrot.reserve(local_x.size());
0289 yrot.reserve(local_x.size());
0290
0291 double sw = 0.0;
0292 double sx = 0.0;
0293 double sy = 0.0;
0294
0295 for (unsigned int i = 0; i < local_x.size(); ++i)
0296 {
0297 const double wi = w[i] > 0.0 ? w[i] : 1.0;
0298 const double yy = local_y[i] - bline;
0299 const double xr = c * local_x[i] + s * yy;
0300 const double yr = -s * local_x[i] + c * yy;
0301
0302 xrot.push_back(xr);
0303 yrot.push_back(yr);
0304
0305 sw += wi;
0306 sx += wi * xr;
0307 sy += wi * yr;
0308 }
0309
0310 if (sw <= 0.0)
0311 {
0312 return false;
0313 }
0314
0315 x0 = sx / sw;
0316 S = sy / sw;
0317
0318 std::vector<double> dx2;
0319 dx2.reserve(xrot.size());
0320 for (double i : xrot)
0321 {
0322 const double dx = i - x0;
0323 dx2.push_back(dx * dx);
0324 }
0325
0326 double q = 0.0;
0327 double qS = 0.0;
0328 double qchi2 = 0.0;
0329 int qndof = 0;
0330 if (weightedLineFit(dx2, yrot, w, q, qS, qchi2, qndof))
0331 {
0332 S = qS;
0333 invR = -2.0 * q;
0334 }
0335 else
0336 {
0337 invR = 0.0;
0338 }
0339
0340 invR = std::min(invR, 1.0);
0341 invR = std::max(invR, -1.0);
0342
0343 chi2 = 0.0;
0344 for (unsigned int i = 0; i < xrot.size(); ++i)
0345 {
0346 const double wi = w[i] > 0.0 ? w[i] : 1.0;
0347 const double r = yrot[i] - sagittaModel(xrot[i], S, x0, invR);
0348 chi2 += wi * r * r;
0349 }
0350
0351 double lambda = 1.0e-6;
0352 for (unsigned int iter = 0; iter < 25; ++iter)
0353 {
0354 double A[3][3] = {{0.0, 0.0, 0.0},
0355 {0.0, 0.0, 0.0},
0356 {0.0, 0.0, 0.0}};
0357 double bvec[3] = {0.0, 0.0, 0.0};
0358
0359 for (unsigned int i = 0; i < xrot.size(); ++i)
0360 {
0361 const double wi = w[i] > 0.0 ? w[i] : 1.0;
0362 const double dx = xrot[i] - x0;
0363 const double dx2v = dx * dx;
0364 const double dx3 = dx2v * dx;
0365 const double dx4 = dx2v * dx2v;
0366 const double dx5 = dx4 * dx;
0367 const double dx6 = dx4 * dx2v;
0368
0369 const double invR2 = invR * invR;
0370 const double invR3 = invR2 * invR;
0371 const double invR4 = invR2 * invR2;
0372 const double invR5 = invR4 * invR;
0373
0374 const double f = S - 0.5 * invR * dx2v - 0.125 * invR3 * dx4 - 0.0625 * invR5 * dx6;
0375
0376 const double resid = yrot[i] - f;
0377
0378 double J[3];
0379 J[0] = 1.0;
0380 J[1] = invR * dx + 0.5 * invR3 * dx3 + 0.375 * invR5 * dx5;
0381 J[2] = -0.5 * dx2v - 0.375 * invR2 * dx4 - 0.3125 * invR4 * dx6;
0382
0383 for (int a = 0; a < 3; ++a)
0384 {
0385 bvec[a] += wi * J[a] * resid;
0386 for (int b = 0; b < 3; ++b)
0387 {
0388 A[a][b] += wi * J[a] * J[b];
0389 }
0390 }
0391 }
0392
0393 A[0][0] += lambda;
0394 A[1][1] += lambda;
0395 A[2][2] += lambda;
0396
0397 double delta[3] = {0.0, 0.0, 0.0};
0398 if (!solve_3x3(A, bvec, delta))
0399 {
0400 break;
0401 }
0402
0403 delta[0] = std::min(delta[0], 10.0);
0404 delta[0] = std::max(delta[0], -10.0);
0405 delta[1] = std::min(delta[1], 10.0);
0406 delta[1] = std::max(delta[1], -10.0);
0407 delta[2] = std::min(delta[2], 0.1);
0408 delta[2] = std::max(delta[2], -0.1);
0409
0410 const double S_new = S + delta[0];
0411 const double x0_new = x0 + delta[1];
0412 double invR_new = invR + delta[2];
0413
0414 invR_new = std::min(invR_new, 1.0);
0415 invR_new = std::max(invR_new, -1.0);
0416
0417 double chi2_new = 0.0;
0418 for (unsigned int i = 0; i < xrot.size(); ++i)
0419 {
0420 const double wi = w[i] > 0.0 ? w[i] : 1.0;
0421 const double r = yrot[i] - sagittaModel(xrot[i], S_new, x0_new, invR_new);
0422 chi2_new += wi * r * r;
0423 }
0424
0425 if (chi2_new <= chi2)
0426 {
0427 S = S_new;
0428 x0 = x0_new;
0429 invR = invR_new;
0430 chi2 = chi2_new;
0431 lambda *= 0.3;
0432
0433 if (std::fabs(delta[0]) < 1.0e-6 &&
0434 std::fabs(delta[1]) < 1.0e-6 &&
0435 std::fabs(delta[2]) < 1.0e-8)
0436 {
0437 break;
0438 }
0439 }
0440 else
0441 {
0442 lambda *= 10.0;
0443 }
0444 }
0445
0446 ndof = static_cast<int>(local_x.size()) - 3;
0447 return true;
0448 }
0449
0450 Tpc_FittingTools::SagittaFit Tpc_FittingTools::fitSagitta(const std::vector<FitPoint>& points)
0451 {
0452 SagittaFit fit;
0453 std::vector<double> x;
0454 std::vector<double> y;
0455 std::vector<double> w;
0456 x.reserve(points.size());
0457 y.reserve(points.size());
0458 w.reserve(points.size());
0459
0460 for (const auto& point : points)
0461 {
0462 x.push_back(point.x);
0463 y.push_back(point.y);
0464 w.push_back(point.w);
0465 }
0466
0467 fit.ok = weightedSagittaFit(x, y, w,
0468 fit.S, fit.x0, fit.invR, fit.theta, fit.b,
0469 fit.chi2, fit.ndof);
0470 return fit;
0471 }
0472
0473 bool Tpc_FittingTools::fitLine3D(const std::vector<Point>& points, FitResult& fit)
0474 {
0475 fit = FitResult();
0476 fit.is_line = true;
0477 if (points.size() < 2)
0478 {
0479 return false;
0480 }
0481
0482 double cx = 0.0;
0483 double cy = 0.0;
0484 double cz = 0.0;
0485 for (const Point& p : points)
0486 {
0487 cx += p.x;
0488 cy += p.y;
0489 cz += p.z;
0490 }
0491 const double inv_n = 1.0 / static_cast<double>(points.size());
0492 cx *= inv_n;
0493 cy *= inv_n;
0494 cz *= inv_n;
0495
0496 double cov[3][3] = {{0.0, 0.0, 0.0}, {0.0, 0.0, 0.0}, {0.0, 0.0, 0.0}};
0497 for (const Point& p : points)
0498 {
0499 const double dx = p.x - cx;
0500 const double dy = p.y - cy;
0501 const double dz = p.z - cz;
0502 cov[0][0] += dx * dx;
0503 cov[0][1] += dx * dy;
0504 cov[0][2] += dx * dz;
0505 cov[1][1] += dy * dy;
0506 cov[1][2] += dy * dz;
0507 cov[2][2] += dz * dz;
0508 }
0509 cov[1][0] = cov[0][1];
0510 cov[2][0] = cov[0][2];
0511 cov[2][1] = cov[1][2];
0512
0513 double vx = points.back().x - points.front().x;
0514 double vy = points.back().y - points.front().y;
0515 double vz = points.back().z - points.front().z;
0516 double vnorm = std::sqrt(vx * vx + vy * vy + vz * vz);
0517 if (vnorm <= 1.0e-20)
0518 {
0519 for (const Point& p : points)
0520 {
0521 vx = p.x - cx;
0522 vy = p.y - cy;
0523 vz = p.z - cz;
0524 vnorm = std::sqrt(vx * vx + vy * vy + vz * vz);
0525 if (vnorm > 1.0e-20)
0526 {
0527 break;
0528 }
0529 }
0530 }
0531 if (vnorm <= 1.0e-20)
0532 {
0533 return false;
0534 }
0535 vx /= vnorm;
0536 vy /= vnorm;
0537 vz /= vnorm;
0538
0539 for (unsigned int iter = 0; iter < 32; ++iter)
0540 {
0541 const double nx = cov[0][0] * vx + cov[0][1] * vy + cov[0][2] * vz;
0542 const double ny = cov[1][0] * vx + cov[1][1] * vy + cov[1][2] * vz;
0543 const double nz = cov[2][0] * vx + cov[2][1] * vy + cov[2][2] * vz;
0544 const double nnorm = std::sqrt(nx * nx + ny * ny + nz * nz);
0545 if (nnorm <= 1.0e-20)
0546 {
0547 return false;
0548 }
0549 vx = nx / nnorm;
0550 vy = ny / nnorm;
0551 vz = nz / nnorm;
0552 }
0553
0554 const double ex = points.back().x - points.front().x;
0555 const double ey = points.back().y - points.front().y;
0556 const double ez = points.back().z - points.front().z;
0557 if (vx * ex + vy * ey + vz * ez < 0.0)
0558 {
0559 vx = -vx;
0560 vy = -vy;
0561 vz = -vz;
0562 }
0563
0564 const double cproj = cx * vx + cy * vy + cz * vz;
0565 fit.line_x = cx - cproj * vx;
0566 fit.line_y = cy - cproj * vy;
0567 fit.line_z = cz - cproj * vz;
0568 fit.line_dx = vx;
0569 fit.line_dy = vy;
0570 fit.line_dz = vz;
0571 fit.phi0 = wrap_pi(std::atan2(vy, vx));
0572 fit.theta = std::atan2(std::sqrt(vx * vx + vy * vy), vz);
0573 fit.d0 = -fit.line_x * std::sin(fit.phi0) + fit.line_y * std::cos(fit.phi0);
0574 fit.z0 = fit.line_z;
0575 fit.curvature = 0.0;
0576
0577 for (const Point& p : points)
0578 {
0579 const double dx = p.x - fit.line_x;
0580 const double dy = p.y - fit.line_y;
0581 const double dz = p.z - fit.line_z;
0582 const double along = dx * vx + dy * vy + dz * vz;
0583 const double rx = dx - along * vx;
0584 const double ry = dy - along * vy;
0585 const double rz = dz - along * vz;
0586 fit.chi2_xy += rx * rx + ry * ry + rz * rz;
0587 }
0588 fit.ndof_xy = std::max(0, 2 * static_cast<int>(points.size()) - 4);
0589 fit.ok = true;
0590 return true;
0591 }
0592
0593 bool Tpc_FittingTools::fit(const std::vector<Point>& points, FitResult& fit)
0594 {
0595 fit = FitResult();
0596 if (points.size() < 3)
0597 {
0598 return false;
0599 }
0600
0601 double A[3][3] = {{0.0, 0.0, 0.0}, {0.0, 0.0, 0.0}, {0.0, 0.0, 0.0}};
0602 double b[3] = {0.0, 0.0, 0.0};
0603 for (const Point& p : points)
0604 {
0605 const double row[3] = {p.x, p.y, 1.0};
0606 const double rhs = -(p.x * p.x + p.y * p.y);
0607 for (int i = 0; i < 3; ++i)
0608 {
0609 b[i] += row[i] * rhs;
0610 for (int j = 0; j < 3; ++j)
0611 {
0612 A[i][j] += row[i] * row[j];
0613 }
0614 }
0615 }
0616
0617 double sol[3] = {0.0, 0.0, 0.0};
0618 if (!solve_3x3(A, b, sol))
0619 {
0620 return false;
0621 }
0622
0623 const double xc = -0.5 * sol[0];
0624 const double yc = -0.5 * sol[1];
0625 const double r2 = xc * xc + yc * yc - sol[2];
0626 if (r2 <= 0.0)
0627 {
0628 return false;
0629 }
0630 const double R = std::sqrt(r2);
0631 const double dc = std::hypot(xc, yc);
0632 if (R <= 0.0 || dc <= 1.0e-12)
0633 {
0634 return false;
0635 }
0636
0637 const Point& first = points.front();
0638 const Point& mid = points[points.size() / 2];
0639 const Point& last = points.back();
0640 const double cross = (mid.x - first.x) * (last.y - mid.y) - (mid.y - first.y) * (last.x - mid.x);
0641 const double sign = (cross >= 0.0) ? 1.0 : -1.0;
0642
0643 fit.curvature = sign / R;
0644 fit.d0 = sign * (dc - R);
0645
0646 const double px = xc * (1.0 - R / dc);
0647 const double py = yc * (1.0 - R / dc);
0648 const double rx = px - xc;
0649 const double ry = py - yc;
0650 const double tx = -sign * ry / R;
0651 const double ty = sign * rx / R;
0652 fit.cx = xc;
0653 fit.cy = yc;
0654 fit.phi0 = wrap_pi(std::atan2(ty, tx));
0655
0656 const double phi_perigee = std::atan2(ry, rx);
0657 std::vector<double> svals;
0658 std::vector<double> zvals;
0659 svals.reserve(points.size());
0660 zvals.reserve(points.size());
0661 double prev_angle = phi_perigee;
0662 for (const Point& p : points)
0663 {
0664 double angle = std::atan2(p.y - yc, p.x - xc);
0665 angle = unwrap_near(angle, prev_angle);
0666 prev_angle = angle;
0667 double dangle = angle - phi_perigee;
0668 if (sign * dangle < 0.0)
0669 {
0670 dangle += sign * 2.0 * M_PI;
0671 }
0672 svals.push_back(std::fabs(R * dangle));
0673 zvals.push_back(p.z);
0674
0675 const double resid = std::hypot(p.x - xc, p.y - yc) - R;
0676 fit.chi2_xy += resid * resid;
0677 }
0678
0679 double dzds = 0.0;
0680 if (!line_fit(svals, zvals, dzds, fit.z0, fit.chi2_z, fit.ndof_z))
0681 {
0682 return false;
0683 }
0684 fit.theta = std::atan2(1.0, dzds);
0685 fit.ndof_xy = static_cast<int>(points.size()) - 3;
0686 fit.ok = true;
0687 return true;
0688 }