Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
RooMinimizerFcn.cxx
Go to the documentation of this file.
1/// \cond ROOFIT_INTERNAL
2
3/*****************************************************************************
4 * Project: RooFit *
5 * Package: RooFitCore *
6 * @(#)root/roofitcore:$Id$
7 * Authors: *
8 * AL, Alfio Lazzaro, INFN Milan, alfio.lazzaro@mi.infn.it *
9 * PB, Patrick Bos, Netherlands eScience Center, p.bos@esciencecenter.nl *
10 * *
11 * *
12 * Redistribution and use in source and binary forms, *
13 * with or without modification, are permitted according to the terms *
14 * listed in LICENSE (http://roofit.sourceforge.net/license.txt) *
15 *****************************************************************************/
16
17//////////////////////////////////////////////////////////////////////////////
18/// \class RooMinimizerFcn
19/// RooMinimizerFcn is an interface to the ROOT::Math::IBaseFunctionMultiDim,
20/// a function that ROOT's minimisers use to carry out minimisations.
21///
22
23#include "RooMinimizerFcn.h"
24
25#include "RooAbsArg.h"
26#include "RooAbsPdf.h"
27#include "RooAddition.h"
28#include "RooArgSet.h"
29#include "RooConstraintSum.h"
30#include "RooEvaluatorWrapper.h"
31#include "RooMinimizer.h"
32#include "RooMsgService.h"
33#include "RooNaNPacker.h"
34#include "RooCategory.h"
35#include "RooRealVar.h"
36
37#include "Math/Functor.h"
39#include "TMatrixDSym.h"
40
41#include <fstream>
42#include <iomanip>
43#include <unordered_map>
44#include <vector>
45
46using std::setprecision;
47
48namespace {
49
50// Check whether two sorted ranges have at least one element in common.
51// Like std::set_intersection, both input ranges must be sorted; the early
52// return on the first match keeps the common case cheap.
53template <class InputIt1, class InputIt2>
55{
56 while (first1 != last1 && first2 != last2) {
57 if (*first1 < *first2) {
58 ++first1;
59 continue;
60 }
61 if (*first2 < *first1) {
62 ++first2;
63 continue;
64 }
65 return true;
66 }
67 return false;
68}
69
70// Helper function that wraps RooAbsArg::getParameters and directly returns the
71// output RooArgSet. To be used in the initializer list of the RooMinimizerFcn
72// constructor. In the case of figuring out all parameters for the minimizer,
73// we don't want to strip disconnected parameters, becuase which parameters are
74// disconnected can change between minimization runs.
76{
77 RooArgSet out;
78 funct.getParameters(nullptr, out, /*stripDisconnected*/ false);
79 return out;
80}
81
82// Groups of computation-graph leaves by the additive term of the minimized
83// function they appear in. Two parameters that share no term index have a
84// mixed second derivative that is identically zero.
85//
86// Note that distinct objects with the same name share their pointer from the
87// name registry, so they end up merged in the same map entry. This is
88// conservative: it can only add co-occurrences, never remove any.
89struct VariableGroups {
90 /// For each leaf (keyed by its unique name pointer), the sorted list of
91 /// indices of the additive terms it appears in.
92 std::unordered_map<TNamed const *, std::vector<int>> groups;
93
94 /// Register one additive term: record for every leaf in the collection
95 /// that it appears in this term.
97 {
98 for (RooAbsArg const *arg : leaves) {
99 groups[arg->namePtr()].push_back(_nextIndex);
100 }
101 ++_nextIndex;
102 }
103
104private:
105 int _nextIndex = 0;
106};
107
108// Fill the map from computation-graph leaves to the additive terms of the
109// minimized function they appear in.
110//
111// Recursing into the components of a node is only correct if the value of
112// the node is a *strictly additive* combination of them: recursing into
113// anything else would wrongly advertise vanishing second derivatives and
114// silently corrupt Hessian results. Any other node contributes all of its
115// leaves as one single term, which advertises no independence but is always
116// correct.
117void fillVariableGroups(RooAbsArg const &arg, VariableGroups &out)
118{
119 if (auto addition = dynamic_cast<RooAddition const *>(&arg)) {
120 for (RooAbsArg *component : addition->list()) {
122 }
123 return;
124 }
125 if (auto constraintSum = dynamic_cast<RooConstraintSum const *>(&arg)) {
126 for (RooAbsArg *component : constraintSum->list()) {
128 }
129 return;
130 }
131 if (auto wrapper = dynamic_cast<RooFit::Experimental::RooEvaluatorWrapper const *>(&arg)) {
132 fillVariableGroups(wrapper->topNode(), out);
133 return;
134 }
135
136 // Get the set of leaves in the computation graph. Do the detour via
137 // RooArgList to avoid deduplication done after adding each element.
140 arg.treeNodeServerList(&leafList, nullptr, /*branches*/ false, /*leaves*/ true, /*valueOnly*/ false,
141 /*recurseFundamental*/ true);
143 out.registerTerm(leafSet);
144}
145
146} // namespace
147
148// use reference wrapper for the Functor, such that the functor points to this RooMinimizerFcn by reference.
149RooMinimizerFcn::RooMinimizerFcn(RooAbsReal *funct, RooMinimizer *context)
151{
152 unsigned int nDim = getNDim();
153
154 if (context->_cfg.useGradient && funct->hasGradient()) {
155 _gradientOutput.resize(_allParams.size());
156 _multiGenFcn = std::make_unique<ROOT::Math::GradFunctor>(this, &RooMinimizerFcn::operator(),
157 &RooMinimizerFcn::evaluateGradient, nDim);
158 } else {
159 _multiGenFcn = std::make_unique<ROOT::Math::Functor>(std::cref(*this), nDim);
160 }
161 if (context->_cfg.useHessian) {
162 _hessianOutput.resize(_allParams.size() * _allParams.size());
163 }
164}
165
166/// Evaluate function given the parameters in `x`.
167double RooMinimizerFcn::operator()(const double *x) const
168{
169 // Set the parameter values for this iteration
170 for (unsigned index = 0; index < getNDim(); index++) {
171 if (_logfile)
172 (*_logfile) << x[index] << " ";
174 }
175
176 // Calculate the function for these parameters
178 double fvalue = _funct->getVal();
180
182
183 // Optional logging
184 if (_logfile)
185 (*_logfile) << setprecision(15) << fvalue << setprecision(4) << std::endl;
186 if (cfg().verbose) {
187 std::cout << "\nprevFCN" << (_funct->isOffsetting() ? "-offset" : "") << " = " << setprecision(10) << fvalue
188 << setprecision(4) << " ";
189 std::cout.flush();
190 }
191
192 finishDoEval();
193
194 return fvalue;
195}
196
197void RooMinimizerFcn::evaluateGradient(const double *x, double *out) const
198{
199 // Set the parameter values for this iteration
200 for (unsigned index = 0; index < getNDim(); index++) {
201 if (_logfile)
202 (*_logfile) << x[index] << " ";
204 }
205
206 _funct->gradient(_gradientOutput.data());
207
208 std::size_t iAll = 0;
209 std::size_t iFloating = 0;
210 for (RooAbsArg *param : _allParamsInit) {
211 if (!treatAsConstant(*param)) {
213 ++iFloating;
214 }
215 ++iAll;
216 }
217
218 // Optional logging
219 if (cfg().verbose) {
220 std::cout << "\n gradient = ";
221 for (std::size_t i = 0; i < getNDim(); ++i) {
222 std::cout << out[i] << ", ";
223 }
224 }
225}
226
227std::string RooMinimizerFcn::getFunctionName() const
228{
229 return _funct->GetName();
230}
231
232std::string RooMinimizerFcn::getFunctionTitle() const
233{
234 return _funct->GetTitle();
235}
236
237void RooMinimizerFcn::setOffsetting(bool flag)
238{
239 _funct->enableOffsetting(flag);
240}
241
242RooArgSet RooMinimizerFcn::freezeDisconnectedParameters() const
243{
244
247
248 _funct->getParameters(nullptr, paramsDisconnected, /*stripDisconnected*/ false);
249 _funct->getParameters(nullptr, paramsConnected, /*stripDisconnected*/ true);
250
251 paramsDisconnected.remove(paramsConnected, true, true);
252
254
256 auto *v = dynamic_cast<RooRealVar *>(a);
257 auto *cv = dynamic_cast<RooCategory *>(a);
258 if (v && !v->isConstant()) {
259 v->setConstant();
260 changedSet.add(*v);
261 } else if (cv && !cv->isConstant()) {
262 cv->setConstant();
263 changedSet.add(*cv);
264 }
265 }
266
267 return changedSet;
268}
269
270bool RooMinimizerFcn::evaluateHessian(std::span<const double> x, double *out) const
271{
272 // Set the parameter values for this iteration
273 for (unsigned index = 0; index < getNDim(); index++) {
274 if (_logfile)
275 (*_logfile) << x[index] << " ";
277 }
278
279 _funct->hessian(_hessianOutput.data());
280
281 std::size_t m = _allParamsInit.size();
282 std::size_t n = getNDim();
283 std::size_t iAll = 0;
284 std::size_t iFloating = 0;
286 if (!treatAsConstant(*param_i)) {
287 std::size_t jAll = 0;
288 std::size_t jFloating = 0;
290 if (!treatAsConstant(*param_j)) {
291 out[iFloating * n + jFloating] = _hessianOutput[iAll * m + jAll];
292 ++jFloating;
293 }
294 ++jAll;
295 }
296 ++iFloating;
297 }
298 ++iAll;
299 }
300
301 // Optional logging
302 if (cfg().verbose) {
303 std::cout << "\n hessian = " << std::endl;
304 for (std::size_t i = 0; i < getNDim(); ++i) {
305 for (std::size_t j = 0; j < getNDim(); ++j) {
306 std::cout << out[i * n + j] << ", ";
307 }
308 std::cout << std::endl;
309 }
310 }
311 return true;
312}
313
314void RooMinimizerFcn::initMinimizer(ROOT::Math::Minimizer &minim, RooMinimizer *context)
315{
316 minim.SetFunction(*_multiGenFcn);
317 if (context->_cfg.useHessian && _funct->hasHessian()) {
318 minim.SetHessianFunction(
319 std::bind(&RooMinimizerFcn::evaluateHessian, this, std::placeholders::_1, std::placeholders::_2));
320 }
321 // The independence information for skipping vanishing second derivatives
322 // in numerical Hessian computations is a Minuit2-only feature, so it is
323 // wired up directly with the concrete minimizer type instead of going
324 // through the ROOT::Math::Minimizer interface.
325 if (auto *minuit2 = dynamic_cast<ROOT::Minuit2::Minuit2Minimizer *>(&minim)) {
326 minuit2->SetSecondDerivativeAlwaysVanishesFunc(
327 [this](unsigned int i, unsigned int j) { return secondDerivativeAlwaysVanishes(i, j); });
328 }
329}
330
331////////////////////////////////////////////////////////////////////////////////
332/// Fill the bitvector that flags for each pair of floatable parameters
333/// whether they appear together in at least one additive term of the
334/// minimized function, i.e. whether their mixed second derivative can be
335/// non-vanishing. Built lazily because it is only needed for Hessian
336/// evaluations, and building it for models with many parameters is not free.
337void RooMinimizerFcn::buildSecondDerivMask() const
338{
341
342 std::size_t nParams = getNDim();
343
344 // Packed bitvector: bit set means the parameter pair shares an additive
345 // term, so the mixed second derivative can be non-zero.
346 _secondDerivMask.assign(nParams * nParams, false);
347 for (std::size_t i = 0; i < nParams; ++i) {
348 _secondDerivMask[nParams * i + i] = true;
349 auto found1 = groups.groups.find(floatableParam(i).namePtr());
350 for (std::size_t j = 0; j < i; ++j) {
351 auto found2 = groups.groups.find(floatableParam(j).namePtr());
352 // A parameter that was not seen in the computation graph traversal
353 // is conservatively treated as intersecting with everything.
354 bool canBeNonZero = found1 == groups.groups.end() || found2 == groups.groups.end() ||
355 intersect(found1->second.begin(), found1->second.end(), found2->second.begin(),
356 found2->second.end());
359 }
360 }
361}
362
363////////////////////////////////////////////////////////////////////////////////
364/// Report whether the second derivative with respect to parameters i and j
365/// (indices in the space of all floatable parameters, matching Minuit's
366/// external parameter indices) is identically zero because the parameters
367/// share no additive term of the minimized function.
368bool RooMinimizerFcn::secondDerivativeAlwaysVanishes(unsigned int i, unsigned int j) const
369{
370 std::call_once(_secondDerivMaskOnce, &RooMinimizerFcn::buildSecondDerivMask, this);
371 return !_secondDerivMask[getNDim() * i + j];
372}
373
374/// \endcond
#define a(i)
Definition RSha256.hxx:99
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 char Point_t Rectangle_t WindowAttributes_t index
Abstract Minimizer class, defining the interface for the various minimizer (like Minuit2,...
Definition Minimizer.h:124
Minuit2Minimizer class implementing the ROOT::Math::Minimizer interface for Minuit2 minimization algo...
const_iterator begin() const
const_iterator end() const
Common abstract base class for objects that represent a value and a "shape" in RooFit.
Definition RooAbsArg.h:76
void treeNodeServerList(RooAbsCollection *list, const RooAbsArg *arg=nullptr, bool doBranch=true, bool doLeaf=true, bool valueOnly=false, bool recurseNonDerived=false) const
Fill supplied list with nodes of the arg tree, following all server links, starting with ourself as t...
void setConstant(bool value=true)
Abstract container object that can hold multiple RooAbsArg objects.
Abstract base class for objects that represent a real value and implements functionality common to al...
Definition RooAbsReal.h:63
static void setHideOffset(bool flag)
Calculates the sum of a set of RooAbsReal terms, or when constructed with two sets,...
Definition RooAddition.h:27
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
Object to represent discrete states.
Definition RooCategory.h:28
Calculates the sum of the -(log) likelihoods of a set of RooAbsPfs that represent constraint function...
Wrapper class around ROOT::Math::Minimizer that provides a seamless interface between the minimizer f...
RooMinimizer::Config _cfg
Variable that can be changed from the outside.
Definition RooRealVar.h:37
Double_t x[n]
Definition legend1.C:17
const Int_t n
Definition legend1.C:16
double constraintSum(DoubleArray comp, unsigned int compSize)
Definition MathFuncs.h:180
TMarker m
Definition textangle.C:8