19 #include <TDecompSVD.h>
24 #include "Math/VectorUtil.h"
25 #include "Math/Minimizer.h"
26 #include "Math/Factory.h"
27 #include "Math/Functor.h"
33 static const char *
green =
"\x1B[32m",
38 static const auto eps = std::numeric_limits<double>::epsilon();
39 static const auto inf = std::numeric_limits<double>::infinity();
49 std::vector<double>
X;
50 size_t degree ()
const {
return X.size()-1; }
64 template<
typename POL>
65 TF1 *
GetTF1 (
const char *
name, POL pol,
double m,
double M)
const {
67 auto f =
new TF1(
name, pol, m, M, pol.npars);
68 f->SetParameters(
X.data());
69 f->SetTitle(Form(
"#chi^{2}_{%zu}/ndf = %.1f/%d",
degree(),
chi2,
ndf));
80 stream <<
bold <<
"Chi2/ndf = " << result.
chi2 <<
" / " << result.
ndf
81 <<
" = " << result.
x2n() <<
"\u00B1" << result.
x2nErr()
82 <<
" (prob = " << result.
prob() <<
")\n" <<
def;
83 size_t nrep = result.
chi2sT.size();
84 assert(nrep == result.
chi2sV.size());
85 stream << result.
X.size() <<
" parameters: ";
86 for (
auto x: result.
X) stream << std::setw(12) << x;
89 stream << nrep <<
" training replicas: Chi2T/" << result.
ndf <<
" = " << result.
x2nT() <<
'\n'
90 << nrep <<
" validation replicas: Chi2V/" << (result.
ndf + result.
X.size()) <<
" = " << result.
x2nV() <<
'\n';
92 else stream <<
"Not a fit\n";
96 static std::deque<Result>
chi2s;
110 if (!former.
valid || !current.
valid)
throw -1.;
111 double RSS1 = former.
chi2,
113 size_t p1 = former.
X.size(),
114 p2 = current.
X.size();
116 int ndf = current.
ndf;
117 double x = ((RSS1-RSS2)/(p2-p1))/(RSS2/ndf);
118 double pvalue = TMath::FDistI(x, p2-p1, ndf);
131 size_t nrep = former.
chi2sV.size();
132 if (nrep == 0)
return 0;
134 int Nf = former .
ndf + former .
X.size(),
135 Nc = current.
ndf + current.
X.size();
137 for (
size_t i = 0; i < nrep; ++i)
147 std::ostream& stream = std::cout,
size_t best = 0)
152 for (
auto result = begin(
chi2s); result != end(
chi2s); ++result)
153 if (result->valid) ++nGoodFits;
155 stream <<
red <<
"Only one valid fit. No test to run.\n" <<
def;
158 stream <<
" " <<
bold;
159 for (
auto result = begin(
chi2s); result != prev(end(
chi2s)); ++result) {
161 size_t deg = result->degree();
162 if (deg == best) stream <<
green;
163 if (!result->valid) stream <<
red;
164 stream << setw(10) << deg <<
def;
167 for (
auto result2 = next(begin(
chi2s)); result2 != end(
chi2s); ++result2) {
169 size_t deg2 = result2->degree();
170 if (deg2 == best) stream <<
green;
171 if (!result2->valid) stream <<
red;
172 stream << setw(3) << deg2 <<
def;
173 if (deg2 == best) stream <<
def;
174 stream << setprecision(4);
175 for (
auto result1 = begin(
chi2s); result1 != result2; ++result1) {
177 try { stream << setw(10) << test(*result1, *result2); }
178 catch (
double) { stream <<
red <<
" -" <<
def; }
180 stream <<
bold <<
'\n';
203 FunctionalForm (
const char * classname,
unsigned int Npars,
double mi,
double Ma, std::ostream& Stream = std::cout) :
207 stream <<
"Declaring a `" << classname <<
"` with " <<
npars <<
" parameters between " << mi <<
" and " << Ma <<
'\n';
216 virtual double operator() (
const double *x,
const double *
p)
const = 0;
226 if (std::abs(x) > 1)
return 0.;
238 if (std::abs(x) > 1)
return 0.;
258 Polynomial (
unsigned int d,
double mi,
double Ma, std::ostream& Stream = std::cout) :
265 double operator() (
const double *x,
const double *
p)
const override
267 double nx = -1 + 2* (Log(*x) - Log(m)) / (Log(M) - Log(m));
269 for (
unsigned int i = 0; i < npars; ++i)
270 result +=
p[i]*T(nx,i);
277 template<
typename FunctionalForm>
303 std::ostream& Stream = std::cout
305 n(iM-im+1), c(n), e(n+1), v(n),
w(n), s(n), cov(n,n), rot(n,n), invcov(n,n),
f(ff), stream(Stream)
312 stream <<
"n = " << n <<
'\n';
315 stream <<
"Checking histogram\n";
316 for (
int i = im; i <= iM; ++i) {
317 auto content = h->GetBinContent(i);
319 stream << setw(3) << i
320 << setw(12) << h->GetBinCenter(i)
321 << setw(12) << content <<
'\n';
322 assert(isnormal(content));
325 if (hcov !=
nullptr) {
327 stream <<
"Checking covariance\n";
328 for (
int i = im; i <= iM; ++i)
329 for (
int j = im; j <= iM; ++j) {
330 auto content = hcov->GetBinContent(i,j);
332 stream << setw(12) << content;
333 if (j == iM) stream <<
'\n';
335 assert(isfinite(content));
343 stream <<
"Converting from histograms to vector and matrix (shift = " << shift <<
"):";
344 for (
int i = 0; i < n; ++i) {
345 e(i) = h->GetBinLowEdge(i+shift);
346 c(i) = h->GetBinCenter (i+shift);
347 v(i) = h->GetBinContent(i+shift);
349 stream <<
'\n' << setw(5) << i << setw(10) << c(i) << setw(15) << v(i) << setw(15);
350 assert(abs(v(i)) > 0);
351 for (
int j = 0; j < n; ++j) {
352 cov(i,j) = hcov !=
nullptr ? hcov->GetBinContent(i+shift,j+shift)
353 : i == j ? pow(h->GetBinError(i+shift),2) : 0;
354 if (i == j &&
verbose) stream << (100*sqrt(cov(i,j))/v(i)) <<
'%';
357 e(n) = h->GetBinLowEdge(n+shift);
361 if (!cov.IsSymmetric()) {
363 stream <<
"Forcing to be symmetric\n";
369 assert(cov.IsSymmetric());
374 rot = cov.EigenVectors(s);
376 stream <<
"Getting eigenvalues, expect different columns only in case of non-zero bin-to-bin correlations:";
377 for (
int k = 0; k < n; ++k) {
379 stream <<
'\n' << setw(5) << k << setw(15) << s[k] << setw(15) << cov(k,k);
388 stream <<
"Inverting...\n";
392 invcov = svd.Invert(status);
396 stream <<
"Ready to fit\n";
401 double operator() (
const double *
p)
404 for (
int i = 0; i < n; ++i) {
405 auto y0 =
f(&e(i),
p),
408 r(i) -= (y0+4*y1+y2)/6;
410 return r * (invcov * r);
418 for (
int k = 0; k < n; ++k)
419 d(k) = r.Gaus(0, s(k));
449 unsigned int maxdegree,
451 double stopParam = 1,
453 std::ostream& stream = std::cout
458 assert(h !=
nullptr);
461 stream <<
"im = " << im <<
", iM = " << iM <<
'\n';
463 unsigned int nbins = iM-im+1;
465 stream <<
"nbins = " << nbins <<
'\n';
468 double m = h->GetBinLowEdge(im),
469 M = h->GetBinLowEdge(iM+1);
471 stream <<
"m = " << m <<
", M = " << M <<
'\n';
475 ROOT::Math::Functor functor(cor, pol.
npars);
477 auto minimiser = ROOT::Math::Factory::CreateMinimizer(
"Minuit2",
"Migrad");
478 const int Ncalls = 1e6;
479 minimiser->SetMaxFunctionCalls(Ncalls);
480 minimiser->SetFunction(functor);
481 minimiser->SetTolerance(0.001);
482 minimiser->SetPrintLevel(
verbose);
484 double fm = Log(h->GetBinContent(im)),
485 fM = Log(h->GetBinContent(iM));
487 double p0 = (fM+fm)/2, p1 = (fM-fm)/2;
488 minimiser->SetVariable(0,
"p0",p0,step);
489 minimiser->SetVariable(1,
"p1",p1,step);
490 for (
unsigned int d = 2; d <= maxdegree; ++d) {
491 minimiser->SetVariable(d,Form(
"p%d", d),0.,step);
492 minimiser->FixVariable(d);
497 chi2s.push_back({
false,
inf, nbins-2, {p0,p1}});
500 while (
chi2s.size() <= maxdegree) {
501 unsigned int degree =
chi2s.size();
502 minimiser->ReleaseVariable(degree);
503 bool valid = minimiser->Minimize();
505 const double * X = minimiser->X();
506 assert(X !=
nullptr);
507 double chi2 = cor(X);
509 assert(isnormal(chi2));
510 unsigned int npars = minimiser->NFree(),
512 assert(npars == degree+1);
514 const auto& former =
chi2s.back();
515 chi2s.push_back({valid, chi2, ndf, vector<double>(X,X+npars)});
516 auto& current =
chi2s.back();
518 switch (minimiser->Status()) {
519 case 0: stream << current;
522 bool plateau = current.x2n() > former.x2n(),
523 goodchi2 = abs(current.x2n()-1) <= stopParam*current.x2nErr();
525 stream <<
red <<
"Chi2/ndf has increased.\n" <<
def;
527 stream <<
green <<
"Chi2 ~ ndf.\n" <<
def;
533 ftestRes =
Ftest(former, current);
534 stream <<
"F-test result: " << ftestRes <<
'\n';
536 catch (
double) { stream <<
red <<
"No F-test possible.\n" <<
def; }
537 bool fvalid = ftestRes > stopParam;
542 current.chi2sT.reserve(nrep);
543 current.chi2sV.reserve(nrep);
544 for (UInt_t seed = 1; seed <= nrep; ++seed) {
546 for (
unsigned int i = 0; i <= degree; ++i)
547 minimiser->SetVariableValue(i, current.X[i]);
548 const double * X = minimiser->X();
549 assert(X !=
nullptr);
550 current.chi2sT.push_back(cor(X));
552 current.chi2sV.push_back(cor(X));
555 for (
unsigned int i = 0; i <= degree; ++i)
556 minimiser->SetVariableValue(i, current.X[i]);
558 xvalFrac =
Xval(current, former);
560 bool xvalid = 1.-xvalFrac > stopParam;
562 stream <<
"Fraction of validation replicas with better fit Chi2/ndf = " << (xvalid ?
green :
red) << xvalFrac <<
def <<
'\n';
568 found = plateau || goodchi2;
583 case 1: stream <<
red <<
"Warning: Fit parameter covariance was made positive defined\n" <<
def;
584 for (
unsigned int i = 0; i < npars; ++i) {
585 for (
unsigned int j = 0; j < npars; ++j)
586 stream << setw(12) << minimiser->CovMatrix(i,j);
590 case 2: stream <<
red <<
"Warning: Hesse is invalid\n" <<
def;
592 double * hessian =
nullptr;
593 minimiser->GetHessianMatrix(hessian);
594 assert(hessian !=
nullptr);
595 for (
unsigned int i = 0; i < npars; ++i) {
596 for (
unsigned int j = 0; j < npars; ++j)
597 stream << setw(12) << hessian[i*pol.
npars + j];
602 case 3: stream <<
red <<
"Warning: Expected Distance reached from the Minimum (EDM = " << minimiser->Edm() <<
") is above max\n" <<
def;
604 case 4: stream <<
red <<
"Warning: Reached call limit (Ncalls = " << Ncalls <<
")\n" <<
def;
607 default: stream <<
red <<
"Warning: Unknown failure\n" <<
def;
613 stream <<
"Correlations of the fit parameters:\n";
614 for (
unsigned int i = 0; i < npars; ++i) {
615 for (
unsigned int j = 0; j < npars; ++j)
616 stream << setw(12) << minimiser->Correlation(i,j);
621 if (current.ndf == 2) {
622 stream <<
red <<
"Only 2 d.o.f. left. Stopping.\n" <<
def;
630 const char *
name = Form(
"smooth_%s", h->GetName());
632 stream <<
"Best result: " <<
f->GetTitle() <<
"\n-----\n";
634 stream <<
bold <<
"Running F-test:\n" <<
def;
638 stream <<
bold <<
"Running Cross-validation:\n" <<
def;
652 std::ostream& stream = std::cout)
654 int im = 1, iM = h->GetNbinsX();
655 return GetSmoothFit<T,Log,Exp>(h, cov, im, iM,
656 maxdegree, criterion, stopParam, nrep, stream);
666 std::ostream& stream = std::cout)
668 return GetSmoothFit<T,Log,Exp>(h,
nullptr, im, iM,
669 maxdegree, criterion, stopParam, nrep, stream);
680 std::ostream& stream = std::cout)
682 return GetSmoothFit<T,Log,Exp>(h,
nullptr,
683 maxdegree, criterion, stopParam, nrep, stream);