Back to home page

sPhenix code displayed by LXR

 
 

    


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 }  // namespace
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 }