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 
18 
19 std::optional<double> KVMultiGaussIsotopeFit::get_total_fit_for_pid(double PID) const
20 {
21  // Calculates the total value (gaussians + background) of the fit for a given PID value.
22  //
23  // If this is less than 1, returns std::nullopt: in this case, the fit should not be used to
24  // deduce an \f$A\f$ etc. for this PID.
25  //
26  // \note The identification functional is fitted to histogrammed data, therefore the resulting
27  // complete function should always evaluate to some finite value \f$\geq 1\f$. If this is not the
28  // case for certain values of PID, it suggests that the 'background' is not well fitted and the
29  // functional cannot be used for identification (gives garbage results). In this case we do not
30  // return a value.
31  auto total = Eval(PID);
32  if(total<1)
33  return {};
34  return total;
35 }
36 
37 
38 
41 
42 KVMultiGaussIsotopeFit::KVMultiGaussIsotopeFit(int z, int Ngauss, double PID_min, double PID_max, const KVNumberList& alist, std::vector<double> pidlist)
43  : TF1(Form("MultiGaussIsotopeFit_Z=%d", z), this, &KVMultiGaussIsotopeFit::FitFunc, PID_min, PID_max, total_number_parameters(Ngauss)),
44  Z{z},
45  Niso{Ngauss},
46  PIDmin{PID_min}, PIDmax{PID_max},
47  Alist{alist.GetArray()},
48  PIDlist{pidlist}
49 {
50  // Constructor used to initialize and prepare a new fit of isotope PID spectrum
51  FixParameter(0, Niso);
52  SetParLimits(fit_param_index::bkg_cst, -10., 25.);
53  SetParameter(fit_param_index::bkg_cst, 4.);
54  SetParName(fit_param_index::bkg_cst, "Norm");
55  PositiveBkgSlope(false);
56  SetParName(fit_param_index::bkg_slp, "Bkg. slope");
57  SetParName(fit_param_index::gauss_wid, "Sigma");
58  SetParLimits(fit_param_index::gauss_wid, min_sigma, max_sigma);
59  SetParameter(fit_param_index::gauss_wid, 0.1);
60 
61  TGraph pid_vs_a;
62  for (int ig = 1; ig <= Niso; ++ig) {
63  SetParName(get_gauss_norm_index(ig), Form("Norm. A=%d", Alist[ig - 1]));
64  SetParLimits(get_gauss_norm_index(ig), 1.e-3, 1.e+06);
65  SetParameter(get_gauss_norm_index(ig), 1.);
66  SetParName(get_mass_index(ig, Niso), Form("A_%d", ig));
67  FixParameter(get_mass_index(ig, Niso), Alist[ig - 1]);
68 
69 #if ROOT_VERSION_CODE >= ROOT_VERSION(6,24,0)
70  pid_vs_a.AddPoint(Alist[ig - 1], PIDlist[ig - 1]);
71 #else
72  pid_vs_a.SetPoint(pid_vs_a.GetN(), Alist[ig - 1], PIDlist[ig - 1]);
73 #endif
74  }
75 
76  // do initial fit of centroids
77  TF1 centroidFit("centroidFit", this, &KVMultiGaussIsotopeFit::centroid_fit, 0., 100., 3);
78  centroidFit.SetParLimits(0, -50, 50);
79  centroidFit.SetParameter(0, 0);
80  centroidFit.SetParLimits(1, 1.e-2, 5.);
81  centroidFit.SetParameter(1, 1.e-1);
82  centroidFit.SetParLimits(2, -2.e-2, 1.);
83  centroidFit.SetParameter(2, 1.e-3);
84  pid_vs_a.Fit(&centroidFit, "N");
85 
86  for (int i = 0; i < 3; ++i) {
87  FixParameter(fit_param_index::pidvsA_a0 + i, centroidFit.GetParameter(i));
88  SetParName(fit_param_index::pidvsA_a0 + i, Form("PIDvsA_a%d", i));
89  }
90 
91  SetLineColor(kBlack);
92  SetLineWidth(2);
93  SetNpx(500);
94 }
95 
96 
97 
102 
103 KVMultiGaussIsotopeFit::KVMultiGaussIsotopeFit(int z, int Ngauss, double PID_min, double PID_max,
104  const KVNumberList& alist, double bkg_cst, double bkg_slp,
105  double gaus_wid, double pidvsa_a0, double pidvsa_a1, double pidvsa_a2)
106  : TF1(Form("MultiGaussIsotopeFit_Z=%d", z), this, &KVMultiGaussIsotopeFit::FitFunc, PID_min, PID_max, total_number_parameters(Ngauss)),
107  Z{z},
108  Niso{Ngauss},
109  PIDmin{PID_min}, PIDmax{PID_max},
110  Alist{alist.GetArray()}
111 {
112  // Constructor which can be used with existing fit results (not to perform new fits)
113  //
114  // Use SetGaussianNorm() to set the normalisation parameters for each gaussian
115 
116  SetParameter(0, Niso);
117  SetParameter(fit_param_index::bkg_cst, bkg_cst);
118  SetParameter(fit_param_index::bkg_slp, bkg_slp);
119  SetParameter(fit_param_index::gauss_wid, gaus_wid);
120  SetParameter(fit_param_index::pidvsA_a0, pidvsa_a0);
121  SetParameter(fit_param_index::pidvsA_a1, pidvsa_a1);
122  SetParameter(fit_param_index::pidvsA_a2, pidvsa_a2);
123 
124  SetParName(fit_param_index::bkg_cst, "Norm");
125  SetParName(fit_param_index::bkg_slp, "Bkg. slope");
126  SetParName(fit_param_index::gauss_wid, "Sigma");
127 
128  for (int ig = 1; ig <= Niso; ++ig) {
129  SetParName(get_gauss_norm_index(ig), Form("Norm. A=%d", Alist[ig - 1]));
130  SetParName(get_mass_index(ig, Niso), Form("A_%d", ig));
131  FixParameter(get_mass_index(ig, Niso), Alist[ig - 1]);
132  }
133  for (int i = 0; i < 3; ++i)
134  SetParName(fit_param_index::pidvsA_a0 + i, Form("PIDvsA_a%d", i));
135 
136  SetLineColor(kBlack);
137  SetLineWidth(2);
138  SetNpx(500);
139 }
140 
141 
142 
145 
146 KVMultiGaussIsotopeFit::KVMultiGaussIsotopeFit(int Z, const KVNameValueList& fitparams)
147  : KVMultiGaussIsotopeFit(Z, fitparams.GetIntValue("Ng"), fitparams.GetDoubleValue("PIDmin"),
148  fitparams.GetDoubleValue("PIDmax"), fitparams.GetStringValue("Alist"),
149  fitparams.GetDoubleValue("Bkg_cst"), fitparams.GetDoubleValue("Bkg_slp"),
150  fitparams.GetDoubleValue("GausWid"),
151  fitparams.GetDoubleValue("PIDvsA_a0"),
152  fitparams.GetDoubleValue("PIDvsA_a1"),
153  fitparams.GetDoubleValue("PIDvsA_a2"))
154 {
155  // initialize from previous fit with parameters stored in KVNameValueList
156  for (int ig = 1; ig <= fitparams.GetIntValue("Ng"); ++ig)
157  SetGaussianNorm(ig, fitparams.GetDoubleValue(Form("Norm_%d", ig)));
158 }
159 
160 
161 
164 
165 void KVMultiGaussIsotopeFit::UnDraw(TVirtualPad* pad) const
166 {
167  // Remove the graphical representation of this fit from the given pad
168 
169  auto old_fit = pad->FindObject(get_name_of_multifit(Z));
170  if (old_fit) delete old_fit;
171  for (auto a : Alist) {
172  UnDrawGaussian(Z, a, pad);
173  }
174 }
175 
176 
177 
180 
181 void KVMultiGaussIsotopeFit::DrawFitWithGaussians(Option_t* opt) const
182 {
183  // Draw the overall fit plus the individual gaussians for each isotope
184 
185  DrawCopy(opt)->SetName(get_name_of_multifit(Z));
186 
187  TF1 fgaus("fgaus", "gausn", PIDmin, PIDmax);
188  // give a different colour to each gaussian
189  auto cstep = TColor::GetPalette().GetSize() / (Niso + 1);
190  int ig = 1;
191  for (auto& a : Alist) {
192  fgaus.SetParameters(GetGaussianNorm(ig), GetCentroid(ig), GetGaussianWidth(ig));
193  fgaus.SetNpx(500);
194  fgaus.SetLineColor(TColor::GetPalette()[cstep * ig]);
195  fgaus.SetLineWidth(2);
196  fgaus.SetLineStyle(9);
197  fgaus.DrawCopy("same")->SetName(get_name_of_isotope_gaussian(Z, a));
198  ++ig;
199  }
200 }
201 
202 
203 
214 
215 std::optional<int> KVMultiGaussIsotopeFit::GetMostProbableA(double PID, double& P) const
216 {
217  // For a given PID, calculate the most probable value of \f$A\f$, P is its probability.
218  //
219  // \returns most probable \f$A\f$
220  //
221  // \note The identification functional is fitted to histogrammed data, therefore the resulting
222  // complete function should always evaluate to some finite value \f$\geq 1\f$. If this is not the
223  // case for certain values of PID, it suggests that the 'background' is not well fitted and the
224  // functional cannot be used for identification (gives garbage results). In this case we do not
225  // return a value.
226 
227  auto total = get_total_fit_for_pid(PID);
228  if(!total)
229  return {};
230  std::map<double, int> probabilities;
231  int ig = 1;
232  for (auto& a : Alist) {
233  probabilities[evaluate_gaussian(ig, PID) / *total] = a;
234  ++ig;
235  }
236  // the largest probability is now the last element in the map:
237  // get a reverse iterator to the beginning of the reversed map
238  auto it = probabilities.rbegin();
239  P = it->first;
240  return it->second;
241 }
242 
243 
244 
255 
256 std::optional<double> KVMultiGaussIsotopeFit::GetMeanA(double PID) const
257 {
258  // for a given PID, calculate the mean value of \f$A\f$ from the weighted sum of all gaussians
259  //
260  // \returns mean value of \f$A\f$
261  //
262  // \note The identification functional is fitted to histogrammed data, therefore the resulting
263  // complete function should always evaluate to some finite value \f$\geq 1\f$. If this is not the
264  // case for certain values of PID, it suggests that the 'background' is not well fitted and the
265  // functional cannot be used for identification (gives garbage results). In this case we do not
266  // return a value.
267 
268  auto total = get_total_fit_for_pid(PID);
269  if(!total)
270  return {};
271  int ig = 1;
272  double amean(0), totprob(0);
273  for (auto& a : Alist) {
274  auto weight = evaluate_gaussian(ig, PID) / *total;
275  amean += weight * a;
276  totprob += weight;
277  ++ig;
278  }
279  return totprob > 0 ? amean / totprob : -1.;
280 }
281 
282 
283 
294 
295 std::optional<std::map<int, double>> KVMultiGaussIsotopeFit::GetADistribution(double PID) const
296 {
297  // For the given PID, the map is filled with all possible values of \f$A\f$
298  //
299  // \note The identification functional is fitted to histogrammed data, therefore the resulting
300  // complete function should always evaluate to some finite value \f$\geq 1\f$. If this is not the
301  // case for certain values of PID, it suggests that the 'background' is not well fitted and the
302  // functional cannot be used for identification (gives garbage results). In this case we do not
303  // return a value.
304  //
305  // \returns std::map containing probability distribution \f$P(A|PID)\f$
306 
307  auto total = get_total_fit_for_pid(PID);
308  if(!total)
309  return {};
310  std::map<int, double> Adist;
311  int ig = 1;
312  for (auto& a : Alist) {
313  Adist[a] = evaluate_gaussian(ig, PID) / *total;
314  ++ig;
315  }
316  return Adist;
317 }
318 
319 
320 
341 
342 std::optional<int> KVMultiGaussIsotopeFit::GetA(double PID, double& P) const
343 {
344  // Probabilistic method to determine \f$A\f$ from PID.
345  //
346  // The A returned will be drawn at random from the probability distribution given by the
347  // sum of all gaussians (and the background) for the given PID.
348  //
349  // The result of the draw may be that this PID is part of the background noise:
350  // in this case we do not return a value (returns std::nullopt : returned value will evaluate as false).
351  //
352  // P is the probability of the chosen result.
353  //
354  // \param[in] PID PID value from \f$Z\f$ identification
355  // \param[out] P probablility the returned value of \f$A\f$ is correct
356  // \returns randomly drawn \f$A\f$ for given PID
357  //
358  // \note The identification functional is fitted to histogrammed data, therefore the resulting
359  // complete function should always evaluate to some finite value \f$\geq 1\f$. If this is not the
360  // case for certain values of PID, it suggests that the 'background' is not well fitted and the
361  // functional cannot be used for identification (gives garbage results). In this case we do not
362  // return a value.
363 
364  auto total = get_total_fit_for_pid(PID);
365  if(!total)
366  return {};
367  double p_tot = 0;
368  int ig = 1;
369  auto X = gRandom->Uniform();
370  for (auto& a : Alist) {
371  auto w = evaluate_gaussian(ig, PID) / *total;
372  p_tot += w;
373  if (X < w) {
374  P = w;
375  return a;
376  }
377  X -= w;
378  ++ig;
379  }
380  P = 1. - p_tot;
381  return {}; // background noise
382 }
383 
384 
385 
391 
392 double KVMultiGaussIsotopeFit::GetProbability(int A, double PID) const
393 {
394  // \param[in] A isotope mass number
395  // \param[in] PID value of PID associated with A
396  // \return the probability that A is the correct mass number for a given PID value
397  // \return zero if A is not associated with a Gaussian in the fit
398 
399  auto it = std::find(std::begin(Alist), std::end(Alist), A);
400  if (it != std::end(Alist)) {
401  int ig = std::distance(std::begin(Alist), it);
402  return evaluate_gaussian(ig, PID) / Eval(PID);
403  }
404  return 0;
405 }
406 
407 
408 
409 
410 
413 
414 void KVMultiGaussIsotopeFit::SetFitRange(double min, double max)
415 {
416  // Change range of fit
417 
418  SetRange(min, max);
419  PIDmin = min;
420  PIDmax = max;
421 }
422 
423 
424 
429 
430 void KVMultiGaussIsotopeFit::PositiveBkgSlope(bool yes)
431 {
432  // if yes=true, only explore positive values for background slope
433  //
434  // if yes=false, only explore negative values
435  if(yes)
436  {
437  SetParLimits(fit_param_index::bkg_slp, 0., 5.);
438  SetParameter(fit_param_index::bkg_slp, 1.);
439  }
440  else
441  {
442  SetParLimits(fit_param_index::bkg_slp, -10, 0);
443  SetParameter(fit_param_index::bkg_slp, -2.);
444  }
445 }
446 
447 
449 
const char Option_t
#define X(type, name)
winID w
Option_t Option_t SetLineWidth
Option_t Option_t SetLineColor
R__EXTERN TRandom * gRandom
char * Form(const char *fmt,...)
Function for fitting PID mass spectra.
double centroid_fit(double *x, double *p)
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:85
Int_t GetSize() const
static const TArrayI & GetPalette()
virtual void SetRange(Double_t xmin, Double_t xmax)
virtual void SetParLimits(Int_t ipar, Double_t parmin, Double_t parmax)
virtual TF1 * DrawCopy(Option_t *option="") const
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 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 TObject * FindObject(const char *name) const
virtual Double_t Uniform(Double_t x1, Double_t x2)
double min(double x, double y)
double max(double x, double y)
TArc a
ClassImp(TPyArg)