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#include "RooNaNPacker.h"
19
21#include <Minuit2/FCNBase.h>
22
23#include <iomanip> // std::setprecision
24
25namespace RooFit {
26namespace TestStatistics {
27
28namespace {
29
30class MinuitGradFunctor : public ROOT::Minuit2::FCNBase {
31public:
32 MinuitGradFunctor(MinuitFcnGrad const &fcn, double errorLevel) : _fcn{fcn}, _up{errorLevel} {}
33
34 double operator()(std::vector<double> const &v) const override { return _fcn(v.data()); }
35 double Up() const override { return _up; }
36 void SetErrorDef(double val) override { _up = val; }
37 bool HasGradient() const override { return true; }
38 std::vector<double> Gradient(std::vector<double> const &params) const override
39 {
40 std::vector<double> grad(_fcn.getNDim());
41 _fcn.Gradient(params.data(), grad.data());
42 return grad;
43 }
44 // Unhide the 4-argument overload from FCNBase, which forwards to Gradient().
45 // Otherwise GCC's -Woverloaded-virtual (enabled with -Werror on some CI
46 // targets) complains because we only override the 5-argument overload here.
47 using FCNBase::GradientWithPrevResult;
48 std::vector<double> GradientWithPrevResult(std::vector<double> const &v, double *previous_grad, double *previous_g2,
49 double *previous_gstep, double fValAtV) const override
50 {
51 std::vector<double> output(v.size());
52 _fcn.GradientWithPrevResult(v.data(), output.data(), previous_grad, previous_g2, previous_gstep, fValAtV);
53 return output;
54 }
55 ROOT::Minuit2::GradientParameterSpace gradParameterSpace() const override
56 {
57 return _fcn.returnsInMinuit2ParameterSpace() ? ROOT::Minuit2::GradientParameterSpace::Internal
59 }
60
61 // TODO: Implement this
62 bool SecondDerivativeAlwaysVanishes(unsigned int /*i*/, unsigned int /*j*/) const override { return false; }
63
64private:
65 MinuitFcnGrad const &_fcn;
66 double _up;
67};
68
69} // namespace
70
71/** \class MinuitFcnGrad
72 *
73 * \brief Minuit-RooMinimizer interface which synchronizes parameter data and coordinates evaluation of likelihood
74 * (gradient) values
75 *
76 * This class provides an interface between RooFit and Minuit. It synchronizes parameter values from Minuit, calls
77 * calculator classes to evaluate likelihood and likelihood gradient values and returns them to Minuit. The Wrapper
78 * objects do the actual calculations. These are constructed inside the MinuitFcnGrad constructor using the RooAbsL
79 * likelihood passed in to the constructor, usually directly from RooMinimizer, with which this class is intimately
80 * coupled, being a RooAbsMinimizerFcn implementation. MinuitFcnGrad inherits from ROOT::Math::IMultiGradFunction as
81 * well, which allows it to be used as the FCN and GRAD parameters Minuit expects.
82 *
83 * \note The class is not intended for use by end-users. We recommend to either use RooMinimizer with a RooAbsL derived
84 * likelihood object, or to use a higher level entry point like RooAbsPdf::fitTo() or RooAbsPdf::createNLL().
85 */
86
87/// \param[in] absL The input likelihood.
88/// \param[in] context RooMinimizer that creates and owns this class.
89/// \param[in] parameters The vector of ParameterSettings objects that describe the parameters used in the Minuit
90/// \param[in] likelihoodMode Lmode
91/// \param[in] likelihoodGradientMode Lgrad
92/// \param[in] verbose true for verbose output
93/// Fitter. Note that these must match the set used in the Fitter used by \p context! It can be passed in from
94/// RooMinimizer with fitter()->Config().ParamsSettings().
95MinuitFcnGrad::MinuitFcnGrad(const std::shared_ptr<RooFit::TestStatistics::RooAbsL> &absL, RooMinimizer *context,
96 std::vector<ROOT::Fit::ParameterSettings> &parameters, LikelihoodMode likelihoodMode,
98 : RooAbsMinimizerFcn(*absL->getParameters(), context), _minuitInternalX(getNDim(), 0), _minuitExternalX(getNDim(), 0)
99{
100 synchronizeParameterSettings(parameters);
101
102 _calculationIsClean = std::make_unique<WrapperCalculationCleanFlags>();
103
105
106 if (likelihoodMode == LikelihoodMode::multiprocess &&
111 } else {
114 }
115
118
119 applyToLikelihood([&](auto &l) { l.synchronizeParameterSettings(parameters); });
120 _gradient->synchronizeParameterSettingsImpl(parameters);
121
122 // Note: can be different than RooGradMinimizerFcn/LikelihoodGradientSerial, where default options are passed
123 // (ROOT::Math::MinimizerOptions::DefaultStrategy() and ROOT::Math::MinimizerOptions::DefaultErrorDef())
124 applyToLikelihood([&](auto &l) { l.synchronizeWithMinimizer(ROOT::Math::MinimizerOptions()); });
125 _gradient->synchronizeWithMinimizer(ROOT::Math::MinimizerOptions());
126}
127
128/// Make sure the offsets are up to date
129///
130/// If the offsets need to be updated, this function triggers a likelihood evaluation.
131/// The likelihood will make sure the offset is set correctly in their shared_ptr
132/// offsets object, that is also shared with possible other LikelihoodWrapper members
133/// of MinuitFcnGrad and also the LikelihoodGradientWrapper member. Other necessary
134/// synchronization steps are also performed from the Wrapper child classes (e.g.
135/// sending the values to workers from MultiProcess::Jobs).
137{
138 if (_likelihood->isOffsetting() && (_evalCounter == 0 || offsets_reset_)) {
139 _likelihoodInGradient->evaluate();
140 offsets_reset_ = false;
141 }
142}
143
144double MinuitFcnGrad::operator()(const double *x) const
145{
147
148 syncOffsets();
149
150 // Calculate the function for these parameters
152 likelihoodHere.evaluate();
153 double fvalue = likelihoodHere.getResult().Sum();
154 _calculationIsClean->likelihood = true;
155
157
158 // Optional logging
159 if (cfg().verbose) {
160 std::cout << "\nprevFCN" << (likelihoodHere.isOffsetting() ? "-offset" : "") << " = " << std::setprecision(10)
161 << fvalue << std::setprecision(4) << " ";
162 std::cout.flush();
163 }
164
165 finishDoEval();
166 return fvalue;
167}
168
169/// Minuit calls (via FcnAdapters etc) DoEval or Gradient with a set of parameters x.
170/// This function syncs these values to the proper places in RooFit.
171///
172/// The first twist, and reason this function is more complicated than one may imagine, is that Minuit internally uses a
173/// transformed parameter space to account for parameter boundaries. Whether we receive these Minuit "internal"
174/// parameter values or "regular"/untransformed RooFit parameter space values depends on the situation.
175/// - The values that arrive here via DoEval are always "normal" parameter values, since Minuit transforms these
176/// back into regular space before passing to DoEval (see MnUserFcn::operator() which wraps the Fcn(Gradient)Base
177/// in ModularFunctionMinimizer::Minimize and is used for direct function calls from that point on in the minimizer).
178/// These can thus always be safely synced with this function's RooFit parameters using SetPdfParamVal.
179/// - The values that arrive here via Gradient will be in internal coordinates if that is
180/// what this class expects, and indeed this is the case for MinuitFcnGrad's current implementation. This is
181/// communicated to Minuit via MinuitFcnGrad::returnsInMinuit2ParameterSpace. Inside Minuit, that function determines
182/// whether this class's gradient calculator is wrapped inside a AnalyticalGradientCalculator, to which Minuit passes
183/// "external" parameter values, or as an ExternalInternalGradientCalculator, which gets "internal" parameter values.
184/// Long story short: when MinuitFcnGrad::returnsInMinuit2ParameterSpace() returns true, Minuit will pass "internal"
185/// values to Gradient. These cannot be synced with this function's RooFit parameters using
186/// SetPdfParamVal, unless a manual transformation step is performed in advance. However, they do need to be passed
187/// on to the gradient calculator, since indeed we expect values there to be in "internal" space. However, this is
188/// calculator dependent. Note that in the current MinuitFcnGrad implementation we do not actually allow for
189/// calculators in "external" (i.e. regular RooFit parameter space) values, since
190/// MinuitFcnGrad::returnsInMinuit2ParameterSpace is hardcoded to true. This should in a future version be changed so
191/// that the calculator (the wrapper) is queried for this information.
192/// Because some gradient calculators may also use the regular RooFit parameters (e.g. for calculating the likelihood's
193/// value itself), this information is also passed on to the gradient wrapper. Vice versa, when updated "internal"
194/// parameters are passed to Gradient, the likelihood may be affected as well. Even though a
195/// transformation from internal to "external" may be necessary before the values can be used, the likelihood can at
196/// least log that its parameter values are possibly no longer in sync with those of the gradient.
197///
198/// The second twist is that the Minuit external parameters may still be different from the ones used in RooFit. This
199/// happens when Minuit tries out values that lay outside the RooFit parameter's range(s). RooFit's setVal (called
200/// inside SetPdfParamVal) then clips the RooAbsArg's value to one of the range limits, instead of setting it to the
201/// value Minuit intended. When this happens, i.e. syncParameterValuesFromMinuitCalls is called with
202/// minuit_internal = false and the values do not match the previous values stored in _minuitInternalX *but* the
203/// values after SetPdfParamVal did not get set to the intended value, the _minuitInternalRooFitXMismatch flag is
204/// set. This information can be used by calculators, if desired, for instance when a calculator does not want to make
205/// use of the range information in the RooAbsArg parameters.
207{
208 bool aParamWasUpdated = false;
209 if (minuit_internal) {
211 throw std::logic_error("Updating Minuit-internal parameters only makes sense for (gradient) calculators that "
212 "are defined in Minuit-internal parameter space.");
213 }
214
215 for (std::size_t ix = 0; ix < getNDim(); ++ix) {
216 bool parameter_changed = (x[ix] != _minuitInternalX[ix]);
217 if (parameter_changed) {
218 _minuitInternalX[ix] = x[ix];
219 }
221 }
222
223 if (aParamWasUpdated) {
224 _calculationIsClean->set_all(false);
225 applyToLikelihood([&](auto &l) { l.updateMinuitInternalParameterValues(_minuitInternalX); });
226 _gradient->updateMinuitInternalParameterValues(_minuitInternalX);
227 }
228 } else {
229 bool aParamIsMismatched = false;
230
231 for (std::size_t ix = 0; ix < getNDim(); ++ix) {
232 // Note: the return value of SetPdfParamVal does not always mean that the parameter's value in the RooAbsReal
233 // changed since last time! If the value was out of range bin, setVal was still called, but the value was not
234 // updated.
235 SetPdfParamVal(ix, x[ix]);
236 _minuitExternalX[ix] = x[ix];
237 // The above is why we need _minuitExternalX. The _minuitExternalX vector can also be passed to
238 // LikelihoodWrappers, if needed, but typically they will make use of the RooFit parameters directly. However,
239 // we log in the flag below whether they are different so that calculators can use this information.
240 bool parameter_changed = (x[ix] != _minuitExternalX[ix]);
242 aParamIsMismatched |= (floatableParam(ix).getVal() != _minuitExternalX[ix]);
243 }
244
246
247 if (aParamWasUpdated) {
248 _calculationIsClean->set_all(false);
249 applyToLikelihood([&](auto &l) { l.updateMinuitExternalParameterValues(_minuitExternalX); });
250 _gradient->updateMinuitExternalParameterValues(_minuitExternalX);
251 }
252 }
253 return aParamWasUpdated;
254}
255
256void MinuitFcnGrad::Gradient(const double *x, double *grad) const
257{
260 syncOffsets();
261 _gradient->fillGradient(grad);
262 _calculatingGradient = false;
263}
264
265void MinuitFcnGrad::GradientWithPrevResult(const double *x, double *grad, double *previous_grad, double *previous_g2,
266 double *previous_gstep, double fValAtX) const
267{
270 syncOffsets();
271 _gradient->fillGradientWithPrevResult(grad, previous_grad, previous_g2, previous_gstep, fValAtX);
272 _calculatingGradient = false;
273}
274
275bool MinuitFcnGrad::Synchronize(std::vector<ROOT::Fit::ParameterSettings> &parameters)
276{
277 bool returnee = synchronizeParameterSettings(parameters);
278 applyToLikelihood([&](auto &l) { l.synchronizeParameterSettings(parameters); });
279 _gradient->synchronizeParameterSettings(parameters);
280
281 applyToLikelihood([&](auto &l) { l.synchronizeWithMinimizer(_context->fitter()->Config().MinimizerOptions()); });
282 _gradient->synchronizeWithMinimizer(_context->fitter()->Config().MinimizerOptions());
283 return returnee;
284}
285
287{
288 auto &minuit = dynamic_cast<ROOT::Minuit2::Minuit2Minimizer &>(minim);
289 minuit.SetFCN(getNDim(), std::make_unique<MinuitGradFunctor>(*this, minim.ErrorDef()));
290}
291
292} // namespace TestStatistics
293} // namespace RooFit
MinuitFcnGrad const & _fcn
double _up
ROOT::Detail::TRangeCast< T, true > TRangeDynCast
TRangeDynCast is an adapter class that allows the typed iteration through a TCollection.
Abstract Minimizer class, defining the interface for the various minimizer (like Minuit2,...
Definition Minimizer.h:124
Interface (abstract class) defining the function to be minimized, which has to be implemented by the ...
Definition FCNBase.h:37
Minuit2Minimizer class implementing the ROOT::Math::Minimizer interface for Minuit2 minimization algo...
static std::unique_ptr< LikelihoodGradientWrapper > create(LikelihoodGradientMode likelihoodGradientMode, std::shared_ptr< RooAbsL > likelihood, std::shared_ptr< WrapperCalculationCleanFlags > calculationIsClean, std::size_t nDim, RooMinimizer *minimizer, SharedOffset offset)
Factory method.
static std::unique_ptr< LikelihoodWrapper > create(LikelihoodMode likelihoodMode, std::shared_ptr< RooAbsL > likelihood, std::shared_ptr< WrapperCalculationCleanFlags > calculationIsClean, SharedOffset offset)
Factory method.
double operator()(const double *x) const
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
std::shared_ptr< LikelihoodWrapper > _likelihood
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
void syncOffsets() const
Make sure the offsets are up to date.
std::shared_ptr< WrapperCalculationCleanFlags > _calculationIsClean
std::vector< double > _minuitExternalX
void initMinimizer(ROOT::Math::Minimizer &, RooMinimizer *context) override
std::shared_ptr< LikelihoodWrapper > _likelihoodInGradient
void applyToLikelihood(Func &&func) const
void GradientWithPrevResult(const double *x, double *grad, double *previous_grad, double *previous_g2, double *previous_gstep, double fValAtX) 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::Math::Minimizer that provides a seamless interface between the minimizer f...
Double_t x[n]
Definition legend1.C:17
GradientParameterSpace
Definition FCNBase.h:28
The namespace RooFit contains mostly switches that change the behaviour of functions of PDFs (or othe...
Definition CodegenImpl.h:73
T * Gradient(Long64_t n, T const *f, double h=1)
Calculate the one-dimensional finite gradient of an array with length n.
Definition TMath.h:1033
TLine l
Definition textangle.C:4