Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
FitHelpers.cxx
Go to the documentation of this file.
1/// \cond ROOFIT_INTERNAL
2
3/*
4 * Project: RooFit
5 * Authors:
6 * Jonas Rembser, CERN 2023
7 *
8 * Copyright (c) 2023, CERN
9 *
10 * Redistribution and use in source and binary forms,
11 * with or without modification, are permitted according to the terms
12 * listed in LICENSE (http://roofit.sourceforge.net/license.txt)
13 */
14
15#include "FitHelpers.h"
16
17#include <RooAbsData.h>
18#include <RooAbsPdf.h>
19#include <RooAbsReal.h>
20#include <RooAddition.h>
21#include <RooBatchCompute.h>
22#include <RooBinSamplingPdf.h>
23#include <RooCategory.h>
24#include <RooCmdConfig.h>
25#include <RooConstraintSum.h>
26#include <RooDataHist.h>
27#include <RooDataSet.h>
28#include <RooDerivative.h>
29#include <RooFit/Evaluator.h>
32#include <RooFitResult.h>
33#include <RooLinkedList.h>
34#include <RooMinimizer.h>
35#include <RooConstVar.h>
36#include <RooRealVar.h>
37#include <RooSimultaneous.h>
38#include <RooFormulaVar.h>
39
40#include <Math/CholeskyDecomp.h>
41#include <Math/Util.h>
42
43#include "ConstraintHelpers.h"
44#include "RooEvaluatorWrapper.h"
45#include "RooFitImplHelpers.h"
47
48#ifdef ROOFIT_LEGACY_EVAL_BACKEND
49#include "RooChi2Var.h"
50#include "RooNLLVar.h"
51
52#ifdef ROOFIT_MULTIPROCESS
54#endif
55#endif
56
57using RooFit::Detail::RooNLLVarNew;
58
59namespace {
60
61constexpr int extendedFitDefault = 2;
62
63#ifdef ROOFIT_LEGACY_EVAL_BACKEND
64/// Print a deprecation warning when the legacy evaluation backend is selected for a fit.
66{
67 oocoutW(&topLevelArg, InputArguments)
68 << "The legacy evaluation backend is deprecated and will be removed in ROOT 6.44.\n"
69 "Please use the default \"cpu\" evaluation backend instead, i.e., don't pass RooFit::EvalBackend(\"legacy\")\n"
70 "or RooFit::BatchMode(\"off\") anymore. If the default backend does not work for your use case, please\n"
71 "report it by opening an issue on the ROOT GitHub repository."
72 << std::endl;
73}
74#endif
75
76////////////////////////////////////////////////////////////////////////////////
77/// Use the asymptotically correct approach to estimate errors in the presence of weights.
78/// This is slower but more accurate than `SumW2Error`. See also https://arxiv.org/abs/1911.01303).
79/// Applies the calculated covaraince matrix to the RooMinimizer and returns
80/// the quality of the covariance matrix.
81/// See also the documentation of RooAbsPdf::fitTo(), where this function is used.
82/// \param[in] minimizer The RooMinimizer to get the fit result from. The state
83/// of the minimizer will be altered by this function: the covariance
84/// matrix caltulated here will be applied to it via
85/// RooMinimizer::applyCovarianceMatrix().
86/// \param[in] data The dataset that was used for the fit.
88{
89 RooFormulaVar logpdf("logpdf", "log(pdf)", "log(@0)", pdf);
90 RooArgSet obs;
91 logpdf.getObservables(data.get(), obs);
92
93 // Warning if the dataset is binned. TODO: in some cases,
94 // people also use RooDataSet to encode binned data,
95 // e.g. for simultaneous fits. It would be useful to detect
96 // this in this future as well.
97 if (dynamic_cast<RooDataHist const *>(&data)) {
98 oocoutW(&pdf, InputArguments)
99 << "RooAbsPdf::fitTo(" << pdf.GetName()
100 << ") WARNING: Asymptotic error correction is requested for a binned data set. "
101 "This method is not designed to handle binned data. A standard chi2 fit will likely be more suitable.";
102 };
103
104 // Calculated corrected errors for weighted likelihood fits
105 std::unique_ptr<RooFitResult> rw(minimizer.save());
106 // Weighted inverse Hessian matrix
107 const TMatrixDSym &matV = rw->covarianceMatrix();
108 oocoutI(&pdf, Fitting)
109 << "RooAbsPdf::fitTo(" << pdf.GetName()
110 << ") Calculating covariance matrix according to the asymptotically correct approach. If you find this "
111 "method useful please consider citing https://arxiv.org/abs/1911.01303.\n";
112
113 // Initialise matrix containing first derivatives
114 int nFloatPars = rw->floatParsFinal().size();
116 for (int k = 0; k < nFloatPars; k++) {
117 for (int l = 0; l < nFloatPars; l++) {
118 num(k, l) = 0.0;
119 }
120 }
121
122 // Create derivative objects
123 std::vector<std::unique_ptr<RooDerivative>> derivatives;
124 const RooArgList &floated = rw->floatParsFinal();
126 logpdf.getParameters(data.get(), allparams);
127 std::unique_ptr<RooArgSet> floatingparams{allparams.selectByAttrib("Constant", false)};
128
129 const double eps = 1.0e-4;
130
131 // Calculate derivatives of logpdf
132 for (const auto paramresult : floated) {
133 auto paraminternal = static_cast<RooRealVar *>(floatingparams->find(*paramresult));
134 assert(floatingparams->find(*paramresult)->IsA() == RooRealVar::Class());
135 double error = static_cast<RooRealVar *>(paramresult)->getError();
136 derivatives.emplace_back(logpdf.derivative(*paraminternal, obs, 1, eps * error));
137 }
138
139 // Calculate derivatives for number of expected events, needed for extended ML fit
140 RooAbsPdf *extended_pdf = dynamic_cast<RooAbsPdf *>(&pdf);
141 std::vector<double> diffs_expected(floated.size(), 0.0);
142 if (extended_pdf && extended_pdf->expectedEvents(obs) != 0.0) {
143 for (std::size_t k = 0; k < floated.size(); k++) {
144 const auto paramresult = static_cast<RooRealVar *>(floated.at(k));
145 auto paraminternal = static_cast<RooRealVar *>(floatingparams->find(*paramresult));
146
147 *paraminternal = paramresult->getVal();
148 double error = paramresult->getError();
149 paraminternal->setVal(paramresult->getVal() + eps * error);
150 double expected_plus = log(extended_pdf->expectedEvents(obs));
151 paraminternal->setVal(paramresult->getVal() - eps * error);
152 double expected_minus = log(extended_pdf->expectedEvents(obs));
153 *paraminternal = paramresult->getVal();
154 double diff = (expected_plus - expected_minus) / (2.0 * eps * error);
155 diffs_expected[k] = diff;
156 }
157 }
158
159 // Loop over data
160 for (int j = 0; j < data.numEntries(); j++) {
161 // Sets obs to current data point, this is where the pdf will be evaluated
162 obs.assign(*data.get(j));
163 // Determine first derivatives
164 std::vector<double> diffs(floated.size(), 0.0);
165 for (std::size_t k = 0; k < floated.size(); k++) {
166 const auto paramresult = static_cast<RooRealVar *>(floated.at(k));
167 auto paraminternal = static_cast<RooRealVar *>(floatingparams->find(*paramresult));
168 // first derivative to parameter k at best estimate point for this measurement
169 double diff = derivatives[k]->getVal();
170 // need to reset to best fit point after differentiation
171 *paraminternal = paramresult->getVal();
172 diffs[k] = diff;
173 }
174
175 // Fill numerator matrix
176 for (std::size_t k = 0; k < floated.size(); k++) {
177 for (std::size_t l = 0; l < floated.size(); l++) {
178 num(k, l) += data.weightSquared() * (diffs[k] + diffs_expected[k]) * (diffs[l] + diffs_expected[l]);
179 }
180 }
181 }
182 num.Similarity(matV);
183
184 // Propagate corrected errors to parameters objects
185 minimizer.applyCovarianceMatrix(num);
186
187 // The derivatives are found in RooFit and not with the minimizer (e.g.
188 // minuit), so the quality of the corrected covariance matrix corresponds to
189 // the quality of the original covariance matrix
190 return rw->covQual();
191}
192
193////////////////////////////////////////////////////////////////////////////////
194/// Apply correction to errors and covariance matrix. This uses two covariance
195/// matrices, one with the weights, the other with squared weights, to obtain
196/// the correct errors for weighted likelihood fits.
197/// Applies the calculated covaraince matrix to the RooMinimizer and returns
198/// the quality of the covariance matrix.
199/// See also the documentation of RooAbsPdf::fitTo(), where this function is used.
200/// \param[in] minimizer The RooMinimizer to get the fit result from. The state
201/// of the minimizer will be altered by this function: the covariance
202/// matrix caltulated here will be applied to it via
203/// RooMinimizer::applyCovarianceMatrix().
204/// \param[in] nll The NLL object that was used for the fit.
205int calcSumW2CorrectedCovariance(RooAbsReal const &pdf, RooMinimizer &minimizer, RooAbsReal &nll)
206{
207 // Calculated corrected errors for weighted likelihood fits
208 std::unique_ptr<RooFitResult> rw{minimizer.save()};
209 nll.applyWeightSquared(true);
210 oocoutI(&pdf, Fitting) << "RooAbsPdf::fitTo(" << pdf.GetName()
211 << ") Calculating sum-of-weights-squared correction matrix for covariance matrix\n";
212 minimizer.hesse();
213 std::unique_ptr<RooFitResult> rw2{minimizer.save()};
214 nll.applyWeightSquared(false);
215
216 // Apply correction matrix
217 const TMatrixDSym &matV = rw->covarianceMatrix();
218 TMatrixDSym matC = rw2->covarianceMatrix();
220 if (!decomp) {
221 oocoutE(&pdf, Fitting) << "RooAbsPdf::fitTo(" << pdf.GetName()
222 << ") ERROR: Cannot apply sum-of-weights correction to covariance matrix: correction "
223 "matrix calculated with weight-squared is singular\n";
224 return -1;
225 }
226
227 // replace C by its inverse
228 decomp.Invert(matC);
229 // the class lies about the matrix being symmetric, so fill in the
230 // part above the diagonal
231 for (int i = 0; i < matC.GetNrows(); ++i) {
232 for (int j = 0; j < i; ++j) {
233 matC(j, i) = matC(i, j);
234 }
235 }
236 matC.Similarity(matV);
237 // C now contains V C^-1 V
238 // Propagate corrected errors to parameters objects
239 minimizer.applyCovarianceMatrix(matC);
240
241 return std::min(rw->covQual(), rw2->covQual());
242}
243
244/// Configuration struct for RooAbsPdf::minimizeNLL with all the default values
245/// that also should be taken as the default values for RooAbsPdf::fitTo.
246struct MinimizerConfig {
247 double recoverFromNaN = 10.;
248 int verbose = 0;
249 int doSave = 0;
250 int doTimer = 0;
251 int printLevel = 1;
252 int strategy = 1;
253 int initHesse = 0;
254 int hesse = 1;
255 int minos = 0;
256 int numee = 10;
257 int doEEWall = 1;
258 int doWarn = 1;
259 int doSumW2 = -1;
260 int doAsymptotic = -1;
261 int maxCalls = -1;
262 int doOffset = -1;
263 int parallelize = 0;
264 bool enableParallelGradient = false;
265 bool enableParallelDescent = false;
268 bool timingAnalysis = false;
269 const RooArgSet *minosSet = nullptr;
270 std::string minType;
271 std::string minAlg = "minuit";
272};
273
274/// Validates the NumCPU() argument for the non-legacy evaluation backends and
275/// returns the effective number of worker threads for the RooFit::Evaluator.
277{
279
280 if (numCpu <= 1) {
281 return 1;
282 }
283 if (evalBackend == Value::Cuda) {
284 oocoutW(&arg, Fitting) << "The NumCPU() option is ignored by the CUDA evaluation backend." << std::endl;
285 return 1;
286 }
287 if (evalBackend == Value::CodegenNoGrad) {
288 oocoutW(&arg, Fitting) << "The NumCPU() option has no effect with EvalBackend(\"codegen_no_grad\"), "
289 "because the test statistic is evaluated with single-threaded generated code."
290 << std::endl;
291 return 1;
292 }
293 if (evalBackend == Value::Codegen) {
294 oocxcoutI(&arg, Fitting) << "NumCPU(" << numCpu << ") enables multi-threaded evaluation of large batches "
295 << "for test statistic values. The generated gradient code is not affected."
296 << std::endl;
297 return numCpu;
298 }
299 oocxcoutI(&arg, Fitting) << "NumCPU(" << numCpu << ") enables multi-threaded evaluation of large batches in "
300 << "the RooBatchCompute library. The interleaving strategy argument of NumCPU() is "
301 << "ignored." << std::endl;
302 return numCpu;
303}
304
306{
307 // Process automatic extended option
310 if (ext) {
311 oocoutI(&pdf, Minimization)
312 << "p.d.f. provides expected number of events, including extended term in likelihood." << std::endl;
313 }
314 return ext;
315 }
316 // If Extended(false) was explicitly set, but the pdf MUST be extended, then
317 // it's time to print an error. This happens when you're fitting a RooAddPdf
318 // with coefficient that represent yields, and without the additional
319 // constraint these coefficients are degenerate because the RooAddPdf
320 // normalizes itself. Nothing correct can come out of this.
321 if (extendedCmdArg == 0) {
323 std::string errMsg = "You used the Extended(false) option on a pdf where the fit MUST be extended! "
324 "The parameters are not well defined and you're getting nonsensical results.";
325 oocoutE(&pdf, InputArguments) << errMsg << std::endl;
326 }
327 }
328 return extendedCmdArg;
329}
330
331/// To set the fitrange attribute of the PDF and custom ranges for the
332/// observables so that RooPlot can automatically plot the fitting range.
333void resetFitrangeAttributes(RooAbsArg &pdf, RooAbsData const &data, std::string const &baseName, const char *rangeName,
334 bool splitRange)
335{
336 // Clear possible range attributes from previous fits.
337 pdf.removeStringAttribute("fitrange");
338
339 // No fitrange was specified, so we do nothing. Or "SplitRange" is used, and
340 // then there are no uniquely defined ranges for the observables (as they
341 // are different in each category).
342 if (!rangeName || splitRange)
343 return;
344
345 RooArgSet observables;
346 pdf.getObservables(data.get(), observables);
347
348 std::string fitrangeValue;
349 auto subranges = ROOT::Split(rangeName, ",");
350 for (auto const &subrange : subranges) {
351 if (subrange.empty())
352 continue;
353 std::string fitrangeValueSubrange = std::string("fit_") + baseName;
354 if (subranges.size() > 1) {
356 }
358 for (RooAbsArg *arg : observables) {
359
360 if (arg->isCategory())
361 continue;
362 auto &observable = static_cast<RooRealVar &>(*arg);
363
364 observable.setRange(fitrangeValueSubrange.c_str(), observable.getMin(subrange.c_str()),
365 observable.getMax(subrange.c_str()));
366 }
367 }
368 pdf.setStringAttribute("fitrange", fitrangeValue.substr(0, fitrangeValue.size() - 1).c_str());
369}
370
371/// Iterate the simultaneous pdf's categories and build one test-statistic term
372/// per channel via `makeTerm`. Channels excluded by the (optional) category
373/// range are skipped. The per-channel term's special variables are prefixed
374/// with `_<catName>_`. The terms are summed into a RooAddition named
375/// `combinedName`.
376template <typename TermFactory>
377std::unique_ptr<RooAddition> createSimultaneousStat(RooSimultaneous const &simPdf, std::string const &rangeName,
378 std::string const &combinedName, TermFactory &&makeTerm)
379{
380 RooAbsCategoryLValue const &simCat = simPdf.indexCat();
381
382 RooArgList terms;
383 for (auto const &catState : simCat) {
384 std::string const &catName = catState.first;
386
387 // Skip channels excluded by a category range (only RooCategory supports
388 // ranges on categorical values).
389 if (!rangeName.empty()) {
390 auto simCatAsRooCategory = dynamic_cast<RooCategory const *>(&simCat);
391 if (simCatAsRooCategory && !simCatAsRooCategory->isStateInRange(rangeName.c_str(), catIndex)) {
392 continue;
393 }
394 }
395
396 RooAbsPdf *channelPdf = simPdf.getPdf(catName.c_str());
397 if (!channelPdf) {
398 continue;
399 }
400 std::unique_ptr<RooArgSet> observables{
401 std::unique_ptr<RooArgSet>(channelPdf->getVariables())->selectByAttrib("__obs__", true)};
402 std::unique_ptr<RooNLLVarNew> term = makeTerm(*channelPdf, *observables);
403 term->setPrefix(std::string("_") + catName + "_");
404 terms.addOwned(std::move(term));
405 }
406
407 auto combined = std::make_unique<RooAddition>(combinedName.c_str(), combinedName.c_str(), terms);
408 combined->addOwnedComponents(std::move(terms));
409 return combined;
410}
411
412std::unique_ptr<RooAbsArg> createSimultaneousChi2(RooSimultaneous const &simPdf, std::string const &rangeName,
414{
415 auto chi2 =
416 createSimultaneousStat(simPdf, rangeName, "simChi2", [&](RooAbsPdf &channelPdf, RooArgSet const &observables) {
417 RooNLLVarNew::Config cfg;
418 cfg.statistic = RooNLLVarNew::Statistic::Chi2;
419 cfg.extended = isSimPdfExtended && channelPdf.extendMode() != RooAbsPdf::CanNotBeExtended;
420 cfg.chi2ErrorType = etype;
421 auto name = std::string("chi2_") + channelPdf.GetName();
422 return std::make_unique<RooNLLVarNew>(name.c_str(), name.c_str(), channelPdf, observables, cfg);
423 });
424 // Flag the top node so RooEvaluatorWrapper knows not to skip zero-weight bins
425 chi2->setAttribute("Chi2EvaluationActive");
426 return chi2;
427}
428
429std::unique_ptr<RooAbsArg> createSimultaneousNLL(RooSimultaneous const &simPdf, bool isSimPdfExtended,
430 std::string const &rangeName, RooFit::OffsetMode offset)
431{
432 auto nll =
433 createSimultaneousStat(simPdf, rangeName, "mynll", [&](RooAbsPdf &channelPdf, RooArgSet const &observables) {
434 RooNLLVarNew::Config cfg;
435 // Only request extended NLLs for channels that can be extended.
436 cfg.extended = isSimPdfExtended && channelPdf.extendMode() != RooAbsPdf::CanNotBeExtended;
437 cfg.offsetMode = offset;
438 auto name = std::string("nll_") + channelPdf.GetName();
439 return std::make_unique<RooNLLVarNew>(name.c_str(), name.c_str(), channelPdf, observables, cfg);
440 });
441
442 const int simCount = nll->list().size();
443 for (auto *child : static_range_cast<RooNLLVarNew *>(nll->list())) {
444 child->setSimCount(simCount);
445 }
446 return nll;
447}
448
449/// Apply the `IntegrateBins` precision to either a single pdf or in-place to
450/// the component pdfs of a RooSimultaneous. Newly-allocated wrapper pdfs are
451/// appended to `ownedOut`. The returned reference points either at one of
452/// those wrappers or at the input pdf itself.
454{
455 if (auto *simPdf = dynamic_cast<RooSimultaneous *>(&pdf)) {
456 simPdf->wrapPdfsInBinSamplingPdfs(data, precision);
457 return pdf;
458 }
459 if (std::unique_ptr<RooAbsPdf> wrapped = RooBinSamplingPdf::create(pdf, data, precision)) {
461 ownedOut.addOwned(std::move(wrapped));
462 return ref;
463 }
464 return pdf;
465}
466
467/// RAII helper for the `setNormRange` + SplitRange/RangeName attribute setting
468/// that must be done around `compileForNormSet` so that the compiled pdf sees
469/// the restricted normalization range. All mutations are reverted on
470/// destruction.
471class NormRangeScope {
472public:
473 NormRangeScope(RooAbsPdf &pdf, const char *rangeName, bool splitRange) : _pdf{&pdf}
474 {
475 if (pdf.normRange()) {
476 _oldNormRange = pdf.normRange();
477 }
479 pdf.setAttribute("SplitRange", splitRange);
480 pdf.setStringAttribute("RangeName", rangeName);
481 }
482 NormRangeScope(NormRangeScope const &) = delete;
483 NormRangeScope &operator=(NormRangeScope const &) = delete;
485 {
486 _pdf->setAttribute("SplitRange", false);
487 _pdf->setStringAttribute("RangeName", nullptr);
488 _pdf->setNormRange(_oldNormRange.empty() ? nullptr : _oldNormRange.c_str());
489 }
490
491private:
492 RooAbsPdf *_pdf;
493 std::string _oldNormRange;
494};
495
496/// Shared `compileForNormSet` sequence used by both the NLL and chi2 CPU
497/// backends. Sets up the reduced normalization range via `NormRangeScope`,
498/// then runs the graph compilation. When `likelihoodMode` is true the
499/// CompileContext is flagged accordingly (enabling binned-likelihood
500/// optimisations in the compiled pdf). The returned compiled pdf has
501/// `fixAddCoefRange` already applied when `addCoefRangeName` is non-empty.
502std::unique_ptr<RooAbsPdf> compilePdfForFit(RooAbsPdf &pdf, RooArgSet const &normSet, const char *rangeName,
503 bool splitRange, const char *addCoefRangeName, bool likelihoodMode)
504{
506
508 ctx.setLikelihoodMode(likelihoodMode);
509 std::unique_ptr<RooAbsArg> head = pdf.compileForNormSet(normSet, ctx);
510 std::unique_ptr<RooAbsPdf> pdfClone{&dynamic_cast<RooAbsPdf &>(*head.release())};
511
513 pdfClone->fixAddCoefRange(addCoefRangeName, false);
514 }
515 return pdfClone;
516}
517
518std::unique_ptr<RooAbsReal> createNLLNew(RooAbsPdf &pdf, RooAbsData &data, std::unique_ptr<RooAbsReal> &&constraints,
519 std::string const &rangeName, RooArgSet const &projDeps, bool isExtended,
521{
522 if (constraints) {
523 // The computation graph for the constraints is very small, no need to do
524 // the tracking of clean and dirty nodes here.
525 constraints->setOperMode(RooAbsArg::ADirty);
526 }
527
528 RooArgSet observables;
529 pdf.getObservables(data.get(), observables);
530 observables.remove(projDeps, true, true);
531
532 oocxcoutI(&pdf, Fitting) << "RooAbsPdf::fitTo(" << pdf.GetName()
533 << ") fixing normalization set for coefficient determination to observables in data"
534 << "\n";
535 pdf.fixAddCoefNormalization(observables, false);
536
539
541 auto *simPdf = dynamic_cast<RooSimultaneous *>(&finalPdf);
542 // A RooSimultaneous whose index category is not among the data columns is
543 // a "switch" pdf selecting the component given by the current index state
544 // (analogous to RooMultiPdf): there are no channels to split the NLL into,
545 // so it is treated like an ordinary pdf.
546 if (simPdf && simPdf->indexCatIsObservable(*data.get())) {
547 nllTerms.addOwned(createSimultaneousNLL(*simPdf, isExtended, rangeName, offset));
548 } else {
549 RooNLLVarNew::Config cfg;
550 cfg.extended = isExtended;
551 cfg.offsetMode = offset;
552 nllTerms.addOwned(std::make_unique<RooNLLVarNew>("RooNLLVarNew", "RooNLLVarNew", finalPdf, observables, cfg));
553 }
554 if (constraints) {
555 nllTerms.addOwned(std::move(constraints));
556 }
557
558 std::string nllName = std::string("nll_") + pdf.GetName() + "_" + data.GetName();
559 auto nll = std::make_unique<RooAddition>(nllName.c_str(), nllName.c_str(), nllTerms);
560 nll->addOwnedComponents(std::move(binSamplingPdfs));
561 nll->addOwnedComponents(std::move(nllTerms));
562
563 return nll;
564}
565
566} // namespace
567
568namespace RooFit::FitHelpers {
569
571{
572 // Default-initialized instance of MinimizerConfig to get the default
573 // minimizer parameter values.
575
576 pc.defineDouble("RecoverFromUndefinedRegions", "RecoverFromUndefinedRegions", 0, minimizerDefaults.recoverFromNaN);
577 pc.defineInt("verbose", "Verbose", 0, minimizerDefaults.verbose);
578 pc.defineInt("doSave", "Save", 0, minimizerDefaults.doSave);
579 pc.defineInt("doTimer", "Timer", 0, minimizerDefaults.doTimer);
580 pc.defineInt("printLevel", "PrintLevel", 0, minimizerDefaults.printLevel);
581 pc.defineInt("strategy", "Strategy", 0, minimizerDefaults.strategy);
582 pc.defineInt("initHesse", "InitialHesse", 0, minimizerDefaults.initHesse);
583 pc.defineInt("hesse", "Hesse", 0, minimizerDefaults.hesse);
584 pc.defineInt("minos", "Minos", 0, minimizerDefaults.minos);
585 pc.defineInt("numee", "PrintEvalErrors", 0, minimizerDefaults.numee);
586 pc.defineInt("doEEWall", "EvalErrorWall", 0, minimizerDefaults.doEEWall);
587 pc.defineInt("doWarn", "Warnings", 0, minimizerDefaults.doWarn);
588 pc.defineInt("doSumW2", "SumW2Error", 0, minimizerDefaults.doSumW2);
589 pc.defineInt("doAsymptoticError", "AsymptoticError", 0, minimizerDefaults.doAsymptotic);
590 pc.defineInt("maxCalls", "MaxCalls", 0, minimizerDefaults.maxCalls);
591 pc.defineInt("doOffset", "OffsetLikelihood", 0, minimizerDefaults.doOffset);
592 pc.defineInt("parallelize", "Parallelize", 0, minimizerDefaults.parallelize); // Three parallelize arguments
593 pc.defineInt("enableParallelGradient", "ParallelGradientOptions", 0, minimizerDefaults.enableParallelGradient);
594 pc.defineInt("enableParallelDescent", "ParallelDescentOptions", 0, minimizerDefaults.enableParallelDescent);
595 pc.defineInt("parallelDescentNumSplits", "ParallelDescentOptions", 1, minimizerDefaults.parallelDescentNumSplits);
596 pc.defineInt("parallelDescentSplitStrategy", "ParallelDescentOptions", 2,
597 minimizerDefaults.parallelDescentSplitStrategy);
598 pc.defineInt("timingAnalysis", "TimingAnalysis", 0, minimizerDefaults.timingAnalysis);
599 pc.defineString("mintype", "Minimizer", 0, minimizerDefaults.minType.c_str());
600 pc.defineString("minalg", "Minimizer", 1, minimizerDefaults.minAlg.c_str());
601 pc.defineSet("minosSet", "Minos", 0, minimizerDefaults.minosSet);
602}
603
604////////////////////////////////////////////////////////////////////////////////
605/// Minimizes a given NLL variable by finding the optimal parameters with the
606/// RooMinimzer. The NLL variable can be created with RooAbsPdf::createNLL.
607/// If you are looking for a function that combines likelihood creation with
608/// fitting, see RooAbsPdf::fitTo.
609/// \param[in] nll The negative log-likelihood variable to minimize.
610/// \param[in] data The dataset that was also used for the NLL. It's a necessary
611/// parameter because it is used in the asymptotic error correction.
612/// \param[in] cfg Configuration struct with all the configuration options for
613/// the RooMinimizer. These are a subset of the options that you can
614/// also pass to RooAbsPdf::fitTo via the RooFit command arguments.
615std::unique_ptr<RooFitResult> minimize(RooAbsReal &pdf, RooAbsReal &nll, RooAbsData const &data, RooCmdConfig const &pc)
616{
617 MinimizerConfig cfg;
618 cfg.recoverFromNaN = pc.getDouble("RecoverFromUndefinedRegions");
619 cfg.verbose = pc.getInt("verbose");
620 cfg.doSave = pc.getInt("doSave");
621 cfg.doTimer = pc.getInt("doTimer");
622 cfg.printLevel = pc.getInt("printLevel");
623 cfg.strategy = pc.getInt("strategy");
624 cfg.initHesse = pc.getInt("initHesse");
625 cfg.hesse = pc.getInt("hesse");
626 cfg.minos = pc.getInt("minos");
627 cfg.numee = pc.getInt("numee");
628 cfg.doEEWall = pc.getInt("doEEWall");
629 cfg.doWarn = pc.getInt("doWarn");
630 cfg.doSumW2 = pc.getInt("doSumW2");
631 cfg.doAsymptotic = pc.getInt("doAsymptoticError");
632 cfg.maxCalls = pc.getInt("maxCalls");
633 cfg.minosSet = pc.getSet("minosSet");
634 cfg.minType = pc.getString("mintype", "");
635 cfg.minAlg = pc.getString("minalg", "minuit");
636 cfg.doOffset = pc.getInt("doOffset");
637 cfg.parallelize = pc.getInt("parallelize");
638 cfg.enableParallelGradient = pc.getInt("enableParallelGradient");
639 cfg.enableParallelDescent = pc.getInt("enableParallelDescent");
640 cfg.parallelDescentNumSplits = pc.getInt("parallelDescentNumSplits");
641 cfg.parallelDescentSplitStrategy = pc.getInt("parallelDescentSplitStrategy");
642 cfg.timingAnalysis = pc.getInt("timingAnalysis");
643
644 // Determine if the dataset has weights
645 bool weightedData = data.isNonPoissonWeighted();
646
647 // The weighted-data uncertainty correction options only apply to likelihood
648 // fits. Skip the NLL-only paths when minimizing a chi-squared test statistic.
649 const bool isChi2 = nll.getAttribute("Chi2EvaluationActive");
650
651 std::string msgPrefix = std::string{"RooAbsPdf::fitTo("} + pdf.GetName() + "): ";
652
653 // Warn user that a method to determine parameter uncertainties should be provided if weighted data is offered
654 if (!isChi2 && weightedData && cfg.doSumW2 == -1 && cfg.doAsymptotic == -1) {
655 oocoutW(&pdf, InputArguments) << msgPrefix <<
656 R"(WARNING: a likelihood fit is requested of what appears to be weighted data.
657 While the estimated values of the parameters will always be calculated taking the weights into account,
658 there are multiple ways to estimate the errors of the parameters. You are advised to make an
659 explicit choice for the error calculation:
660 - Either provide SumW2Error(true), to calculate a sum-of-weights-corrected HESSE error matrix
661 (error will be proportional to the number of events in MC).
662 - Or provide SumW2Error(false), to return errors from original HESSE error matrix
663 (which will be proportional to the sum of the weights, i.e., a dataset with <sum of weights> events).
664 - Or provide AsymptoticError(true), to use the asymptotically correct expression
665 (for details see https://arxiv.org/abs/1911.01303)."
666)";
667 }
668
669 if (cfg.minos && (cfg.doSumW2 == 1 || cfg.doAsymptotic == 1)) {
670 oocoutE(&pdf, InputArguments)
671 << msgPrefix
672 << " sum-of-weights and asymptotic error correction do not work with MINOS errors. Not fitting.\n";
673 return nullptr;
674 }
675 if (cfg.doAsymptotic == 1 && cfg.minos) {
676 oocoutW(&pdf, InputArguments) << msgPrefix << "WARNING: asymptotic correction does not apply to MINOS errors\n";
677 }
678
679 // avoid setting both SumW2 and Asymptotic for uncertainty correction
680 if (cfg.doSumW2 == 1 && cfg.doAsymptotic == 1) {
681 oocoutE(&pdf, InputArguments) << msgPrefix
682 << "ERROR: Cannot compute both asymptotically correct and SumW2 errors.\n";
683 return nullptr;
684 }
685
686 // Apply the experimental likelihood-splitting settings from
687 // ParallelDescentOptions(). A numSplits value of zero keeps the automatic
688 // task-splitting defaults of RooFit::MultiProcess.
689 if (cfg.parallelDescentNumSplits > 0) {
690#ifdef ROOFIT_MULTIPROCESS
691 if (cfg.parallelDescentSplitStrategy == 0) {
693 } else {
695 }
696#else
697 oocoutW(&pdf, InputArguments) << "Likelihood-splitting settings passed via ParallelDescentOptions() are "
698 "ignored, because ROOT was built without RooFit::MultiProcess support"
699 << std::endl;
700#endif
701 }
702
703 // Instantiate RooMinimizer
705 minimizerConfig.enableParallelGradient = cfg.enableParallelGradient;
706 minimizerConfig.enableParallelDescent = cfg.enableParallelDescent;
707 minimizerConfig.parallelize = cfg.parallelize;
708 minimizerConfig.timingAnalysis = cfg.timingAnalysis;
709 minimizerConfig.offsetting = cfg.doOffset;
711
712 m.setMinimizerType(cfg.minType);
713 m.setEvalErrorWall(cfg.doEEWall);
714 m.setRecoverFromNaNStrength(cfg.recoverFromNaN);
715 m.setPrintEvalErrors(cfg.numee);
716 if (cfg.maxCalls > 0)
717 m.setMaxFunctionCalls(cfg.maxCalls);
718 if (cfg.printLevel != 1)
719 m.setPrintLevel(cfg.printLevel);
720 if (cfg.verbose)
721 m.setVerbose(true); // Activate verbose options
722 if (cfg.doTimer)
723 m.setProfile(true); // Activate timer options
724 if (cfg.strategy != 1)
725 m.setStrategy(cfg.strategy); // Modify fit strategy
726 if (cfg.initHesse)
727 m.hesse(); // Initialize errors with hesse
728 m.minimize(cfg.minType.c_str(), cfg.minAlg.c_str()); // Minimize using chosen algorithm
729 if (cfg.hesse)
730 m.hesse(); // Evaluate errors with Hesse
731
732 int corrCovQual = -1;
733
734 if (!isChi2 && m.getNPar() > 0) {
735 if (cfg.doAsymptotic == 1)
736 corrCovQual = calcAsymptoticCorrectedCovariance(pdf, m, data); // Asymptotically correct
737 if (cfg.doSumW2 == 1)
739 }
740
741 if (cfg.minos)
742 cfg.minosSet ? m.minos(*cfg.minosSet) : m.minos(); // Evaluate errs with Minos
743
744 // Optionally return fit result
745 std::unique_ptr<RooFitResult> ret;
746 if (cfg.doSave) {
747 auto name = std::string("fitresult_") + pdf.GetName() + "_" + data.GetName();
748 auto title = std::string("Result of fit of p.d.f. ") + pdf.GetName() + " to dataset " + data.GetName();
749 ret = std::unique_ptr<RooFitResult>{m.save(name.c_str(), title.c_str())};
750 if ((cfg.doSumW2 == 1 || cfg.doAsymptotic == 1) && m.getNPar() > 0)
751 ret->setCovQual(corrCovQual);
752 }
753
754 return ret;
755}
756
757std::unique_ptr<RooAbsReal> createNLL(RooAbsPdf &pdf, RooAbsData &data, const RooLinkedList &cmdList)
758{
759 auto timingScope = std::make_unique<ROOT::Math::Util::TimingScope>(
760 [&pdf](std::string const &msg) { oocoutI(&pdf, Fitting) << msg << std::endl; }, "Creation of NLL object took");
761
762 auto baseName = std::string("nll_") + pdf.GetName() + "_" + data.GetName();
763
764 // Select the pdf-specific commands
765 RooCmdConfig pc("RooAbsPdf::createNLL(" + std::string(pdf.GetName()) + ")");
766
767 pc.defineString("rangeName", "RangeWithName", 0, "", true);
768 pc.defineString("addCoefRange", "SumCoefRange", 0, "");
769 pc.defineString("globstag", "GlobalObservablesTag", 0, "");
770 pc.defineString("globssource", "GlobalObservablesSource", 0, "data");
771 pc.defineDouble("rangeLo", "Range", 0, -999.);
772 pc.defineDouble("rangeHi", "Range", 1, -999.);
773 pc.defineInt("splitRange", "SplitRange", 0, 0);
774 pc.defineInt("ext", "Extended", 0, extendedFitDefault);
775 pc.defineInt("numcpu", "NumCPU", 0, 1);
776 pc.defineInt("interleave", "NumCPU", 1, 0);
777 pc.defineInt("verbose", "Verbose", 0, 0);
778 pc.defineInt("cloneData", "CloneData", 0, 0);
779 pc.defineSet("projDepSet", "ProjectedObservables", 0, nullptr);
780 pc.defineSet("cPars", "Constrain", 0, nullptr);
781 pc.defineSet("glObs", "GlobalObservables", 0, nullptr);
782 pc.defineInt("doOffset", "OffsetLikelihood", 0, 0);
783 pc.defineSet("extCons", "ExternalConstraints", 0, nullptr);
784 pc.defineInt("EvalBackend", "EvalBackend", 0, static_cast<int>(RooFit::EvalBackend::defaultValue()));
785 pc.defineDouble("IntegrateBins", "IntegrateBins", 0, -1.);
786 pc.defineMutex("Range", "RangeWithName");
787 pc.defineMutex("GlobalObservables", "GlobalObservablesTag");
788 pc.defineInt("ModularL", "ModularL", 0, 0);
789
790 // New style likelihoods define parallelization through Parallelize(...) on fitTo or attributes on
791 // RooMinimizer::Config.
792 pc.defineMutex("ModularL", "NumCPU");
793
794 // New style likelihoods define offsetting on minimizer, not on likelihood
795 pc.defineMutex("ModularL", "OffsetLikelihood");
796
797 // Process and check varargs
798 pc.process(cmdList);
799 if (!pc.ok(true)) {
800 return nullptr;
801 }
802
803 if (pc.getInt("ModularL")) {
804 int lut[3] = {2, 1, 0};
806 static_cast<RooFit::TestStatistics::RooAbsL::Extended>(lut[pc.getInt("ext")])};
807
811
812 if (auto tmp = pc.getSet("cPars"))
813 cParsSet.add(*tmp);
814
815 if (auto tmp = pc.getSet("extCons"))
816 extConsSet.add(*tmp);
817
818 if (auto tmp = pc.getSet("glObs"))
819 glObsSet.add(*tmp);
820
821 const std::string rangeName = pc.getString("globstag", "", false);
822
824 builder.Extended(ext)
825 .ConstrainedParameters(cParsSet)
826 .ExternalConstraints(extConsSet)
827 .GlobalObservables(glObsSet)
828 .GlobalObservablesTag(rangeName.c_str());
829
830 return std::make_unique<RooFit::TestStatistics::RooRealL>("likelihood", "", builder.build());
831 }
832
833 // Decode command line arguments
834 const char *rangeName = pc.getString("rangeName", nullptr, true);
835 const char *addCoefRangeName = pc.getString("addCoefRange", nullptr, true);
836 const bool ext = interpretExtendedCmdArg(pdf, pc.getInt("ext"));
837
838 int splitRange = pc.getInt("splitRange");
839 int cloneData = pc.getInt("cloneData");
840 auto offset = static_cast<RooFit::OffsetMode>(pc.getInt("doOffset"));
841
842 if (pc.hasProcessed("Range")) {
843 double rangeLo = pc.getDouble("rangeLo");
844 double rangeHi = pc.getDouble("rangeHi");
845
846 // Create range with name 'fit' with above limits on all observables
847 RooArgSet obs;
848 pdf.getObservables(data.get(), obs);
849 for (auto *rrv : dynamic_range_cast<RooRealVar *>(obs)) {
850 if (rrv)
851 rrv->setRange("fit", rangeLo, rangeHi);
852 }
853
854 // Set range name to be fitted to "fit"
855 rangeName = "fit";
856 }
857
858 // Set the fitrange attribute of th PDF, add observables ranges for plotting
860
861 RooArgSet projDeps;
862 auto tmp = pc.getSet("projDepSet");
863 if (tmp) {
864 projDeps.add(*tmp);
865 }
866
867 const std::string globalObservablesSource = pc.getString("globssource", "data", false);
868 if (globalObservablesSource != "data" && globalObservablesSource != "model") {
869 std::string errMsg = "RooAbsPdf::fitTo: GlobalObservablesSource can only be \"data\" or \"model\"!";
870 oocoutE(&pdf, InputArguments) << errMsg << std::endl;
871 throw std::invalid_argument(errMsg);
872 }
874
875 // Lambda function to create the correct constraint term for a PDF. In old
876 // RooFit, we use this PDF itself as the argument, for the new BatchMode
877 // we're passing a clone.
878 auto createConstr = [&]() -> std::unique_ptr<RooAbsReal> {
879 return createConstraintTerm(baseName + "_constr", // name
880 pdf, // pdf
881 data, // data
882 pc.getSet("cPars"), // Constrain RooCmdArg
883 pc.getSet("extCons"), // ExternalConstraints RooCmdArg
884 pc.getSet("glObs"), // GlobalObservables RooCmdArg
885 pc.getString("globstag", nullptr, true), // GlobalObservablesTag RooCmdArg
886 takeGlobalObservablesFromData); // From GlobalObservablesSource RooCmdArg
887 };
888
889 auto evalBackend = static_cast<RooFit::EvalBackend::Value>(pc.getInt("EvalBackend"));
890
891 // Construct BatchModeNLL if requested
893
895 pdf.getObservables(data.get(), normSet);
896
897 auto *simPdfForProjDeps = dynamic_cast<RooSimultaneous const *>(&pdf);
898 if (simPdfForProjDeps && simPdfForProjDeps->indexCatIsObservable(normSet)) {
899 for (auto i : projDeps) {
900 auto res = normSet.find(i->GetName());
901 if (res != nullptr) {
902 res->setAttribute("__conditional__");
903 }
904 }
905 } else {
906 normSet.remove(projDeps);
907 }
908
909 std::unique_ptr<RooAbsPdf> pdfClone =
910 compilePdfForFit(pdf, normSet, rangeName, splitRange, addCoefRangeName, /*likelihoodMode=*/true);
911
912 if (addCoefRangeName) {
913 oocxcoutI(&pdf, Fitting) << "RooAbsPdf::fitTo(" << pdf.GetName()
914 << ") fixing interpretation of coefficients of any component to range "
915 << addCoefRangeName << "\n";
916 }
917
918 std::unique_ptr<RooAbsReal> compiledConstr;
919 if (std::unique_ptr<RooAbsReal> constr = createConstr()) {
921 compiledConstr->addOwnedComponents(std::move(constr));
922 }
923
924 auto nll = createNLLNew(*pdfClone, data, std::move(compiledConstr), rangeName ? rangeName : "", projDeps, ext,
925 pc.getDouble("IntegrateBins"), offset);
926
927 const double correction = pdfClone->getCorrection();
928
929 if (correction > 0) {
930 oocoutI(&pdf, Fitting) << "[FitHelpers] Detected correction term from RooAbsPdf::getCorrection(). "
931 << "Adding penalty to NLL." << std::endl;
932
933 // Convert the multiplicative correction to an additive term in -log L
934 auto penaltyTerm = std::make_unique<RooConstVar>((baseName + "_Penalty").c_str(),
935 "Penalty term from getCorrection()", correction);
936
937 // add penalty and NLL
938 auto correctedNLL = std::make_unique<RooAddition>((baseName + "_corrected").c_str(), "NLL + penalty",
940
941 // transfer ownership of terms
942 correctedNLL->addOwnedComponents(std::move(nll), std::move(penaltyTerm));
943 nll = std::move(correctedNLL);
944 }
945
946 const int nWorkers = effectiveNumWorkers(pdf, evalBackend, pc.getInt("numcpu"));
947
948 auto nllWrapper = std::make_unique<RooFit::Experimental::RooEvaluatorWrapper>(
951
952 // We destroy the timing scrope for createNLL prematurely, because we
953 // separately measure the time for jitting and gradient creation
954 // inside the RooFuncWrapper.
955 timingScope.reset();
956
958 nllWrapper->generateGradient();
959 }
961 nllWrapper->setUseGeneratedFunctionCode(true);
962 }
963
964 nllWrapper->addOwnedComponents(std::move(nll));
965 nllWrapper->addOwnedComponents(std::move(pdfClone));
966
967 return nllWrapper;
968 }
969
970 std::unique_ptr<RooAbsReal> nll;
971
972#ifdef ROOFIT_LEGACY_EVAL_BACKEND
974
975 bool verbose = pc.getInt("verbose");
976
977 int numcpu = pc.getInt("numcpu");
978 int numcpu_strategy = pc.getInt("interleave");
979 // strategy 3 works only for RooSimultaneous.
980 if (numcpu_strategy == 3 && !pdf.InheritsFrom("RooSimultaneous")) {
981 oocoutW(&pdf, Minimization) << "Cannot use a NumCpu Strategy = 3 when the pdf is not a RooSimultaneous, "
982 "falling back to default strategy = 0"
983 << std::endl;
984 numcpu_strategy = 0;
985 }
987
989 RooAbsPdf &actualPdf = binnedLInfo.binnedPdf ? *binnedLInfo.binnedPdf : pdf;
990
991 // Construct NLL
993 RooAbsTestStatistic::Configuration cfg;
994 cfg.addCoefRangeName = addCoefRangeName ? addCoefRangeName : "";
995 cfg.nCPU = numcpu;
996 cfg.interleave = interl;
997 cfg.verbose = verbose;
998 cfg.splitCutRange = static_cast<bool>(splitRange);
999 cfg.cloneInputData = static_cast<bool>(cloneData);
1000 cfg.integrateOverBinsPrecision = pc.getDouble("IntegrateBins");
1001 cfg.binnedL = binnedLInfo.isBinnedL;
1002 cfg.takeGlobalObservablesFromData = takeGlobalObservablesFromData;
1003 cfg.rangeName = rangeName ? rangeName : "";
1004 auto nllVar = std::make_unique<RooNLLVar>(baseName.c_str(), "-log(likelihood)", actualPdf, data, projDeps, ext, cfg);
1005 nllVar->enableBinOffsetting(offset == RooFit::OffsetMode::Bin);
1006 nll = std::move(nllVar);
1008
1009 // Include constraints, if any, in likelihood
1010 if (std::unique_ptr<RooAbsReal> constraintTerm = createConstr()) {
1011
1012 // Even though it is technically only required when the computation graph
1013 // is changed because global observables are taken from data, it is safer
1014 // to clone the constraint model in general to reset the normalization
1015 // integral caches and avoid ASAN build failures (the PDF of the main
1016 // measurement is cloned too anyway, so not much overhead). This can be
1017 // reconsidered after the caching of normalization sets by pointer is changed
1018 // to a more memory-safe solution.
1019 constraintTerm = RooHelpers::cloneTreeWithSameParameters(*constraintTerm, data.get());
1020
1021 // Redirect the global observables to the ones from the dataset if applicable.
1022 constraintTerm->setData(data, false);
1023
1024 // The computation graph for the constraints is very small, no need to do
1025 // the tracking of clean and dirty nodes here.
1026 constraintTerm->setOperMode(RooAbsArg::ADirty);
1027
1028 auto orignll = std::move(nll);
1029 nll = std::make_unique<RooAddition>((baseName + "_with_constr").c_str(), "nllWithCons",
1030 RooArgSet(*orignll, *constraintTerm));
1031 nll->addOwnedComponents(std::move(orignll), std::move(constraintTerm));
1032 }
1033
1035 nll->enableOffsetting(true);
1036 }
1037
1038 if (const double correction = pdf.getCorrection(); correction > 0) {
1039 oocoutI(&pdf, Fitting) << "[FitHelpers] Detected correction term from RooAbsPdf::getCorrection(). "
1040 << "Adding penalty to NLL." << std::endl;
1041
1042 // Convert the multiplicative correction to an additive term in -log L
1043 auto penaltyTerm = std::make_unique<RooConstVar>((baseName + "_Penalty").c_str(),
1044 "Penalty term from getCorrection()", correction);
1045
1046 auto correctedNLL = std::make_unique<RooAddition>(
1047 // add penalty and NLL
1048 (baseName + "_corrected").c_str(), "NLL + penalty", RooArgSet(*nll, *penaltyTerm));
1049
1050 // transfer ownership of terms
1051 correctedNLL->addOwnedComponents(std::move(nll), std::move(penaltyTerm));
1052 nll = std::move(correctedNLL);
1053 }
1054#else
1055 throw std::runtime_error("RooFit was not built with the legacy evaluation backend");
1056#endif
1057
1058 return nll;
1059}
1060
1061std::unique_ptr<RooAbsReal> createChi2(RooAbsReal &real, RooDataHist &data, const RooLinkedList &cmdList)
1062{
1063 RooCmdConfig pc("createChi2(" + std::string(real.GetName()) + ")");
1064
1065 pc.defineInt("EvalBackend", "EvalBackend", 0, static_cast<int>(RooFit::EvalBackend::defaultValue()));
1066 pc.defineInt("numcpu", "NumCPU", 0, 1);
1067 pc.defineInt("verbose", "Verbose", 0, 0);
1068 pc.defineString("rangeName", "RangeWithName", 0, "", true);
1069 pc.defineDouble("rangeLo", "Range", 0, -999.);
1070 pc.defineDouble("rangeHi", "Range", 1, -999.);
1071 pc.defineMutex("Range", "RangeWithName");
1072 pc.defineInt("etype", "DataError", 0, (Int_t)RooDataHist::Auto);
1073 pc.defineInt("extended", "Extended", 0, extendedFitDefault);
1074 pc.defineInt("splitRange", "SplitRange", 0, 0);
1075 pc.defineDouble("integrate_bins", "IntegrateBins", 0, -1);
1076 pc.defineString("addCoefRange", "SumCoefRange", 0, "");
1077 pc.allowUndefined();
1078
1079 pc.process(cmdList);
1080 if (!pc.ok(true)) {
1081 return nullptr;
1082 }
1083
1084 // Clear possible range attributes from previous fits.
1085 real.removeStringAttribute("fitrange");
1086
1087 std::string baseName = "chi2_" + std::string(real.GetName()) + "_" + data.GetName();
1088
1089 auto evalBackend = static_cast<RooFit::EvalBackend::Value>(pc.getInt("EvalBackend"));
1090
1091 RooDataHist::ErrorType etype = static_cast<RooDataHist::ErrorType>(pc.getInt("etype"));
1092 // Resolve Auto to a concrete mode so it's consistent across backends.
1093 if (etype == RooDataHist::Auto) {
1094 etype = data.isNonPoissonWeighted() ? RooDataHist::SumW2 : RooDataHist::Expected;
1095 }
1096
1097 auto *pdf = dynamic_cast<RooAbsPdf *>(&real);
1098 const char *rangeName = pc.getString("rangeName", nullptr, true);
1099
1100 // Translate Range(lo, hi) into a "fit" named range on all observables.
1101 if (pc.hasProcessed("Range")) {
1102 const double rangeLo = pc.getDouble("rangeLo");
1103 const double rangeHi = pc.getDouble("rangeHi");
1104 RooArgSet obs;
1105 real.getObservables(data.get(), obs);
1106 for (auto *rrv : dynamic_range_cast<RooRealVar *>(obs)) {
1107 if (rrv) {
1108 rrv->setRange("fit", rangeLo, rangeHi);
1109 }
1110 }
1111 rangeName = "fit";
1112 }
1113
1116
1117 const int splitRange = pc.getInt("splitRange");
1119
1120 std::unique_ptr<RooFit::Experimental::RooEvaluatorWrapper> wrapper;
1121
1122 const int nWorkers = effectiveNumWorkers(real, evalBackend, pc.getInt("numcpu"));
1123
1124 // Function mode: the input is a non-pdf RooAbsReal. We can short-circuit
1125 // the pdf-compilation pipeline since there's no real pdf to normalize.
1126 if (!pdf) {
1127 RooArgSet observables;
1128 real.getObservables(data.get(), observables);
1129 RooNLLVarNew::Config cfg;
1130 cfg.statistic = RooNLLVarNew::Statistic::Chi2;
1131 cfg.chi2ErrorType = etype;
1132 auto chi2 = std::make_unique<RooNLLVarNew>(baseName.c_str(), baseName.c_str(), real, observables, cfg);
1133 wrapper = std::make_unique<RooFit::Experimental::RooEvaluatorWrapper>(
1135 /*simPdf=*/nullptr,
1136 /*takeGlobalObservablesFromData=*/true, nWorkers);
1137 wrapper->addOwnedComponents(std::move(chi2));
1138 } else {
1139 const bool extended = interpretExtendedCmdArg(*pdf, pc.getInt("extended"));
1140
1142 pdf->getObservables(data.get(), normSet);
1143
1144 oocxcoutI(pdf, Fitting) << "createChi2(" << pdf->GetName()
1145 << ") fixing normalization set for coefficient determination to observables in data\n";
1146 pdf->fixAddCoefNormalization(normSet, false);
1147
1148 std::unique_ptr<RooAbsPdf> pdfClone =
1149 compilePdfForFit(*pdf, normSet, rangeName, splitRange, pc.getString("addCoefRange", nullptr, true),
1150 /*likelihoodMode=*/false);
1151
1155
1156 std::unique_ptr<RooAbsReal> chi2;
1157 auto *simPdfClone = dynamic_cast<RooSimultaneous *>(&finalPdf);
1158 // Like in createNLLNew(): a "switch"-mode RooSimultaneous (index
1159 // category not among the data columns) is treated as an ordinary pdf.
1160 if (simPdfClone && simPdfClone->indexCatIsObservable(*data.get())) {
1161 chi2 = std::unique_ptr<RooAbsReal>{dynamic_cast<RooAbsReal *>(
1162 createSimultaneousChi2(*simPdfClone, rangeName ? rangeName : "", extended, etype).release())};
1163 } else {
1164 RooArgSet observables;
1165 finalPdf.getObservables(data.get(), observables);
1166 RooNLLVarNew::Config cfg;
1167 cfg.statistic = RooNLLVarNew::Statistic::Chi2;
1168 cfg.extended = extended;
1169 cfg.chi2ErrorType = etype;
1170 chi2 = std::make_unique<RooNLLVarNew>(baseName.c_str(), baseName.c_str(), finalPdf, observables, cfg);
1171 }
1172
1173 wrapper = std::make_unique<RooFit::Experimental::RooEvaluatorWrapper>(
1175 /*takeGlobalObservablesFromData=*/true, nWorkers);
1176 wrapper->addOwnedComponents(std::move(binSamplingPdfs));
1177 wrapper->addOwnedComponents(std::move(chi2));
1178 wrapper->addOwnedComponents(std::move(pdfClone));
1179 }
1180
1182 wrapper->generateGradient();
1183 }
1185 wrapper->setUseGeneratedFunctionCode(true);
1186 }
1187
1189 return wrapper;
1190 }
1191
1192#ifdef ROOFIT_LEGACY_EVAL_BACKEND
1194
1195 RooAbsTestStatistic::Configuration cfg;
1196
1198
1199 bool extended = false;
1200 if (pdf) {
1201 extended = interpretExtendedCmdArg(*pdf, pc.getInt("extended"));
1202 }
1203
1204 const char *addCoefRangeName = pc.getString("addCoefRange", nullptr, true);
1205 int splitRange = pc.getInt("splitRange");
1206
1207 // Set the fitrange attribute of th PDF, add observables ranges for plotting
1209
1210 cfg.rangeName = rangeName ? rangeName : "";
1211 cfg.nCPU = pc.getInt("numcpu");
1212 cfg.interleave = RooFit::Interleave;
1213 cfg.verbose = static_cast<bool>(pc.getInt("verbose"));
1214 cfg.cloneInputData = false;
1215 cfg.integrateOverBinsPrecision = pc.getDouble("integrate_bins");
1216 cfg.addCoefRangeName = addCoefRangeName ? addCoefRangeName : "";
1217 cfg.splitCutRange = static_cast<bool>(splitRange);
1218 auto chi2 = std::make_unique<RooChi2Var>(baseName.c_str(), baseName.c_str(), real, static_cast<RooDataHist &>(data),
1219 extended, etype, cfg);
1220
1222
1223 return chi2;
1224#else
1225 throw std::runtime_error("createChi2() is not supported without the legacy evaluation backend");
1226 return nullptr;
1227#endif
1228}
1229
1230std::unique_ptr<RooFitResult> fitTo(RooAbsReal &real, RooAbsData &data, const RooLinkedList &cmdList, bool chi2)
1231{
1232 const bool isDataHist = dynamic_cast<RooDataHist const *>(&data);
1233
1234 RooCmdConfig pc("fitTo(" + std::string(real.GetName()) + ")");
1235
1237 std::string nllCmdListString;
1238
1239 // Check on the raw command list whether parallel minimization is requested,
1240 // because in that case ModularL(true) is implied below. The check needs to
1241 // happen before filtering the command list: with a modular likelihood,
1242 // offsetting is configured on the minimizer instead of the likelihood, so
1243 // the OffsetLikelihood argument must not be forwarded to createNLL(), where
1244 // it is mutually exclusive with ModularL.
1245 auto cmdEnabled = [&cmdList](const char *cmdName) {
1246 auto *arg = static_cast<RooCmdArg *>(cmdList.FindObject(cmdName));
1247 return arg && arg->getInt(0) != 0;
1248 };
1249 const bool parallelRequested =
1250 cmdEnabled("Parallelize") || cmdEnabled("ParallelGradientOptions") || cmdEnabled("ParallelDescentOptions");
1251
1252 if (!chi2) {
1253 nllCmdListString = "ProjectedObservables,Extended,Range,"
1254 "RangeWithName,SumCoefRange,NumCPU,SplitRange,Constrained,Constrain,ExternalConstraints,"
1255 "CloneData,GlobalObservables,GlobalObservablesSource,GlobalObservablesTag,"
1256 "EvalBackend,IntegrateBins,ModularL";
1257
1258 if (!parallelRequested && !cmdEnabled("ModularL")) {
1259 nllCmdListString += ",OffsetLikelihood";
1260 }
1261 } else {
1262 auto createChi2DataHistCmdArgs = "Range,RangeWithName,NumCPU,IntegrateBins,ProjectedObservables,"
1263 "AddCoefRange,SplitRange,DataError,Extended,EvalBackend";
1264 auto createChi2DataSetCmdArgs = "YVar,Integrate,RangeWithName,NumCPU,Verbose";
1266 }
1267
1269
1270 pc.defineDouble("prefit", "Prefit", 0, 0);
1272
1273 // Process and check varargs
1274 pc.process(fitCmdList);
1275 if (!pc.ok(true)) {
1276 return nullptr;
1277 }
1278
1279 // TimingAnalysis works only for RooSimultaneous.
1280 if (pc.getInt("timingAnalysis") && !real.InheritsFrom("RooSimultaneous")) {
1281 oocoutW(&real, Minimization) << "The timingAnalysis feature was built for minimization with RooSimultaneous "
1282 "and is not implemented for other PDF's. Please create a RooSimultaneous to "
1283 "enable this feature."
1284 << std::endl;
1285 }
1286
1287 // Decode command line arguments
1288 double prefit = pc.getDouble("prefit");
1289
1290 if (prefit != 0) {
1291 size_t nEvents = static_cast<size_t>(prefit * data.numEntries());
1292 if (prefit > 0.5 || nEvents < 100) {
1293 oocoutW(&real, InputArguments) << "PrefitDataFraction should be in suitable range."
1294 << "With the current PrefitDataFraction=" << prefit
1295 << ", the number of events would be " << nEvents << " out of "
1296 << data.numEntries() << ". Skipping prefit..." << std::endl;
1297 } else {
1298 size_t step = data.numEntries() / nEvents;
1299
1300 RooDataSet tiny("tiny", "tiny", *data.get(), data.isWeighted() ? RooFit::WeightVar() : RooCmdArg());
1301
1302 for (int i = 0; i < data.numEntries(); i += step) {
1303 const RooArgSet *event = data.get(i);
1304 tiny.add(*event, data.weight());
1305 }
1307 pc.filterCmdList(tinyCmdList, "Prefit,Hesse,Minos,Verbose,Save,Timer");
1310
1313
1314 fitTo(real, tiny, tinyCmdList, chi2);
1315 }
1316 }
1317
1319 if (parallelRequested) {
1320 // Set to new style likelihood if parallelization is requested
1323 }
1324
1325 std::unique_ptr<RooAbsReal> nll;
1326 if (chi2) {
1327 if (isDataHist) {
1328 nll = std::unique_ptr<RooAbsReal>{real.createChi2(static_cast<RooDataHist &>(data), nllCmdList)};
1329 }
1330 } else {
1331 nll = std::unique_ptr<RooAbsReal>{dynamic_cast<RooAbsPdf &>(real).createNLL(data, nllCmdList)};
1332 }
1333
1334 if (!nll) {
1335 oocoutE(&real, InputArguments) << "RooFit::FitHelpers::fitTo(" << real.GetName()
1336 << ") could not create the test statistic, no fit performed" << std::endl;
1337 return nullptr;
1338 }
1339
1340 return RooFit::FitHelpers::minimize(real, *nll, data, pc);
1341}
1342
1343} // namespace RooFit::FitHelpers
1344
1345/// \endcond
header file containing the templated implementation of matrix inversion routines for use with ROOT's ...
ROOT::RRangeCast< T, true, Range_t > dynamic_range_cast(Range_t &&coll)
ROOT::RRangeCast< T, false, Range_t > static_range_cast(Range_t &&coll)
cudaEvent_t event
#define oocoutW(o, a)
#define oocoutE(o, a)
#define oocoutI(o, a)
#define oocxcoutI(o, a)
int Int_t
Signed integer 4 bytes (int)
Definition RtypesCore.h:60
ROOT::Detail::TRangeCast< T, true > TRangeDynCast
TRangeDynCast is an adapter class that allows the typed iteration through a TCollection.
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void data
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 Int_t Int_t UInt_t UInt_t Rectangle_t Int_t Int_t Window_t TString Int_t GCValues_t GetPrimarySelectionOwner GetDisplay GetScreen GetColormap GetNativeEvent const char const char dpyName wid window const char font_name cursor keysym reg const char only_if_exist regb h Point_t winding char text const char depth char const char Int_t count const char ColorStruct_t color const char Pixmap_t Pixmap_t PictureAttributes_t attr const char char ret_data h unsigned char height h offset
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 Int_t Int_t UInt_t UInt_t Rectangle_t Int_t Int_t Window_t child
char name[80]
Definition TGX11.cxx:142
class to compute the Cholesky decomposition of a matrix
Common abstract base class for objects that represent a value and a "shape" in RooFit.
Definition RooAbsArg.h:76
virtual bool isCategory() const
Definition RooAbsArg.h:501
void setStringAttribute(const Text_t *key, const Text_t *value)
Associate string 'value' to this object under key 'key'.
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.
void removeStringAttribute(const Text_t *key)
Delete a string attribute with a given key.
void setAttribute(const Text_t *name, bool value=true)
Set (default) or clear a named boolean attribute of this object.
Abstract base class for objects that represent a discrete value that can be set from the outside,...
virtual bool remove(const RooAbsArg &var, bool silent=false, bool matchByNameOnly=false)
Remove the specified argument from our list.
virtual bool add(const RooAbsArg &var, bool silent=false)
Add the specified argument to list.
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...
virtual bool addOwned(RooAbsArg &var, bool silent=false)
Add an argument and transfer the ownership to the collection.
Abstract base class for binned and unbinned datasets.
Definition RooAbsData.h:55
Abstract interface for all probability density functions.
Definition RooAbsPdf.h:32
std::unique_ptr< RooAbsArg > compileForNormSet(RooArgSet const &normSet, RooFit::Detail::CompileContext &ctx) const override
void setNormRange(const char *rangeName)
virtual double getCorrection() const
This function returns the penalty term.
@ CanBeExtended
Definition RooAbsPdf.h:208
@ MustBeExtended
Definition RooAbsPdf.h:208
@ CanNotBeExtended
Definition RooAbsPdf.h:208
const char * normRange() const
Definition RooAbsPdf.h:246
virtual ExtendMode extendMode() const
Returns ability of PDF to provide extended likelihood terms.
Definition RooAbsPdf.h:212
Abstract base class for objects that represent a real value and implements functionality common to al...
Definition RooAbsReal.h:63
virtual void fixAddCoefNormalization(const RooArgSet &addNormSet=RooArgSet(), bool force=true)
Fix the interpretation of the coefficient of any RooAddPdf component in the expression tree headed by...
static void setEvalErrorLoggingMode(ErrorLoggingMode m)
Set evaluation error logging mode.
RooArgList is a container object that can hold multiple RooAbsArg objects.
Definition RooArgList.h:22
RooArgSet is a container object that can hold multiple RooAbsArg objects.
Definition RooArgSet.h:24
RooArgSet * selectByAttrib(const char *name, bool value) const
Use RooAbsCollection::selectByAttrib(), but return as RooArgSet.
Definition RooArgSet.h:144
static std::unique_ptr< RooAbsPdf > create(RooAbsPdf &pdf, RooAbsData const &data, double precision)
Creates a wrapping RooBinSamplingPdf if appropriate.
Object to represent discrete states.
Definition RooCategory.h:28
Named container for two doubles, two integers two object points and three string pointers that can be...
Definition RooCmdArg.h:26
Configurable parser for RooCmdArg named arguments.
void defineMutex(const char *head, Args_t &&... tail)
Define arguments where any pair is mutually exclusive.
bool process(const RooCmdArg &arg)
Process given RooCmdArg.
bool hasProcessed(const char *cmdName) const
Return true if RooCmdArg with name 'cmdName' has been processed.
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 ...
bool ok(bool verbose) const
Return true of parsing was successful.
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...
void allowUndefined(bool flag=true)
If flag is true the processing of unrecognized RooCmdArgs is not considered an error.
int getInt(const char *name, int defaultValue=0) const
Return integer property registered with name 'name'.
RooLinkedList filterCmdList(RooLinkedList &cmdInList, const char *cmdNameList, bool removeFromInList=true) const
Utility function to filter commands listed in cmdNameList from cmdInList.
Container class to hold N-dimensional binned data.
Definition RooDataHist.h:40
Container class to hold unbinned data.
Definition RooDataSet.h:32
static Value & defaultValue()
A RooFormulaVar is a generic implementation of a real-valued object, which takes a RooArgList of serv...
Collection class for internal use, storing a collection of RooAbsArg pointers in a doubly linked list...
Wrapper class around ROOT::Math::Minimizer that provides a seamless interface between the minimizer f...
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.
Variable that can be changed from the outside.
Definition RooRealVar.h:37
static TClass * Class()
void setRange(const char *name, double min, double max, bool shared=true)
Set a fit or plotting range.
Facilitates simultaneous fitting of multiple PDFs to subsets of a given dataset.
const char * GetName() const override
Returns name of object.
Definition TNamed.h:49
virtual Bool_t InheritsFrom(const char *classname) const
Returns kTRUE if object inherits from class "classname".
Definition TObject.cxx:548
RooCmdArg WeightVar(const char *name="weight", bool reinterpretAsWeight=false)
RooCmdArg Hesse(bool flag=true)
RooCmdArg ModularL(bool flag=false)
RooCmdArg PrintLevel(Int_t code)
RVec< PromoteType< T > > log(const RVec< T > &v)
Definition RVec.hxx:1821
CoordSystem::Scalar get(DisplacementVector2D< CoordSystem, Tag > const &p)
std::vector< std::string > Split(std::string_view str, std::string_view delims, bool skipEmpty=false)
Splits a string at each character in delims.
double nll(double pdf, double weight, int binnedL, int doBinOffset)
Definition MathFuncs.h:449
std::unique_ptr< T > compileForNormSet(T const &arg, RooArgSet const &normSet)
OffsetMode
For setting the offset mode with the Offset() command argument to RooAbsPdf::fitTo()
std::unique_ptr< T > cloneTreeWithSameParameters(T const &arg, RooArgSet const *observables=nullptr)
Clone RooAbsArg object and reattach to original parameters.
BinnedLOutput getBinnedL(RooAbsPdf const &pdf)
static std::size_t defaultNComponentTasks
Definition Config.h:38
Config argument to RooMinimizer constructor.
TMarker m
Definition textangle.C:8
TLine l
Definition textangle.C:4