Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
FitUtil.cxx
Go to the documentation of this file.
1// @(#)root/mathcore:$Id$
2// Author: L. Moneta Tue Nov 28 10:52:47 2006
3
4/**********************************************************************
5 * *
6 * Copyright (c) 2006 LCG ROOT Math Team, CERN/PH-SFT *
7 * *
8 * *
9 **********************************************************************/
10
11// Implementation file for class FitUtil
12
13#include "Fit/FitUtil.h"
14
15#include "Fit/BinData.h"
16#include "Fit/UnBinData.h"
17
18#include "Math/IFunctionfwd.h"
19#include "Math/IParamFunction.h"
20#include "Math/Integrator.h"
25
26#include "Math/Error.h"
27#include "Math/Util.h" // for safe log(x)
28
29#ifdef R__USE_IMT
31#endif
33
34#include <limits>
35#include <cmath>
36#include <cassert>
37#include <algorithm>
38#include <numeric>
39
40#include "TROOT.h"
41
42//#define DEBUG
43#ifdef DEBUG
44#define NSAMPLE 10
45#include <iostream>
46#endif
47
48// need to implement integral option
49
50namespace ROOT {
51
52 namespace Fit {
53
54 namespace FitUtil {
55
56 // derivative with respect of the parameter to be integrated
57 template<class GradFunc = IGradModelFunction>
59 ParamDerivFunc(const GradFunc & f) : fFunc(f), fIpar(0) {}
60 void SetDerivComponent(unsigned int ipar) { fIpar = ipar; }
61 double operator() (const double *x, const double *p) const {
62 return fFunc.ParameterDerivative( x, p, fIpar );
63 }
64 unsigned int NDim() const { return fFunc.NDim(); }
65 const GradFunc & fFunc;
66 unsigned int fIpar;
67 };
68
69// simple gradient calculator using the 2 points rule
70
72
73 public:
74 // construct from function and gradient dimension gdim
75 // gdim = npar for parameter gradient
76 // gdim = ndim for coordinate gradients
77 // construct (the param values will be passed later)
78 // one can choose between 2 points rule (1 extra evaluation) istrat=1
79 // or two point rule (2 extra evaluation)
80 // (found 2 points rule does not work correctly - minuit2FitBench fails)
81 SimpleGradientCalculator(int gdim, const IModelFunction & func,double eps = 2.E-8, int istrat = 1) :
82 fEps(eps),
83 fPrecision(1.E-8 ), // sqrt(epsilon)
85 fN(gdim ),
86 fFunc(func),
87 fVec(std::vector<double>(gdim) ) // this can be probably optimized
88 {}
89
90 // internal method to calculate single partial derivative
91 // assume cached vector fVec is already set
92 double DoParameterDerivative(const double *x, const double *p, double f0, int k) const {
93 double p0 = p[k];
94 double h = std::max( fEps* std::abs(p0), 8.0*fPrecision*(std::abs(p0) + fPrecision) );
95 fVec[k] += h;
96 double deriv = 0;
97 // t.b.d : treat case of infinities
98 //if (fval > - std::numeric_limits<double>::max() && fval < std::numeric_limits<double>::max() )
99 double f1 = fFunc(x, &fVec.front() );
100 if (fStrategy > 1) {
101 fVec[k] = p0 - h;
102 double f2 = fFunc(x, &fVec.front() );
103 deriv = 0.5 * ( f2 - f1 )/h;
104 }
105 else
106 deriv = ( f1 - f0 )/h;
107
108 fVec[k] = p[k]; // restore original p value
109 return deriv;
110 }
111 // number of dimension in x (needed when calculating the integrals)
112 unsigned int NDim() const {
113 return fFunc.NDim();
114 }
115 // number of parameters (needed for grad ccalculation)
116 unsigned int NPar() const {
117 return fFunc.NPar();
118 }
119
120 double ParameterDerivative(const double *x, const double *p, int ipar) const {
121 // fVec are the cached parameter values
122 std::copy(p, p+fN, fVec.begin());
123 double f0 = fFunc(x, p);
124 return DoParameterDerivative(x,p,f0,ipar);
125 }
126
127 // calculate all gradient at point (x,p) knnowing already value f0 (we gain a function eval.)
128 void ParameterGradient(const double * x, const double * p, double f0, double * g) {
129 // fVec are the cached parameter values
130 std::copy(p, p+fN, fVec.begin());
131 for (unsigned int k = 0; k < fN; ++k) {
132 g[k] = DoParameterDerivative(x,p,f0,k);
133 }
134 }
135
136 // calculate gradient w.r coordinate values
137 void Gradient(const double * x, const double * p, double f0, double * g) {
138 // fVec are the cached coordinate values
139 std::copy(x, x+fN, fVec.begin());
140 for (unsigned int k = 0; k < fN; ++k) {
141 double x0 = x[k];
142 double h = std::max( fEps* std::abs(x0), 8.0*fPrecision*(std::abs(x0) + fPrecision) );
143 fVec[k] += h;
144 // t.b.d : treat case of infinities
145 //if (fval > - std::numeric_limits<double>::max() && fval < std::numeric_limits<double>::max() )
146 double f1 = fFunc( &fVec.front(), p );
147 if (fStrategy > 1) {
148 fVec[k] = x0 - h;
149 double f2 = fFunc( &fVec.front(), p );
150 g[k] = 0.5 * ( f2 - f1 )/h;
151 }
152 else
153 g[k] = ( f1 - f0 )/h;
154
155 fVec[k] = x[k]; // restore original x value
156 }
157 }
158
159 private:
160
161 double fEps;
163 int fStrategy; // strategy in calculation ( =1 use 2 point rule( 1 extra func) , = 2 use r point rule)
164 unsigned int fN; // gradient dimension
166 mutable std::vector<double> fVec; // cached coordinates (or parameter values in case of gradientpar)
167 };
168
169
170 // function to avoid infinities or nan
171 double CorrectValue(double rval) {
172 // avoid infinities or nan in rval
173 if (rval > - std::numeric_limits<double>::max() && rval < std::numeric_limits<double>::max() )
174 return rval;
175 else if (rval < 0)
176 // case -inf
177 return -std::numeric_limits<double>::max();
178 else
179 // case + inf or nan
180 return + std::numeric_limits<double>::max();
181 }
182
183 // Check if the value is a finite number. The argument rval is updated if it is infinite or NaN,
184 // setting it to the maximum finite value (preserving the sign).
185 bool CheckInfNaNValue(double &rval)
186 {
187 if (rval > - std::numeric_limits<double>::max() && rval < std::numeric_limits<double>::max() )
188 return true;
189 else if (rval < 0) {
190 // case -inf
191 rval = -std::numeric_limits<double>::max();
192 return false;
193 }
194 else {
195 // case + inf or nan
196 rval = + std::numeric_limits<double>::max();
197 return false;
198 }
199 }
200
201
202 // calculation of the integral of the gradient functions
203 // for a function providing derivative w.r.t parameters
204 // x1 and x2 defines the integration interval , p the parameters
205 template <class GFunc>
207 const double *x1, const double * x2, const double * p, double *g) {
208
209 // needs to calculate the integral for each partial derivative
212 // loop on the parameters
213 unsigned int npar = gfunc.NPar();
214 for (unsigned int k = 0; k < npar; ++k ) {
215 pfunc.SetDerivComponent(k);
216 g[k] = igDerEval( x1, x2 );
217 }
218 }
219
220
221
222 } // end namespace FitUtil
223
224
225
226//___________________________________________________________________________________________________________________________
227// for chi2 functions
228//___________________________________________________________________________________________________________________________
229
230double FitUtil::EvaluateChi2(const IModelFunction &func, const BinData &data, const double *p, unsigned int &nPoints,
232{
233 // evaluate the chi2 given a function reference , the data and returns the value and also in nPoints
234 // the actual number of used points
235 // normal chi2 using only error on values (from fitting histogram)
236 // optionally the integral of function in the bin is used
237
238 unsigned int n = data.Size();
239
240 // set parameters of the function to cache integral value
241#ifdef USE_PARAMCACHE
242 (const_cast<IModelFunction &>(func)).SetParameters(p);
243#endif
244 // do not cache parameter values (it is not thread safe)
245 //func.SetParameters(p);
246
247
248 // get fit option and check case if using integral of bins
249 const DataOptions & fitOpt = data.Opt();
250 bool useBinIntegral = fitOpt.fIntegral && data.HasBinEdges();
251 bool useBinVolume = (fitOpt.fBinVolume && data.HasBinEdges());
252 bool useExpErrors = (fitOpt.fExpErrors);
253 bool isWeighted = fitOpt.fExpErrors && !fitOpt.fErrors1 && data.IsWeighted(); //used in case of Person weighted chi2 fits
254#ifdef DEBUG
255 std::cout << "\n\nFit data size = " << n << std::endl;
256 std::cout << "evaluate chi2 using function " << &func << " " << p << std::endl;
257 std::cout << "use empty bins " << fitOpt.fUseEmpty << std::endl;
258 std::cout << "use integral " << useBinIntegral << std::endl;
259 std::cout << "use binvolume " << useBinVolume << std::endl;
260 std::cout << "use Exp Errors " << useExpErrors << std::endl;
261 std::cout << "use all error=1 " << fitOpt.fErrors1 << std::endl;
262 if (isWeighted) std::cout << "Weighted data set - sumw = " << data.SumOfContent() << " sumw2 = " << data.SumOfError2() << std::endl;
263#endif
264
267 // do not use GSL integrator which is not thread safe
269 }
270#ifdef USE_PARAMCACHE
272#else
274#endif
275 double maxResValue = std::numeric_limits<double>::max() /n;
276 double wrefVolume = 1.0;
277 if (useBinVolume) {
278 if (fitOpt.fNormBinVolume) wrefVolume /= data.RefVolume();
279 }
280
281 (const_cast<IModelFunction &>(func)).SetParameters(p);
282
283 auto mapFunction = [&](const unsigned i){
284
285 double chi2{};
286 double fval{};
287
288 const auto x1 = data.GetCoordComponent(i, 0);
289 const auto y = data.Value(i);
290 auto invError = data.InvError(i);
291
292 //invError = (invError!= 0.0) ? 1.0/invError :1;
293
294 const double * x = nullptr;
295 std::vector<double> xc;
296 double binVolume = 1.0;
297 if (useBinVolume) {
298 unsigned int ndim = data.NDim();
299 xc.resize(data.NDim());
300 for (unsigned int j = 0; j < ndim; ++j) {
301 double xx = *data.GetCoordComponent(i, j);
302 double x2 = data.GetBinUpEdgeComponent(i, j);
303 binVolume *= std::abs(x2 - xx);
304 xc[j] = (useBinIntegral) ? xx : 0.5*(x2 + xx);
305 }
306 x = xc.data();
307 // normalize the bin volume using a reference value
308 binVolume *= wrefVolume;
309 } else if(data.NDim() > 1) {
310 // multi-dim case (no bin volume)
311 // in case of bin integral xc is x1
312 xc.resize(data.NDim());
313 xc[0] = *x1;
314 for (unsigned int j = 1; j < data.NDim(); ++j)
315 xc[j] = *data.GetCoordComponent(i, j);
316 x = xc.data();
317 } else {
318 // for dim 1
319 x = x1;
320 }
321
322
323 if (!useBinIntegral) {
324#ifdef USE_PARAMCACHE
325 fval = func ( x );
326#else
327 fval = func ( x, p );
328#endif
329 }
330 else {
331 // calculate integral normalized (divided) by bin volume
332 // need to set function and parameters here in case loop is parallelized
333 std::vector<double> x2(data.NDim());
334 data.GetBinUpEdgeCoordinates(i, x2.data());
335 fval = igEval(x, x2.data());
336 }
337 // normalize result if requested according to bin volume
338 // we need to multiply by the bin volume (e.g. for variable bins histograms)
339 if (useBinVolume) fval *= binVolume;
340
341 // expected errors
342 if (useExpErrors) {
343 double invWeight = 1.0;
344 // case of weighted Pearson chi2 fit
345 if (isWeighted) {
346 // in case of requested a weighted Pearson fit (option "PW") a weight factor needs to be applied
347 // the bin inverse weight is estimated from bin error and bin content
348 if (y != 0)
350 else
351 // when y is 0 we use a global weight estimated form all histogram (correct if scaling the histogram)
352 // note that if the data is weighted data.SumOfError2 will not be equal to zero
353 invWeight = data.SumOfContent()/ data.SumOfError2();
354 }
355 // compute expected error as f(x) or f(x) / weight (if weighted fit)
356 double invError2 = (fval > 0) ? invWeight / fval : 0.0;
357 invError = std::sqrt(invError2);
358 //std::cout << "using Pearson chi2 " << x[0] << " " << 1./invError2 << " " << fval << std::endl;
359 }
360
361#ifdef DEBUG
362 std::cout << x[0] << " " << y << " " << 1./invError << " params : ";
363 for (unsigned int ipar = 0; ipar < func.NPar(); ++ipar)
364 std::cout << p[ipar] << "\t";
365 std::cout << "\tfval = " << fval << " bin volume " << binVolume << " ref " << wrefVolume << std::endl;
366#endif
367
368 if (invError > 0) {
369
370 double tmp = ( y -fval )* invError;
371 double resval = tmp * tmp;
372
373
374 // avoid infinity or nan in chi2 values due to wrong function values
375 if ( resval < maxResValue )
376 chi2 += resval;
377 else {
378 //nRejected++;
379 chi2 += maxResValue;
380 }
381 }
382 return chi2;
383 };
384
385#ifdef R__USE_IMT
386 auto redFunction = [](const std::vector<double> & objs){
387 return std::accumulate(objs.begin(), objs.end(), double{});
388 };
389#else
390 (void)nChunks;
391
392 // If IMT is disabled, force the execution policy to the serial case
394 Warning("FitUtil::EvaluateChi2", "Multithread execution policy requires IMT, which is disabled. Changing "
395 "to ROOT::EExecutionPolicy::kSequential.");
397 }
398#endif
399
400 double res{};
402 for (unsigned int i=0; i<n; ++i) {
403 res += mapFunction(i);
404 }
405#ifdef R__USE_IMT
408 auto chunks = nChunks !=0? nChunks: setAutomaticChunking(data.Size());
410#endif
411// } else if(executionPolicy == ROOT::Fit::kMultitProcess){
412 // ROOT::TProcessExecutor pool;
413 // res = pool.MapReduce(mapFunction, ROOT::TSeq<unsigned>(0, n), redFunction);
414 } else{
415 Error("FitUtil::EvaluateChi2","Execution policy unknown. Available choices:\n ROOT::EExecutionPolicy::kSequential (default)\n ROOT::EExecutionPolicy::kMultiThread (requires IMT)\n");
416 }
417
418 // reset the number of fitting data points
419 nPoints = n; // no points are rejected
420 //if (nRejected != 0) nPoints = n - nRejected;
421
422 return res;
423}
424
425
426//___________________________________________________________________________________________________________________________
427
428double FitUtil::EvaluateChi2Effective(const IModelFunction & func, const BinData & data, const double * p, unsigned int & nPoints,
430 // evaluate the chi2 given a function reference , the data and returns the value and also in nPoints
431 // the actual number of used points
432 // method using the error in the coordinates
433 // integral of bin does not make sense in this case
434
435 unsigned int n = data.Size();
436
437#ifdef DEBUG
438 std::cout << "\n\nFit data size = " << n << std::endl;
439 std::cout << "evaluate effective chi2 using function " << &func << " " << p << std::endl;
440#endif
441
442 assert(data.HaveCoordErrors() || data.HaveAsymErrors());
443
444 //func.SetParameters(p);
445
446 unsigned int ndim = func.NDim();
447
448 double maxResValue = std::numeric_limits<double>::max() /n;
449
450 auto mapFunction = [&](const unsigned i) {
451
452 // use a Richardson derivator local to the iteration since it is not thread safe
454
455 // copy the coordinates and the coordinate errors of the point into thread-local
456 // buffers: the BinData::GetPoint and GetPointError accessors return pointers into
457 // shared temporary storage and are not thread safe (in contrast to the per-component
458 // accessors used here)
459 // TODO: add threadsafe getters to FitData and BinData!
460 std::vector<double> x(ndim);
461 std::vector<double> ex(ndim);
462 for (unsigned int icoord = 0; icoord < ndim; ++icoord) {
463 x[icoord] = *data.GetCoordComponent(i, icoord);
464 ex[icoord] = data.GetCoordErrorComponent(i, icoord);
465 }
466
467 double y = data.Value(i);
468
469 double fval = func( x.data(), p );
470
471 double delta_y_func = y - fval;
472
473
474 double ey = 0;
475 if (!data.HaveAsymErrors() )
476 ey = data.Error(i);
477 else {
478 double eylow = 0, eyhigh = 0;
479 data.GetAsymError(i, eylow, eyhigh);
480 if ( delta_y_func < 0)
481 ey = eyhigh; // function is higher than points
482 else
483 ey = eylow;
484 }
485 double e2 = ey * ey;
486 // before calculating the gradient check that all error in x are not zero
487 unsigned int j = 0;
488 while ( j < ndim && ex[j] == 0.) { j++; }
489 // if j is less ndim some elements are not zero
490 if (j < ndim) {
491 // need an adapter from a multi-dim function to a one-dimensional
493 // select optimal step size (use 10--2 by default as was done in TF1:
494 double kEps = 0.01;
495 double kPrecision = 1.E-8;
496 for (unsigned int icoord = 0; icoord < ndim; ++icoord) {
497 // calculate derivative for each coordinate
498 if (ex[icoord] > 0) {
499 //gradCalc.Gradient(x, p, fval, &grad[0]);
500 f1D.SetCoord(icoord);
501 // optimal step size (take ex[] as scale for the points and 1% of it
502 double x0= x[icoord];
503 double h = std::max( kEps* std::abs(ex[icoord]), 8.0*kPrecision*(std::abs(x0) + kPrecision) );
504 double deriv = derivator.Derivative1(f1D, x[icoord], h);
505 double edx = ex[icoord] * deriv;
506 e2 += edx * edx;
507#ifdef DEBUG
508 std::cout << "error for coord " << icoord << " = " << ex[icoord] << " deriv " << deriv << std::endl;
509#endif
510 }
511 }
512 }
513 double w2 = (e2 > 0) ? 1.0/e2 : 0;
514 double resval = w2 * ( y - fval ) * ( y - fval);
515
516#ifdef DEBUG
517 std::cout << x[0] << " " << y << " ex " << ex[0] << " ey " << ey << " params : ";
518 for (unsigned int ipar = 0; ipar < func.NPar(); ++ipar)
519 std::cout << p[ipar] << "\t";
520 std::cout << "\tfval = " << fval << "\tresval = " << resval << std::endl;
521#endif
522
523 // avoid (infinity and nan ) in the chi2 sum
524 // eventually add possibility of excluding some points (like singularity)
525 return ( resval < maxResValue ) ? resval : maxResValue;
526 //nRejected++;
527 };
528
529#ifdef R__USE_IMT
530 auto redFunction = [](const std::vector<double> & objs) {
531 return std::accumulate(objs.begin(), objs.end(), double{});
532 };
533#else
534 (void)nChunks;
535
536 // If IMT is disabled, force the execution policy to the serial case
538 Warning("FitUtil::EvaluateChi2Effective", "Multithread execution policy requires IMT, which is disabled. Changing "
539 "to ROOT::EExecutionPolicy::kSequential.");
541 }
542#endif
543
544 double chi2 = 0;
546 for (unsigned int i = 0; i < n; ++i) {
547 chi2 += mapFunction(i);
548 }
549#ifdef R__USE_IMT
552 auto chunks = nChunks != 0 ? nChunks : setAutomaticChunking(data.Size());
554#endif
555 } else {
556 Error("FitUtil::EvaluateChi2Effective", "Execution policy unknown. Available choices:\n ROOT::EExecutionPolicy::kSequential (default)\n ROOT::EExecutionPolicy::kMultiThread (requires IMT)\n");
557 }
558
559 // reset the number of fitting data points
560 nPoints = n; // no points are rejected
561 //if (nRejected != 0) nPoints = n - nRejected;
562
563#ifdef DEBUG
564 std::cout << "chi2 = " << chi2 << " n = " << nPoints << std::endl;
565#endif
566
567 return chi2;
568
569}
570
571
572////////////////////////////////////////////////////////////////////////////////
573/// evaluate the chi2 contribution (residual term) only for data with no coord-errors
574/// This function is used in the specialized least square algorithms like FUMILI or L.M.
575/// if we have error on the coordinates the residual weight depends on the function value and
576/// the approximation used by Fumili and Levenberg-Marquardt cannot be used.
577/// Also the expected error and bin integral options should not be used in this case
578
579double FitUtil::EvaluateChi2Residual(const IModelFunction & func, const BinData & data, const double * p, unsigned int i, double * g, double * h, bool hasGrad, bool useFullHessian) {
580 if (data.GetErrorType() == BinData::kCoordError && data.Opt().fCoordErrors ) {
581 MATH_ERROR_MSG("FitUtil::EvaluateChi2Residual","Error on the coordinates are not used in calculating Chi2 residual");
582 return 0; // it will assert otherwise later in GetPoint
583 }
584
585 double y, invError = 0;
586
587 const DataOptions & fitOpt = data.Opt();
588 bool useBinIntegral = fitOpt.fIntegral && data.HasBinEdges();
589 bool useBinVolume = (fitOpt.fBinVolume && data.HasBinEdges());
590 bool useExpErrors = (fitOpt.fExpErrors);
591 bool useNormBinVolume = (useBinVolume && fitOpt.fNormBinVolume);
592
593 const double * x1 = data.GetPoint(i,y, invError);
594
595 unsigned int ndim = data.NDim();
596 double binVolume = 1.0;
597 const double * x2 = nullptr;
598 if (useBinVolume || useBinIntegral) x2 = data.BinUpEdge(i);
599
600 std::vector<double> xc;
601
602 if (useBinVolume) {
603 if (!useBinIntegral) {
604 xc.resize(ndim);
605 for (unsigned int j = 0; j < ndim; ++j) {
606 binVolume *= std::abs( x2[j]-x1[j] );
607 xc[j] = 0.5*(x2[j]+ x1[j]);
608 }
609 }
610 // normalize the bin volume using a reference value
611 if (useNormBinVolume) binVolume /= data.RefVolume();
612 }
613
614 const double * x = (useBinVolume) ? xc.data() : x1;
615
616 // calculate integral (normalized by bin volume)
617 // need to set function and parameters here in case loop is parallelized
619 double fval0 = (useBinIntegral) ? igEval( x1, x2) : func ( x, p );
620
621 // normalize result if requested according to bin volume
622 double fval = fval0;
623 if (useBinVolume) fval = fval0*binVolume;
624
625 // expected errors
626 if (useExpErrors) {
627 // check if a weight factor needs to be applied
628 // for bins with y = 0 use as weight the global of the full histogram
629 double invWeight = 1.0;
630 if (data.IsWeighted() && !fitOpt.fErrors1 ) { // case of weighted Pearson chi2 fit
632 if (y == 0) invWeight = (data.SumOfError2() > 0) ? data.SumOfContent()/ data.SumOfError2() : 1.0;
633 }
634 // compute expected error as f(x) / weight
635 double invError2 = (fval > 0) ? invWeight / fval : 0.0;
636 invError = std::sqrt(invError2);
637 }
638
639
640 double resval = ( y -fval ) * invError;
641
642 // avoid infinities or nan in resval
644
645 // estimate gradient
646 if (g) {
647
648 unsigned int npar = func.NPar();
649
650 // use gradient of model function only if FCN support gradient
651 const IGradModelFunction * gfunc = (hasGrad) ?
652 dynamic_cast<const IGradModelFunction *>( &func) : nullptr;
653
654 if (!h ) useFullHessian = false;
655 // this is not supported yet!
656 if (useFullHessian && (!gfunc || useBinIntegral || (gfunc && !gfunc->HasParameterHessian())))
657 return std::numeric_limits<double>::quiet_NaN();
658
659 if (gfunc) {
660 //case function provides gradient
661 if (!useBinIntegral ) {
662 gfunc->ParameterGradient(x , p, g);
663 if (useFullHessian) {
664 gfunc->ParameterHessian(x, p, h);
665 }
666 }
667 else {
668 // needs to calculate the integral for each partial derivative
670 }
671 }
672 else {
674 if (!useBinIntegral ) {
675 // need to use un-normalized fval
676 gc.ParameterGradient(x, p, fval0, g);
677 } else {
678 // needs to calculate the integral for each partial derivative
680 }
681 }
682 // multiply by - 1 * weight
683 for (unsigned int k = 0; k < npar; ++k) {
684 g[k] *= - invError;
685 if (useBinVolume) g[k] *= binVolume;
686 if (h) {
687 for (unsigned int l = 0; l <= k; l++) { // use lower diagonal because I modify g[k]
688 unsigned int idx = l + k * (k + 1) / 2;
689 if (useFullHessian) {
690 h[idx] *= 2.* resval * (-invError); // hessian of model function
691 if (useBinVolume) h[idx] *= binVolume;
692 }
693 else {
694 h[idx] = 0;
695 }
696 // add term depending on only gradient of model function
697 h[idx] += 2. * g[k]*g[l];
698 }
699 }
700 }
701 }
702
703
704 return resval;
705
706}
707
708void FitUtil::EvaluateChi2Gradient(const IModelFunction &f, const BinData &data, const double *p, double *grad,
709 unsigned int &nPoints, ROOT::EExecutionPolicy executionPolicy, unsigned nChunks)
710{
711 // evaluate the gradient of the chi2 function
712 // this function is used when the model function knows how to calculate the derivative and we can
713 // avoid that the minimizer re-computes them
714 //
715 // case of chi2 effective (errors on coordinate) is not supported
716
717 if (data.HaveCoordErrors()) {
718 MATH_ERROR_MSG("FitUtil::EvaluateChi2Gradient",
719 "Error on the coordinates are not used in calculating Chi2 gradient");
720 return; // it will assert otherwise later in GetPoint
721 }
722
723 const IGradModelFunction *fg = dynamic_cast<const IGradModelFunction *>(&f);
724 assert(fg != nullptr); // must be called by a gradient function
725
726 const IGradModelFunction &func = *fg;
727
728#ifdef DEBUG
729 std::cout << "\n\nFit data size = " << nPoints << std::endl;
730 std::cout << "evaluate chi2 using function gradient " << &func << " " << p << std::endl;
731#endif
732
733 const DataOptions &fitOpt = data.Opt();
734 bool useBinIntegral = fitOpt.fIntegral && data.HasBinEdges();
735 bool useBinVolume = (fitOpt.fBinVolume && data.HasBinEdges());
736
737 double wrefVolume = 1.0;
738 if (useBinVolume) {
739 if (fitOpt.fNormBinVolume) wrefVolume /= data.RefVolume();
740 }
741
744 // do not use GSL integrator which is not thread safe
746 }
748
749 unsigned int npar = func.NPar();
750 unsigned initialNPoints = data.Size();
751
752 std::vector<bool> isPointRejected(initialNPoints);
753
754 auto mapFunction = [&](const unsigned int i) {
755 // set all vector values to zero
756 std::vector<double> gradFunc(npar);
757 std::vector<double> pointContribution(npar);
758
759 const auto x1 = data.GetCoordComponent(i, 0);
760 const auto y = data.Value(i);
761 auto invError = data.Error(i);
762
763 invError = (invError != 0.0) ? 1.0 / invError : 1;
764
765 double fval = 0;
766
767 const double *x = nullptr;
768 std::vector<double> xc;
769
770 unsigned int ndim = data.NDim();
771 double binVolume = 1;
772 if (useBinVolume) {
773 xc.resize(ndim);
774 for (unsigned int j = 0; j < ndim; ++j) {
775 double x1_j = *data.GetCoordComponent(i, j);
776 double x2_j = data.GetBinUpEdgeComponent(i, j);
777 binVolume *= std::abs(x2_j - x1_j);
778 xc[j] = (useBinIntegral) ? x1_j : 0.5 * (x2_j + x1_j);
779 }
780
781 x = xc.data();
782
783 // normalize the bin volume using a reference value
784 binVolume *= wrefVolume;
785 } else if (ndim > 1) {
786 xc.resize(ndim);
787 xc[0] = *x1;
788 for (unsigned int j = 1; j < ndim; ++j)
789 xc[j] = *data.GetCoordComponent(i, j);
790 x = xc.data();
791 } else {
792 x = x1;
793 }
794
795 if (!useBinIntegral) {
796 fval = func(x, p);
797 func.ParameterGradient(x, p, &gradFunc[0]);
798 } else {
799 std::vector<double> x2(data.NDim());
800 data.GetBinUpEdgeCoordinates(i, x2.data());
801 // calculate normalized integral and gradient (divided by bin volume)
802 // need to set function and parameters here in case loop is parallelized
803 fval = igEval(x, x2.data());
804 CalculateGradientIntegral(func, x, x2.data(), p, &gradFunc[0]);
805 }
806 if (useBinVolume)
807 fval *= binVolume;
808
809#ifdef DEBUG
810 std::cout << x[0] << " " << y << " " << 1. / invError << " params : ";
811 for (unsigned int ipar = 0; ipar < npar; ++ipar)
812 std::cout << p[ipar] << "\t";
813 std::cout << "\tfval = " << fval << std::endl;
814#endif
815 if (!CheckInfNaNValue(fval)) {
816 isPointRejected[i] = true;
817 // Return a zero contribution to all partial derivatives on behalf of the current point
818 return pointContribution;
819 }
820
821 // loop on the parameters
822 unsigned int ipar = 0;
823 for (; ipar < npar; ++ipar) {
824
825 // correct gradient for bin volumes
826 if (useBinVolume)
827 gradFunc[ipar] *= binVolume;
828
829 // avoid singularity in the function (infinity and nan ) in the chi2 sum
830 // eventually add possibility of excluding some points (like singularity)
831 double dfval = gradFunc[ipar];
832 if (!CheckInfNaNValue(dfval)) {
833 break; // exit loop on parameters
834 }
835
836 // calculate derivative point contribution
837 pointContribution[ipar] = -2.0 * (y - fval) * invError * invError * gradFunc[ipar];
838 }
839
840 if (ipar < npar) {
841 // case loop was broken for an overflow in the gradient calculation
842 isPointRejected[i] = true;
843 }
844
845 return pointContribution;
846 };
847
848 // Vertically reduce the set of vectors by summing its equally-indexed components
849 auto redFunction = [&](const std::vector<std::vector<double>> &pointContributions) {
850 std::vector<double> result(npar);
851
852 for (auto const &pointContribution : pointContributions) {
853 for (unsigned int parameterIndex = 0; parameterIndex < npar; parameterIndex++)
855 }
856
857 return result;
858 };
859
860 std::vector<double> g(npar);
861
862#ifndef R__USE_IMT
863 // If IMT is disabled, force the execution policy to the serial case
865 Warning("FitUtil::EvaluateChi2Gradient", "Multithread execution policy requires IMT, which is disabled. Changing "
866 "to ROOT::EExecutionPolicy::kSequential.");
868 }
869#endif
870
872 std::vector<std::vector<double>> allGradients(initialNPoints);
873 for (unsigned int i = 0; i < initialNPoints; ++i) {
875 }
877 }
878#ifdef R__USE_IMT
883 }
884#endif
885 // else if(executionPolicy == ROOT::Fit::kMultiprocess){
886 // ROOT::TProcessExecutor pool;
887 // g = pool.MapReduce(mapFunction, ROOT::TSeq<unsigned>(0, n), redFunction);
888 // }
889 else {
890 Error("FitUtil::EvaluateChi2Gradient",
891 "Execution policy unknown. Available choices:\n 0: Serial (default)\n 1: MultiThread (requires IMT)\n");
892 }
893
894#ifndef R__USE_IMT
895 //to fix compiler warning
896 (void)nChunks;
897#endif
898
899 // correct the number of points
901
902 if (std::any_of(isPointRejected.begin(), isPointRejected.end(), [](bool point) { return point; })) {
903 unsigned nRejected = std::accumulate(isPointRejected.begin(), isPointRejected.end(), 0);
906
907 if (nPoints < npar)
908 MATH_ERROR_MSG("FitUtil::EvaluateChi2Gradient",
909 "Error - too many points rejected for overflow in gradient calculation");
910 }
911
912 // copy result
913 std::copy(g.begin(), g.end(), grad);
914}
915
916//______________________________________________________________________________________________________
917//
918// Log Likelihood functions
919//_______________________________________________________________________________________________________
920
921// utility function used by the likelihoods
922
923// for LogLikelihood functions
924
925double FitUtil::EvaluatePdf(const IModelFunction & func, const UnBinData & data, const double * p, unsigned int i, double * g, double * /*h*/, bool hasGrad, bool) {
926 // evaluate the pdf contribution to the generic logl function in case of bin data
927 // return actually the log of the pdf and its derivatives
928
929
930 //func.SetParameters(p);
931
932 const double * x = data.Coords(i);
933 double fval = func ( x, p );
935 //return
936 if (g == nullptr) return logPdf;
937
938 const IGradModelFunction * gfunc = (hasGrad) ?
939 dynamic_cast<const IGradModelFunction *>( &func) : nullptr;
940
941 // gradient calculation
942 if (gfunc) {
943 //case function provides gradient
944 gfunc->ParameterGradient( x , p, g );
945 }
946 else {
947 // estimate gradient numerically with simple 2 point rule
948 // should probably calculate gradient of log(pdf) is more stable numerically
949 SimpleGradientCalculator gc(func.NPar(), func);
950 gc.ParameterGradient(x, p, fval, g );
951 }
952 // divide gradient by function value since returning the logs
953 for (unsigned int ipar = 0; ipar < func.NPar(); ++ipar) {
954 g[ipar] /= fval; // this should be checked against infinities
955 }
956
957#ifdef DEBUG
958 std::cout << x[i] << "\t";
959 std::cout << "\tpar = [ " << func.NPar() << " ] = ";
960 for (unsigned int ipar = 0; ipar < func.NPar(); ++ipar)
961 std::cout << p[ipar] << "\t";
962 std::cout << "\tfval = " << fval;
963 std::cout << "\tgrad = [ ";
964 for (unsigned int ipar = 0; ipar < func.NPar(); ++ipar)
965 std::cout << g[ipar] << "\t";
966 std::cout << " ] " << std::endl;
967#endif
968
969
970 return logPdf;
971}
972
973double FitUtil::EvaluateLogL(const IModelFunction &func, const UnBinData &data, const double *p,
974 int iWeight, bool extended, unsigned int &nPoints,
976{
977 // evaluate the LogLikelihood
978
979 unsigned int n = data.Size();
980
981 //unsigned int nRejected = 0;
982
983 bool normalizeFunc = false;
984
985 // set parameters of the function to cache integral value
986#ifdef USE_PARAMCACHE
987 (const_cast<IModelFunctionTempl<double> &>(func)).SetParameters(p);
988#endif
989
990 nPoints = data.Size(); // npoints
991
992#ifdef R__USE_IMT
993 // in case parameter needs to be propagated to user function use trick to set parameters by calling one time the function
994 // this will be done in sequential mode and parameters can be set in a thread safe manner
995 if (!normalizeFunc) {
996 if (data.NDim() == 1) {
997 const double * x = data.GetCoordComponent(0,0);
998 func( x, p);
999 }
1000 else {
1001 std::vector<double> x(data.NDim());
1002 for (unsigned int j = 0; j < data.NDim(); ++j)
1003 x[j] = *data.GetCoordComponent(0, j);
1004 func( x.data(), p);
1005 }
1006 }
1007#endif
1008
1009 double norm = 1.0;
1010 if (normalizeFunc) {
1011 // compute integral of the function
1012 std::vector<double> xmin(data.NDim());
1013 std::vector<double> xmax(data.NDim());
1014 IntegralEvaluator<> igEval(func, p, true);
1015 // compute integral in the ranges where is defined
1016 if (data.Range().Size() > 0) {
1017 norm = 0;
1018 for (unsigned int ir = 0; ir < data.Range().Size(); ++ir) {
1019 data.Range().GetRange(&xmin[0], &xmax[0], ir);
1020 norm += igEval.Integral(xmin.data(), xmax.data());
1021 }
1022 } else {
1023 // use (-inf +inf)
1024 data.Range().GetRange(&xmin[0], &xmax[0]);
1025 // check if funcition is zero at +- inf
1026 if (func(xmin.data(), p) != 0 || func(xmax.data(), p) != 0) {
1027 MATH_ERROR_MSG("FitUtil::EvaluateLogLikelihood",
1028 "A range has not been set and the function is not zero at +/- inf");
1029 return 0;
1030 }
1031 norm = igEval.Integral(&xmin[0], &xmax[0]);
1032 }
1033 }
1034
1035 // needed to compute effective global weight in case of extended likelihood
1036
1037 auto mapFunction = [&](const unsigned i) {
1038 double W = 0;
1039 double W2 = 0;
1040 double fval = 0;
1041
1042 if (data.NDim() > 1) {
1043 std::vector<double> x(data.NDim());
1044 for (unsigned int j = 0; j < data.NDim(); ++j)
1045 x[j] = *data.GetCoordComponent(i, j);
1046#ifdef USE_PARAMCACHE
1047 fval = func(x.data());
1048#else
1049 fval = func(x.data(), p);
1050#endif
1051
1052 // one -dim case
1053 } else {
1054 const auto x = data.GetCoordComponent(i, 0);
1055#ifdef USE_PARAMCACHE
1056 fval = func(x);
1057#else
1058 fval = func(x, p);
1059#endif
1060 }
1061
1062 if (normalizeFunc)
1063 fval = fval * (1 / norm);
1064
1065 // function EvalLog protects against negative or too small values of fval
1067 if (iWeight > 0) {
1068 double weight = data.Weight(i);
1069 logval *= weight;
1070 if (iWeight == 2) {
1071 logval *= weight; // use square of weights in likelihood
1072 if (!extended) {
1073 // needed sum of weights and sum of weight square if likelkihood is extended
1074 W = weight;
1075 W2 = weight * weight;
1076 }
1077 }
1078 }
1079 return LikelihoodAux<double>(logval, W, W2);
1080 };
1081
1082#ifdef R__USE_IMT
1083 // auto redFunction = [](const std::vector<LikelihoodAux<double>> & objs){
1084 // return std::accumulate(objs.begin(), objs.end(), LikelihoodAux<double>(0.0,0.0,0.0),
1085 // [](const LikelihoodAux<double> &l1, const LikelihoodAux<double> &l2){
1086 // return l1+l2;
1087 // });
1088 // };
1089 // do not use std::accumulate to be sure to maintain always the same order
1090 auto redFunction = [](const std::vector<LikelihoodAux<double>> & objs){
1091 auto l0 = LikelihoodAux<double>(0.0,0.0,0.0);
1092 for ( auto & l : objs ) {
1093 l0 = l0 + l;
1094 }
1095 return l0;
1096 };
1097#else
1098 (void)nChunks;
1099
1100 // If IMT is disabled, force the execution policy to the serial case
1102 Warning("FitUtil::EvaluateLogL", "Multithread execution policy requires IMT, which is disabled. Changing "
1103 "to ROOT::EExecutionPolicy::kSequential.");
1105 }
1106#endif
1107
1108 double logl{};
1109 double sumW{};
1110 double sumW2{};
1112 for (unsigned int i=0; i<n; ++i) {
1113 auto resArray = mapFunction(i);
1114 logl+=resArray.logvalue;
1115 sumW+=resArray.weight;
1116 sumW2+=resArray.weight2;
1117 }
1118#ifdef R__USE_IMT
1121 auto chunks = nChunks !=0? nChunks: setAutomaticChunking(data.Size());
1123 logl=resArray.logvalue;
1124 sumW=resArray.weight;
1125 sumW2=resArray.weight2;
1126#endif
1127// } else if(executionPolicy == ROOT::Fit::kMultiProcess){
1128 // ROOT::TProcessExecutor pool;
1129 // res = pool.MapReduce(mapFunction, ROOT::TSeq<unsigned>(0, n), redFunction);
1130 } else{
1131 Error("FitUtil::EvaluateLogL","Execution policy unknown. Available choices:\n ROOT::EExecutionPolicy::kSequential (default)\n ROOT::EExecutionPolicy::kMultiThread (requires IMT)\n");
1132 }
1133
1134 if (extended) {
1135 // add Poisson extended term
1136 double extendedTerm = 0; // extended term in likelihood
1137 double nuTot = 0;
1138 // nuTot is integral of function in the range
1139 // if function has been normalized integral has been already computed
1140 if (!normalizeFunc) {
1141 IntegralEvaluator<> igEval( func, p, true);
1142 std::vector<double> xmin(data.NDim());
1143 std::vector<double> xmax(data.NDim());
1144
1145 // compute integral in the ranges where is defined
1146 if (data.Range().Size() > 0 ) {
1147 nuTot = 0;
1148 for (unsigned int ir = 0; ir < data.Range().Size(); ++ir) {
1149 data.Range().GetRange(&xmin[0],&xmax[0],ir);
1150 nuTot += igEval.Integral(xmin.data(),xmax.data());
1151 }
1152 } else {
1153 // use (-inf +inf)
1154 data.Range().GetRange(&xmin[0],&xmax[0]);
1155 // check if function is zero at +- inf
1156 if (func(xmin.data(), p) != 0 || func(xmax.data(), p) != 0) {
1157 MATH_ERROR_MSG("FitUtil::EvaluateLogLikelihood","A range has not been set and the function is not zero at +/- inf");
1158 return 0;
1159 }
1160 nuTot = igEval.Integral(&xmin[0],&xmax[0]);
1161 }
1162
1163 // force to be last parameter value
1164 //nutot = p[func.NDim()-1];
1165 if (iWeight != 2)
1166 extendedTerm = - nuTot; // no need to add in this case n log(nu) since is already computed before
1167 else {
1168 // case use weight square in likelihood : compute total effective weight = sw2/sw
1169 // ignore for the moment case when sumW is zero
1170 extendedTerm = - (sumW2 / sumW) * nuTot;
1171 }
1172
1173 }
1174 else {
1175 nuTot = norm;
1176 extendedTerm = - nuTot + double(n) * ROOT::Math::Util::EvalLog( nuTot);
1177 // in case of weights need to use here sum of weights (to be done)
1178 }
1179 logl += extendedTerm;
1180
1181 }
1182
1183#ifdef DEBUG
1184 std::cout << "Evaluated log L for parameters (";
1185 for (unsigned int ip = 0; ip < func.NPar(); ++ip)
1186 std::cout << " " << p[ip];
1187 std::cout << ") fval = " << -logl << std::endl;
1188#endif
1189
1190 return -logl;
1191}
1192
1193void FitUtil::EvaluateLogLGradient(const IModelFunction &f, const UnBinData &data, const double *p, double *grad,
1194 unsigned int &nPoints, ROOT::EExecutionPolicy executionPolicy, unsigned nChunks)
1195{
1196 // evaluate the gradient of the log likelihood function
1197
1198 const IGradModelFunction *fg = dynamic_cast<const IGradModelFunction *>(&f);
1199 assert(fg != nullptr); // must be called by a grad function
1200
1201 const IGradModelFunction &func = *fg;
1202
1203 unsigned int npar = func.NPar();
1204 unsigned initialNPoints = data.Size();
1205
1206 (const_cast<IGradModelFunction &>(func)).SetParameters(p);
1207
1208#ifdef DEBUG
1209 std::cout << "\n===> Evaluate Gradient for parameters ";
1210 for (unsigned int ip = 0; ip < npar; ++ip)
1211 std::cout << " " << p[ip];
1212 std::cout << "\n";
1213#endif
1214
1215 const double kdmax1 = std::sqrt(std::numeric_limits<double>::max());
1216 const double kdmax2 = std::numeric_limits<double>::max() / (4 * initialNPoints);
1217
1218 auto mapFunction = [&](const unsigned int i) {
1219 std::vector<double> gradFunc(npar);
1220 std::vector<double> pointContribution(npar);
1221
1222
1223 const double * x = nullptr;
1224 std::vector<double> xc;
1225 if (data.NDim() > 1) {
1226 xc.resize(data.NDim() );
1227 for (unsigned int j = 0; j < data.NDim(); ++j)
1228 xc[j] = *data.GetCoordComponent(i, j);
1229 x = xc.data();
1230 } else {
1231 x = data.GetCoordComponent(i, 0);
1232 }
1233
1234 double fval = func(x, p);
1235 func.ParameterGradient(x, p, &gradFunc[0]);
1236
1237#ifdef DEBUG
1238 {
1240 if (i < 5 || (i > data.Size()-5) ) {
1241 if (data.NDim() > 1) std::cout << i << " x " << x[0] << " y " << x[1] << " func " << fval
1242 << " gradient " << gradFunc[0] << " " << gradFunc[1] << " " << gradFunc[3] << std::endl;
1243 else std::cout << i << " x " << x[0] << " gradient " << gradFunc[0] << " " << gradFunc[1] << " " << gradFunc[3] << std::endl;
1244 }
1245 }
1246#endif
1247
1248 for (unsigned int kpar = 0; kpar < npar; ++kpar) {
1249 if (fval > 0)
1251 else if (gradFunc[kpar] != 0) {
1252 double gg = kdmax1 * gradFunc[kpar];
1253 if (gg > 0)
1254 gg = std::min(gg, kdmax2);
1255 else
1256 gg = std::max(gg, -kdmax2);
1258 }
1259 // if func derivative is zero term is also zero so do not add in g[kpar]
1260 }
1261
1262 return pointContribution;
1263 };
1264
1265 // Vertically reduce the set of vectors by summing its equally-indexed components
1266 auto redFunction = [&](const std::vector<std::vector<double>> &pointContributions) {
1267 std::vector<double> result(npar);
1268
1269 for (auto const &pointContribution : pointContributions) {
1270 for (unsigned int parameterIndex = 0; parameterIndex < npar; parameterIndex++)
1272 }
1273
1274 return result;
1275 };
1276
1277 std::vector<double> g(npar);
1278
1279#ifndef R__USE_IMT
1280 // If IMT is disabled, force the execution policy to the serial case
1282 Warning("FitUtil::EvaluateLogLGradient", "Multithread execution policy requires IMT, which is disabled. Changing "
1283 "to ROOT::EExecutionPolicy::kSequential.");
1285 }
1286#endif
1287
1289 std::vector<std::vector<double>> allGradients(initialNPoints);
1290 for (unsigned int i = 0; i < initialNPoints; ++i) {
1291 allGradients[i] = mapFunction(i);
1292 }
1294 }
1295#ifdef R__USE_IMT
1300 }
1301#endif
1302 else {
1303 Error("FitUtil::EvaluateLogLGradient", "Execution policy unknown. Available choices:\n "
1304 "ROOT::EExecutionPolicy::kSequential (default)\n "
1305 "ROOT::EExecutionPolicy::kMultiThread (requires IMT)\n");
1306 }
1307
1308#ifndef R__USE_IMT
1309 // to fix compiler warning
1310 (void)nChunks;
1311#endif
1312
1313 // copy result
1314 std::copy(g.begin(), g.end(), grad);
1315 nPoints = data.Size(); // npoints
1316
1317#ifdef DEBUG
1318 std::cout << "FitUtil.cxx : Final gradient ";
1319 for (unsigned int param = 0; param < npar; param++) {
1320 std::cout << " " << grad[param];
1321 }
1322 std::cout << "\n";
1323#endif
1324}
1325//_________________________________________________________________________________________________
1326// for binned log likelihood functions
1327////////////////////////////////////////////////////////////////////////////////
1328/// evaluate the pdf (Poisson) contribution to the logl (return actually log of pdf)
1329/// and its gradient (gradient of log(pdf))
1330
1331double FitUtil::EvaluatePoissonBinPdf(const IModelFunction & func, const BinData & data, const double * p, unsigned int i, double * g, double * h, bool hasGrad, bool useFullHessian) {
1332 double y = 0;
1333 const double * x1 = data.GetPoint(i,y);
1334
1335 const DataOptions & fitOpt = data.Opt();
1336 bool useBinIntegral = fitOpt.fIntegral && data.HasBinEdges();
1337 bool useBinVolume = (fitOpt.fBinVolume && data.HasBinEdges());
1338
1340 const double * x2 = nullptr;
1341 // calculate the bin volume
1342 double binVolume = 1;
1343 std::vector<double> xc;
1344 if (useBinVolume) {
1345 unsigned int ndim = data.NDim();
1346 xc.resize(ndim);
1347 for (unsigned int j = 0; j < ndim; ++j) {
1348 double x2j = data.GetBinUpEdgeComponent(i, j);
1349 binVolume *= std::abs( x2j-x1[j] );
1350 xc[j] = 0.5*(x2j+ x1[j]);
1351 }
1352 // normalize the bin volume using a reference value
1353 binVolume /= data.RefVolume();
1354 }
1355
1356 const double * x = (useBinVolume) ? &xc.front() : x1;
1357
1358 double fval0 = 0;
1359 if (!useBinIntegral ) {
1360 fval0 = func ( x, p );
1361 }
1362 else {
1363 // calculate integral normalized (divided by bin volume)
1364 std::vector<double> vx2(data.NDim());
1365 data.GetBinUpEdgeCoordinates(i, vx2.data());
1366 fval0 = igEval( x1, vx2.data() ) ;
1367 }
1368 double fval = fval0;
1369 if (useBinVolume) fval = fval0*binVolume;
1370
1371 // logPdf for Poisson: ignore constant term depending on N
1372 fval = std::max(fval, 0.0); // avoid negative or too small values
1373 double nlogPdf = fval;
1374 if (y > 0.0) {
1375 // include also constants due to saturate model (see Baker-Cousins paper)
1377 }
1378
1379 if (g == nullptr) return nlogPdf;
1380
1381 unsigned int npar = func.NPar();
1382 const IGradModelFunction * gfunc = (hasGrad) ?
1383 dynamic_cast<const IGradModelFunction *>( &func) : nullptr;
1384
1385 // for full Hessian we need a gradient function and not bin intgegral computation
1386 if (useFullHessian && (!gfunc || useBinIntegral || (gfunc && !gfunc->HasParameterHessian())))
1387 return std::numeric_limits<double>::quiet_NaN();
1388
1389 // gradient calculation
1390 if (gfunc) {
1391 //case function provides gradient
1392 if (!useBinIntegral ) {
1393 gfunc->ParameterGradient( x , p, g );
1394 if (useFullHessian && h) {
1395 if (!gfunc->HasParameterHessian())
1396 return std::numeric_limits<double>::quiet_NaN();
1397 bool goodHessFunc = gfunc->ParameterHessian(x , p, h);
1398 if (!goodHessFunc) {
1399 return std::numeric_limits<double>::quiet_NaN();
1400 }
1401 }
1402 }
1403 else {
1404 // needs to calculate the integral for each partial derivative
1406 }
1407
1408 }
1409 else {
1410 SimpleGradientCalculator gc(func.NPar(), func);
1411 if (!useBinIntegral )
1412 gc.ParameterGradient(x, p, fval0, g);
1413 else {
1414 // needs to calculate the integral for each partial derivative
1416 }
1417 }
1418 // correct g[] do be derivative of poisson term. We compute already derivative w.r.t. LL
1419 double coeffGrad = (fval > 0) ? (1. - y/fval) : ( (y > 0) ? std::sqrt( std::numeric_limits<double>::max() ) : 1. );
1420 double coeffHess = (fval > 0) ? y/(fval*fval) : ( (y > 0) ? std::sqrt( std::numeric_limits<double>::max() ) : 0. );
1421 if (useBinVolume) {
1422 coeffGrad *= binVolume;
1423 coeffHess *= binVolume*binVolume;
1424 }
1425 for (unsigned int k = 0; k < npar; ++k) {
1426 // compute also approximate Hessian (excluding term with second derivative of model function)
1427 if (h) {
1428 for (unsigned int l = k; l < npar; ++l) {
1429 unsigned int idx = k + l * (l + 1) / 2;
1430 if (useFullHessian) {
1431 h[idx] *= coeffGrad; // h contains first model function derivatives
1432 }
1433 else {
1434 h[idx] = 0;
1435 }
1436 // add term deoending on only gradient of model function
1437 h[idx] += coeffHess * g[k]*g[l]; // g are model function derivatives
1438 }
1439 }
1440 // compute gradient of NLL element
1441 // and apply bin volume correction if needed
1442 g[k] *= coeffGrad;
1443 if (useBinVolume)
1444 g[k] *= binVolume;
1445 }
1446
1447#ifdef DEBUG
1448 std::cout << "x = " << x[0] << " y " << y << " fval " << fval << " logPdf = " << nlogPdf << " gradient : ";
1449 for (unsigned int ipar = 0; ipar < npar; ++ipar)
1450 std::cout << g[ipar] << "\t";
1451 if (h) {
1452 std::cout << "\thessian : ";
1453 for (unsigned int ipar = 0; ipar < npar; ++ipar) {
1454 std::cout << " {";
1455 for (unsigned int jpar = 0; jpar <= ipar; ++jpar) {
1456 std::cout << h[ipar + jpar * (jpar + 1) / 2] << "\t";
1457 }
1458 std::cout << "}";
1459 }
1460 }
1461 std::cout << std::endl;
1462#endif
1463#undef DEBUG
1464
1465 return nlogPdf;
1466}
1467
1468double FitUtil::EvaluatePoissonLogL(const IModelFunction &func, const BinData &data, const double *p, int iWeight,
1470 unsigned nChunks)
1471{
1472 // evaluate the Poisson Log Likelihood
1473 // for binned likelihood fits
1474 // this is Sum ( f(x_i) - y_i * log( f (x_i) ) )
1475 // add as well constant term for saturated model to make it like a Chi2/2
1476 // by default is extended. If extended is false the fit is not extended and
1477 // the global poisson term is removed (i.e is a binomial fit)
1478 // (remember that in this case one needs to have a function with a fixed normalization
1479 // like in a non extended unbinned fit)
1480 //
1481 // if use Weight use a weighted dataset
1482 // iWeight = 1 ==> logL = Sum( w f(x_i) )
1483 // case of iWeight==1 is actually identical to weight==0
1484 // iWeight = 2 ==> logL = Sum( w*w * f(x_i) )
1485 //
1486 // nPoints returns the points where bin content is not zero
1487
1488
1489 unsigned int n = data.Size();
1490
1491#ifdef USE_PARAMCACHE
1492 (const_cast<IModelFunction &>(func)).SetParameters(p);
1493#endif
1494
1495 nPoints = data.Size(); // npoints
1496
1497
1498 // get fit option and check case of using integral of bins
1499 const DataOptions &fitOpt = data.Opt();
1500 bool useBinIntegral = fitOpt.fIntegral && data.HasBinEdges();
1501 bool useBinVolume = (fitOpt.fBinVolume && data.HasBinEdges());
1502 bool useW2 = (iWeight == 2);
1503
1504 // normalize if needed by a reference volume value
1505 double wrefVolume = 1.0;
1506 if (useBinVolume) {
1507 if (fitOpt.fNormBinVolume) wrefVolume /= data.RefVolume();
1508 }
1509
1510//#define DEBUG
1511#ifdef DEBUG
1512 std::cout << "Evaluate PoissonLogL for params = [ ";
1513 for (unsigned int j = 0; j < func.NPar(); ++j) std::cout << p[j] << " , ";
1514 std::cout << "] - data size = " << n << " useBinIntegral " << useBinIntegral << " useBinVolume "
1515 << useBinVolume << " useW2 " << useW2 << " wrefVolume = " << wrefVolume << std::endl;
1516#endif
1517
1518
1521 // do not use GSL integrator which is not thread safe
1523 }
1524#ifdef USE_PARAMCACHE
1526#else
1528#endif
1529
1530 auto mapFunction = [&](const unsigned i) {
1531 auto x1 = data.GetCoordComponent(i, 0);
1532 auto y = *data.ValuePtr(i);
1533
1534 const double *x = nullptr;
1535 std::vector<double> xc;
1536 double fval = 0;
1537 double binVolume = 1.0;
1538
1539 if (useBinVolume) {
1540 unsigned int ndim = data.NDim();
1541 xc.resize(data.NDim());
1542 for (unsigned int j = 0; j < ndim; ++j) {
1543 double xx = *data.GetCoordComponent(i, j);
1544 double x2 = data.GetBinUpEdgeComponent(i, j);
1545 binVolume *= std::abs(x2 - xx);
1546 xc[j] = (useBinIntegral) ? xx : 0.5 * (x2 + xx);
1547 }
1548 x = xc.data();
1549 // normalize the bin volume using a reference value
1550 binVolume *= wrefVolume;
1551 } else if (data.NDim() > 1) {
1552 xc.resize(data.NDim());
1553 xc[0] = *x1;
1554 for (unsigned int j = 1; j < data.NDim(); ++j) {
1555 xc[j] = *data.GetCoordComponent(i, j);
1556 }
1557 x = xc.data();
1558 } else {
1559 x = x1;
1560 }
1561
1562 if (!useBinIntegral) {
1563#ifdef USE_PARAMCACHE
1564 fval = func(x);
1565#else
1566 fval = func(x, p);
1567#endif
1568 } else {
1569 // calculate integral (normalized by bin volume)
1570 // need to set function and parameters here in case loop is parallelized
1571 std::vector<double> x2(data.NDim());
1572 data.GetBinUpEdgeCoordinates(i, x2.data());
1573 fval = igEval(x, x2.data());
1574 }
1575 if (useBinVolume) fval *= binVolume;
1576
1577
1578
1579#ifdef DEBUG
1580 int NSAMPLE = 100;
1581 if (i % NSAMPLE == 0) {
1582 std::cout << "evt " << i << " x = [ ";
1583 for (unsigned int j = 0; j < func.NDim(); ++j) std::cout << x[j] << " , ";
1584 std::cout << "] ";
1585 if (fitOpt.fIntegral) {
1586 std::cout << "x2 = [ ";
1587 for (unsigned int j = 0; j < func.NDim(); ++j) std::cout << data.GetBinUpEdgeComponent(i, j) << " , ";
1588 std::cout << "] ";
1589 }
1590 std::cout << " y = " << y << " fval = " << fval << std::endl;
1591 }
1592#endif
1593
1594
1595 // EvalLog protects against 0 values of fval but don't want to add in the -log sum
1596 // negative values of fval
1597 fval = std::max(fval, 0.0);
1598
1599 double nloglike = 0; // negative loglikelihood
1600 if (useW2) {
1601 // apply weight correction . Effective weight is error^2/ y
1602 // and expected events in bins is fval/weight
1603 // can apply correction only when y is not zero otherwise weight is undefined
1604 // (in case of weighted likelihood I don't care about the constant term due to
1605 // the saturated model)
1606
1607 // use for the empty bins the global weight
1608 double weight = 1.0;
1609 if (y != 0) {
1610 double error = data.Error(i);
1611 weight = (error * error) / y; // this is the bin effective weight
1612 nloglike -= weight * y * ( ROOT::Math::Util::EvalLog(fval/y) );
1613 }
1614 else {
1615 // for empty bin use the average weight computed from the total data weight
1616 weight = data.SumOfError2()/ data.SumOfContent();
1617 }
1618 if (extended) {
1619 nloglike += weight * ( fval - y);
1620 }
1621
1622 } else {
1623 // standard case no weights or iWeight=1
1624 // this is needed for Poisson likelihood (which are extended and not for multinomial)
1625 // the formula below include constant term due to likelihood of saturated model (f(x) = y)
1626 // (same formula as in Baker-Cousins paper, page 439 except a factor of 2
1627 if (extended) nloglike = fval - y;
1628
1629 if (y > 0) {
1631 }
1632 }
1633#ifdef DEBUG
1634 {
1636 std::cout << " nll = " << nloglike << std::endl;
1637 }
1638#endif
1639 return nloglike;
1640 };
1641
1642#ifdef R__USE_IMT
1643 auto redFunction = [](const std::vector<double> &objs) {
1644 return std::accumulate(objs.begin(), objs.end(), double{});
1645 };
1646#else
1647 (void)nChunks;
1648
1649 // If IMT is disabled, force the execution policy to the serial case
1651 Warning("FitUtil::EvaluatePoissonLogL", "Multithread execution policy requires IMT, which is disabled. Changing "
1652 "to ROOT::EExecutionPolicy::kSequential.");
1654 }
1655#endif
1656
1657 double res{};
1659 for (unsigned int i = 0; i < n; ++i) {
1660 res += mapFunction(i);
1661 }
1662#ifdef R__USE_IMT
1665 auto chunks = nChunks != 0 ? nChunks : setAutomaticChunking(data.Size());
1666 res = pool.MapReduce(mapFunction, ROOT::TSeq<unsigned>(0, n), redFunction, chunks);
1667#endif
1668 // } else if(executionPolicy == ROOT::Fit::kMultitProcess){
1669 // ROOT::TProcessExecutor pool;
1670 // res = pool.MapReduce(mapFunction, ROOT::TSeq<unsigned>(0, n), redFunction);
1671 } else {
1672 Error("FitUtil::EvaluatePoissonLogL",
1673 "Execution policy unknown. Available choices:\n ROOT::EExecutionPolicy::kSequential (default)\n ROOT::EExecutionPolicy::kMultiThread (requires IMT)\n");
1674 }
1675
1676#ifdef DEBUG
1677 std::cout << "Loglikelihood = " << res << std::endl;
1678#endif
1679
1680 return res;
1681}
1682
1683void FitUtil::EvaluatePoissonLogLGradient(const IModelFunction &f, const BinData &data, const double *p, double *grad,
1684 unsigned int &, ROOT::EExecutionPolicy executionPolicy, unsigned nChunks)
1685{
1686 // evaluate the gradient of the Poisson log likelihood function
1687
1688 const IGradModelFunction *fg = dynamic_cast<const IGradModelFunction *>(&f);
1689 assert(fg != nullptr); // must be called by a grad function
1690
1691 const IGradModelFunction &func = *fg;
1692
1693#ifdef USE_PARAMCACHE
1694 (const_cast<IGradModelFunction &>(func)).SetParameters(p);
1695#endif
1696
1697 const DataOptions &fitOpt = data.Opt();
1698 bool useBinIntegral = fitOpt.fIntegral && data.HasBinEdges();
1699 bool useBinVolume = (fitOpt.fBinVolume && data.HasBinEdges());
1700
1701 double wrefVolume = 1.0;
1702 if (useBinVolume && fitOpt.fNormBinVolume)
1703 wrefVolume /= data.RefVolume();
1704
1707 // do not use GSL integrator which is not thread safe
1709 }
1710
1712
1713 unsigned int npar = func.NPar();
1714 unsigned initialNPoints = data.Size();
1715
1716 auto mapFunction = [&](const unsigned int i) {
1717 // set all vector values to zero
1718 std::vector<double> gradFunc(npar);
1719 std::vector<double> pointContribution(npar);
1720
1721 const auto x1 = data.GetCoordComponent(i, 0);
1722 const auto y = data.Value(i);
1723 auto invError = data.Error(i);
1724
1725 invError = (invError != 0.0) ? 1.0 / invError : 1;
1726
1727 double fval = 0;
1728
1729 const double *x = nullptr;
1730 std::vector<double> xc;
1731
1732 unsigned ndim = data.NDim();
1733 double binVolume = 1.0;
1734 if (useBinVolume) {
1735
1736 xc.resize(ndim);
1737
1738 for (unsigned int j = 0; j < ndim; ++j) {
1739 double x1_j = *data.GetCoordComponent(i, j);
1740 double x2_j = data.GetBinUpEdgeComponent(i, j);
1741 binVolume *= std::abs(x2_j - x1_j);
1742 xc[j] = (useBinIntegral) ? x1_j : 0.5 * (x2_j + x1_j);
1743 }
1744
1745 x = xc.data();
1746
1747 // normalize the bin volume using a reference value
1748 binVolume *= wrefVolume;
1749 } else if (ndim > 1) {
1750 xc.resize(ndim);
1751 xc[0] = *x1;
1752 for (unsigned int j = 1; j < ndim; ++j)
1753 xc[j] = *data.GetCoordComponent(i, j);
1754 x = xc.data();
1755 } else {
1756 x = x1;
1757 }
1758
1759 if (!useBinIntegral) {
1760 fval = func(x, p);
1761 func.ParameterGradient(x, p, &gradFunc[0]);
1762 } else {
1763 // calculate integral (normalized by bin volume)
1764 // need to set function and parameters here in case loop is parallelized
1765 std::vector<double> x2(data.NDim());
1766 data.GetBinUpEdgeCoordinates(i, x2.data());
1767 fval = igEval(x, x2.data());
1768 CalculateGradientIntegral(func, x, x2.data(), p, &gradFunc[0]);
1769 }
1770 if (useBinVolume)
1771 fval *= binVolume;
1772
1773#ifdef DEBUG
1774 {
1776 if (i < 5 || (i > data.Size()-5) ) {
1777 if (data.NDim() > 1) std::cout << i << " x " << x[0] << " y " << x[1] << " func " << fval
1778 << " gradient " << gradFunc[0] << " " << gradFunc[1] << " " << gradFunc[3] << std::endl;
1779 else std::cout << i << " x " << x[0] << " gradient " << gradFunc[0] << " " << gradFunc[1] << " " << gradFunc[3] << std::endl;
1780 }
1781 }
1782#endif
1783
1784 // correct the gradient
1785 for (unsigned int ipar = 0; ipar < npar; ++ipar) {
1786
1787 // correct gradient for bin volumes
1788 if (useBinVolume)
1789 gradFunc[ipar] *= binVolume;
1790
1791 // df/dp * (1. - y/f )
1792 if (fval > 0)
1793 pointContribution[ipar] = gradFunc[ipar] * (1. - y / fval);
1794 else if (gradFunc[ipar] != 0) {
1795 const double kdmax1 = std::sqrt(std::numeric_limits<double>::max());
1796 const double kdmax2 = std::numeric_limits<double>::max() / (4 * initialNPoints);
1797 double gg = kdmax1 * gradFunc[ipar];
1798 if (gg > 0)
1799 gg = std::min(gg, kdmax2);
1800 else
1801 gg = std::max(gg, -kdmax2);
1802 pointContribution[ipar] = -gg;
1803 }
1804 }
1805
1806
1807 return pointContribution;
1808 };
1809
1810 // Vertically reduce the set of vectors by summing its equally-indexed components
1811 auto redFunction = [&](const std::vector<std::vector<double>> &pointContributions) {
1812 std::vector<double> result(npar);
1813
1814 for (auto const &pointContribution : pointContributions) {
1815 for (unsigned int parameterIndex = 0; parameterIndex < npar; parameterIndex++)
1817 }
1818
1819 return result;
1820 };
1821
1822 std::vector<double> g(npar);
1823
1824#ifndef R__USE_IMT
1825 // If IMT is disabled, force the execution policy to the serial case
1827 Warning("FitUtil::EvaluatePoissonLogLGradient",
1828 "Multithread execution policy requires IMT, which is disabled. Changing "
1829 "to ROOT::EExecutionPolicy::kSequential.");
1831 }
1832#endif
1833
1835 std::vector<std::vector<double>> allGradients(initialNPoints);
1836 for (unsigned int i = 0; i < initialNPoints; ++i) {
1837 allGradients[i] = mapFunction(i);
1838 }
1840 }
1841#ifdef R__USE_IMT
1846 }
1847#endif
1848
1849 // else if(executionPolicy == ROOT::Fit::kMultiprocess){
1850 // ROOT::TProcessExecutor pool;
1851 // g = pool.MapReduce(mapFunction, ROOT::TSeq<unsigned>(0, n), redFunction);
1852 // }
1853 else {
1854 Error("FitUtil::EvaluatePoissonLogLGradient",
1855 "Execution policy unknown. Available choices:\n 0: Serial (default)\n 1: MultiThread (requires IMT)\n");
1856 }
1857
1858#ifndef R__USE_IMT
1859 //to fix compiler warning
1860 (void)nChunks;
1861#endif
1862
1863 // copy result
1864 std::copy(g.begin(), g.end(), grad);
1865
1866#ifdef DEBUG
1867 std::cout << "***** Final gradient : ";
1868 for (unsigned int ii = 0; ii< npar; ++ii) std::cout << grad[ii] << " ";
1869 std::cout << "\n";
1870#endif
1871
1872}
1873
1874
1875unsigned FitUtil::setAutomaticChunking(unsigned nEvents){
1877 if (nEvents/ncpu < 1000) return ncpu;
1878 return nEvents/1000;
1879 //return ((nEvents/ncpu + 1) % 1000) *40 ; //arbitrary formula
1880}
1881
1882#ifdef R__HAS_STD_EXPERIMENTAL_SIMD
1883namespace FitUtil {
1884
1885namespace {
1886
1887template <class T>
1888T NumericMax()
1889{
1890 return T{std::numeric_limits<typename T::value_type>::max()};
1891}
1892
1893template <typename V, typename S>
1894void Load(V &v, S const *ptr)
1895{
1896 for (size_t i = 0; i < V::size(); ++i)
1897 v[i] = ptr[i];
1898}
1899
1900template <typename T>
1901auto ReduceAdd(const T &v)
1902{
1903 typename T::value_type result(0);
1904 for (size_t i = 0; i < T::size(); ++i) {
1905 result += v[i];
1906 }
1907 return result;
1908}
1909
1910template <typename T>
1911bool MaskEmpty(T mask)
1912{
1913 for (size_t i = 0; i < T::size(); ++i)
1914 if (mask[i])
1915 return false;
1916 return true;
1917}
1918
1919template <class T = ROOT::Double_v>
1920auto Int2Mask(unsigned i)
1921{
1922 T x;
1923 for (unsigned j = 0; j < T::size(); j++) {
1924 x[j] = j;
1925 }
1926 return x < T(i);
1927}
1928
1929} // namespace
1930
1931double Evaluate<Double_v>::EvalChi2(const IModelFunctionTempl<Double_v> &func, const BinData &data, const double *p,
1933{
1934 // evaluate the chi2 given a vectorized function reference , the data and returns the value and also in nPoints
1935 // the actual number of used points
1936 // normal chi2 using only error on values (from fitting histogram)
1937 // optionally the integral of function in the bin is used
1938
1939 // Info("EvalChi2","Using vectorized implementation %d",(int) data.Opt().fIntegral);
1940
1941 unsigned int n = data.Size();
1942 nPoints = data.Size(); // npoints
1943
1944 // set parameters of the function to cache integral value
1945#ifdef USE_PARAMCACHE
1946 (const_cast<IModelFunctionTempl<Double_v> &>(func)).SetParameters(p);
1947#endif
1948 // do not cache parameter values (it is not thread safe)
1949 // func.SetParameters(p);
1950
1951 // get fit option and check case if using integral of bins
1952 const DataOptions &fitOpt = data.Opt();
1953 if (fitOpt.fBinVolume || fitOpt.fIntegral || fitOpt.fExpErrors)
1954 Error("FitUtil::EvaluateChi2",
1955 "The vectorized implementation doesn't support Integrals, BinVolume or ExpErrors\n. Aborting operation.");
1956
1957 (const_cast<IModelFunctionTempl<Double_v> &>(func)).SetParameters(p);
1958
1959 double maxResValue = std::numeric_limits<double>::max() / n;
1960 std::vector<double> ones{1., 1., 1., 1.};
1961 auto vecSize = Double_v::size();
1962
1963 auto mapFunction = [&](unsigned int i) {
1964 // in case of no error in y invError=1 is returned
1966 Load(x1, data.GetCoordComponent(i * vecSize, 0));
1967 Load(y, data.ValuePtr(i * vecSize));
1968 const auto invError = data.ErrorPtr(i * vecSize);
1969 auto invErrorptr = (invError != nullptr) ? invError : &ones.front();
1970 Load(invErrorVec, invErrorptr);
1971
1972 const Double_v *x;
1973 std::vector<Double_v> xc;
1974 if (data.NDim() > 1) {
1975 xc.resize(data.NDim());
1976 xc[0] = x1;
1977 for (unsigned int j = 1; j < data.NDim(); ++j)
1978 Load(xc[j], data.GetCoordComponent(i * vecSize, j));
1979 x = xc.data();
1980 } else {
1981 x = &x1;
1982 }
1983
1984 Double_v fval{};
1985
1986#ifdef USE_PARAMCACHE
1987 fval = func(x);
1988#else
1989 fval = func(x, p);
1990#endif
1991
1992 Double_v tmp = (y - fval) * invErrorVec;
1993 Double_v chi2 = tmp * tmp;
1994
1995 // avoid infinity or nan in chi2 values due to wrong function values
1997
1998 return chi2;
1999 };
2000
2001 auto redFunction = [](const std::vector<Double_v> &objs) {
2002 return std::accumulate(objs.begin(), objs.end(), Double_v{});
2003 };
2004
2005#ifndef R__USE_IMT
2006 (void)nChunks;
2007
2008 // If IMT is disabled, force the execution policy to the serial case
2010 Warning("FitUtil::EvaluateChi2", "Multithread execution policy requires IMT, which is disabled. Changing "
2011 "to ::ROOT::EExecutionPolicy::kSequential.");
2013 }
2014#endif
2015
2016 Double_v res{};
2019 res = pool.MapReduce(mapFunction, ROOT::TSeq<unsigned>(0, data.Size() / vecSize), redFunction);
2020#ifdef R__USE_IMT
2023 auto chunks = nChunks != 0 ? nChunks : setAutomaticChunking(data.Size() / vecSize);
2024 res = pool.MapReduce(mapFunction, ROOT::TSeq<unsigned>(0, data.Size() / vecSize), redFunction, chunks);
2025#endif
2026 } else {
2027 Error("FitUtil::EvaluateChi2",
2028 "Execution policy unknown. Available choices:\n ::ROOT::EExecutionPolicy::kSequential (default)\n "
2029 "::ROOT::EExecutionPolicy::kMultiThread (requires IMT)\n");
2030 }
2031
2032 // Last SIMD vector of elements (if padding needed)
2033 if (data.Size() % vecSize != 0)
2034 where(Int2Mask(data.Size() % vecSize), res) = res + mapFunction(data.Size() / vecSize);
2035
2036 return ReduceAdd(res);
2037}
2038
2039double Evaluate<Double_v>::EvalLogL(const IModelFunctionTempl<Double_v> &func, const UnBinData &data,
2040 const double *const p, int iWeight, bool extended, unsigned int &nPoints,
2042{
2043 // evaluate the LogLikelihood
2044 unsigned int n = data.Size();
2045 nPoints = data.Size(); // npoints
2046
2047 // unsigned int nRejected = 0;
2048 bool normalizeFunc = false;
2049
2050 // set parameters of the function to cache integral value
2051#ifdef USE_PARAMCACHE
2052 (const_cast<IModelFunctionTempl<Double_v> &>(func)).SetParameters(p);
2053#endif
2054
2055#ifdef R__USE_IMT
2056 // in case parameter needs to be propagated to user function use trick to set parameters by calling one time the
2057 // function this will be done in sequential mode and parameters can be set in a thread safe manner
2058 if (!normalizeFunc) {
2059 if (data.NDim() == 1) {
2060 Double_v x;
2061 Load(x, data.GetCoordComponent(0, 0));
2062 func(&x, p);
2063 } else {
2064 std::vector<Double_v> x(data.NDim());
2065 for (unsigned int j = 0; j < data.NDim(); ++j)
2066 Load(x[j], data.GetCoordComponent(0, j));
2067 func(x.data(), p);
2068 }
2069 }
2070#endif
2071
2072 // this is needed if function must be normalized
2073 double norm = 1.0;
2074 if (normalizeFunc) {
2075 // compute integral of the function
2076 std::vector<double> xmin(data.NDim());
2077 std::vector<double> xmax(data.NDim());
2079 // compute integral in the ranges where is defined
2080 if (data.Range().Size() > 0) {
2081 norm = 0;
2082 for (unsigned int ir = 0; ir < data.Range().Size(); ++ir) {
2083 data.Range().GetRange(&xmin[0], &xmax[0], ir);
2084 norm += igEval.Integral(xmin.data(), xmax.data());
2085 }
2086 } else {
2087 // use (-inf +inf)
2088 data.Range().GetRange(&xmin[0], &xmax[0]);
2089 // check if function is zero at +- inf
2092 Load(xmin_v, xmin.data());
2093 Load(xmax_v, xmax.data());
2094 if (ReduceAdd(func(&xmin_v, p)) != 0 || ReduceAdd(func(&xmax_v, p)) != 0) {
2095 MATH_ERROR_MSG("FitUtil::EvaluateLogLikelihood",
2096 "A range has not been set and the function is not zero at +/- inf");
2097 return 0;
2098 }
2099 norm = igEval.Integral(&xmin[0], &xmax[0]);
2100 }
2101 }
2102
2103 // needed to compute effective global weight in case of extended likelihood
2104
2105 auto vecSize = Double_v::size();
2106 unsigned int numVectors = n / vecSize;
2107
2108 auto mapFunction = [&, p](const unsigned i) {
2109 Double_v W{};
2110 Double_v W2{};
2111 Double_v fval{};
2112
2113 (void)p; /* avoid unused lambda capture warning if PARAMCACHE is disabled */
2114
2115 Double_v x1;
2116 Load(x1, data.GetCoordComponent(i * vecSize, 0));
2117 const Double_v *x = nullptr;
2118 unsigned int ndim = data.NDim();
2119 std::vector<Double_v> xc;
2120 if (ndim > 1) {
2121 xc.resize(ndim);
2122 xc[0] = x1;
2123 for (unsigned int j = 1; j < ndim; ++j)
2124 Load(xc[j], data.GetCoordComponent(i * vecSize, j));
2125 x = xc.data();
2126 } else {
2127 x = &x1;
2128 }
2129
2130#ifdef USE_PARAMCACHE
2131 fval = func(x);
2132#else
2133 fval = func(x, p);
2134#endif
2135
2136#ifdef DEBUG_FITUTIL
2137 if (i < 5 || (i > numVectors - 5)) {
2138 if (ndim == 1)
2139 std::cout << i << " x " << x[0] << " fval = " << fval;
2140 else
2141 std::cout << i << " x " << x[0] << " y " << x[1] << " fval = " << fval;
2142 }
2143#endif
2144
2145 if (normalizeFunc)
2146 fval = fval * (1 / norm);
2147
2148 // function EvalLog protects against negative or too small values of fval
2150 if (iWeight > 0) {
2151 Double_v weight{};
2152 if (data.WeightsPtr(i) == nullptr)
2153 weight = 1;
2154 else
2155 Load(weight, data.WeightsPtr(i * vecSize));
2156 logval *= weight;
2157 if (iWeight == 2) {
2158 logval *= weight; // use square of weights in likelihood
2159 if (!extended) {
2160 // needed sum of weights and sum of weight square if likelkihood is extended
2161 W = weight;
2162 W2 = weight * weight;
2163 }
2164 }
2165 }
2166#ifdef DEBUG_FITUTIL
2167 if (i < 5 || (i > numVectors - 5)) {
2168 std::cout << " " << fval << " logfval " << logval << std::endl;
2169 }
2170#endif
2171
2173 };
2174
2175 auto redFunction = [](const std::vector<LikelihoodAux<Double_v>> &objs) {
2176 return std::accumulate(
2178 [](const LikelihoodAux<Double_v> &l1, const LikelihoodAux<Double_v> &l2) { return l1 + l2; });
2179 };
2180
2181#ifndef R__USE_IMT
2182 (void)nChunks;
2183
2184 // If IMT is disabled, force the execution policy to the serial case
2186 Warning("FitUtil::EvaluateLogL", "Multithread execution policy requires IMT, which is disabled. Changing "
2187 "to ::ROOT::EExecutionPolicy::kSequential.");
2189 }
2190#endif
2191
2192 Double_v logl_v{};
2193 Double_v sumW_v{};
2194 Double_v sumW2_v{};
2199#ifdef R__USE_IMT
2204#endif
2205 } else {
2206 Error("FitUtil::EvaluateLogL",
2207 "Execution policy unknown. Available choices:\n ::ROOT::EExecutionPolicy::kSequential (default)\n "
2208 "::ROOT::EExecutionPolicy::kMultiThread (requires IMT)\n");
2209 }
2210
2211 logl_v = resArray.logvalue;
2212 sumW_v = resArray.weight;
2213 sumW2_v = resArray.weight2;
2214
2215 // Compute the contribution from the remaining points ( Last padded SIMD vector of elements )
2216 unsigned int remainingPoints = n % vecSize;
2217 if (remainingPoints > 0) {
2219 // Add the contribution from the valid remaining points and store the result in the output variable
2224 }
2225
2226 // reduce vector type to double.
2227 double logl = ReduceAdd(logl_v);
2228 double sumW = ReduceAdd(sumW_v);
2229 double sumW2 = ReduceAdd(sumW2_v);
2230
2231 if (extended) {
2232 // add Poisson extended term
2233 double extendedTerm = 0; // extended term in likelihood
2234 double nuTot = 0;
2235 // nuTot is integral of function in the range
2236 // if function has been normalized integral has been already computed
2237 if (!normalizeFunc) {
2239 std::vector<double> xmin(data.NDim());
2240 std::vector<double> xmax(data.NDim());
2241
2242 // compute integral in the ranges where is defined
2243 if (data.Range().Size() > 0) {
2244 nuTot = 0;
2245 for (unsigned int ir = 0; ir < data.Range().Size(); ++ir) {
2246 data.Range().GetRange(&xmin[0], &xmax[0], ir);
2247 nuTot += igEval.Integral(xmin.data(), xmax.data());
2248 }
2249 } else {
2250 // use (-inf +inf)
2251 data.Range().GetRange(&xmin[0], &xmax[0]);
2252 // check if function is zero at +- inf
2254 Load(xmin_v, xmin.data());
2255 Load(xmax_v, xmax.data());
2256 if (ReduceAdd(func(&xmin_v, p)) != 0 || ReduceAdd(func(&xmax_v, p)) != 0) {
2257 MATH_ERROR_MSG("FitUtil::EvaluateLogLikelihood",
2258 "A range has not been set and the function is not zero at +/- inf");
2259 return 0;
2260 }
2261 nuTot = igEval.Integral(&xmin[0], &xmax[0]);
2262 }
2263
2264 // force to be last parameter value
2265 // nutot = p[func.NDim()-1];
2266 if (iWeight != 2)
2267 extendedTerm = -nuTot; // no need to add in this case n log(nu) since is already computed before
2268 else {
2269 // case use weight square in likelihood : compute total effective weight = sw2/sw
2270 // ignore for the moment case when sumW is zero
2271 extendedTerm = -(sumW2 / sumW) * nuTot;
2272 }
2273
2274 } else {
2275 nuTot = norm;
2276 extendedTerm = -nuTot + double(n) * ROOT::Math::Util::EvalLog(nuTot);
2277 // in case of weights need to use here sum of weights (to be done)
2278 }
2279
2280 logl += extendedTerm;
2281 }
2282
2283#ifdef DEBUG_FITUTIL
2284 std::cout << "Evaluated log L for parameters (";
2285 for (unsigned int ip = 0; ip < func.NPar(); ++ip)
2286 std::cout << " " << p[ip];
2287 std::cout << ") nll = " << -logl << std::endl;
2288#endif
2289
2290 return -logl;
2291}
2292
2293double Evaluate<Double_v>::EvalPoissonLogL(const IModelFunctionTempl<Double_v> &func, const BinData &data,
2294 const double *p, int iWeight, bool extended, unsigned int,
2296{
2297 // evaluate the Poisson Log Likelihood
2298 // for binned likelihood fits
2299 // this is Sum ( f(x_i) - y_i * log( f (x_i) ) )
2300 // add as well constant term for saturated model to make it like a Chi2/2
2301 // by default is extended. If extended is false the fit is not extended and
2302 // the global poisson term is removed (i.e is a binomial fit)
2303 // (remember that in this case one needs to have a function with a fixed normalization
2304 // like in a non extended binned fit)
2305 //
2306 // if use Weight use a weighted dataset
2307 // iWeight = 1 ==> logL = Sum( w f(x_i) )
2308 // case of iWeight==1 is actually identical to weight==0
2309 // iWeight = 2 ==> logL = Sum( w*w * f(x_i) )
2310 //
2311
2312#ifdef USE_PARAMCACHE
2313 (const_cast<IModelFunctionTempl<Double_v> &>(func)).SetParameters(p);
2314#endif
2315 auto vecSize = Double_v::size();
2316 // get fit option and check case of using integral of bins
2317 const DataOptions &fitOpt = data.Opt();
2318 if (fitOpt.fExpErrors || fitOpt.fIntegral)
2319 Error("FitUtil::EvaluateChi2",
2320 "The vectorized implementation doesn't support Integrals or BinVolume\n. Aborting operation.");
2321 bool useW2 = (iWeight == 2);
2322
2323 auto mapFunction = [&](unsigned int i) {
2324 Double_v y;
2325 Load(y, data.ValuePtr(i * vecSize));
2326 Double_v fval{};
2327
2328 if (data.NDim() > 1) {
2329 std::vector<Double_v> x(data.NDim());
2330 for (unsigned int j = 0; j < data.NDim(); ++j)
2331 Load(x[j], data.GetCoordComponent(i * vecSize, j));
2332#ifdef USE_PARAMCACHE
2333 fval = func(x.data());
2334#else
2335 fval = func(x.data(), p);
2336#endif
2337 // one -dim case
2338 } else {
2339 Double_v x;
2340 Load(x, data.GetCoordComponent(i * vecSize, 0));
2341#ifdef USE_PARAMCACHE
2342 fval = func(&x);
2343#else
2344 fval = func(&x, p);
2345#endif
2346 }
2347
2348 // EvalLog protects against 0 values of fval but don't want to add in the -log sum
2349 // negative values of fval
2350 where(fval < 0.0, fval) = 0.0;
2351
2352 Double_v nloglike{}; // negative loglikelihood
2353
2354 if (useW2) {
2355 // apply weight correction . Effective weight is error^2/ y
2356 // and expected events in bins is fval/weight
2357 // can apply correction only when y is not zero otherwise weight is undefined
2358 // (in case of weighted likelihood I don't care about the constant term due to
2359 // the saturated model)
2361 Double_v error = 0.0;
2362 Load(error, data.ErrorPtr(i * vecSize));
2363 // for empty bin use the average weight computed from the total data weight
2364 auto m = y != 0.0;
2365 Double_v weight{};
2366 where(m, weight) = (error * error) / y;
2367 where(!m, weight) = Double_v{data.SumOfError2() / data.SumOfContent()};
2368 if (extended) {
2369 nloglike = weight * (fval - y);
2370 }
2371 where(y != 0, nloglike) =
2373
2374 } else {
2375 // standard case no weights or iWeight=1
2376 // this is needed for Poisson likelihood (which are extended and not for multinomial)
2377 // the formula below include constant term due to likelihood of saturated model (f(x) = y)
2378 // (same formula as in Baker-Cousins paper, page 439 except a factor of 2
2379 if (extended)
2380 nloglike = fval - y;
2381
2383 }
2384
2385 return nloglike;
2386 };
2387
2388#ifdef R__USE_IMT
2389 auto redFunction = [](const std::vector<Double_v> &objs) {
2390 return std::accumulate(objs.begin(), objs.end(), Double_v{});
2391 };
2392#else
2393 (void)nChunks;
2394
2395 // If IMT is disabled, force the execution policy to the serial case
2397 Warning("FitUtil::Evaluate<T>::EvalPoissonLogL",
2398 "Multithread execution policy requires IMT, which is disabled. Changing "
2399 "to ::ROOT::EExecutionPolicy::kSequential.");
2401 }
2402#endif
2403
2404 Double_v res{};
2406 for (unsigned int i = 0; i < (data.Size() / vecSize); i++) {
2407 res += mapFunction(i);
2408 }
2409#ifdef R__USE_IMT
2412 auto chunks = nChunks != 0 ? nChunks : setAutomaticChunking(data.Size() / vecSize);
2413 res = pool.MapReduce(mapFunction, ROOT::TSeq<unsigned>(0, data.Size() / vecSize), redFunction, chunks);
2414#endif
2415 } else {
2416 Error("FitUtil::Evaluate<T>::EvalPoissonLogL",
2417 "Execution policy unknown. Available choices:\n ::ROOT::EExecutionPolicy::kSequential (default)\n "
2418 "::ROOT::EExecutionPolicy::kMultiThread (requires IMT)\n");
2419 }
2420
2421 // Last padded SIMD vector of elements
2422 if (data.Size() % vecSize != 0)
2423 where(Int2Mask(data.Size() % vecSize), res) = res + mapFunction(data.Size() / vecSize);
2424
2425 return ReduceAdd(res);
2426}
2427
2428namespace {
2429
2430// Compute a mask to filter out infinite numbers and NaN values.
2431// The argument rval is updated so infinite numbers and NaN values are replaced by
2432// maximum finite values (preserving the original sign).
2434{
2436
2437 // Case +inf or nan
2439
2440 // Case -inf
2441 where(!mask && rval < 0, rval) = -NumericMax<Double_v>();
2442
2443 return mask;
2444}
2445
2446} // namespace
2447
2448void Evaluate<Double_v>::EvalChi2Gradient(const IModelFunctionTempl<Double_v> &f, const BinData &data, const double *p,
2449 double *grad, unsigned int &nPoints, ::ROOT::EExecutionPolicy executionPolicy,
2450 unsigned nChunks)
2451{
2452 // evaluate the gradient of the chi2 function
2453 // this function is used when the model function knows how to calculate the derivative and we can
2454 // avoid that the minimizer re-computes them
2455 //
2456 // case of chi2 effective (errors on coordinate) is not supported
2457
2458 if (data.HaveCoordErrors()) {
2459 MATH_ERROR_MSG("FitUtil::EvaluateChi2Gradient",
2460 "Error on the coordinates are not used in calculating Chi2 gradient");
2461 return; // it will assert otherwise later in GetPoint
2462 }
2463
2464 auto *fg = dynamic_cast<const IGradModelFunctionTempl<Double_v> *>(&f);
2465 assert(fg != nullptr); // must be called by a gradient function
2466
2467 auto &func = *fg;
2468
2469 const DataOptions &fitOpt = data.Opt();
2470 if (fitOpt.fBinVolume || fitOpt.fIntegral || fitOpt.fExpErrors)
2471 Error("FitUtil::EvaluateChi2Gradient", "The vectorized implementation doesn't support Integrals,"
2472 "BinVolume or ExpErrors\n. Aborting operation.");
2473
2474 unsigned int npar = func.NPar();
2475 auto vecSize = Double_v::size();
2476 unsigned initialNPoints = data.Size();
2477 unsigned numVectors = initialNPoints / vecSize;
2478
2479 // numVectors + 1 because of the padded data (call to mapFunction with i = numVectors after the main loop)
2480 std::vector<Double_v::mask_type> validPointsMasks(numVectors + 1);
2481
2482 auto mapFunction = [&](const unsigned int i) {
2483 // set all vector values to zero
2484 std::vector<Double_v> gradFunc(npar);
2485 std::vector<Double_v> pointContributionVec(npar);
2486
2488
2489 Load(x1, data.GetCoordComponent(i * vecSize, 0));
2490 Load(y, data.ValuePtr(i * vecSize));
2491 const auto invErrorPtr = data.ErrorPtr(i * vecSize);
2492
2493 if (invErrorPtr == nullptr)
2494 invError = 1;
2495 else
2496 Load(invError, invErrorPtr);
2497
2498 // TODO: Check error options and invert if needed
2499
2500 Double_v fval = 0;
2501
2502 const Double_v *x = nullptr;
2503
2504 unsigned int ndim = data.NDim();
2505 // need to declare vector outside if statement
2506 // otherwise pointer will be invalid
2507 std::vector<Double_v> xc;
2508 if (ndim > 1) {
2509 xc.resize(ndim);
2510 xc[0] = x1;
2511 for (unsigned int j = 1; j < ndim; ++j)
2512 Load(xc[j], data.GetCoordComponent(i * vecSize, j));
2513 x = xc.data();
2514 } else {
2515 x = &x1;
2516 }
2517
2518 fval = func(x, p);
2519 func.ParameterGradient(x, p, &gradFunc[0]);
2520
2522 if (MaskEmpty(validPointsMasks[i])) {
2523 // Return a zero contribution to all partial derivatives on behalf of the current points
2524 return pointContributionVec;
2525 }
2526
2527 // loop on the parameters
2528 for (unsigned int ipar = 0; ipar < npar; ++ipar) {
2529 // avoid singularity in the function (infinity and nan ) in the chi2 sum
2530 // eventually add possibility of excluding some points (like singularity)
2532
2533 if (MaskEmpty(validPointsMasks[i])) {
2534 break; // exit loop on parameters
2535 }
2536
2537 // calculate derivative point contribution (only for valid points)
2539 -2.0 * (y - fval) * invError * invError * gradFunc[ipar];
2540 }
2541
2542 return pointContributionVec;
2543 };
2544
2545 // Reduce the set of vectors by summing its equally-indexed components
2546 auto redFunction = [&](const std::vector<std::vector<Double_v>> &partialResults) {
2547 std::vector<Double_v> result(npar);
2548
2549 for (auto const &pointContributionVec : partialResults) {
2550 for (unsigned int parameterIndex = 0; parameterIndex < npar; parameterIndex++)
2552 }
2553
2554 return result;
2555 };
2556
2557 std::vector<Double_v> gVec(npar);
2558 std::vector<double> g(npar);
2559
2560#ifndef R__USE_IMT
2561 // to fix compiler warning
2562 (void)nChunks;
2563
2564 // If IMT is disabled, force the execution policy to the serial case
2566 Warning("FitUtil::EvaluateChi2Gradient", "Multithread execution policy requires IMT, which is disabled. Changing "
2567 "to ::ROOT::EExecutionPolicy::kSequential.");
2569 }
2570#endif
2571
2575 }
2576#ifdef R__USE_IMT
2581 }
2582#endif
2583 else {
2584 Error("FitUtil::EvaluateChi2Gradient",
2585 "Execution policy unknown. Available choices:\n 0: Serial (default)\n 1: MultiThread (requires IMT)\n");
2586 }
2587
2588 // Compute the contribution from the remaining points
2589 unsigned int remainingPoints = initialNPoints % vecSize;
2590 if (remainingPoints > 0) {
2592 // Add the contribution from the valid remaining points and store the result in the output variable
2594 for (unsigned int param = 0; param < npar; param++) {
2595 where(remainingMask, gVec[param]) = gVec[param] + remainingPointsContribution[param];
2596 }
2597 }
2598 // reduce final gradient result from T to double
2599 for (unsigned int param = 0; param < npar; param++) {
2600 grad[param] = ReduceAdd(gVec[param]);
2601 }
2602
2603 // correct the number of points
2605
2606 if (std::any_of(validPointsMasks.begin(), validPointsMasks.end(), [](auto validPoints) {
2607 // Check if the mask is not full
2608 for (size_t i = 0; i < Double_v::mask_type::size(); ++i)
2609 if (!validPoints[i])
2610 return true;
2611 return false;
2612 })) {
2613 unsigned nRejected = 0;
2614
2615 for (const auto &mask : validPointsMasks) {
2616 for (unsigned int i = 0; i < vecSize; i++) {
2617 nRejected += !mask[i];
2618 }
2619 }
2620
2623
2624 if (nPoints < npar) {
2625 MATH_ERROR_MSG("FitUtil::EvaluateChi2Gradient",
2626 "Too many points rejected for overflow in gradient calculation");
2627 }
2628 }
2629}
2630
2631void Evaluate<Double_v>::EvalPoissonLogLGradient(const IModelFunctionTempl<Double_v> &f, const BinData &data,
2632 const double *p, double *grad, unsigned int &,
2634{
2635 // evaluate the gradient of the Poisson log likelihood function
2636
2637 auto *fg = dynamic_cast<const IGradModelFunctionTempl<Double_v> *>(&f);
2638 assert(fg != nullptr); // must be called by a grad function
2639
2640 auto &func = *fg;
2641
2642 (const_cast<IGradModelFunctionTempl<Double_v> &>(func)).SetParameters(p);
2643
2644 const DataOptions &fitOpt = data.Opt();
2645 if (fitOpt.fBinVolume || fitOpt.fIntegral || fitOpt.fExpErrors)
2646 Error("FitUtil::EvaluatePoissonLogLGradient", "The vectorized implementation doesn't support Integrals,"
2647 "BinVolume or ExpErrors\n. Aborting operation.");
2648
2649 unsigned int npar = func.NPar();
2650 auto vecSize = Double_v::size();
2651 unsigned initialNPoints = data.Size();
2652 unsigned numVectors = initialNPoints / vecSize;
2653
2654 auto mapFunction = [&](const unsigned int i) {
2655 // set all vector values to zero
2656 std::vector<Double_v> gradFunc(npar);
2657 std::vector<Double_v> pointContributionVec(npar);
2658
2659 Double_v x1, y;
2660
2661 Load(x1, data.GetCoordComponent(i * vecSize, 0));
2662 Load(y, data.ValuePtr(i * vecSize));
2663
2664 Double_v fval = 0;
2665
2666 const Double_v *x = nullptr;
2667
2668 unsigned ndim = data.NDim();
2669 std::vector<Double_v> xc;
2670 if (ndim > 1) {
2671 xc.resize(ndim);
2672 xc[0] = x1;
2673 for (unsigned int j = 1; j < ndim; ++j)
2674 Load(xc[j], data.GetCoordComponent(i * vecSize, j));
2675 x = xc.data();
2676 } else {
2677 x = &x1;
2678 }
2679
2680 fval = func(x, p);
2681 func.ParameterGradient(x, p, &gradFunc[0]);
2682
2683 // correct the gradient
2684 for (unsigned int ipar = 0; ipar < npar; ++ipar) {
2685 auto positiveValuesMask = fval > 0;
2686
2687 // df/dp * (1. - y/f )
2688 where(positiveValuesMask, pointContributionVec[ipar]) = gradFunc[ipar] * (1. - y / fval);
2689
2691
2692 if (!MaskEmpty(validNegativeValuesMask)) {
2693 const Double_v kdmax1 = sqrt(NumericMax<Double_v>());
2695 Double_v gg = kdmax1 * gradFunc[ipar];
2696 auto mask = gg > 0;
2697 where(mask, pointContributionVec[ipar]) = min(gg, kdmax2);
2698 where(!mask, pointContributionVec[ipar]) = max(gg, -kdmax2);
2700 }
2701 }
2702
2703#ifdef DEBUG_FITUTIL
2704 {
2706 if (i < 5 || (i > data.Size() - 5)) {
2707 if (data.NDim() > 1)
2708 std::cout << i << " x " << x[0] << " y " << x[1];
2709 else
2710 std::cout << i << " x " << x[0];
2711 std::cout << " func " << fval << " gradient ";
2712 for (unsigned int ii = 0; ii < npar; ++ii)
2713 std::cout << " " << pointContributionVec[ii];
2714 std::cout << "\n";
2715 }
2716 }
2717#endif
2718
2719 return pointContributionVec;
2720 };
2721
2722 // Vertically reduce the set of vectors by summing its equally-indexed components
2723 auto redFunction = [&](const std::vector<std::vector<Double_v>> &partialResults) {
2724 std::vector<Double_v> result(npar);
2725
2726 for (auto const &pointContributionVec : partialResults) {
2727 for (unsigned int parameterIndex = 0; parameterIndex < npar; parameterIndex++)
2729 }
2730
2731 return result;
2732 };
2733
2734 std::vector<Double_v> gVec(npar);
2735
2736#ifndef R__USE_IMT
2737 // to fix compiler warning
2738 (void)nChunks;
2739
2740 // If IMT is disabled, force the execution policy to the serial case
2742 Warning("FitUtil::EvaluatePoissonLogLGradient",
2743 "Multithread execution policy requires IMT, which is disabled. Changing "
2744 "to ::ROOT::EExecutionPolicy::kSequential.");
2746 }
2747#endif
2748
2752 }
2753#ifdef R__USE_IMT
2758 }
2759#endif
2760 else {
2761 Error("FitUtil::EvaluatePoissonLogLGradient", "Execution policy unknown. Available choices:\n "
2762 "::ROOT::EExecutionPolicy::kSequential (default)\n "
2763 "::ROOT::EExecutionPolicy::kMultiThread (requires IMT)\n");
2764 }
2765
2766 // Compute the contribution from the remaining points
2767 unsigned int remainingPoints = initialNPoints % vecSize;
2768 if (remainingPoints > 0) {
2770 // Add the contribution from the valid remaining points and store the result in the output variable
2772 for (unsigned int param = 0; param < npar; param++) {
2773 where(remainingMask, gVec[param]) = gVec[param] + remainingPointsContribution[param];
2774 }
2775 }
2776 // reduce final gradient result from T to double
2777 for (unsigned int param = 0; param < npar; param++) {
2778 grad[param] = ReduceAdd(gVec[param]);
2779 }
2780
2781#ifdef DEBUG_FITUTIL
2782 std::cout << "***** Final gradient : ";
2783 for (unsigned int ii = 0; ii < npar; ++ii)
2784 std::cout << grad[ii] << " ";
2785 std::cout << "\n";
2786#endif
2787}
2788
2789void Evaluate<Double_v>::EvalLogLGradient(const IModelFunctionTempl<Double_v> &f, const UnBinData &data,
2790 const double *p, double *grad, unsigned int &,
2792{
2793 // evaluate the gradient of the log likelihood function
2794
2795 auto *fg = dynamic_cast<const IGradModelFunctionTempl<Double_v> *>(&f);
2796 assert(fg != nullptr); // must be called by a grad function
2797
2798 auto &func = *fg;
2799
2800 unsigned int npar = func.NPar();
2801 auto vecSize = Double_v::size();
2802 unsigned initialNPoints = data.Size();
2803 unsigned numVectors = initialNPoints / vecSize;
2804
2805#ifdef DEBUG_FITUTIL
2806 std::cout << "\n===> Evaluate Gradient for parameters ";
2807 for (unsigned int ip = 0; ip < npar; ++ip)
2808 std::cout << " " << p[ip];
2809 std::cout << "\n";
2810#endif
2811
2812 (const_cast<IGradModelFunctionTempl<Double_v> &>(func)).SetParameters(p);
2813
2814 const Double_v kdmax1 = sqrt(NumericMax<Double_v>());
2816
2817 auto mapFunction = [&](const unsigned int i) {
2818 std::vector<Double_v> gradFunc(npar);
2819 std::vector<Double_v> pointContributionVec(npar);
2820
2821 Double_v x1;
2822 Load(x1, data.GetCoordComponent(i * vecSize, 0));
2823
2824 const Double_v *x = nullptr;
2825
2826 unsigned int ndim = data.NDim();
2827 std::vector<Double_v> xc(ndim);
2828 if (ndim > 1) {
2829 xc.resize(ndim);
2830 xc[0] = x1;
2831 for (unsigned int j = 1; j < ndim; ++j)
2832 Load(xc[j], data.GetCoordComponent(i * vecSize, j));
2833 x = xc.data();
2834 } else {
2835 x = &x1;
2836 }
2837
2838 Double_v fval = func(x, p);
2839 func.ParameterGradient(x, p, &gradFunc[0]);
2840
2841#ifdef DEBUG_FITUTIL
2842 if (i < 5 || (i > numVectors - 5)) {
2843 if (ndim > 1)
2844 std::cout << i << " x " << x[0] << " y " << x[1] << " gradient " << gradFunc[0] << " " << gradFunc[1]
2845 << " " << gradFunc[3] << std::endl;
2846 else
2847 std::cout << i << " x " << x[0] << " gradient " << gradFunc[0] << " " << gradFunc[1] << " "
2848 << gradFunc[3] << std::endl;
2849 }
2850#endif
2851
2852 auto positiveValues = fval > 0;
2853
2854 for (unsigned int kpar = 0; kpar < npar; ++kpar) {
2855 if (!MaskEmpty(positiveValues)) {
2857 }
2858
2860 if (!MaskEmpty(nonZeroGradientValues)) {
2862 auto mask = nonZeroGradientValues && gg > 0;
2865 }
2866 // if func derivative is zero term is also zero so do not add in g[kpar]
2867 }
2868
2869 return pointContributionVec;
2870 };
2871
2872 // Vertically reduce the set of vectors by summing its equally-indexed components
2873 auto redFunction = [&](const std::vector<std::vector<Double_v>> &pointContributions) {
2874 std::vector<Double_v> result(npar);
2875
2876 for (auto const &pointContributionVec : pointContributions) {
2877 for (unsigned int parameterIndex = 0; parameterIndex < npar; parameterIndex++)
2879 }
2880
2881 return result;
2882 };
2883
2884 std::vector<Double_v> gVec(npar);
2885 std::vector<double> g(npar);
2886
2887#ifndef R__USE_IMT
2888 // to fix compiler warning
2889 (void)nChunks;
2890
2891 // If IMT is disabled, force the execution policy to the serial case
2893 Warning("FitUtil::EvaluateLogLGradient", "Multithread execution policy requires IMT, which is disabled. Changing "
2894 "to ::ROOT::EExecutionPolicy::kSequential.");
2896 }
2897#endif
2898
2902 }
2903#ifdef R__USE_IMT
2908 }
2909#endif
2910 else {
2911 Error("FitUtil::EvaluateLogLGradient", "Execution policy unknown. Available choices:\n "
2912 "::ROOT::EExecutionPolicy::kSequential (default)\n "
2913 "::ROOT::EExecutionPolicy::kMultiThread (requires IMT)\n");
2914 }
2915
2916 // Compute the contribution from the remaining points
2917 unsigned int remainingPoints = initialNPoints % vecSize;
2918 if (remainingPoints > 0) {
2920 // Add the contribution from the valid remaining points and store the result in the output variable
2922 for (unsigned int param = 0; param < npar; param++) {
2923 where(remainingMask, gVec[param]) = gVec[param] + remainingPointsContribution[param];
2924 }
2925 }
2926 // reduce final gradient result from T to double
2927 for (unsigned int param = 0; param < npar; param++) {
2928 grad[param] = ReduceAdd(gVec[param]);
2929 }
2930
2931#ifdef DEBUG_FITUTIL
2932 std::cout << "Final gradient ";
2933 for (unsigned int param = 0; param < npar; param++) {
2934 std::cout << " " << grad[param];
2935 }
2936 std::cout << "\n";
2937#endif
2938}
2939
2940} // namespace FitUtil
2941
2942#endif // R__HAS_STD_EXPERIMENTAL_SIMD
2943}
2944
2945} // end namespace ROOT
#define MATH_ERROR_MSG(loc, str)
Definition Error.h:83
#define f(i)
Definition RSha256.hxx:104
#define g(i)
Definition RSha256.hxx:105
#define h(i)
Definition RSha256.hxx:106
ROOT::Detail::TRangeCast< T, true > TRangeDynCast
TRangeDynCast is an adapter class that allows the typed iteration through a TCollection.
void Error(const char *location, const char *msgfmt,...)
Use this function in case an error occurred.
Definition TError.cxx:208
void Warning(const char *location, const char *msgfmt,...)
Use this function in warning situations.
Definition TError.cxx:252
winID h TVirtualViewer3D TVirtualGLPainter p
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 mask
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 result
Option_t Option_t TPoint TPoint const char x2
Option_t Option_t TPoint TPoint const char x1
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void gc
float xmin
float xmax
const Double_t kEps
R__EXTERN TVirtualMutex * gROOTMutex
Definition TROOT.h:63
#define R__LOCKGUARD(mutex)
Definition TF1.cxx:152
Class describing the binned data sets : vectors of x coordinates, y values and optionally error on y ...
Definition BinData.h:52
SimpleGradientCalculator(int gdim, const IModelFunction &func, double eps=2.E-8, int istrat=1)
Definition FitUtil.cxx:81
double ParameterDerivative(const double *x, const double *p, int ipar) const
Definition FitUtil.cxx:120
void Gradient(const double *x, const double *p, double f0, double *g)
Definition FitUtil.cxx:137
void ParameterGradient(const double *x, const double *p, double f0, double *g)
Definition FitUtil.cxx:128
double DoParameterDerivative(const double *x, const double *p, double f0, int k) const
Definition FitUtil.cxx:92
Class describing the un-binned data sets (just x coordinates values) of any dimensions.
Definition UnBinData.h:46
virtual unsigned int NDim() const =0
Retrieve the dimension of the function.
virtual void SetParameters(const double *p)=0
Set the parameter values.
virtual unsigned int NPar() const =0
Return the number of Parameters.
Interface (abstract class) for parametric gradient multi-dimensional functions providing in addition ...
OneDimMultiFunctionAdapter class to wrap a multidimensional function in one dimensional one.
User class for calculating the derivatives of a function.
const_iterator begin() const
const_iterator end() const
A pseudo container class which is a generator of indices.
Definition TSeq.hxx:67
This class provides a simple interface to execute the same task multiple times in parallel threads,...
Type
enumeration specifying the integration types.
@ kGAUSS
simple Gauss integration method with fixed rule
@ kDEFAULT
default type specified in the static options
Double_t y[n]
Definition legend1.C:17
Double_t x[n]
Definition legend1.C:17
Double_t ey[n]
Definition legend1.C:17
const Int_t n
Definition legend1.C:16
Double_t ex[n]
Definition legend1.C:17
TF1 * f1
Definition legend1.C:11
TFitResultPtr Fit(FitObject *h1, TF1 *f1, Foption_t &option, const ROOT::Math::MinimizerOptions &moption, const char *goption, ROOT::Fit::DataRange &range)
Definition HFitImpl.cxx:133
ROOT::Math::IParamMultiGradFunction IGradModelFunction
Definition FitUtil.h:47
void CalculateGradientIntegral(const GFunc &gfunc, const double *x1, const double *x2, const double *p, double *g)
Definition FitUtil.cxx:206
double EvaluatePoissonBinPdf(const IModelFunction &func, const BinData &data, const double *x, unsigned int ipoint, double *g=nullptr, double *h=nullptr, bool hasGrad=false, bool fullHessian=false)
evaluate the pdf contribution to the Poisson LogL given a model function and the BinPoint data.
Definition FitUtil.cxx:1331
double CorrectValue(double rval)
Definition FitUtil.cxx:171
void EvaluatePoissonLogLGradient(const IModelFunction &func, const BinData &data, const double *p, double *grad, unsigned int &nPoints, ::ROOT::EExecutionPolicy executionPolicy=::ROOT::EExecutionPolicy::kSequential, unsigned nChunks=0)
evaluate the Poisson LogL given a model function and the data at the point p.
double EvaluateChi2Residual(const IModelFunction &func, const BinData &data, const double *p, unsigned int ipoint, double *g=nullptr, double *h=nullptr, bool hasGrad=false, bool fullHessian=false)
evaluate the residual contribution to the Chi2 given a model function and the BinPoint data and if th...
Definition FitUtil.cxx:579
double EvaluatePoissonLogL(const IModelFunction &func, const BinData &data, const double *p, int iWeight, bool extended, unsigned int &nPoints, ::ROOT::EExecutionPolicy executionPolicy, unsigned nChunks=0)
evaluate the Poisson LogL given a model function and the data at the point p.
double EvaluateChi2Effective(const IModelFunction &func, const BinData &data, const double *x, unsigned int &nPoints, ::ROOT::EExecutionPolicy executionPolicy=::ROOT::EExecutionPolicy::kSequential, unsigned nChunks=0)
evaluate the effective Chi2 given a model function and the data at the point x.
Definition FitUtil.cxx:428
void EvaluateLogLGradient(const IModelFunction &func, const UnBinData &data, const double *p, double *grad, unsigned int &nPoints, ::ROOT::EExecutionPolicy executionPolicy=::ROOT::EExecutionPolicy::kSequential, unsigned nChunks=0)
evaluate the LogL gradient given a model function and the data at the point p.
double EvaluatePdf(const IModelFunction &func, const UnBinData &data, const double *p, unsigned int ipoint, double *g=nullptr, double *h=nullptr, bool hasGrad=false, bool fullHessian=false)
evaluate the pdf contribution to the LogL given a model function and the BinPoint data.
Definition FitUtil.cxx:925
bool CheckInfNaNValue(double &rval)
Definition FitUtil.cxx:185
unsigned setAutomaticChunking(unsigned nEvents)
Definition FitUtil.cxx:1875
double EvaluateLogL(const IModelFunction &func, const UnBinData &data, const double *p, int iWeight, bool extended, unsigned int &nPoints, ::ROOT::EExecutionPolicy executionPolicy, unsigned nChunks=0)
evaluate the LogL given a model function and the data at the point x.
double EvaluateChi2(const IModelFunction &func, const BinData &data, const double *p, unsigned int &nPoints, ::ROOT::EExecutionPolicy executionPolicy, unsigned nChunks=0)
Chi2 Functions.
Definition FitUtil.cxx:230
void EvaluateChi2Gradient(const IModelFunction &func, const BinData &data, const double *p, double *grad, unsigned int &nPoints, ::ROOT::EExecutionPolicy executionPolicy=::ROOT::EExecutionPolicy::kSequential, unsigned nChunks=0)
evaluate the Chi2 gradient given a model function and the data at the point p.
T EvalLog(T x)
safe evaluation of log(x) with a protections against negative or zero argument to the log smooth line...
Definition Util.h:78
UInt_t GetThreadPoolSize()
Returns the size of ROOT's thread pool.
Definition TROOT.cxx:676
const char * Size
Definition TXMLSetup.cxx:55
DataOptions : simple structure holding the options on how the data are filled.
Definition DataOptions.h:28
double operator()(const double *x, const double *p) const
Definition FitUtil.cxx:61
void SetDerivComponent(unsigned int ipar)
Definition FitUtil.cxx:60
unsigned int NDim() const
Definition FitUtil.cxx:64
ParamDerivFunc(const GradFunc &f)
Definition FitUtil.cxx:59
TMarker m
Definition textangle.C:8
TLine l
Definition textangle.C:4