Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
MinuitFcnGrad.cxx
Go to the documentation of this file.
1/*
2 * Project: RooFit
3 * Authors:
4 * PB, Patrick Bos, Netherlands eScience Center, p.bos@esciencecenter.nl
5 *
6 * Copyright (c) 2021, 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 "MinuitFcnGrad.h"
14
15#include "RooMinimizer.h"
16#include "RooMsgService.h"
17#include "RooAbsPdf.h"
18
19#include <iomanip> // std::setprecision
20
21namespace RooFit {
22namespace TestStatistics {
23
24namespace {
25
26class MinuitGradFunctor : public ROOT::Math::IMultiGradFunction {
27
28public:
29 MinuitGradFunctor(MinuitFcnGrad const &fcn) : _fcn{fcn} {}
30
31 ROOT::Math::IMultiGradFunction *Clone() const override { return new MinuitGradFunctor(_fcn); }
32
33 unsigned int NDim() const override { return _fcn.NDim(); }
34
35 void Gradient(const double *x, double *grad) const override { return _fcn.Gradient(x, grad); }
36
37 void GradientWithPrevResult(const double *x, double *grad, double *previous_grad, double *previous_g2,
38 double *previous_gstep) const override
39 {
40 return _fcn.GradientWithPrevResult(x, grad, previous_grad, previous_g2, previous_gstep);
41 }
42
43 bool returnsInMinuit2ParameterSpace() const override { return _fcn.returnsInMinuit2ParameterSpace(); }
44
45private:
46 double DoEval(const double *x) const override { return _fcn(x); }
47
48 double DoDerivative(double const * /*x*/, unsigned int /*icoord*/) const override
49 {
50 throw std::runtime_error("MinuitFcnGrad::DoDerivative is not implemented, please use Gradient instead.");
51 }
52
53 MinuitFcnGrad const &_fcn;
54};
55
56} // namespace
57
58/** \class MinuitFcnGrad
59 *
60 * \brief Minuit-RooMinimizer interface which synchronizes parameter data and coordinates evaluation of likelihood
61 * (gradient) values
62 *
63 * This class provides an interface between RooFit and Minuit. It synchronizes parameter values from Minuit, calls
64 * calculator classes to evaluate likelihood and likelihood gradient values and returns them to Minuit. The Wrapper
65 * objects do the actual calculations. These are constructed inside the MinuitFcnGrad constructor using the RooAbsL
66 * likelihood passed in to the constructor, usually directly from RooMinimizer, with which this class is intimately
67 * coupled, being a RooAbsMinimizerFcn implementation. MinuitFcnGrad inherits from ROOT::Math::IMultiGradFunction as
68 * well, which allows it to be used as the FCN and GRAD parameters Minuit expects.
69 *
70 * \note The class is not intended for use by end-users. We recommend to either use RooMinimizer with a RooAbsL derived
71 * likelihood object, or to use a higher level entry point like RooAbsPdf::fitTo() or RooAbsPdf::createNLL().
72 */
73
74/// \param[in] absL The input likelihood.
75/// \param[in] context RooMinimizer that creates and owns this class.
76/// \param[in] parameters The vector of ParameterSettings objects that describe the parameters used in the Minuit
77/// \param[in] likelihoodMode Lmode
78/// \param[in] likelihoodGradientMode Lgrad
79/// \param[in] verbose true for verbose output
80/// Fitter. Note that these must match the set used in the Fitter used by \p context! It can be passed in from
81/// RooMinimizer with fitter()->Config().ParamsSettings().
82MinuitFcnGrad::MinuitFcnGrad(const std::shared_ptr<RooFit::TestStatistics::RooAbsL> &absL, RooMinimizer *context,
83 std::vector<ROOT::Fit::ParameterSettings> &parameters, LikelihoodMode likelihoodMode,
84 LikelihoodGradientMode likelihoodGradientMode)
85 : RooAbsMinimizerFcn(*absL->getParameters(), context),
86 _minuitInternalX(NDim(), 0),
87 _minuitExternalX(NDim(), 0),
88 _multiGenFcn{std::make_unique<MinuitGradFunctor>(*this)}
89{
90 synchronizeParameterSettings(parameters, true);
91
92 _calculationIsClean = std::make_unique<WrapperCalculationCleanFlags>();
93
95 if (likelihoodMode == LikelihoodMode::multiprocess &&
96 likelihoodGradientMode == LikelihoodGradientMode::multiprocess) {
98 }
99 _gradient =
101
102 applyToLikelihood([&](auto &l) { l.synchronizeParameterSettings(parameters); });
103 _gradient->synchronizeParameterSettings(getMultiGenFcn(), parameters);
104
105 // Note: can be different than RooGradMinimizerFcn/LikelihoodGradientSerial, where default options are passed
106 // (ROOT::Math::MinimizerOptions::DefaultStrategy() and ROOT::Math::MinimizerOptions::DefaultErrorDef())
107 applyToLikelihood([&](auto &l) { l.synchronizeWithMinimizer(ROOT::Math::MinimizerOptions()); });
108 _gradient->synchronizeWithMinimizer(ROOT::Math::MinimizerOptions());
109}
110
111double MinuitFcnGrad::operator()(const double *x) const
112{
113 bool parameters_changed = syncParameterValuesFromMinuitCalls(x, false);
114
115 // Calculate the function for these parameters
116 // RooAbsReal::setHideOffset(false);
117 auto &likelihoodHere(_likelihoodInGradient && _gradient->isCalculating() ? *_likelihoodInGradient : *_likelihood);
118 likelihoodHere.evaluate();
119 double fvalue = likelihoodHere.getResult().Sum();
120 _calculationIsClean->likelihood = true;
121 // RooAbsReal::setHideOffset(true);
122
123 if (!parameters_changed) {
124 return fvalue;
125 }
126
127 if (!std::isfinite(fvalue) || RooAbsReal::numEvalErrors() > 0 || fvalue > 1e30) {
128
129 if (cfg().printEvalErrors >= 0) {
130
131 if (cfg().doEEWall) {
132 oocoutW(nullptr, Eval) << "MinuitFcnGrad: Minimized function has error status." << std::endl
133 << "Returning maximum FCN so far (" << _maxFCN
134 << ") to force MIGRAD to back out of this region. Error log follows" << std::endl;
135 } else {
136 oocoutW(nullptr, Eval) << "MinuitFcnGrad: Minimized function has error status but is ignored" << std::endl;
137 }
138
139 bool first(true);
140 ooccoutW(nullptr, Eval) << "Parameter values: ";
141 for (auto *var : static_range_cast<const RooRealVar *>(*_floatParamList)) {
142 if (first) {
143 first = false;
144 } else {
145 ooccoutW(nullptr, Eval) << ", ";
146 }
147 ooccoutW(nullptr, Eval) << var->GetName() << "=" << var->getVal();
148 }
149 ooccoutW(nullptr, Eval) << std::endl;
150
152 ooccoutW(nullptr, Eval) << std::endl;
153 }
154
155 if (cfg().doEEWall) {
156 fvalue = _maxFCN + 1;
157 }
158
160 _numBadNLL++;
161 } else if (fvalue > _maxFCN) {
162 _maxFCN = fvalue;
163 }
164
165 // Optional logging
166 if (cfg().verbose) {
167 std::cout << "\nprevFCN" << (likelihoodHere.isOffsetting() ? "-offset" : "") << " = " << std::setprecision(10)
168 << fvalue << std::setprecision(4) << " ";
169 std::cout.flush();
170 }
171
172 finishDoEval();
173 return fvalue;
174}
175
176/// Minuit calls (via FcnAdapters etc) DoEval or Gradient with a set of parameters x.
177/// This function syncs these values to the proper places in RooFit.
178///
179/// The first twist, and reason this function is more complicated than one may imagine, is that Minuit internally uses a
180/// transformed parameter space to account for parameter boundaries. Whether we receive these Minuit "internal"
181/// parameter values or "regular"/untransformed RooFit parameter space values depends on the situation.
182/// - The values that arrive here via DoEval are always "normal" parameter values, since Minuit transforms these
183/// back into regular space before passing to DoEval (see MnUserFcn::operator() which wraps the Fcn(Gradient)Base
184/// in ModularFunctionMinimizer::Minimize and is used for direct function calls from that point on in the minimizer).
185/// These can thus always be safely synced with this function's RooFit parameters using SetPdfParamVal.
186/// - The values that arrive here via Gradient will be in internal coordinates if that is
187/// what this class expects, and indeed this is the case for MinuitFcnGrad's current implementation. This is
188/// communicated to Minuit via MinuitFcnGrad::returnsInMinuit2ParameterSpace. Inside Minuit, that function determines
189/// whether this class's gradient calculator is wrapped inside a AnalyticalGradientCalculator, to which Minuit passes
190/// "external" parameter values, or as an ExternalInternalGradientCalculator, which gets "internal" parameter values.
191/// Long story short: when MinuitFcnGrad::returnsInMinuit2ParameterSpace() returns true, Minuit will pass "internal"
192/// values to Gradient. These cannot be synced with this function's RooFit parameters using
193/// SetPdfParamVal, unless a manual transformation step is performed in advance. However, they do need to be passed
194/// on to the gradient calculator, since indeed we expect values there to be in "internal" space. However, this is
195/// calculator dependent. Note that in the current MinuitFcnGrad implementation we do not actually allow for
196/// calculators in "external" (i.e. regular RooFit parameter space) values, since
197/// MinuitFcnGrad::returnsInMinuit2ParameterSpace is hardcoded to true. This should in a future version be changed so
198/// that the calculator (the wrapper) is queried for this information.
199/// Because some gradient calculators may also use the regular RooFit parameters (e.g. for calculating the likelihood's
200/// value itself), this information is also passed on to the gradient wrapper. Vice versa, when updated "internal"
201/// parameters are passed to Gradient, the likelihood may be affected as well. Even though a
202/// transformation from internal to "external" may be necessary before the values can be used, the likelihood can at
203/// least log that its parameter values are possibly no longer in sync with those of the gradient.
204///
205/// The second twist is that the Minuit external parameters may still be different from the ones used in RooFit. This
206/// happens when Minuit tries out values that lay outside the RooFit parameter's range(s). RooFit's setVal (called
207/// inside SetPdfParamVal) then clips the RooAbsArg's value to one of the range limits, instead of setting it to the
208/// value Minuit intended. When this happens, i.e. syncParameterValuesFromMinuitCalls is called with
209/// minuit_internal = false and the values do not match the previous values stored in _minuitInternalX *but* the
210/// values after SetPdfParamVal did not get set to the intended value, the _minuitInternalRooFitXMismatch flag is
211/// set. This information can be used by calculators, if desired, for instance when a calculator does not want to make
212/// use of the range information in the RooAbsArg parameters.
213bool MinuitFcnGrad::syncParameterValuesFromMinuitCalls(const double *x, bool minuit_internal) const
214{
215 bool aParamWasUpdated = false;
216 if (minuit_internal) {
218 throw std::logic_error("Updating Minuit-internal parameters only makes sense for (gradient) calculators that "
219 "are defined in Minuit-internal parameter space.");
220 }
221
222 for (std::size_t ix = 0; ix < NDim(); ++ix) {
223 bool parameter_changed = (x[ix] != _minuitInternalX[ix]);
224 if (parameter_changed) {
225 _minuitInternalX[ix] = x[ix];
226 }
227 aParamWasUpdated |= parameter_changed;
228 }
229
230 if (aParamWasUpdated) {
231 _calculationIsClean->set_all(false);
232 applyToLikelihood([&](auto &l) { l.updateMinuitInternalParameterValues(_minuitInternalX); });
233 _gradient->updateMinuitInternalParameterValues(_minuitInternalX);
234 }
235 } else {
236 bool aParamIsMismatched = false;
237
238 for (std::size_t ix = 0; ix < NDim(); ++ix) {
239 // Note: the return value of SetPdfParamVal does not always mean that the parameter's value in the RooAbsReal
240 // changed since last time! If the value was out of range bin, setVal was still called, but the value was not
241 // updated.
242 SetPdfParamVal(ix, x[ix]);
243 _minuitExternalX[ix] = x[ix];
244 // The above is why we need _minuitExternalX. The _minuitExternalX vector can also be passed to
245 // LikelihoodWrappers, if needed, but typically they will make use of the RooFit parameters directly. However,
246 // we log in the flag below whether they are different so that calculators can use this information.
247 bool parameter_changed = (x[ix] != _minuitExternalX[ix]);
248 aParamWasUpdated |= parameter_changed;
249 aParamIsMismatched |=
250 (static_cast<RooRealVar const *>(_floatParamList->at(ix))->getVal() != _minuitExternalX[ix]);
251 }
252
253 _minuitInternalRooFitXMismatch = aParamIsMismatched;
254
255 if (aParamWasUpdated) {
256 _calculationIsClean->set_all(false);
257 applyToLikelihood([&](auto &l) { l.updateMinuitExternalParameterValues(_minuitExternalX); });
258 _gradient->updateMinuitExternalParameterValues(_minuitExternalX);
259 }
260 }
261 return aParamWasUpdated;
262}
263
264void MinuitFcnGrad::Gradient(const double *x, double *grad) const
265{
268 _gradient->fillGradient(grad);
269 _calculatingGradient = false;
270}
271
272void MinuitFcnGrad::GradientWithPrevResult(const double *x, double *grad, double *previous_grad, double *previous_g2,
273 double *previous_gstep) const
274{
277 _gradient->fillGradientWithPrevResult(grad, previous_grad, previous_g2, previous_gstep);
278 _calculatingGradient = false;
279}
280
281bool MinuitFcnGrad::Synchronize(std::vector<ROOT::Fit::ParameterSettings> &parameters)
282{
283 bool returnee = synchronizeParameterSettings(parameters, _optConst);
284 applyToLikelihood([&](auto &l) { l.synchronizeParameterSettings(parameters); });
285 _gradient->synchronizeParameterSettings(parameters);
286
287 applyToLikelihood([&](auto &l) { l.synchronizeWithMinimizer(_context->fitter()->Config().MinimizerOptions()); });
288 _gradient->synchronizeWithMinimizer(_context->fitter()->Config().MinimizerOptions());
289 return returnee;
290}
291
292} // namespace TestStatistics
293} // namespace RooFit
MinuitFcnGrad const & _fcn
#define oocoutW(o, a)
#define ooccoutW(o, a)
ROOT::Math::MinimizerOptions & MinimizerOptions()
access to the minimizer control parameter (non const method)
Definition FitConfig.h:167
const FitConfig & Config() const
access to the fit configuration (const method)
Definition Fitter.h:422
Interface (abstract class) for multi-dimensional functions providing a gradient calculation.
Definition IFunction.h:168
std::unique_ptr< RooArgList > _floatParamList
RooMinimizer::Config const & cfg() const
void printEvalErrors() const
Print information about why evaluation failed.
bool synchronizeParameterSettings(std::vector< ROOT::Fit::ParameterSettings > &parameters, bool optConst)
Informs Minuit through its parameter_settings vector of RooFit parameter properties.
unsigned int NDim() const
bool SetPdfParamVal(int index, double value) const
Set value of parameter i.
unsigned int getNDim() const
static Int_t numEvalErrors()
Return the number of logged evaluation errors since the last clearing.
static void printEvalErrors(std::ostream &os=std::cout, Int_t maxPerNode=10000000)
Print all outstanding logged evaluation error on the given ostream.
static void clearEvalErrorLog()
Clear the stack of evaluation error messages.
static std::unique_ptr< LikelihoodGradientWrapper > create(LikelihoodGradientMode likelihoodGradientMode, std::shared_ptr< RooAbsL > likelihood, std::shared_ptr< WrapperCalculationCleanFlags > calculationIsClean, std::size_t nDim, RooMinimizer *minimizer)
Factory method.
static std::unique_ptr< LikelihoodWrapper > create(LikelihoodMode likelihoodMode, std::shared_ptr< RooAbsL > likelihood, std::shared_ptr< WrapperCalculationCleanFlags > calculationIsClean)
Factory method.
void GradientWithPrevResult(const double *x, double *grad, double *previous_grad, double *previous_g2, double *previous_gstep) const
double operator()(const double *x) const
std::unique_ptr< LikelihoodWrapper > _likelihoodInGradient
bool Synchronize(std::vector< ROOT::Fit::ParameterSettings > &parameter_settings) override
Overridden from RooAbsMinimizerFcn to include gradient strategy synchronization.
void Gradient(const double *x, double *grad) const
IMultiGradFunction overrides necessary for Minuit.
std::vector< double > _minuitInternalX
ROOT::Math::IMultiGenFunction * getMultiGenFcn() override
bool syncParameterValuesFromMinuitCalls(const double *x, bool minuit_internal) const
Minuit calls (via FcnAdapters etc) DoEval or Gradient with a set of parameters x.
std::unique_ptr< LikelihoodGradientWrapper > _gradient
std::unique_ptr< LikelihoodWrapper > _likelihood
std::shared_ptr< WrapperCalculationCleanFlags > _calculationIsClean
std::vector< double > _minuitExternalX
void applyToLikelihood(Func &&func) const
MinuitFcnGrad(const std::shared_ptr< RooFit::TestStatistics::RooAbsL > &absL, RooMinimizer *context, std::vector< ROOT::Fit::ParameterSettings > &parameters, LikelihoodMode likelihoodMode, LikelihoodGradientMode likelihoodGradientMode)
Wrapper class around ROOT::Fit:Fitter that provides a seamless interface between the minimizer functi...
ROOT::Fit::Fitter * fitter()
Return underlying ROOT fitter object.
Variable that can be changed from the outside.
Definition RooRealVar.h:37
Double_t x[n]
Definition legend1.C:17
The namespace RooFit contains mostly switches that change the behaviour of functions of PDFs (or othe...
Definition JSONIO.h:26
TLine l
Definition textangle.C:4