KaliVeda
Toolkit for HIC analysis
KVMultiGaussIsotopeFit.cpp
1 #include "KVMultiGaussIsotopeFit.h"
2 #include "TGraph.h"
3 #include "TRandom.h"
4 #include <TCanvas.h>
5 
6 
16 
17 double KVMultiGaussIsotopeFit::centroid_fit(double* x, double* p)
18 {
19  // centroids of gaussians are expected to increase linearly with mass A but we allow a quadratic dependence
20  //
21  //~~~
22  // x[0]=A
23  // p[0]=offset
24  // p[1]=slope
25  // p[2]=quadratic term
26  //~~~
27 
28  return p[0] + p[1] * x[0] + p[2] * x[0] * x[0];
29 }
30 
31 
32 
45 
46 double KVMultiGaussIsotopeFit::FitFunc(double* x, double* p)
47 {
48  //~~~
49  // x[0] = PID
50  // p[0] = number of gaussians = Ng (fixed)
51  // p[1],p[2] = background parameters: exp(p[1]+p[2]*x)
52  // p[3] = sigma for all gaussians
53  // p[4],p[5],p[6] = offset & slope for centroid PID vs. A correlation
54  // p[7]...p[6+Ng] = norm of each gaussian
55  // p[6+Ng+1]...p[6+2*Ng] = A of each gaussian
56  //~~~
57  //
58  // Total number of parameters is 7+2*Ng
59 
60  int Ng = p[0];
61  double background = TMath::Exp(p[fit_param_index::bkg_cst] + p[fit_param_index::bkg_slp] * x[0]);
62  for (int i = 1; i <= Ng; ++i) {
63  background +=
64  p[get_gauss_norm_index(i)] * TMath::Gaus(x[0], centroid_fit(&p[get_mass_index(i, Ng)],
65  &p[fit_param_index::pidvsA_a0]), p[fit_param_index::gauss_wid], kTRUE);
66  }
67  return background;
68 }
69 
70 
71 
83 
84 std::optional<double> KVMultiGaussIsotopeFit::get_total_fit_for_pid(double PID) const
85 {
86  // Calculates the total value (gaussians + background) of the fit for a given PID value.
87  //
88  // If this is less than 1, returns std::nullopt: in this case, the fit should not be used to
89  // deduce an \f$A\f$ etc. for this PID.
90  //
91  // \note The identification functional is fitted to histogrammed data, therefore the resulting
92  // complete function should always evaluate to some finite value \f$\geq 1\f$. If this is not the
93  // case for certain values of PID, it suggests that the 'background' is not well fitted and the
94  // functional cannot be used for identification (gives garbage results). In this case we do not
95  // return a value.
96  auto total = Eval(PID);
97  if(total<1)
98  return {};
99  return total;
100 }
101 
102 
103 
112 
113 KVMultiGaussIsotopeFit::KVMultiGaussIsotopeFit(int z, int Ngauss, double PID_min, double PID_max, const KVNumberList& alist, std::vector<double> pidlist)
114  : TF1(Form("MultiGaussIsotopeFit_Z=%d", z), this, &KVMultiGaussIsotopeFit::FitFunc, PID_min, PID_max, total_number_parameters(Ngauss)),
115  Z{z},
116  Niso{Ngauss},
117  PIDmin{PID_min}, PIDmax{PID_max},
118  Alist{alist.GetArray()},
119  PIDlist{pidlist}
120 {
121  // Constructor used to initialize and prepare a new fit of isotope PID spectrum
122  //
123  // \param[in] z atomic number \f$Z\f$ of element to fit
124  // \param[in] Ngauss number of gaussians to use in the fit (corresponds to number of isotopes in PID spectrum)
125  // \param[in] PID_min,PID_max limits of PID spectrum to which fit is performed
126  // \param[in] alist ordered list of increasing \f$A\f$-values of isotopes in PID spectrum
127  // \param[in] pidlist corresponding initial values of PID for centroid of each isotope-gaussian in PID spectrum
128 
129  FixParameter(0, Niso);
130  SetParLimits(fit_param_index::bkg_cst, -10., 25.);
131  SetParameter(fit_param_index::bkg_cst, 4.);
132  SetParName(fit_param_index::bkg_cst, "Norm");
133  PositiveBkgSlope(false);
134  SetParName(fit_param_index::bkg_slp, "Bkg. slope");
135  SetParName(fit_param_index::gauss_wid, "Sigma");
136  SetParLimits(fit_param_index::gauss_wid, min_sigma, max_sigma);
137  SetParameter(fit_param_index::gauss_wid, 0.1);
138 
139  for (int ig = 1; ig <= Niso; ++ig) {
140  SetParName(get_gauss_norm_index(ig), Form("Norm. A=%d", Alist[ig - 1]));
141  SetParLimits(get_gauss_norm_index(ig), 1.e-3, 1.e+06);
142  SetParameter(get_gauss_norm_index(ig), 1.);
143  SetParName(get_mass_index(ig, Niso), Form("A_%d", ig));
144  FixParameter(get_mass_index(ig, Niso), Alist[ig - 1]);
145  }
146 
147  FitCentroids();
148 
150  SetLineWidth(2);
151  SetNpx(500);
152 }
153 
154 
155 
157 
159 {
160  SetParLimits(fit_param_index::bkg_cst, -10., 25.);
161  PositiveBkgSlope(GetParameter(fit_param_index::bkg_slp)>0);
162  SetParLimits(fit_param_index::gauss_wid, min_sigma, max_sigma);
163  for (int ig = 1; ig <= Niso; ++ig)
164  {
165  SetParLimits(get_gauss_norm_index(ig), 1.e-3, 1.e+06);
166  }
167 }
168 
169 
170 
183 
184 KVMultiGaussIsotopeFit::KVMultiGaussIsotopeFit(int z, int Ngauss, double PID_min, double PID_max,
185  const KVNumberList& alist, double bkg_cst, double bkg_slp,
186  double gaus_wid, double pidvsa_a0, double pidvsa_a1, double pidvsa_a2)
187  : TF1(Form("MultiGaussIsotopeFit_Z=%d", z), this, &KVMultiGaussIsotopeFit::FitFunc, PID_min, PID_max, total_number_parameters(Ngauss)),
188  Z{z},
189  Niso{Ngauss},
190  PIDmin{PID_min}, PIDmax{PID_max},
191  Alist{alist.GetArray()}
192 {
193  // Constructor which can be used with existing fit results (not to perform new fits)
194  //
195  // Use SetGaussianNorm() to set the normalisation parameters for each gaussian
196  //
197  // \param[in] z atomic number \f$Z\f$ of element in fit
198  // \param[in] Ngauss number of gaussians used in the fit (corresponds to number of isotopes in PID spectrum)
199  // \param[in] PID_min,PID_max limits of PID spectrum to which fit was performed
200  // \param[in] alist ordered list of increasing \f$A\f$-values of isotopes in PID spectrum
201  // \param[in] bkg_cst,bkg_slp constant and slope of fitted exponential background function
202  // \param[in] gaus_wid width of gaussians used in fit
203  // \param[in] pidvsa_a0,pidvsa_a1,pidvsa_a2 parameters of polynomial fit to PID-coordinate \f$A\f$-dependence
204 
205  FixParameter(0, Niso);
206  SetParameter(fit_param_index::bkg_cst, bkg_cst);
207  SetParameter(fit_param_index::bkg_slp, bkg_slp);
208  SetParameter(fit_param_index::gauss_wid, gaus_wid);
209  SetParameter(fit_param_index::pidvsA_a0, pidvsa_a0);
210  SetParameter(fit_param_index::pidvsA_a1, pidvsa_a1);
211  SetParameter(fit_param_index::pidvsA_a2, pidvsa_a2);
212 
213  SetParName(fit_param_index::bkg_cst, "Norm");
214  SetParName(fit_param_index::bkg_slp, "Bkg. slope");
215  SetParName(fit_param_index::gauss_wid, "Sigma");
216 
218 
219  for (int i = 0; i < 3; ++i)
220  SetParName(fit_param_index::pidvsA_a0 + i, Form("PIDvsA_a%d", i));
221 
223  SetLineWidth(2);
224  SetNpx(500);
225 }
226 
227 
228 
231 
233  : KVMultiGaussIsotopeFit(Z, fitparams.GetIntValue("Ng"), fitparams.GetDoubleValue("PIDmin"),
234  fitparams.GetDoubleValue("PIDmax"), fitparams.GetStringValue("Alist"),
235  fitparams.GetDoubleValue("Bkg_cst"), fitparams.GetDoubleValue("Bkg_slp"),
236  fitparams.GetDoubleValue("GausWid"),
237  fitparams.GetDoubleValue("PIDvsA_a0"),
238  fitparams.GetDoubleValue("PIDvsA_a1"),
239  fitparams.GetDoubleValue("PIDvsA_a2"))
240 {
241  // initialize from previous fit with parameters stored in KVNameValueList
242  for (int ig = 1; ig <= fitparams.GetIntValue("Ng"); ++ig)
243  SetGaussianNorm(ig, fitparams.GetDoubleValue(Form("Norm_%d", ig)));
244 }
245 
246 
247 
250 
252 {
253  // Required in case the attribution of masses to the gaussians changes
254  for (int ig = 1; ig <= Niso; ++ig) {
255  SetParName(get_gauss_norm_index(ig), Form("Norm. A=%d", Alist[ig - 1]));
256  SetParName(get_mass_index(ig, Niso), Form("A_%d", ig));
257  FixParameter(get_mass_index(ig, Niso), Alist[ig - 1]);
258  }
259 }
260 
261 
262 
264 
266 {
267  TGraph pid_vs_a;
268  for (int ig = 1; ig <= Niso; ++ig) {
269 #if ROOT_VERSION_CODE >= ROOT_VERSION(6,24,0)
270  pid_vs_a.AddPoint(Alist[ig - 1], PIDlist[ig - 1]);
271 #else
272  pid_vs_a.SetPoint(pid_vs_a.GetN(), Alist[ig - 1], PIDlist[ig - 1]);
273 #endif
274  }
275  TF1 centroidFit("centroidFit", this, &KVMultiGaussIsotopeFit::centroid_fit, 0., 100., 3);
276  centroidFit.SetParLimits(0, -50, 50);
277  centroidFit.SetParameter(0, 0);
278  centroidFit.SetParLimits(1, 1.e-2, 5.);
279  centroidFit.SetParameter(1, 1.e-1);
280  centroidFit.SetParLimits(2, -2.e-2, 1.);
281  centroidFit.SetParameter(2, 1.e-3);
282  pid_vs_a.Fit(&centroidFit, "N");
283  for (int i = 0; i < 3; ++i) {
284  FixParameter(fit_param_index::pidvsA_a0 + i, centroidFit.GetParameter(i));
285  SetParName(fit_param_index::pidvsA_a0 + i, Form("PIDvsA_a%d", i));
286  }
287 }
288 
289 
290 
293 
295 {
296  // Release the constraint on the positions of the centroids
297  SetParLimits(fit_param_index::pidvsA_a0, -50, 50);
298  SetParLimits(fit_param_index::pidvsA_a1, 1.e-2, 5.);
299  SetParLimits(fit_param_index::pidvsA_a2, -2.e-2, 5.);
300 }
301 
302 
303 
306 
308 {
309  // Remove the graphical representation of this fit from the given pad
310 
311  auto old_fit = pad->FindObject(get_name_of_multifit(Z));
312  if (old_fit) delete old_fit;
313  for (auto a : Alist) {
314  UnDrawGaussian(Z, a, pad);
315  }
316 }
317 
318 
319 
322 
324 {
325  // Remove the graphical representation of any gaussian for this Z from the given pad
326 
327  TIter it(pad->GetListOfPrimitives());
328  TObject* ob;
329  KVList to_delete;
330  while ((ob = it())) {
331  TString obname = ob->GetName();
332  if (obname.BeginsWith(get_root_name_of_isotope_gaussian(z))) to_delete.Add(ob);
333  }
334 }
335 
336 
337 
343 
345 {
346  // Draw the overall fit plus the individual gaussians for each isotope
347  //
348  // \param[in] opt drawing option, will be used for main fit (could be `"same"`)
349  // \param[in] fit_title optional, if given will be used as title of fit function (displayed in TLegend and also above frame in TPad with option 'show histogram title')
350 
351  auto ff = DrawCopy(opt);
352  ff->SetName(get_name_of_multifit(Z));
353  if(!fit_title.IsNull())
354  ff->SetTitle(fit_title);
355  TF1 fgaus("fgaus", "gausn", PIDmin, PIDmax);
356  // give a different colour to each gaussian
357  auto cstep = TColor::GetPalette().GetSize() / (Niso + 1);
358  int ig = 1;
359  for (auto& a : Alist) {
361  fgaus.SetNpx(500);
362  fgaus.SetLineColor(TColor::GetPalette()[cstep * ig]);
363  fgaus.SetLineWidth(2);
364  fgaus.SetLineStyle(9);
365  auto gg = fgaus.DrawCopy("same");
367  gg->SetTitle(get_name_of_isotope_gaussian(Z, a));
368  ++ig;
369  }
370 }
371 
372 
373 
384 
385 std::optional<int> KVMultiGaussIsotopeFit::GetMostProbableA(double PID, double& P) const
386 {
387  // For a given PID, calculate the most probable value of \f$A\f$, P is its probability.
388  //
389  // \returns most probable \f$A\f$
390  //
391  // \note The identification functional is fitted to histogrammed data, therefore the resulting
392  // complete function should always evaluate to some finite value \f$\geq 1\f$. If this is not the
393  // case for certain values of PID, it suggests that the 'background' is not well fitted and the
394  // functional cannot be used for identification (gives garbage results). In this case we do not
395  // return a value.
396 
397  auto total = get_total_fit_for_pid(PID);
398  if(!total)
399  return {};
400  std::map<double, int> probabilities;
401  int ig = 1;
402  for (auto& a : Alist) {
403  probabilities[evaluate_gaussian(ig, PID) / *total] = a;
404  ++ig;
405  }
406  // the largest probability is now the last element in the map:
407  // get a reverse iterator to the beginning of the reversed map
408  auto it = probabilities.rbegin();
409  P = it->first;
410  return it->second;
411 }
412 
413 
414 
425 
426 std::optional<double> KVMultiGaussIsotopeFit::GetMeanA(double PID) const
427 {
428  // for a given PID, calculate the mean value of \f$A\f$ from the weighted sum of all gaussians
429  //
430  // \returns mean value of \f$A\f$
431  //
432  // \note The identification functional is fitted to histogrammed data, therefore the resulting
433  // complete function should always evaluate to some finite value \f$\geq 1\f$. If this is not the
434  // case for certain values of PID, it suggests that the 'background' is not well fitted and the
435  // functional cannot be used for identification (gives garbage results). In this case we do not
436  // return a value.
437 
438  auto total = get_total_fit_for_pid(PID);
439  if(!total)
440  return {};
441  int ig = 1;
442  double amean(0), totprob(0);
443  for (auto& a : Alist) {
444  auto weight = evaluate_gaussian(ig, PID) / *total;
445  amean += weight * a;
446  totprob += weight;
447  ++ig;
448  }
449  return totprob > 0 ? amean / totprob : -1.;
450 }
451 
452 
453 
464 
465 std::optional<std::map<int, double>> KVMultiGaussIsotopeFit::GetADistribution(double PID) const
466 {
467  // For the given PID, the map is filled with all possible values of \f$A\f$
468  //
469  // \note The identification functional is fitted to histogrammed data, therefore the resulting
470  // complete function should always evaluate to some finite value \f$\geq 1\f$. If this is not the
471  // case for certain values of PID, it suggests that the 'background' is not well fitted and the
472  // functional cannot be used for identification (gives garbage results). In this case we do not
473  // return a value.
474  //
475  // \returns std::map containing probability distribution \f$P(A|PID)\f$
476 
477  auto total = get_total_fit_for_pid(PID);
478  if(!total)
479  return {};
480  std::map<int, double> Adist;
481  int ig = 1;
482  for (auto& a : Alist) {
483  Adist[a] = evaluate_gaussian(ig, PID) / *total;
484  ++ig;
485  }
486  return Adist;
487 }
488 
489 
490 
511 
512 std::optional<int> KVMultiGaussIsotopeFit::GetA(double PID, double& P) const
513 {
514  // Probabilistic method to determine \f$A\f$ from PID.
515  //
516  // The A returned will be drawn at random from the probability distribution given by the
517  // sum of all gaussians (and the background) for the given PID.
518  //
519  // The result of the draw may be that this PID is part of the background noise:
520  // in this case we do not return a value (returns std::nullopt : returned value will evaluate as false).
521  //
522  // P is the probability of the chosen result.
523  //
524  // \param[in] PID PID value from \f$Z\f$ identification
525  // \param[out] P probablility the returned value of \f$A\f$ is correct
526  // \returns randomly drawn \f$A\f$ for given PID
527  //
528  // \note The identification functional is fitted to histogrammed data, therefore the resulting
529  // complete function should always evaluate to some finite value \f$\geq 1\f$. If this is not the
530  // case for certain values of PID, it suggests that the 'background' is not well fitted and the
531  // functional cannot be used for identification (gives garbage results). In this case we do not
532  // return a value.
533 
534  auto total = get_total_fit_for_pid(PID);
535  if(!total)
536  return {};
537  double p_tot = 0;
538  int ig = 1;
539  auto X = gRandom->Uniform();
540  for (auto& a : Alist) {
541  auto w = evaluate_gaussian(ig, PID) / *total;
542  p_tot += w;
543  if (X < w) {
544  P = w;
545  return a;
546  }
547  X -= w;
548  ++ig;
549  }
550  P = 1. - p_tot;
551  return {}; // background noise
552 }
553 
554 
555 
561 
562 double KVMultiGaussIsotopeFit::GetProbability(int A, double PID) const
563 {
564  // \param[in] A isotope mass number
565  // \param[in] PID value of PID associated with A
566  // \return the probability that A is the correct mass number for a given PID value
567  // \return zero if A is not associated with a Gaussian in the fit
568 
569  auto it = std::find(std::begin(Alist), std::end(Alist), A);
570  if (it != std::end(Alist)) {
571  int ig = std::distance(std::begin(Alist), it);
572  return evaluate_gaussian(ig, PID) / Eval(PID);
573  }
574  return 0;
575 }
576 
577 
578 
582 
584 {
585  // \returns interpolated A from PID using the quadratic fit parameters
586  // analytic inversion of \f$f(A_i) = a_0 + a_1 A_i + a_2 A_i^2\f$
587 
588  auto a = GetPIDvsAfit_a2();
589  auto b = GetPIDvsAfit_a1();
590  auto c = GetPIDvsAfit_a0() - PID;
591  return (TMath::Sqrt(b * b - 4.*a * c) - b) / 2. / a;
592 }
593 
594 
595 
598 
600 {
601  // \returns the fitted centroid position of the ith gaussian (i=1,2,...,Niso) i.e. \f$f(A_i) = a_0 + a_1 A_i + a_2 A_i^2\f$
602  assert(i > 0 && i <= Niso);
603  return GetParameter(fit_param_index::pidvsA_a0)
604  + (GetParameter(fit_param_index::pidvsA_a1)
605  + GetParameter(fit_param_index::pidvsA_a2) * Alist[i - 1]) * Alist[i - 1];
606 }
607 
608 
609 
614 
616 {
617  // The PID value stored in the KVIdentificationResult corresponding to this identification is the interpolated
618  // value of \f$A\f$ calculated using GetInterpolatedA(). Calling this method with that value returns the
619  // actual PID value which can be compared with the gaussians of the fit.
620  return GetParameter(fit_param_index::pidvsA_a0)
621  + (GetParameter(fit_param_index::pidvsA_a1)
622  + GetParameter(fit_param_index::pidvsA_a2) * interpA) * interpA;
623 }
624 
625 
626 
627 
628 
631 
632 void KVMultiGaussIsotopeFit::SetFitRange(double min, double max)
633 {
634  // Change range of fit
635 
636  SetRange(min, max);
637  PIDmin = min;
638  PIDmax = max;
639 }
640 
641 
642 
647 
649 {
650  // if yes=true, only explore positive values for background slope
651  //
652  // if yes=false, only explore negative values
653  if(yes)
654  {
655  SetParLimits(fit_param_index::bkg_slp, 0., 5.);
656  SetParameter(fit_param_index::bkg_slp, 1.);
657  }
658  else
659  {
660  SetParLimits(fit_param_index::bkg_slp, -10, 0);
661  SetParameter(fit_param_index::bkg_slp, -2.);
662  }
663 }
664 
665 
667 
#define c(i)
const char Option_t
kBlack
#define X(type, name)
winID w
winID h TVirtualViewer3D TVirtualGLPainter p
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t Float_t Float_t b
R__EXTERN TRandom * gRandom
char * Form(const char *fmt,...)
Extended TList class which owns its objects by default.
Definition: KVList.h:22
Function for fitting PID mass spectra.
static TString get_name_of_isotope_gaussian(int z, int a)
static TString get_name_of_multifit(int z)
double GetGaussianWidth(int) const
static TString get_root_name_of_isotope_gaussian(int z)
static void UnDrawAnyGaussian(int z, TVirtualPad *pad=gPad)
Remove the graphical representation of any gaussian for this Z from the given pad.
void DrawFitWithGaussians(Option_t *opt="", const TString &fit_title="") const
std::optional< double > GetMeanA(double PID) const
std::optional< std::map< int, double > > GetADistribution(double PID) const
void PositiveBkgSlope(bool yes=true)
void UpdateGaussianCentroidParameters()
Required in case the attribution of masses to the gaussians changes.
void SetFitRange(double min, double max)
Change range of fit.
std::optional< int > GetA(double PID, double &P) const
void SetGaussianNorm(int i, double v)
double GetInterpolatedA(double PID) const
double GetPIDFromInterpolatedA(double interpA)
std::optional< int > GetMostProbableA(double PID, double &P) const
double GetProbability(int A, double PID) const
double GetGaussianNorm(int i) const
void ReleaseCentroids()
Release the constraint on the positions of the centroids.
void UnDraw(TVirtualPad *pad=gPad) const
Remove the graphical representation of this fit from the given pad.
static void UnDrawGaussian(int z, int a, TVirtualPad *pad=gPad)
Handles lists of named parameters with different types, a list of KVNamedParameter objects.
Int_t GetIntValue(const Char_t *name) const
Double_t GetDoubleValue(const Char_t *name) const
Strings used to represent a set of ranges of values.
Definition: KVNumberList.h:86
void Add(TObject *obj) override
Int_t GetSize() const
virtual void SetLineStyle(Style_t lstyle)
virtual void SetLineWidth(Width_t lwidth)
virtual void SetLineColor(Color_t lcolor)
static const TArrayI & GetPalette()
virtual Double_t GetParameter(const TString &name) const
virtual void SetRange(Double_t xmin, Double_t xmax)
virtual void SetNpx(Int_t npx=100)
virtual void SetParLimits(Int_t ipar, Double_t parmin, Double_t parmax)
virtual void SetParName(Int_t ipar, const char *name)
virtual TF1 * DrawCopy(Option_t *option="") const
virtual void SetParameters(const Double_t *params)
virtual Double_t Eval(Double_t x, Double_t y=0, Double_t z=0, Double_t t=0) const
virtual void SetParameter(const TString &name, Double_t value)
virtual void FixParameter(Int_t ipar, Double_t value)
virtual void AddPoint(Double_t x, Double_t y)
virtual void SetPoint(Int_t i, Double_t x, Double_t y)
Int_t GetN() const
virtual TFitResultPtr Fit(const char *formula, Option_t *option="", Option_t *goption="", Axis_t xmin=0, Axis_t xmax=0)
virtual void SetName(const char *name)
virtual const char * GetName() const
virtual TObject * FindObject(const char *name) const
virtual Double_t Uniform(Double_t x1, Double_t x2)
Bool_t BeginsWith(const char *s, ECaseCompare cmp=kExact) const
Bool_t IsNull() const
virtual TList * GetListOfPrimitives() const=0
Double_t x[n]
double min(double x, double y)
double max(double x, double y)
Double_t Gaus(Double_t x, Double_t mean=0, Double_t sigma=1, Bool_t norm=kFALSE)
Double_t Exp(Double_t x)
Double_t Sqrt(Double_t x)
TArc a
ClassImp(TPyArg)