NAGASH 0.9.8
Next Generation Analysis System
Loading...
Searching...
No Matches
Chi2Fitter.cxx
Go to the documentation of this file.
1//**************************************************************************************
4//**************************************************************************************
5
6#include "NAGASH/Chi2Fitter.h"
7
8using namespace NAGASH;
9using namespace std;
10
32Chi2Fitter::Chi2Fitter(std::shared_ptr<MSGTool> msg, uint64_t dim, TH1D *_target_, TH2D *_cov_)
33 : Tool(msg), dimension(dim), target(_target_), targetcov(_cov_), cov(dim, dim), histtool(msg)
34{
35 auto titleguard = MSGUser()->StartTitleWithGuard("Chi2Fitter::Chi2Fitter");
36 if (!target)
37 {
38 MSGUser()->MSG_ERROR("Target histogram is not valid");
39 }
40 else
41 {
42 RangeMin = 1;
43 RangeMax = target->GetNbinsX();
44 isvalid = true;
45 }
46}
47
51Chi2Fitter::Chi2Fitter(std::shared_ptr<MSGTool> msg, uint64_t dim)
52 : Tool(msg), dimension(dim), cov(dim, dim), histtool(msg)
53{
54 isvalid = true;
55}
56
60void Chi2Fitter::SetRange(int min, int max)
61{
62 auto titleguard = MSGUser()->StartTitleWithGuard("Chi2Fitter::SetRange");
63 if (min > max || min <= 0 || max > RangeMax)
64 {
65 MSGUser()->MSG_WARNING("Wrong range input, brought back to default");
66 }
67 else
68 {
69 RangeMin = min;
70 RangeMax = max;
71 }
72}
73
77void Chi2Fitter::SetModel(const std::vector<double> &vv, double _chi2_)
78{
79 auto titleguard = MSGUser()->StartTitleWithGuard("Chi2Fitter::SetModel");
80 if (vv.size() != dimension)
81 {
82 MSGUser()->MSG_ERROR("Size of list of model parameters does not match");
83 return;
84 }
85
86 vchi2.emplace_back(_chi2_);
87 vvalue.emplace_back(vv);
88
89 // prepare for normalization
90 if (vchi2.size() == 1)
91 {
92 vvaluemax = vv;
93 vvaluemin = vv;
94 }
95 else
96 {
97 for (size_t i = 0; i < dimension; i++)
98 {
99 if (vv[i] > vvaluemax[i])
100 vvaluemax[i] = vv[i];
101 if (vv[i] < vvaluemin[i])
102 vvaluemin[i] = vv[i];
103 }
104 }
105}
106
111void Chi2Fitter::SetModel(const std::vector<double> &vv, TH1D *hmodel, TH2D *covmodel)
112{
113 if (!isvalid)
114 return;
115
116 auto titleguard = MSGUser()->StartTitleWithGuard("Chi2Fitter::SetModel");
117 if (!hmodel)
118 {
119 MSGUser()->MSG_ERROR("Model histogram is not valid");
120 return;
121 }
122 if (!target)
123 {
124 MSGUser()->MSG_ERROR("Target histogram is not valid");
125 return;
126 }
127
128 if (hmodel->GetNbinsX() < RangeMax)
129 {
130 MSGUser()->MSG_ERROR("Model histogram has less bins than fit range");
131 return;
132 }
133
134 if (covmodel && (hmodel->GetNbinsX() != covmodel->GetNbinsX() || hmodel->GetNbinsX() != covmodel->GetNbinsY()))
135 {
136 MSGUser()->MSG_ERROR("Model histogram and covariance matrix do not match");
137 return;
138 }
139
140 if (vv.size() != dimension)
141 {
142 MSGUser()->MSG_ERROR("Size of list of model parameters does not match");
143 return;
144 }
145
146 double chi2 = 0;
147 if (!covmodel && !targetcov)
148 {
149 // no cov
150 for (int i = RangeMin; i <= RangeMax; i++)
151 {
152 if (target->GetBinError(i) != 0 || hmodel->GetBinError(i) != 0)
153 chi2 += pow(target->GetBinContent(i) - hmodel->GetBinContent(i), 2) / (target->GetBinError(i) * target->GetBinError(i) + hmodel->GetBinError(i) * hmodel->GetBinError(i));
154 }
155 }
156 else
157 {
158 TMatrixD matcovtarget(target->GetNbinsX(), target->GetNbinsX());
159 TMatrixD matcovmodel(hmodel->GetNbinsX(), hmodel->GetNbinsX());
160 matcovtarget *= 0;
161 matcovmodel *= 0;
162
163 if (targetcov)
164 matcovtarget = histtool.ConvertTH2DToMatrix(targetcov);
165 else
166 for (int i = 1; i <= target->GetNbinsX(); i++)
167 matcovtarget(i - 1, i - 1) = target->GetBinError(i) * target->GetBinError(i);
168
169 if (covmodel)
170 matcovmodel = histtool.ConvertTH2DToMatrix(covmodel);
171 else
172 for (int i = 1; i <= hmodel->GetNbinsX(); i++)
173 matcovmodel(i - 1, i - 1) = hmodel->GetBinError(i) * hmodel->GetBinError(i);
174
175 int psize = RangeMax - RangeMin + 1;
176 TMatrixD partialmatcov(psize, psize);
177
178 for (int i = 0; i < psize; i++)
179 for (int j = 0; j < psize; j++)
180 partialmatcov(i, j) = matcovtarget(i + RangeMin - 1, j + RangeMin - 1) + matcovmodel(i + RangeMin - 1, j + RangeMin - 1);
181
182 auto invertcov = partialmatcov.InvertFast();
183 for (int i = 0; i < psize; i++)
184 for (int j = 0; j < psize; j++)
185 chi2 += invertcov(i, j) * (target->GetBinContent(i + RangeMin) - hmodel->GetBinContent(i + RangeMin)) *
186 (target->GetBinContent(j + RangeMin) - hmodel->GetBinContent(j + RangeMin));
187 }
188
189 vchi2.emplace_back(chi2);
190 vvalue.emplace_back(vv);
191
192 // prepare for normalization
193 if (vchi2.size() == 1)
194 {
195 vvaluemax = vv;
196 vvaluemin = vv;
197 }
198 else
199 {
200 for (size_t i = 0; i < dimension; i++)
201 {
202 if (vv[i] > vvaluemax[i])
203 vvaluemax[i] = vv[i];
204 if (vv[i] < vvaluemin[i])
205 vvaluemin[i] = vv[i];
206 }
207 }
208}
209
213double Chi2Fitter::GetChi2(uint64_t index)
214{
215 if (index < vchi2.size() && index >= 0)
216 return vchi2[index];
217 else
218 return -1;
219}
220
224{
225 return chi2;
226}
227
231double Chi2Fitter::Value(uint64_t index)
232{
233 if (index < value.size() && index >= 0)
234 return value[index];
235 else
236 return -1;
237}
238
242double Chi2Fitter::Error(uint64_t index)
243{
244 if ((int)index < cov.GetNcols() && index >= 0)
245 return sqrt(cov(index, index));
246 else
247 return -1;
248}
249
253{
254 return this->offset;
255}
256
257// structure of parameters
258// first (dim + 1) * dim / 2 of elements are lower triangular of inverse of cov
259// e.g. matrix
260// 4 1 0
261// 1 2 5
262// 0 5 9
263// are stored as {4, 1, 2, 0, 5, 9}
264// then "dim" of elements are parameters of the linear terms
265// last is offset
266
267TMatrixD RestoreMatrix(int dim, const Eigen::VectorXf &vpara)
268{
269 TMatrixD mat(dim, dim);
270 int count = 0;
271 for (int i = 0; i < dim; i++)
272 for (int j = 0; j <= i; j++)
273 {
274 mat(i, j) = vpara(count);
275 ++count;
276 }
277
278 // since the covariance matrix is symmetric
279 for (int i = 0; i < dim; i++)
280 {
281 for (int j = i + 1; j < dim; j++)
282 {
283 mat(i, j) = mat(j, i);
284 }
285 }
286
287 return mat;
288}
289
290TMatrixD RestoreMatrix(int dim, const std::vector<double> &vpara)
291{
292 TMatrixD mat(dim, dim);
293 int count = 0;
294 for (int i = 0; i < dim; i++)
295 for (int j = 0; j <= i; j++)
296 {
297 mat(i, j) = vpara[count];
298 ++count;
299 }
300
301 // since the covariance matrix is symmetric
302 for (int i = 0; i < dim; i++)
303 {
304 for (int j = i + 1; j < dim; j++)
305 {
306 mat(i, j) = mat(j, i);
307 }
308 }
309
310 return mat;
311}
312
316{
317 if (!isvalid)
318 return;
319
320 auto titleguard = MSGUser()->StartTitleWithGuard("Chi2Fitter::Fit");
321 // firstly, transform the range of value into [1, 3]
322 // to avoid large difference between x and x^2
323 std::vector<double> vnf1, vnf2;
324 std::vector<std::vector<double>> vvaluescaled;
325 for (size_t i = 0; i < dimension; i++)
326 {
327 vnf1.emplace_back((vvaluemax[i] - vvaluemin[i]) / 2);
328 vnf2.emplace_back(-vvaluemax[i] / 2 + 3 * vvaluemin[i] / 2);
329 if (fabs(vnf1.back()) < 1e-8)
330 vnf1.back() = 1;
331
332 /*
333 vnf1.emplace_back(1);
334 vnf2.emplace_back(0);
335 */
336 }
337
338 std::vector<double> vpara;
339 if (fm == FitMethod::Eigen)
340 {
341 Eigen::MatrixXf X = Eigen::MatrixXf(vchi2.size(), (dimension * dimension + dimension) / 2 + dimension + 1);
342 Eigen::VectorXf Y = Eigen::VectorXf(vchi2.size());
343 for (size_t n = 0; n < vchi2.size(); n++)
344 {
345 int count = 0;
346 for (size_t i = 0; i < dimension; i++)
347 for (size_t j = 0; j <= i; j++)
348 {
349 X(n, count) = ((vvalue[n][i] - vnf2[i]) / vnf1[i]) * (vvalue[n][j] - vnf2[j]) / vnf1[j];
350 ++count;
351 }
352
353 for (size_t i = 0; i < dimension; i++)
354 {
355 X(n, count) = ((vvalue[n][i] - vnf2[i]) / vnf1[i]);
356 ++count;
357 }
358
359 X(n, count) = 1;
360 Y(n) = vchi2[n];
361 }
362
363 /*
364 for (int n = 0; n < vchi2.size(); n++)
365 {
366 for (int i = 0; i < X.cols(); i++)
367 std::cout << X(n, i) << " ";
368 std::cout << Y(n) << std::endl;
369 }
370 */
371
372 Eigen::VectorXf B = X.bdcSvd(Eigen::ComputeThinU | Eigen::ComputeThinV).solve(Y); // SVD
373 // Eigen::VectorXf B = X.colPivHouseholderQr().solve(Y); // QR decomposition
374 // std::cout << B << std::endl;
375 // std::cout << (X.transpose() * X).ldlt().solve(X.transpose() * Y) << std::endl; // direct solve
376
377 // convert fitted parameters to target parameters
378 for (int i = 0; i < B.size(); i++)
379 vpara.emplace_back(B(i));
380 }
381
382 if (fm == FitMethod::Minuit)
383 {
384 std::vector<std::vector<double>> scaledvvalue;
385 for (size_t i = 0; i < vchi2.size(); i++)
386 {
387 auto sv = vvalue[i];
388 for (size_t j = 0; j < dimension; j++)
389 sv[j] = (sv[j] - vnf2[j]) / vnf1[j];
390
391 scaledvvalue.emplace_back(sv);
392 }
393 Chi2FitterFCN fcn(dimension, &scaledvvalue, &vchi2);
394
395 ROOT::Minuit2::MnUserParameters upar;
396 for (size_t i = 0; i < dimension; i++)
397 for (size_t j = 0; j <= i; j++)
398 upar.Add(TString::Format("invertcov_%lu_%lu", i, j).Data(), 0, 1);
399
400 for (size_t i = 0; i < dimension; i++)
401 upar.Add(TString::Format("linearpar_%lu", i).Data(), 2, 2);
402
403 upar.Add("offset", 0, 1);
404
405 // use simplex first
406 ROOT::Minuit2::MnSimplex simplex(fcn, upar, 2);
407 auto minsimplex = simplex(1e5);
408 // then use migrad
409 ROOT::Minuit2::MnMigrad migrad(fcn, minsimplex.UserState(), ROOT::Minuit2::MnStrategy(2));
410 auto minmigrad = migrad(1e5);
411
412 chi2 = minmigrad.Fval();
413 vpara = minmigrad.UserState().Params();
414 }
415
416 cov = RestoreMatrix(dimension, vpara); // actually now the cov is the inverse of the covariance
417 for (size_t i = 0; i < dimension; i++)
418 for (size_t j = 0; j < dimension; j++)
419 cov(i, j) /= vnf1[i] * vnf1[j];
420
421 auto invertcov = cov;
422 cov.Invert();
423
424 std::vector<double> linearterm;
425 value = std::vector<double>(dimension, 0);
426 for (size_t i = 0; i < dimension; i++)
427 linearterm.push_back(vpara[i + (dimension * dimension + dimension) / 2]);
428
429 for (size_t i = 0; i < dimension; i++)
430 {
431 for (size_t j = 0; j < dimension; j++)
432 value[i] -= cov(i, j) * linearterm[j] / (2 * vnf1[j]);
433
434 value[i] += vnf2[i];
435 }
436
437 // calculation of the offset
438 offset = vpara.back();
439 auto tempcov = RestoreMatrix(dimension, vpara).Invert();
440 auto tempinvertcov = RestoreMatrix(dimension, vpara);
441 std::vector<double> tempvalue(dimension, 0);
442 for (size_t i = 0; i < dimension; i++)
443 for (size_t j = 0; j < dimension; j++)
444 tempvalue[i] -= tempcov(i, j) * linearterm[j] / 2;
445
446 for (size_t i = 0; i < dimension; i++)
447 for (size_t j = 0; j < dimension; j++)
448 offset -= tempvalue[i] * tempinvertcov(i, j) * tempvalue[j];
449
450 // check the variance
451 for (size_t i = 0; i < dimension; i++)
452 if (cov(i, i) <= 0 || !isfinite(cov(i, i)))
453 MSGUser()->MSG_WARNING("Variance of value ", i, " is negative or is not finite, the fit results are not trustworthy");
454}
455
456double Chi2Fitter::Chi2FitterFCN::QuadraticChi2(const std::vector<double> &vx, const std::vector<double> &vpara) const
457{
458 double chi2 = vpara.back();
459 auto mat = RestoreMatrix(dim, vpara);
460
461 for (size_t i = 0; i < dim; i++)
462 for (size_t j = 0; j < dim; j++)
463 chi2 += vx[i] * mat(i, j) * vx[j];
464
465 for (size_t i = 0; i < dim; i++)
466 chi2 += vx[i] * vpara[i + (dim * dim + dim) / 2];
467
468 return chi2;
469}
470
471std::vector<double> Chi2Fitter::Chi2FitterFCN::GradientQuadraticChi2(const std::vector<double> &vx, const std::vector<double> &vpara) const
472{
473 std::vector<double> gradient(vpara.size(), 0);
474 gradient.back() = 1;
475 auto mat = RestoreMatrix(dim, vpara);
476
477 int count = 0;
478 for (size_t i = 0; i < dim; i++)
479 for (size_t j = 0; j <= i; j++)
480 {
481 if (i == j)
482 gradient[count] = vx[i] * vx[i];
483 else
484 gradient[count] = 2 * vx[i] * vx[j];
485
486 ++count;
487 }
488
489 for (size_t i = 0; i < dim; i++)
490 gradient[count + i] = vx[i];
491
492 return gradient;
493}
494
495double Chi2Fitter::Chi2FitterFCN::operator()(const std::vector<double> &vpara) const
496{
497 double chi2 = 0;
498 for (size_t i = 0; i < vc->size(); i++)
499 chi2 += pow(QuadraticChi2(vv->at(i), vpara) - vc->at(i), 2);
500
501 return chi2;
502}
503
504std::vector<double> Chi2Fitter::Chi2FitterFCN::Gradient(const std::vector<double> &vpara) const
505{
506 std::vector<double> gradient(vpara.size(), 0);
507 for (size_t i = 0; i < vc->size(); i++)
508 {
509 auto chi2gradient = GradientQuadraticChi2(vv->at(i), vpara);
510 double dchi2 = QuadraticChi2(vv->at(i), vpara) - vc->at(i);
511 for (size_t j = 0; j < gradient.size(); j++)
512 gradient[j] += 2 * dchi2 * chi2gradient[j];
513 }
514
515 return gradient;
516}
TMatrixD RestoreMatrix(int dim, const Eigen::VectorXf &vpara)
fcn function to calculate the
Definition Chi2Fitter.h:49
double operator()(const std::vector< double > &) const override
double QuadraticChi2(const std::vector< double > &, const std::vector< double > &) const
std::vector< double > Gradient(const std::vector< double > &) const override
std::vector< double > GradientQuadraticChi2(const std::vector< double > &, const std::vector< double > &) const
std::vector< double > vvaluemax
Definition Chi2Fitter.h:77
void SetModel(const std::vector< double > &, TH1D *, TH2D *cov=nullptr)
Set the model with a set of parameter.
std::vector< double > vvaluemin
Definition Chi2Fitter.h:78
Chi2Fitter(std::shared_ptr< MSGTool >, uint64_t, TH1D *, TH2D *_cov_=nullptr)
Constructor.
double GetChi2(uint64_t)
Get the of ith model.
std::vector< double > value
Definition Chi2Fitter.h:79
double FitChi2()
Get the of the fitting procedure.
void Fit(FitMethod fm=FitMethod::Eigen)
Start to fit.
std::vector< double > vchi2
Definition Chi2Fitter.h:75
void SetRange(int min, int max)
Set the range of calculation of of the target histogram.
FitMethod
Fit method.
Definition Chi2Fitter.h:28
@ Eigen
use matrix calculation to get the fitted value, Eigen library is used.
@ Minuit
use minuit library to fit the value.
double Error(uint64_t)
Get the fit error of ith parameter.
std::vector< std::vector< double > > vvalue
Definition Chi2Fitter.h:76
const std::vector< double > & Value()
Definition Chi2Fitter.h:43
double OffSet()
The offset parameter of the fitted function.
TMatrixD ConvertTH2DToMatrix(TH2D *h2d)
Convert a 2D histogram to a matrix.
Provide interface for all tools in NAGASH.
Definition Tool.h:72
std::shared_ptr< MSGTool > MSGUser()
return the MSGTool inside.
Definition Tool.h:91