ATLAS Offline Software
Loading...
Searching...
No Matches
IterativeGaussianFit.cxx
Go to the documentation of this file.
1/*
2 Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration
3*/
4
5#include "dqm_core/AlgorithmConfig.h"
6#include "dqm_core/AlgorithmManager.h"
9#include "ers/ers.h"
10
11#include "TClass.h"
12#include "TObject.h"
13#include "TH1.h"
14#include "TF1.h"
15
16#include <memory>
17#include <iostream>
18#include <cmath>
19
20namespace { // anonymous
21dqm_algorithms::IterativeGaussianFit instance("IterativeGaussianFit"); // global instance to have this algorithm registered with the manager
22}
23
25{
26 dqm_core::AlgorithmManager::instance().registerAlgorithm(m_name, this);
27}
28
33
38
39dqm_core::Result *dqm_algorithms::IterativeGaussianFit::execute(const std::string &name, const TObject &object, const dqm_core::AlgorithmConfig &config)
40{
41 if (!object.IsA()->InheritsFrom("TH1")) { // this could also be TH2 or TH3
42 throw dqm_core::BadConfig(ERS_HERE, name, "histogram does not inherit from TH1");
43 }
44 const TH1 &histogram = dynamic_cast<const TH1 &>(object); // should be possible after the previous check (otherwise we'd get a std::bad_cast)
45 if (histogram.GetDimension() > 1) { // sorry, we only do one-dimensional fits here
46 throw dqm_core::BadConfig(ERS_HERE, name, "histogram has more than one dimension");
47 }
48 const int minstat = dqm_algorithms::tools::GetFirstFromMap("MinStat", config.getParameters(), 1000);
49 if (histogram.GetEffectiveEntries() < minstat) { // you'd better have at least some entries, or the fit may yield strange results
50 dqm_core::Result *result = new dqm_core::Result(dqm_core::Result::Undefined);
51 result->tags_["InsufficientEffectiveEntries"] = histogram.GetEffectiveEntries();
52 return result;
53 }
54 const double sigmaRange = dqm_algorithms::tools::GetFirstFromMap("SigmaRange", config.getParameters()); // mandatory parameter
55 if (sigmaRange <= 0) {
56 throw dqm_core::BadConfig(ERS_HERE, name, "SigmaRange must be greater than zero.");
57 }
58 if (sigmaRange < 1) {
59 ERS_DEBUG(1, "Careful, your SigmaRange = " << sigmaRange << " is rather small"); // or should we just throw an exception?
60 }
61 const double meanNominal = dqm_algorithms::tools::GetFirstFromMap("MeanNominal", config.getParameters(), 0); // optional parameter
62 const double sigmaMax = dqm_algorithms::tools::GetFirstFromMap("SigmaMax", config.getParameters(), 0); // optional parameter
63 if (sigmaMax < 0) {
64 throw dqm_core::BadConfig(ERS_HERE, name, "SigmaMax must be greater than zero."); // zero means unlimited
65 }
66
67 std::unique_ptr<TH1> h(static_cast<TH1 *>(histogram.Clone())); // we need a non-const copy of the object passed to this function
68 // under normal conditions we would have used the copy constructor here, but the one provided by ROOT gives nothing but a segmentation fault
69 auto f = std::make_unique<TF1> ("f", "gaus"); // we use unique_ptrs to avoid cleaning up manually, because this function has some emergency exits
70 if (sigmaMax) {
71 f->SetParLimits(2, 0, sigmaMax); // limit the possible range of sigma to avoid meaningless fit results
72 f->SetParameter(2, sigmaMax / 2); // set some arbitrary initial value that falls into the allowed range, otherwise Minuit will complain
73 f->SetRange(meanNominal - sigmaMax, meanNominal + sigmaMax); // very first guess, let's hope that this is not completely off
74 }
75
76 const unsigned int maxIterations = 10; // don't do more than this number of iterations, otherwise terminate abnormally
77 const double sufficientPrecision = 0.01; // terminate the iteration as soon as the relative change of sigma is less than this
78
79 unsigned int i = 0; // iteration counter
80 while (true) { // don't worry, there's a break down there
81 const double sigmaOld = (i == 0) ? 0 : f->GetParameter(2); // compare old and new sigma to terminate the iteration
82 std::string fitOptions("INQ"); // integrate over each bin, do not draw the function, do not print messages
83 if (i > 0 || sigmaMax) fitOptions.append("R"); // do the initial fit without a range restriction, but only if sigma is not limited
84 if (sigmaMax) fitOptions.append("B"); // if a limit for sigma was set, then use it (but always setting this would not work)
85
86 const int fitStatus = h->Fit(f.get(), fitOptions.c_str()); // you hardly notice you've got an unique_ptr, except in cases like this
87 if (fitStatus) { // see the documentation of TH1::Fit about the meaning of this status flag ... it should be zero if the fit went well
88 ERS_DEBUG(1, "Fit failed in iteration " << i << ", status = " << fitStatus);
89 dqm_core::Result *result = new dqm_core::Result(dqm_core::Result::Red);
90 if (fitStatus < 0) result->status_ = dqm_core::Result::Undefined; // this usually happens because of an empty histogram (normally already caught by MinStat)
91 result->tags_["Iteration"] = i;
92 result->tags_["FitStatus"] = fitStatus;
93 return result;
94 }
95
96 const double mean = f->GetParameter(1);
97 const double sigma = f->GetParameter(2);
98 ERS_DEBUG(2, "Iteration " << i << ": mean = " << mean << ", sigma = " << sigma);
99 f->SetRange(mean - sigmaRange * sigma, mean + sigmaRange * sigma); // this is the important feature!
100
101 if (sigma == 0) { // maybe this is not really fatal, but at least it shows that something strange is going on
102 ERS_DEBUG(1, "Fit yielded sigma = 0 in iteration " << i);
103 dqm_core::Result *result = new dqm_core::Result(dqm_core::Result::Red);
104 result->tags_["Iteration"] = i;
105 result->tags_["Mean"] = mean;
106 result->tags_["Sigma"] = sigma;
107 return result;
108 } // at least we're now sure that we can safely divide by sigma
109
110 if (sigma == sigmaMax) { // we've hit the parameter limit, so the fit cannot yield sensible results
111 ERS_DEBUG(1, "Fit result for sigma is at the limit " << sigmaMax);
112 dqm_core::Result *result = new dqm_core::Result(dqm_core::Result::Red);
113 result->tags_["Iteration"] = i;
114 result->tags_["Sigma"] = sigma;
115 result->tags_["SigmaMax"] = sigmaMax;
116 return result;
117 }
118
119 if ((std::fabs(sigma - sigmaOld) / sigma) < sufficientPrecision) break; // this is good enough for us
120
121 if (i >= maxIterations) { // stop here, but we should have reached the break (above) much earlier
122 ERS_DEBUG(1, "Sorry, this is not taking us anywhere: the iteration does not seem to converge");
123 dqm_core::Result *result = new dqm_core::Result(dqm_core::Result::Undefined); // may sometimes happen by chance, especially if the distribution is rather narrow
124 result->tags_["Iteration"] = i;
125 result->tags_["SigmaOld"] = sigmaOld;
126 result->tags_["SigmaNew"] = sigma;
127 return result;
128 }
129 ++i; // next iteration
130 } // while
131
132 const int ndf = f->GetNDF();
133 const double chi2 = f->GetChisquare();
134 const double chi2NDF = (ndf > 0) ? (chi2 / ndf) : 0; // prevent fits with no degrees of freedom from returning NaN or INF.
135
136 std::map<std::string, double> resultValues; // all elements in this map will be published, and they can also be used as thresholds
137 resultValues["Constant"] = f->GetParameter(0); // hm, does the constant tell us anything?
138 resultValues["ConstantError"] = f->GetParError(0); // and does anybody care about the errors of the fit parameters?
139 resultValues["Mean"] = f->GetParameter(1);
140 resultValues["MeanError"] = f->GetParError(1);
141 resultValues["Sigma"] = f->GetParameter(2);
142 resultValues["SigmaError"] = f->GetParError(2);
143 resultValues["Chi2NDF"] = chi2NDF; // for the sake of completeness
144 resultValues["Probability"] = f->GetProb(); // maybe somebody wants to set a threshold on this?
145 resultValues["MeanDeviation"] = std::fabs(f->GetParameter(1) - meanNominal); // useful to detect shifts from the expected value
146 return dqm_algorithms::tools::MakeComparisons(resultValues, config.getGreenThresholds(), config.getRedThresholds()); // this function does all the magic
147}
148
150{
151 out << m_name << ": Applies a Gaussian fit over a limited fit range.\n"
152 " The fit range is iteratively determined in terms of Gaussian sigmas on each side of the mean.\n"
153 " The algorithm stops if sigma doesn't change anymore, or after a maximum number of iterations.\n"
154 "Mandatory parameter: SigmaRange - over plus/minus how many sigmas the fit should be applied.\n"
155 "Optional parameter: MeanNominal - the expected mean value, used for calculating MeanDeviation (default 0)\n"
156 "Optional parameter: SigmaMax - limit the maximum possible fit result for sigma (default unlimited)\n"
157 "Optional parameter: MinStat - minimum number of entries required in the histogram (default 1000).\n"
158 "Returned values: Constant, Mean, and Sigma of the fitted Gaussian including their errors, Chi2NDF and Probability.\n"
159 " MeanDeviation - the absolute difference of Mean and MeanNominal, useful for detecting shifts.\n"
160 "Thresholds can be set on any of the returned values, or a combination thereof." << std::endl;
161 // xmin/xmax parameters wouldn't make sense, because it's a feature of this algorithm to determine these values itself
162}
std::map< std::string, double > instance
std::string histogram
Definition chains.cxx:52
Header file for AthHistogramAlgorithm.
virtual dqm_core::Result * execute(const std::string &name, const TObject &object, const dqm_core::AlgorithmConfig &config)
virtual void printDescription(std::ostream &out)
virtual IterativeGaussianFit * clone()
double chi2(TH1 *h0, TH1 *h1)
void mean(std::vector< double > &bins, std::vector< double > &values, const std::vector< std::string > &files, const std::string &histname, const std::string &tplotname, const std::string &label="")
double GetFirstFromMap(const std::string &paramName, const std::map< std::string, double > &params)
dqm_core::Result * MakeComparisons(const std::map< std::string, double > &algparams, const std::map< std::string, double > &gthreshold, const std::map< std::string, double > &rthreshold)
#define IsA
Declare the TObject style functions.