/* This file is part of MAUS: http://micewww.pp.rl.ac.uk:8080/projects/maus * * MAUS is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * MAUS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with MAUS. If not, see . * */ #include #include #include // ROOT headers #include "TLinearFitter.h" #include "TMatrixTBase.h" #include "TMath.h" #include "Math/Functor.h" #include "Fit/Fitter.h" #include "Minuit2/Minuit2Minimizer.h" // MAUS headers #include "src/common_cpp/Recon/SciFi/RootFitter.hh" #define PI 3.14159265 namespace RootFitter { bool FitLineLinear(const std::vector& x, const std::vector& y, const std::vector& yerr, MAUS::SimpleLine& line, TMatrixD& cov_matrix) { TLinearFitter lf(1); lf.SetFormula("pol1"); // Have to copy data into new elements as AssignData wants non-const arguments for some reason std::vector x1 = x; std::vector y1 = y; std::vector yerr1 = yerr; lf.AssignData(x.size(), 1, &x1[0], &y1[0], &yerr1[0]); lf.Eval(); TVectorD params; TVectorD errors; lf.GetParameters(params); lf.GetErrors(errors); line.set_c(params[0]); line.set_m(params[1]); line.set_c_err(errors[0]); line.set_m_err(errors[1]); line.set_chisq(lf.GetChisquare()); lf.GetCovarianceMatrix(cov_matrix); return true; } bool FitCircleMinuit(const std::vector& x, const std::vector& y, const std::vector& err, MAUS::SimpleCircle& circ, TMatrixD& cov_matrix) { auto Chi2Function = [&x, &y, &err](const double *par) { // Minimisation function computing the sum of squares of residuals // looping over the points double f = 0.0; for (size_t i = 0; i < x.size(); i++) { double u = (x[i] - par[0]); double v = y[i] - par[1]; double dr = par[2] - std::sqrt(u*u+v*v); f += (dr*dr) / (err[i]*err[i]); } return f; }; // Wrap chi2 function in a function object for the fit // 3 is the number of fit parameters (size of array par) ROOT::Math::Functor fcn(Chi2Function, 3); ROOT::Fit::Fitter fitter; double pStart[3] = {0, 0, 1}; fitter.SetFCN(fcn, pStart); fitter.Config().ParSettings(0).SetName("x0"); fitter.Config().ParSettings(1).SetName("y0"); fitter.Config().ParSettings(2).SetName("R"); // do the fit bool ok = fitter.FitFCN(); if (!ok) { return ok; } const ROOT::Fit::FitResult & result = fitter.Result(); // result.Print(std::cout); // Create a circle object with the results circ = MAUS::SimpleCircle(result.Parameter(0), result.Parameter(1), result.Parameter(2)); circ.set_x0_err(result.Error(0)); circ.set_y0_err(result.Error(1)); circ.set_R_err(result.Error(2)); // circ.set_chisq(result.Chi2()); circ.set_chisq(result.MinFcnValue()); circ.set_pvalue(result.Prob()); result.GetCovarianceMatrix(cov_matrix); return true; } bool FitHelixMinuit(const std::vector& x, const std::vector& y, const std::vector& z, const std::vector& err, const double* pStart, MAUS::SimpleHelix& helix, int handedness, double cut) { // Pull out x and y at the tracker reference station double x0 = x[0]; double y0 = y[0]; // This is a C++11 lambda function, which defines the quantity to be minimised by MINUIT, // using a sum over squared residuals looping over the seed spacepoints std::function //NOLINT(*) Chi2Function = [&x, &y, &z, &err, &x0, &y0](const double *par) { double xc = par[0]; double yc = par[1]; double rad = par[2]; double dsdz = par[3]; // s0 set to 0 as not presently used in minimisation, calculated analytically afterwards double s0 = 0.0; double chisq = 0.0; for (size_t i = 0; i < x.size(); i++) { double theta = (z[i]*dsdz) / rad; double f = xc + (x0 - xc)*cos(theta) - (y0 - yc)*sin(theta); // x model prediction double g = yc + (y0 - yc)*cos(theta) + (x0 - xc)*sin(theta); // y model prediction // double theta = (z[i]*dsdz + s0) / rad; // double f = xc + rad*cos(theta); // x model prediction // double g = yc + rad*sin(theta); // y model prediction double dx = f - x[i]; double dy = g - y[i]; double dchisq = ((dx*dx) + (dy*dy)) / (err[i]*err[i]); chisq += dchisq; } return chisq; }; // Set up the minimiser - ROOT::Math::Minimizer interface to MINUIT2, Migrad algorithm ROOT::Minuit2::Minuit2Minimizer min(ROOT::Minuit2::kMigrad); min.SetMaxFunctionCalls(1000000); min.SetMaxIterations(100000); min.SetTolerance(0.001); ROOT::Math::Functor fcn(Chi2Function, 4); min.SetFunction(fcn); // Determine the approximate dsdz value which corresponds to the global minimum of the chisq func double dsdz_seed = LocateGlobalChiSqMinimum(pStart, handedness, cut, Chi2Function); // Set the variables to be minimized min.SetFixedVariable(0, "xc", pStart[0]); min.SetFixedVariable(1, "yc", pStart[1]); min.SetFixedVariable(2, "R", pStart[2]); double step[4] = {0.01, 0.01, 0.01, 0.01}; min.SetLimitedVariable(3, "dsdz", dsdz_seed, step[3], -1.0, 1.0); // Do the fit bool ok = min.Minimize(); if (!ok) { // std::cerr << "Full helix fit did not converge\n"; return ok; } const double *xs = min.X(); const double *errors = min.Errors(); // Calculate s0 double phi = atan2((y0 - xs[1]), (x0 - xs[0])); // Use atan2 not atan, or lose half circle range if (phi < 0.0) { phi = phi + 2*PI; } double s0 = xs[2] * phi; // Pull out the covariance matrix TMatrixD cov(4, 4); for (size_t i = 0; i < 4; ++i) { for (size_t j = 0; j < 4; ++j) { cov(i, j) = min.CovMatrix(i, j); } } // Populate the helix std::vector params = {xs[0], xs[1], xs[2], xs[3], s0}; std::vector errs = {errors[0], errors[1], errors[2], errors[3], 0.0}; int zsize = static_cast(z.size()); std::vector ndfs = {(2*zsize - 3), (2*zsize - 1), (2*zsize - 5)}; std::vector chisqs = {-1.0, min.MinValue(), -1.0}; helix = MAUS::SimpleHelix(params, errs, ndfs, chisqs, -1.0, cov); return true; } double LocateGlobalChiSqMinimum(const double* pStart, int handedness, double cut, std::function Chi2Function) { //NOLINT(*) double nsteps = 100.0; // How many pieces do we slice the chisq function into if (pStart[2] < 10.0) nsteps = 200.0; // Search more carefully when the radius is below 10mm double lower_limit = -1.0; // dsdz bounds double upper_limit = 1.0; if (handedness == 1) { lower_limit = 0.0; upper_limit = 1.0; } else if (handedness == -1) { lower_limit = -1.0; upper_limit = 0.0; } double step_size = (upper_limit - lower_limit) / nsteps; double min_chisq = 10000000000.0; // Just a huge number to start double dsdz_seed = pStart[3]; double threshold = 50.0; // min chisq for dsdz to be considered as candidate if (threshold < cut) threshold = cut; // Musn't have a lower value than the patrec level cut std::vector candidate_seeds; for (double seed = lower_limit; seed < upper_limit + step_size; seed += step_size) { const double params[4] = {pStart[0], pStart[1], pStart[2], seed}; double resid = Chi2Function(params); if (resid < min_chisq) { min_chisq = resid; dsdz_seed = seed; } // Can only look for other candidates if we have the expected dsdz sign (the handedness) if ((handedness == 1 || handedness == -1) && (resid < threshold)) { candidate_seeds.push_back(seed); } } // Check if we have other viable dsdz_seed values which are better based on proximity to 0 // (in value of the actual seed, not the corresponding chisq). If no candidates pass the // threshold cut just stick with the smallest chisq value from above if (candidate_seeds.size() > 0) { double new_dsdz_seed = 2.0; // outside allowed range to start for (auto cs : candidate_seeds) { // If candidate value has the expected handedness if (((cs < 0.0) && (handedness < 0)) || ((cs > 0.0) && (handedness > 0))) { if (fabs(cs) < fabs(new_dsdz_seed)) new_dsdz_seed = cs; } } if (new_dsdz_seed <= 1.0) { // Check we are not still on new_dsdz_seed = 2.0 dsdz_seed = new_dsdz_seed; } } return dsdz_seed; } }