1 // @(#)root/mathcore:$Id$
2 // Authors: L. Moneta, J.T. Offermann, E.G.P. Bos 2013-2018
3 //
4 /**********************************************************************
5 * *
6 * Copyright (c) 2013 , LCG ROOT MathLib Team *
7 * *
8 **********************************************************************/
9 /**
10 * \class NumericalDerivator
11 *
12 * Original version created on: Aug 14, 2013
13 * Authors: L. Moneta, J. T. Offermann
14 * Modified version created on: Sep 27, 2017
15 * Author: E. G. P. Bos
16 *
17 * NumericalDerivator was essentially a slightly modified copy of code
18 * written by M. Winkler, F. James, L. Moneta, and A. Zsenei for Minuit2,
19 * Copyright (c) 2005 LCG ROOT Math team, CERN/PH-SFT. Original version:
20 * https://github.com/lmoneta/root/blob/lvmini/math/mathcore/src/NumericalDerivator.cxx
21 *
22 * This class attempts to more closely follow the Minuit2 implementation.
23 * Modified things (w.r.t. original) are indicated by MODIFIED.
24 */
25
26 #include "Minuit2/NumericalDerivator.h"
27 #include <cmath>
28 #include <algorithm>
29 #include <Math/IFunction.h>
30 #include <iostream>
31 #include <TMath.h>
32 #include <cassert>
33 #include "Fit/ParameterSettings.h"
34
35 #include <Math/Minimizer.h> // needed here because in Fitter is only a forward declaration
36
37 namespace ROOT {
38 namespace Minuit2 {
39
NumericalDerivator(bool always_exactly_mimic_minuit2)40 NumericalDerivator::NumericalDerivator(bool always_exactly_mimic_minuit2)
41 : fAlwaysExactlyMimicMinuit2(always_exactly_mimic_minuit2)
42 {
43 }
44
NumericalDerivator(double step_tolerance,double grad_tolerance,unsigned int ncycles,double error_level,bool always_exactly_mimic_minuit2)45 NumericalDerivator::NumericalDerivator(double step_tolerance, double grad_tolerance, unsigned int ncycles,
46 double error_level, bool always_exactly_mimic_minuit2)
47 : fStepTolerance(step_tolerance), fGradTolerance(grad_tolerance), fUp(error_level), fNCycles(ncycles),
48 fAlwaysExactlyMimicMinuit2(always_exactly_mimic_minuit2)
49 {
50 }
51
52 NumericalDerivator::NumericalDerivator(const NumericalDerivator &/*other*/) = default;
53
54
55 /// This function sets internal state based on input parameters. This state
56 /// setup is used in the actual (partial) derivative calculations.
SetupDifferentiate(const ROOT::Math::IBaseFunctionMultiDim * function,const double * cx,const std::vector<ROOT::Fit::ParameterSettings> & parameters)57 void NumericalDerivator::SetupDifferentiate(const ROOT::Math::IBaseFunctionMultiDim *function, const double *cx,
58 const std::vector<ROOT::Fit::ParameterSettings> ¶meters)
59 {
60 assert(function != nullptr && "function is a nullptr");
61
62 fVx.resize(function->NDim());
63 fVxExternal.resize(function->NDim());
64 fVxFValCache.resize(function->NDim());
65 std::copy(cx, cx + function->NDim(), fVx.data());
66
67 // convert to Minuit external parameters
68 for (unsigned i = 0; i < function->NDim(); i++) {
69 fVxExternal[i] = Int2ext(parameters[i], fVx[i]);
70 }
71
72 if (fVx != fVxFValCache) {
73 fVxFValCache = fVx;
74 fVal = (*function)(fVxExternal.data()); // value of function at given points
75 }
76
77 fDfmin = 8. * fPrecision.Eps2() * (std::abs(fVal) + fUp);
78 fVrysml = 8. * fPrecision.Eps() * fPrecision.Eps();
79 }
80
PartialDerivative(const ROOT::Math::IBaseFunctionMultiDim * function,const double * x,const std::vector<ROOT::Fit::ParameterSettings> & parameters,unsigned int i_component,DerivatorElement previous)81 DerivatorElement NumericalDerivator::PartialDerivative(const ROOT::Math::IBaseFunctionMultiDim *function,
82 const double *x,
83 const std::vector<ROOT::Fit::ParameterSettings> ¶meters,
84 unsigned int i_component, DerivatorElement previous)
85 {
86 SetupDifferentiate(function, x, parameters);
87 return FastPartialDerivative(function, parameters, i_component, previous);
88 }
89
90 // leaves the parameter setup to the caller
FastPartialDerivative(const ROOT::Math::IBaseFunctionMultiDim * function,const std::vector<ROOT::Fit::ParameterSettings> & parameters,unsigned int i_component,const DerivatorElement & previous)91 DerivatorElement NumericalDerivator::FastPartialDerivative(const ROOT::Math::IBaseFunctionMultiDim *function,
92 const std::vector<ROOT::Fit::ParameterSettings> ¶meters,
93 unsigned int i_component, const DerivatorElement &previous)
94 {
95 DerivatorElement deriv{previous};
96
97 double xtf = fVx[i_component];
98 double epspri = fPrecision.Eps2() + std::abs(deriv.derivative * fPrecision.Eps2());
99 double step_old = 0.;
100
101 for (unsigned int j = 0; j < fNCycles; ++j) {
102 double optstp = std::sqrt(fDfmin / (std::abs(deriv.second_derivative) + epspri));
103 double step = std::max(optstp, std::abs(0.1 * deriv.step_size));
104
105 if (parameters[i_component].IsBound()) {
106 if (step > 0.5)
107 step = 0.5;
108 }
109
110 double stpmax = 10. * std::abs(deriv.step_size);
111 if (step > stpmax)
112 step = stpmax;
113
114 double stpmin = std::max(fVrysml, 8. * std::abs(fPrecision.Eps2() * fVx[i_component]));
115 if (step < stpmin)
116 step = stpmin;
117 if (std::abs((step - step_old) / step) < fStepTolerance) {
118 break;
119 }
120 deriv.step_size = step;
121 step_old = step;
122 fVx[i_component] = xtf + step;
123 fVxExternal[i_component] = Int2ext(parameters[i_component], fVx[i_component]);
124 double fs1 = (*function)(fVxExternal.data());
125
126 fVx[i_component] = xtf - step;
127 fVxExternal[i_component] = Int2ext(parameters[i_component], fVx[i_component]);
128 double fs2 = (*function)(fVxExternal.data());
129
130 fVx[i_component] = xtf;
131 fVxExternal[i_component] = Int2ext(parameters[i_component], fVx[i_component]);
132
133 double fGrd_old = deriv.derivative;
134 deriv.derivative = 0.5 * (fs1 - fs2) / step;
135
136 deriv.second_derivative = (fs1 + fs2 - 2. * fVal) / step / step;
137
138 if (std::abs(fGrd_old - deriv.derivative) / (std::abs(deriv.derivative) + fDfmin / step) < fGradTolerance) {
139 break;
140 }
141 }
142 return deriv;
143 }
144
operator ()(const ROOT::Math::IBaseFunctionMultiDim * function,const double * x,const std::vector<ROOT::Fit::ParameterSettings> & parameters,unsigned int i_component,const DerivatorElement & previous)145 DerivatorElement NumericalDerivator::operator()(const ROOT::Math::IBaseFunctionMultiDim *function, const double *x,
146 const std::vector<ROOT::Fit::ParameterSettings> ¶meters,
147 unsigned int i_component, const DerivatorElement &previous)
148 {
149 return PartialDerivative(function, x, parameters, i_component, previous);
150 }
151
152 std::vector<DerivatorElement>
Differentiate(const ROOT::Math::IBaseFunctionMultiDim * function,const double * cx,const std::vector<ROOT::Fit::ParameterSettings> & parameters,const std::vector<DerivatorElement> & previous_gradient)153 NumericalDerivator::Differentiate(const ROOT::Math::IBaseFunctionMultiDim *function, const double *cx,
154 const std::vector<ROOT::Fit::ParameterSettings> ¶meters,
155 const std::vector<DerivatorElement> &previous_gradient)
156 {
157 SetupDifferentiate(function, cx, parameters);
158
159 std::vector<DerivatorElement> gradient;
160 gradient.reserve(function->NDim());
161
162 for (unsigned int ix = 0; ix < function->NDim(); ++ix) {
163 gradient.emplace_back(FastPartialDerivative(function, parameters, ix, previous_gradient[ix]));
164 }
165
166 return gradient;
167 }
168
Int2ext(const ROOT::Fit::ParameterSettings & parameter,double val) const169 double NumericalDerivator::Int2ext(const ROOT::Fit::ParameterSettings ¶meter, double val) const
170 {
171 // return external value from internal value for parameter i
172 if (parameter.IsBound()) {
173 if (parameter.IsDoubleBound()) {
174 return fDoubleLimTrafo.Int2ext(val, parameter.UpperLimit(), parameter.LowerLimit());
175 } else if (parameter.HasUpperLimit() && !parameter.HasLowerLimit()) {
176 return fUpperLimTrafo.Int2ext(val, parameter.UpperLimit());
177 } else {
178 return fLowerLimTrafo.Int2ext(val, parameter.LowerLimit());
179 }
180 }
181
182 return val;
183 }
184
Ext2int(const ROOT::Fit::ParameterSettings & parameter,double val) const185 double NumericalDerivator::Ext2int(const ROOT::Fit::ParameterSettings ¶meter, double val) const
186 {
187 // return the internal value for parameter i with external value val
188
189 if (parameter.IsBound()) {
190 if (parameter.IsDoubleBound()) {
191 return fDoubleLimTrafo.Ext2int(val, parameter.UpperLimit(), parameter.LowerLimit(), fPrecision);
192 } else if (parameter.HasUpperLimit() && !parameter.HasLowerLimit()) {
193 return fUpperLimTrafo.Ext2int(val, parameter.UpperLimit(), fPrecision);
194 } else {
195 return fLowerLimTrafo.Ext2int(val, parameter.LowerLimit(), fPrecision);
196 }
197 }
198
199 return val;
200 }
201
DInt2Ext(const ROOT::Fit::ParameterSettings & parameter,double val) const202 double NumericalDerivator::DInt2Ext(const ROOT::Fit::ParameterSettings ¶meter, double val) const
203 {
204 // return the derivative of the int->ext transformation: dPext(i) / dPint(i)
205 // for the parameter i with value val
206
207 double dd = 1.;
208 if (parameter.IsBound()) {
209 if (parameter.IsDoubleBound()) {
210 dd = fDoubleLimTrafo.DInt2Ext(val, parameter.UpperLimit(), parameter.LowerLimit());
211 } else if (parameter.HasUpperLimit() && !parameter.HasLowerLimit()) {
212 dd = fUpperLimTrafo.DInt2Ext(val, parameter.UpperLimit());
213 } else {
214 dd = fLowerLimTrafo.DInt2Ext(val, parameter.LowerLimit());
215 }
216 }
217
218 return dd;
219 }
220
221 // MODIFIED:
222 /// This function was not implemented as in Minuit2. Now it copies the behavior
223 /// of InitialGradientCalculator. See https://github.com/roofit-dev/root/issues/10
SetInitialGradient(const ROOT::Math::IBaseFunctionMultiDim *,const std::vector<ROOT::Fit::ParameterSettings> & parameters,std::vector<DerivatorElement> & gradient)224 void NumericalDerivator::SetInitialGradient(const ROOT::Math::IBaseFunctionMultiDim *,
225 const std::vector<ROOT::Fit::ParameterSettings> ¶meters,
226 std::vector<DerivatorElement> &gradient)
227 {
228 // set an initial gradient using some given steps
229 // (used in the first iteration)
230
231 double eps2 = fPrecision.Eps2();
232
233 unsigned ix = 0;
234 for (auto parameter = parameters.begin(); parameter != parameters.end(); ++parameter, ++ix) {
235 // What Minuit2 calls "Error" is stepsize on the ROOT side.
236 double werr = parameter->StepSize();
237
238 // Actually, sav in Minuit2 is the external parameter value, so that is
239 // what we called var before and var is unnecessary here.
240 double sav = parameter->Value();
241
242 // However, we do need var below, so let's calculate it using Ext2int:
243 double var = Ext2int(*parameter, sav);
244
245 if (fAlwaysExactlyMimicMinuit2) {
246 // this transformation can lose a few bits, but Minuit2 does it too
247 sav = Int2ext(*parameter, var);
248 }
249
250 double sav2 = sav + werr;
251
252 if (parameter->HasUpperLimit() && sav2 > parameter->UpperLimit()) {
253 sav2 = parameter->UpperLimit();
254 }
255
256 double var2 = Ext2int(*parameter, sav2);
257 double vplu = var2 - var;
258
259 sav2 = sav - werr;
260
261 if (parameter->HasLowerLimit() && sav2 < parameter->LowerLimit()) {
262 sav2 = parameter->LowerLimit();
263 }
264
265 var2 = Ext2int(*parameter, sav2);
266 double vmin = var2 - var;
267 double gsmin = 8. * eps2 * (std::abs(var) + eps2);
268 // protect against very small step sizes which can cause dirin to zero and then nan values in grd
269 double dirin = std::max(0.5 * (std::abs(vplu) + std::abs(vmin)), gsmin);
270 double g2 = 2.0 * fUp / (dirin * dirin);
271 double gstep = std::max(gsmin, 0.1 * dirin);
272 double grd = g2 * dirin;
273
274 if (parameter->IsBound()) {
275 gstep = std::min(gstep, 0.5);
276 }
277
278 gradient[ix].derivative = grd;
279 gradient[ix].second_derivative = g2;
280 gradient[ix].step_size = gstep;
281 }
282 }
283
operator <<(std::ostream & out,const DerivatorElement & value)284 std::ostream &operator<<(std::ostream &out, const DerivatorElement &value)
285 {
286 return out << "(derivative: " << value.derivative << ", second_derivative: " << value.second_derivative
287 << ", step_size: " << value.step_size << ")";
288 }
289
290 } // namespace Minuit2
291 } // namespace ROOT
292