Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
FitHelpers.cxx
Go to the documentation of this file.
1/*
2 * Project: RooFit
3 * Authors:
4 * Jonas Rembser, CERN 2023
5 *
6 * Copyright (c) 2023, CERN
7 *
8 * Redistribution and use in source and binary forms,
9 * with or without modification, are permitted according to the terms
10 * listed in LICENSE (http://roofit.sourceforge.net/license.txt)
11 */
12
13#include "FitHelpers.h"
14
15#include <RooAbsData.h>
16#include <RooAbsPdf.h>
17#include <RooAbsReal.h>
18#include <RooCmdConfig.h>
19#include <RooDerivative.h>
20#include <RooFitResult.h>
21#include <RooLinkedList.h>
22#include <RooMinimizer.h>
23#include <RooRealVar.h>
24
25#include <Math/CholeskyDecomp.h>
26
27namespace RooFit {
28namespace FitHelpers {
29
30////////////////////////////////////////////////////////////////////////////////
31/// Use the asymptotically correct approach to estimate errors in the presence of weights.
32/// This is slower but more accurate than `SumW2Error`. See also https://arxiv.org/abs/1911.01303).
33/// Applies the calculated covaraince matrix to the RooMinimizer and returns
34/// the quality of the covariance matrix.
35/// See also the documentation of RooAbsPdf::fitTo(), where this function is used.
36/// \param[in] minimizer The RooMinimizer to get the fit result from. The state
37/// of the minimizer will be altered by this function: the covariance
38/// matrix caltulated here will be applied to it via
39/// RooMinimizer::applyCovarianceMatrix().
40/// \param[in] data The dataset that was used for the fit.
42{
43 // Calculated corrected errors for weighted likelihood fits
44 std::unique_ptr<RooFitResult> rw(minimizer.save());
45 // Weighted inverse Hessian matrix
46 const TMatrixDSym &matV = rw->covarianceMatrix();
47 oocoutI(&pdf, Fitting)
48 << "RooAbsPdf::fitTo(" << pdf.GetName()
49 << ") Calculating covariance matrix according to the asymptotically correct approach. If you find this "
50 "method useful please consider citing https://arxiv.org/abs/1911.01303.\n";
51
52 // Initialise matrix containing first derivatives
53 auto nFloatPars = rw->floatParsFinal().getSize();
54 TMatrixDSym num(nFloatPars);
55 for (int k = 0; k < nFloatPars; k++) {
56 for (int l = 0; l < nFloatPars; l++) {
57 num(k, l) = 0.0;
58 }
59 }
60 RooArgSet obs;
61 pdf.getObservables(data.get(), obs);
62 // Create derivative objects
63 std::vector<std::unique_ptr<RooDerivative>> derivatives;
64 const RooArgList &floated = rw->floatParsFinal();
65 std::unique_ptr<RooArgSet> floatingparams{
66 static_cast<RooArgSet *>(pdf.getParameters(data)->selectByAttrib("Constant", false))};
67 for (const auto paramresult : floated) {
68 auto paraminternal = static_cast<RooRealVar *>(floatingparams->find(*paramresult));
69 assert(floatingparams->find(*paramresult)->IsA() == RooRealVar::Class());
70 derivatives.emplace_back(pdf.derivative(*paraminternal, obs, 1));
71 }
72
73 // Loop over data
74 for (int j = 0; j < data.numEntries(); j++) {
75 // Sets obs to current data point, this is where the pdf will be evaluated
76 obs.assign(*data.get(j));
77 // Determine first derivatives
78 std::vector<double> diffs(floated.getSize(), 0.0);
79 for (int k = 0; k < floated.getSize(); k++) {
80 const auto paramresult = static_cast<RooRealVar *>(floated.at(k));
81 auto paraminternal = static_cast<RooRealVar *>(floatingparams->find(*paramresult));
82 // first derivative to parameter k at best estimate point for this measurement
83 double diff = derivatives[k]->getVal();
84 // need to reset to best fit point after differentiation
85 *paraminternal = paramresult->getVal();
86 diffs[k] = diff;
87 }
88 // Fill numerator matrix
89 double prob = pdf.getVal(&obs);
90 for (int k = 0; k < floated.getSize(); k++) {
91 for (int l = 0; l < floated.getSize(); l++) {
92 num(k, l) += data.weightSquared() * diffs[k] * diffs[l] / (prob * prob);
93 }
94 }
95 }
96 num.Similarity(matV);
97
98 // Propagate corrected errors to parameters objects
99 minimizer.applyCovarianceMatrix(num);
100
101 // The derivatives are found in RooFit and not with the minimizer (e.g.
102 // minuit), so the quality of the corrected covariance matrix corresponds to
103 // the quality of the original covariance matrix
104 return rw->covQual();
105}
106
107////////////////////////////////////////////////////////////////////////////////
108/// Apply correction to errors and covariance matrix. This uses two covariance
109/// matrices, one with the weights, the other with squared weights, to obtain
110/// the correct errors for weighted likelihood fits.
111/// Applies the calculated covaraince matrix to the RooMinimizer and returns
112/// the quality of the covariance matrix.
113/// See also the documentation of RooAbsPdf::fitTo(), where this function is used.
114/// \param[in] minimizer The RooMinimizer to get the fit result from. The state
115/// of the minimizer will be altered by this function: the covariance
116/// matrix caltulated here will be applied to it via
117/// RooMinimizer::applyCovarianceMatrix().
118/// \param[in] nll The NLL object that was used for the fit.
119int calcSumW2CorrectedCovariance(RooAbsReal const &pdf, RooMinimizer &minimizer, RooAbsReal &nll)
120{
121 // Calculated corrected errors for weighted likelihood fits
122 std::unique_ptr<RooFitResult> rw{minimizer.save()};
123 nll.applyWeightSquared(true);
124 oocoutI(&pdf, Fitting) << "RooAbsPdf::fitTo(" << pdf.GetName()
125 << ") Calculating sum-of-weights-squared correction matrix for covariance matrix\n";
126 minimizer.hesse();
127 std::unique_ptr<RooFitResult> rw2{minimizer.save()};
128 nll.applyWeightSquared(false);
129
130 // Apply correction matrix
131 const TMatrixDSym &matV = rw->covarianceMatrix();
132 TMatrixDSym matC = rw2->covarianceMatrix();
134 if (!decomp) {
135 oocoutE(&pdf, Fitting) << "RooAbsPdf::fitTo(" << pdf.GetName()
136 << ") ERROR: Cannot apply sum-of-weights correction to covariance matrix: correction "
137 "matrix calculated with weight-squared is singular\n";
138 return -1;
139 }
140
141 // replace C by its inverse
142 decomp.Invert(matC);
143 // the class lies about the matrix being symmetric, so fill in the
144 // part above the diagonal
145 for (int i = 0; i < matC.GetNrows(); ++i) {
146 for (int j = 0; j < i; ++j) {
147 matC(j, i) = matC(i, j);
148 }
149 }
150 matC.Similarity(matV);
151 // C now contains V C^-1 V
152 // Propagate corrected errors to parameters objects
153 minimizer.applyCovarianceMatrix(matC);
154
155 return std::min(rw->covQual(), rw2->covQual());
156}
157
158namespace {
159
160/// Configuration struct for RooAbsPdf::minimizeNLL with all the default values
161/// that also should be taked as the default values for RooAbsPdf::fitTo.
162struct MinimizerConfig {
163 double recoverFromNaN = 10.;
164 int optConst = 2;
165 int verbose = 0;
166 int doSave = 0;
167 int doTimer = 0;
168 int printLevel = 1;
169 int strategy = 1;
170 int initHesse = 0;
171 int hesse = 1;
172 int minos = 0;
173 int numee = 10;
174 int doEEWall = 1;
175 int doWarn = 1;
176 int doSumW2 = -1;
177 int doAsymptotic = -1;
178 int maxCalls = -1;
179 int doOffset = -1;
180 int parallelize = 0;
181 bool enableParallelGradient = true;
182 bool enableParallelDescent = false;
183 bool timingAnalysis = false;
184 const RooArgSet *minosSet = nullptr;
185 std::string minType;
186 std::string minAlg = "minuit";
187};
188
189} // namespace
190
192{
193 // Default-initialized instance of MinimizerConfig to get the default
194 // minimizer parameter values.
195 MinimizerConfig minimizerDefaults;
196
197 pc.defineDouble("RecoverFromUndefinedRegions", "RecoverFromUndefinedRegions", 0, minimizerDefaults.recoverFromNaN);
198 pc.defineInt("optConst", "Optimize", 0, minimizerDefaults.optConst);
199 pc.defineInt("verbose", "Verbose", 0, minimizerDefaults.verbose);
200 pc.defineInt("doSave", "Save", 0, minimizerDefaults.doSave);
201 pc.defineInt("doTimer", "Timer", 0, minimizerDefaults.doTimer);
202 pc.defineInt("printLevel", "PrintLevel", 0, minimizerDefaults.printLevel);
203 pc.defineInt("strategy", "Strategy", 0, minimizerDefaults.strategy);
204 pc.defineInt("initHesse", "InitialHesse", 0, minimizerDefaults.initHesse);
205 pc.defineInt("hesse", "Hesse", 0, minimizerDefaults.hesse);
206 pc.defineInt("minos", "Minos", 0, minimizerDefaults.minos);
207 pc.defineInt("numee", "PrintEvalErrors", 0, minimizerDefaults.numee);
208 pc.defineInt("doEEWall", "EvalErrorWall", 0, minimizerDefaults.doEEWall);
209 pc.defineInt("doWarn", "Warnings", 0, minimizerDefaults.doWarn);
210 pc.defineInt("doSumW2", "SumW2Error", 0, minimizerDefaults.doSumW2);
211 pc.defineInt("doAsymptoticError", "AsymptoticError", 0, minimizerDefaults.doAsymptotic);
212 pc.defineInt("maxCalls", "MaxCalls", 0, minimizerDefaults.maxCalls);
213 pc.defineInt("doOffset", "OffsetLikelihood", 0, 0);
214 pc.defineInt("parallelize", "Parallelize", 0, 0); // Three parallelize arguments
215 pc.defineInt("enableParallelGradient", "ParallelGradientOptions", 0, 0);
216 pc.defineInt("enableParallelDescent", "ParallelDescentOptions", 0, 0);
217 pc.defineInt("timingAnalysis", "TimingAnalysis", 0, 0);
218 pc.defineString("mintype", "Minimizer", 0, minimizerDefaults.minType.c_str());
219 pc.defineString("minalg", "Minimizer", 1, minimizerDefaults.minAlg.c_str());
220 pc.defineSet("minosSet", "Minos", 0, minimizerDefaults.minosSet);
221}
222
223////////////////////////////////////////////////////////////////////////////////
224/// Minimizes a given NLL variable by finding the optimal parameters with the
225/// RooMinimzer. The NLL variable can be created with RooAbsPdf::createNLL.
226/// If you are looking for a function that combines likelihood creation with
227/// fitting, see RooAbsPdf::fitTo.
228/// \param[in] nll The negative log-likelihood variable to minimize.
229/// \param[in] data The dataset that was also used for the NLL. It's a necessary
230/// parameter because it is used in the asymptotic error correction.
231/// \param[in] cfg Configuration struct with all the configuration options for
232/// the RooMinimizer. These are a subset of the options that you can
233/// also pass to RooAbsPdf::fitTo via the RooFit command arguments.
234std::unique_ptr<RooFitResult> minimize(RooAbsReal &pdf, RooAbsReal &nll, RooAbsData const &data, RooCmdConfig const &pc)
235{
236 MinimizerConfig cfg;
237 cfg.recoverFromNaN = pc.getDouble("RecoverFromUndefinedRegions");
238 cfg.optConst = pc.getInt("optConst");
239 cfg.verbose = pc.getInt("verbose");
240 cfg.doSave = pc.getInt("doSave");
241 cfg.doTimer = pc.getInt("doTimer");
242 cfg.printLevel = pc.getInt("printLevel");
243 cfg.strategy = pc.getInt("strategy");
244 cfg.initHesse = pc.getInt("initHesse");
245 cfg.hesse = pc.getInt("hesse");
246 cfg.minos = pc.getInt("minos");
247 cfg.numee = pc.getInt("numee");
248 cfg.doEEWall = pc.getInt("doEEWall");
249 cfg.doWarn = pc.getInt("doWarn");
250 cfg.doSumW2 = pc.getInt("doSumW2");
251 cfg.doAsymptotic = pc.getInt("doAsymptoticError");
252 cfg.maxCalls = pc.getInt("maxCalls");
253 cfg.minosSet = pc.getSet("minosSet");
254 cfg.minType = pc.getString("mintype", "");
255 cfg.minAlg = pc.getString("minalg", "minuit");
256 cfg.doOffset = pc.getInt("doOffset");
257 cfg.parallelize = pc.getInt("parallelize");
258 cfg.enableParallelGradient = pc.getInt("enableParallelGradient");
259 cfg.enableParallelDescent = pc.getInt("enableParallelDescent");
260 cfg.timingAnalysis = pc.getInt("timingAnalysis");
261
262 // Determine if the dataset has weights
263 bool weightedData = data.isNonPoissonWeighted();
264
265 std::string msgPrefix = std::string{"RooAbsPdf::fitTo("} + pdf.GetName() + "): ";
266
267 // Warn user that a method to determine parameter uncertainties should be provided if weighted data is offered
268 if (weightedData && cfg.doSumW2 == -1 && cfg.doAsymptotic == -1) {
269 oocoutW(&pdf, InputArguments) << msgPrefix <<
270 R"(WARNING: a likelihood fit is requested of what appears to be weighted data.
271 While the estimated values of the parameters will always be calculated taking the weights into account,
272 there are multiple ways to estimate the errors of the parameters. You are advised to make an
273 explicit choice for the error calculation:
274 - Either provide SumW2Error(true), to calculate a sum-of-weights-corrected HESSE error matrix
275 (error will be proportional to the number of events in MC).
276 - Or provide SumW2Error(false), to return errors from original HESSE error matrix
277 (which will be proportional to the sum of the weights, i.e., a dataset with <sum of weights> events).
278 - Or provide AsymptoticError(true), to use the asymptotically correct expression
279 (for details see https://arxiv.org/abs/1911.01303)."
280)";
281 }
282
283 if (cfg.minos && (cfg.doSumW2 == 1 || cfg.doAsymptotic == 1)) {
285 << msgPrefix
286 << " sum-of-weights and asymptotic error correction do not work with MINOS errors. Not fitting.\n";
287 return nullptr;
288 }
289 if (cfg.doAsymptotic == 1 && cfg.minos) {
290 oocoutW(&pdf, InputArguments) << msgPrefix << "WARNING: asymptotic correction does not apply to MINOS errors\n";
291 }
292
293 // avoid setting both SumW2 and Asymptotic for uncertainty correction
294 if (cfg.doSumW2 == 1 && cfg.doAsymptotic == 1) {
295 oocoutE(&pdf, InputArguments) << msgPrefix
296 << "ERROR: Cannot compute both asymptotically correct and SumW2 errors.\n";
297 return nullptr;
298 }
299
300 // Instantiate RooMinimizer
301 RooMinimizer::Config minimizerConfig;
302 minimizerConfig.enableParallelGradient = cfg.enableParallelGradient;
303 minimizerConfig.enableParallelDescent = cfg.enableParallelDescent;
304 minimizerConfig.parallelize = cfg.parallelize;
305 minimizerConfig.timingAnalysis = cfg.timingAnalysis;
306 minimizerConfig.offsetting = cfg.doOffset;
307 RooMinimizer m(nll, minimizerConfig);
308
309 m.setMinimizerType(cfg.minType);
310 m.setEvalErrorWall(cfg.doEEWall);
311 m.setRecoverFromNaNStrength(cfg.recoverFromNaN);
312 m.setPrintEvalErrors(cfg.numee);
313 if (cfg.maxCalls > 0)
314 m.setMaxFunctionCalls(cfg.maxCalls);
315 if (cfg.printLevel != 1)
316 m.setPrintLevel(cfg.printLevel);
317 if (cfg.optConst)
318 m.optimizeConst(cfg.optConst); // Activate constant term optimization
319 if (cfg.verbose)
320 m.setVerbose(true); // Activate verbose options
321 if (cfg.doTimer)
322 m.setProfile(true); // Activate timer options
323 if (cfg.strategy != 1)
324 m.setStrategy(cfg.strategy); // Modify fit strategy
325 if (cfg.initHesse)
326 m.hesse(); // Initialize errors with hesse
327 m.minimize(cfg.minType.c_str(), cfg.minAlg.c_str()); // Minimize using chosen algorithm
328 if (cfg.hesse)
329 m.hesse(); // Evaluate errors with Hesse
330
331 int corrCovQual = -1;
332
333 if (m.getNPar() > 0) {
334 if (cfg.doAsymptotic == 1)
335 corrCovQual = RooFit::FitHelpers::calcAsymptoticCorrectedCovariance(pdf, m, data); // Asymptotically correct
336 if (cfg.doSumW2 == 1)
338 }
339
340 if (cfg.minos)
341 cfg.minosSet ? m.minos(*cfg.minosSet) : m.minos(); // Evaluate errs with Minos
342
343 // Optionally return fit result
344 std::unique_ptr<RooFitResult> ret;
345 if (cfg.doSave) {
346 auto name = std::string("fitresult_") + pdf.GetName() + "_" + data.GetName();
347 auto title = std::string("Result of fit of p.d.f. ") + pdf.GetName() + " to dataset " + data.GetName();
348 ret = std::unique_ptr<RooFitResult>{m.save(name.c_str(), title.c_str())};
349 if ((cfg.doSumW2 == 1 || cfg.doAsymptotic == 1) && m.getNPar() > 0)
350 ret->setCovQual(corrCovQual);
351 }
352
353 if (cfg.optConst)
354 m.optimizeConst(0);
355 return ret;
356}
357
358} // namespace FitHelpers
359} // namespace RooFit
header file containing the templated implementation of matrix inversion routines for use with ROOT's ...
#define oocoutW(o, a)
#define oocoutE(o, a)
#define oocoutI(o, a)
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void data
char name[80]
Definition TGX11.cxx:110
class to compute the Cholesky decomposition of a matrix
RooFit::OwningPtr< RooArgSet > getParameters(const RooAbsData *data, bool stripDisconnected=true) const
Create a list of leaf nodes in the arg tree starting with ourself as top node that don't match any of...
RooFit::OwningPtr< RooArgSet > getObservables(const RooArgSet &set, bool valueOnly=true) const
Given a set of possible observables, return the observables that this PDF depends on.
virtual void applyWeightSquared(bool flag)
Disables or enables the usage of squared weights.
Int_t getSize() const
Return the number of elements in the collection.
void assign(const RooAbsCollection &other) const
Sets the value, cache and constant attribute of any argument in our set that also appears in the othe...
Abstract base class for binned and unbinned datasets.
Definition RooAbsData.h:57
Abstract base class for objects that represent a real value and implements functionality common to al...
Definition RooAbsReal.h:59
double getVal(const RooArgSet *normalisationSet=nullptr) const
Evaluate object.
Definition RooAbsReal.h:103
RooDerivative * derivative(RooRealVar &obs, Int_t order=1, double eps=0.001)
Return function representing first, second or third order derivative of this function.
RooArgList is a container object that can hold multiple RooAbsArg objects.
Definition RooArgList.h:22
RooAbsArg * at(Int_t idx) const
Return object at given index, or nullptr if index is out of range.
Definition RooArgList.h:110
RooArgSet is a container object that can hold multiple RooAbsArg objects.
Definition RooArgSet.h:55
Class RooCmdConfig is a configurable parser for RooCmdArg named arguments.
double getDouble(const char *name, double defaultValue=0.0) const
Return double property registered with name 'name'.
bool defineDouble(const char *name, const char *argName, int doubleNum, double defValue=0.0)
Define double property name 'name' mapped to double in slot 'doubleNum' in RooCmdArg with name argNam...
RooArgSet * getSet(const char *name, RooArgSet *set=nullptr) const
Return RooArgSet property registered with name 'name'.
bool defineSet(const char *name, const char *argName, int setNum, const RooArgSet *set=nullptr)
Define TObject property name 'name' mapped to object in slot 'setNum' in RooCmdArg with name argName ...
const char * getString(const char *name, const char *defaultValue="", bool convEmptyToNull=false) const
Return string property registered with name 'name'.
bool defineString(const char *name, const char *argName, int stringNum, const char *defValue="", bool appendMode=false)
Define double property name 'name' mapped to double in slot 'stringNum' in RooCmdArg with name argNam...
bool defineInt(const char *name, const char *argName, int intNum, int defValue=0)
Define integer property name 'name' mapped to integer in slot 'intNum' in RooCmdArg with name argName...
int getInt(const char *name, int defaultValue=0) const
Return integer property registered with name 'name'.
RooMinimizer is a wrapper class around ROOT::Fit:Fitter that provides a seamless interface between th...
RooFit::OwningPtr< RooFitResult > save(const char *name=nullptr, const char *title=nullptr)
Save and return a RooFitResult snapshot of current minimizer status.
int hesse()
Execute HESSE.
void applyCovarianceMatrix(TMatrixDSym const &V)
Apply results of given external covariance matrix.
RooRealVar represents a variable that can be changed from the outside.
Definition RooRealVar.h:37
static TClass * Class()
Int_t GetNrows() const
TMatrixTSym< Element > & Similarity(const TMatrixT< Element > &n)
Calculate B * (*this) * B^T , final matrix will be (nrowsb x nrowsb) This is a similarity transform w...
const char * GetName() const override
Returns name of object.
Definition TNamed.h:47
void defineMinimizationOptions(RooCmdConfig &pc)
std::unique_ptr< RooFitResult > minimize(RooAbsReal &model, RooAbsReal &nll, RooAbsData const &data, RooCmdConfig const &pc)
int calcSumW2CorrectedCovariance(RooAbsPdf const &pdf, RooMinimizer &minimizer, RooAbsReal &nll)
int calcAsymptoticCorrectedCovariance(RooAbsPdf &pdf, RooMinimizer &minimizer, RooAbsData const &data)
The namespace RooFit contains mostly switches that change the behaviour of functions of PDFs (or othe...
Definition JSONIO.h:26
@ InputArguments
Config argument to RooMinimizer ctor.
TMarker m
Definition textangle.C:8
TLine l
Definition textangle.C:4