ATLAS Offline Software
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 
20 namespace { // anonymous
21 dqm_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 
30 {
31  /* noop */
32 }
33 
35 {
36  return new IterativeGaussianFit(m_name); // somebody else must delete this
37 }
38 
39 dqm_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
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);
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);
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);
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 }
covarianceTool.ndf
ndf
Definition: covarianceTool.py:678
Undefined
@ Undefined
Definition: MaterialTypes.h:8
pdg_comparison.sigma
sigma
Definition: pdg_comparison.py:324
mean
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="")
Definition: dependence.cxx:254
get_generator_info.result
result
Definition: get_generator_info.py:21
IsA
#define IsA
Declare the TObject style functions.
Definition: xAODTEventBranch.h:59
python.AthDsoLogger.out
out
Definition: AthDsoLogger.py:71
dqm_algorithms::IterativeGaussianFit::execute
virtual dqm_core::Result * execute(const std::string &name, const TObject &object, const dqm_core::AlgorithmConfig &config)
Definition: IterativeGaussianFit.cxx:39
dqm_algorithms::IterativeGaussianFit::m_name
const std::string m_name
Definition: IterativeGaussianFit.h:31
dqm_algorithms::IterativeGaussianFit::clone
virtual IterativeGaussianFit * clone()
Definition: IterativeGaussianFit.cxx:34
dqm_algorithms::IterativeGaussianFit
Definition: IterativeGaussianFit.h:19
config
Definition: PhysicsAnalysis/AnalysisCommon/AssociationUtils/python/config.py:1
dqm_algorithms::IterativeGaussianFit::~IterativeGaussianFit
virtual ~IterativeGaussianFit()
Definition: IterativeGaussianFit.cxx:29
instance
std::map< std::string, double > instance
Definition: Run_To_Get_Tags.h:8
IterativeGaussianFit.h
lumiFormat.i
int i
Definition: lumiFormat.py:85
Result
ICscStripFitter::Result Result
Definition: CalibCscStripFitter.cxx:13
extractSporadic.h
list h
Definition: extractSporadic.py:97
dqm_algorithms::tools::MakeComparisons
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)
Definition: AlgorithmHelper.cxx:60
chi2
double chi2(TH1 *h0, TH1 *h1)
Definition: comparitor.cxx:523
hist_file_dump.f
f
Definition: hist_file_dump.py:135
python.handimod.Red
Red
Definition: handimod.py:551
name
std::string name
Definition: Control/AthContainers/Root/debug.cxx:228
dqm_algorithms::IterativeGaussianFit::IterativeGaussianFit
IterativeGaussianFit(const std::string &name)
Definition: IterativeGaussianFit.cxx:24
h
AlgorithmHelper.h
dqm_algorithms::IterativeGaussianFit::printDescription
virtual void printDescription(std::ostream &out)
Definition: IterativeGaussianFit.cxx:149
pickleTool.object
object
Definition: pickleTool.py:30
dqm_algorithms::tools::GetFirstFromMap
double GetFirstFromMap(const std::string &paramName, const std::map< std::string, double > &params)
Definition: AlgorithmHelper.cxx:339
histogram
std::string histogram
Definition: chains.cxx:52