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) {
429 // evaluate the chi2 given a function reference , the data and returns the value and also in nPoints
430 // the actual number of used points
431 // method using the error in the coordinates
432 // integral of bin does not make sense in this case
433
434 unsigned int n = data.Size();
435
436#ifdef DEBUG
437 std::cout << "\n\nFit data size = " << n << std::endl;
438 std::cout << "evaluate effective chi2 using function " << &func << " " << p << std::endl;
439#endif
440
441 assert(data.HaveCoordErrors() || data.HaveAsymErrors());
442
443 double chi2 = 0;
444 //int nRejected = 0;
445
446
447 //func.SetParameters(p);
448
449 unsigned int ndim = func.NDim();
450
451 // use Richardson derivator
453
454 double maxResValue = std::numeric_limits<double>::max() /n;
455
456
457
458 for (unsigned int i = 0; i < n; ++ i) {
459
460
461 double y = 0;
462 const double * x = data.GetPoint(i,y);
463
464 double fval = func( x, p );
465
466 double delta_y_func = y - fval;
467
468
469 double ey = 0;
470 const double * ex = nullptr;
471 if (!data.HaveAsymErrors() )
472 ex = data.GetPointError(i, ey);
473 else {
474 double eylow, eyhigh = 0;
475 ex = data.GetPointError(i, eylow, eyhigh);
476 if ( delta_y_func < 0)
477 ey = eyhigh; // function is higher than points
478 else
479 ey = eylow;
480 }
481 double e2 = ey * ey;
482 // before calculating the gradient check that all error in x are not zero
483 unsigned int j = 0;
484 while ( j < ndim && ex[j] == 0.) { j++; }
485 // if j is less ndim some elements are not zero
486 if (j < ndim) {
487 // need an adapter from a multi-dim function to a one-dimensional
489 // select optimal step size (use 10--2 by default as was done in TF1:
490 double kEps = 0.01;
491 double kPrecision = 1.E-8;
492 for (unsigned int icoord = 0; icoord < ndim; ++icoord) {
493 // calculate derivative for each coordinate
494 if (ex[icoord] > 0) {
495 //gradCalc.Gradient(x, p, fval, &grad[0]);
496 f1D.SetCoord(icoord);
497 // optimal step size (take ex[] as scale for the points and 1% of it
498 double x0= x[icoord];
499 double h = std::max( kEps* std::abs(ex[icoord]), 8.0*kPrecision*(std::abs(x0) + kPrecision) );
500 double deriv = derivator.Derivative1(f1D, x[icoord], h);
501 double edx = ex[icoord] * deriv;
502 e2 += edx * edx;
503#ifdef DEBUG
504 std::cout << "error for coord " << icoord << " = " << ex[icoord] << " deriv " << deriv << std::endl;
505#endif
506 }
507 }
508 }
509 double w2 = (e2 > 0) ? 1.0/e2 : 0;
510 double resval = w2 * ( y - fval ) * ( y - fval);
511
512#ifdef DEBUG
513 std::cout << x[0] << " " << y << " ex " << ex[0] << " ey " << ey << " params : ";
514 for (unsigned int ipar = 0; ipar < func.NPar(); ++ipar)
515 std::cout << p[ipar] << "\t";
516 std::cout << "\tfval = " << fval << "\tresval = " << resval << std::endl;
517#endif
518
519 // avoid (infinity and nan ) in the chi2 sum
520 // eventually add possibility of excluding some points (like singularity)
521 if ( resval < maxResValue )
522 chi2 += resval;
523 else
524 chi2 += maxResValue;
525 //nRejected++;
526
527 }
528
529 // reset the number of fitting data points
530 nPoints = n; // no points are rejected
531 //if (nRejected != 0) nPoints = n - nRejected;
532
533#ifdef DEBUG
534 std::cout << "chi2 = " << chi2 << " n = " << nPoints << std::endl;
535#endif
536
537 return chi2;
538
539}
540
541
542////////////////////////////////////////////////////////////////////////////////
543/// evaluate the chi2 contribution (residual term) only for data with no coord-errors
544/// This function is used in the specialized least square algorithms like FUMILI or L.M.
545/// if we have error on the coordinates the residual weight depends on the function value and
546/// the approximation used by Fumili and Levenberg-Marquardt cannot be used.
547/// Also the expected error and bin integral options should not be used in this case
548
549double FitUtil::EvaluateChi2Residual(const IModelFunction & func, const BinData & data, const double * p, unsigned int i, double * g, double * h, bool hasGrad, bool useFullHessian) {
550 if (data.GetErrorType() == BinData::kCoordError && data.Opt().fCoordErrors ) {
551 MATH_ERROR_MSG("FitUtil::EvaluateChi2Residual","Error on the coordinates are not used in calculating Chi2 residual");
552 return 0; // it will assert otherwise later in GetPoint
553 }
554
555 double y, invError = 0;
556
557 const DataOptions & fitOpt = data.Opt();
558 bool useBinIntegral = fitOpt.fIntegral && data.HasBinEdges();
559 bool useBinVolume = (fitOpt.fBinVolume && data.HasBinEdges());
560 bool useExpErrors = (fitOpt.fExpErrors);
561 bool useNormBinVolume = (useBinVolume && fitOpt.fNormBinVolume);
562
563 const double * x1 = data.GetPoint(i,y, invError);
564
565 unsigned int ndim = data.NDim();
566 double binVolume = 1.0;
567 const double * x2 = nullptr;
568 if (useBinVolume || useBinIntegral) x2 = data.BinUpEdge(i);
569
570 std::vector<double> xc;
571
572 if (useBinVolume) {
573 if (!useBinIntegral) {
574 xc.resize(ndim);
575 for (unsigned int j = 0; j < ndim; ++j) {
576 binVolume *= std::abs( x2[j]-x1[j] );
577 xc[j] = 0.5*(x2[j]+ x1[j]);
578 }
579 }
580 // normalize the bin volume using a reference value
581 if (useNormBinVolume) binVolume /= data.RefVolume();
582 }
583
584 const double * x = (useBinVolume) ? xc.data() : x1;
585
586 // calculate integral (normalized by bin volume)
587 // need to set function and parameters here in case loop is parallelized
589 double fval0 = (useBinIntegral) ? igEval( x1, x2) : func ( x, p );
590
591 // normalize result if requested according to bin volume
592 double fval = fval0;
593 if (useBinVolume) fval = fval0*binVolume;
594
595 // expected errors
596 if (useExpErrors) {
597 // check if a weight factor needs to be applied
598 // for bins with y = 0 use as weight the global of the full histogram
599 double invWeight = 1.0;
600 if (data.IsWeighted() && !fitOpt.fErrors1 ) { // case of weighted Pearson chi2 fit
602 if (y == 0) invWeight = (data.SumOfError2() > 0) ? data.SumOfContent()/ data.SumOfError2() : 1.0;
603 }
604 // compute expected error as f(x) / weight
605 double invError2 = (fval > 0) ? invWeight / fval : 0.0;
606 invError = std::sqrt(invError2);
607 }
608
609
610 double resval = ( y -fval ) * invError;
611
612 // avoid infinities or nan in resval
614
615 // estimate gradient
616 if (g) {
617
618 unsigned int npar = func.NPar();
619
620 // use gradient of model function only if FCN support gradient
621 const IGradModelFunction * gfunc = (hasGrad) ?
622 dynamic_cast<const IGradModelFunction *>( &func) : nullptr;
623
624 if (!h ) useFullHessian = false;
625 // this is not supported yet!
626 if (useFullHessian && (!gfunc || useBinIntegral || (gfunc && !gfunc->HasParameterHessian())))
627 return std::numeric_limits<double>::quiet_NaN();
628
629 if (gfunc) {
630 //case function provides gradient
631 if (!useBinIntegral ) {
632 gfunc->ParameterGradient(x , p, g);
633 if (useFullHessian) {
634 gfunc->ParameterHessian(x, p, h);
635 }
636 }
637 else {
638 // needs to calculate the integral for each partial derivative
640 }
641 }
642 else {
644 if (!useBinIntegral ) {
645 // need to use un-normalized fval
646 gc.ParameterGradient(x, p, fval0, g);
647 } else {
648 // needs to calculate the integral for each partial derivative
650 }
651 }
652 // multiply by - 1 * weight
653 for (unsigned int k = 0; k < npar; ++k) {
654 g[k] *= - invError;
655 if (useBinVolume) g[k] *= binVolume;
656 if (h) {
657 for (unsigned int l = 0; l <= k; l++) { // use lower diagonal because I modify g[k]
658 unsigned int idx = l + k * (k + 1) / 2;
659 if (useFullHessian) {
660 h[idx] *= 2.* resval * (-invError); // hessian of model function
661 if (useBinVolume) h[idx] *= binVolume;
662 }
663 else {
664 h[idx] = 0;
665 }
666 // add term depending on only gradient of model function
667 h[idx] += 2. * g[k]*g[l];
668 }
669 }
670 }
671 }
672
673
674 return resval;
675
676}
677
678void FitUtil::EvaluateChi2Gradient(const IModelFunction &f, const BinData &data, const double *p, double *grad,
679 unsigned int &nPoints, ROOT::EExecutionPolicy executionPolicy, unsigned nChunks)
680{
681 // evaluate the gradient of the chi2 function
682 // this function is used when the model function knows how to calculate the derivative and we can
683 // avoid that the minimizer re-computes them
684 //
685 // case of chi2 effective (errors on coordinate) is not supported
686
687 if (data.HaveCoordErrors()) {
688 MATH_ERROR_MSG("FitUtil::EvaluateChi2Gradient",
689 "Error on the coordinates are not used in calculating Chi2 gradient");
690 return; // it will assert otherwise later in GetPoint
691 }
692
693 const IGradModelFunction *fg = dynamic_cast<const IGradModelFunction *>(&f);
694 assert(fg != nullptr); // must be called by a gradient function
695
696 const IGradModelFunction &func = *fg;
697
698#ifdef DEBUG
699 std::cout << "\n\nFit data size = " << nPoints << std::endl;
700 std::cout << "evaluate chi2 using function gradient " << &func << " " << p << std::endl;
701#endif
702
703 const DataOptions &fitOpt = data.Opt();
704 bool useBinIntegral = fitOpt.fIntegral && data.HasBinEdges();
705 bool useBinVolume = (fitOpt.fBinVolume && data.HasBinEdges());
706
707 double wrefVolume = 1.0;
708 if (useBinVolume) {
709 if (fitOpt.fNormBinVolume) wrefVolume /= data.RefVolume();
710 }
711
714 // do not use GSL integrator which is not thread safe
716 }
718
719 unsigned int npar = func.NPar();
720 unsigned initialNPoints = data.Size();
721
722 std::vector<bool> isPointRejected(initialNPoints);
723
724 auto mapFunction = [&](const unsigned int i) {
725 // set all vector values to zero
726 std::vector<double> gradFunc(npar);
727 std::vector<double> pointContribution(npar);
728
729 const auto x1 = data.GetCoordComponent(i, 0);
730 const auto y = data.Value(i);
731 auto invError = data.Error(i);
732
733 invError = (invError != 0.0) ? 1.0 / invError : 1;
734
735 double fval = 0;
736
737 const double *x = nullptr;
738 std::vector<double> xc;
739
740 unsigned int ndim = data.NDim();
741 double binVolume = 1;
742 if (useBinVolume) {
743 xc.resize(ndim);
744 for (unsigned int j = 0; j < ndim; ++j) {
745 double x1_j = *data.GetCoordComponent(i, j);
746 double x2_j = data.GetBinUpEdgeComponent(i, j);
747 binVolume *= std::abs(x2_j - x1_j);
748 xc[j] = (useBinIntegral) ? x1_j : 0.5 * (x2_j + x1_j);
749 }
750
751 x = xc.data();
752
753 // normalize the bin volume using a reference value
754 binVolume *= wrefVolume;
755 } else if (ndim > 1) {
756 xc.resize(ndim);
757 xc[0] = *x1;
758 for (unsigned int j = 1; j < ndim; ++j)
759 xc[j] = *data.GetCoordComponent(i, j);
760 x = xc.data();
761 } else {
762 x = x1;
763 }
764
765 if (!useBinIntegral) {
766 fval = func(x, p);
767 func.ParameterGradient(x, p, &gradFunc[0]);
768 } else {
769 std::vector<double> x2(data.NDim());
770 data.GetBinUpEdgeCoordinates(i, x2.data());
771 // calculate normalized integral and gradient (divided by bin volume)
772 // need to set function and parameters here in case loop is parallelized
773 fval = igEval(x, x2.data());
774 CalculateGradientIntegral(func, x, x2.data(), p, &gradFunc[0]);
775 }
776 if (useBinVolume)
777 fval *= binVolume;
778
779#ifdef DEBUG
780 std::cout << x[0] << " " << y << " " << 1. / invError << " params : ";
781 for (unsigned int ipar = 0; ipar < npar; ++ipar)
782 std::cout << p[ipar] << "\t";
783 std::cout << "\tfval = " << fval << std::endl;
784#endif
785 if (!CheckInfNaNValue(fval)) {
786 isPointRejected[i] = true;
787 // Return a zero contribution to all partial derivatives on behalf of the current point
788 return pointContribution;
789 }
790
791 // loop on the parameters
792 unsigned int ipar = 0;
793 for (; ipar < npar; ++ipar) {
794
795 // correct gradient for bin volumes
796 if (useBinVolume)
797 gradFunc[ipar] *= binVolume;
798
799 // avoid singularity in the function (infinity and nan ) in the chi2 sum
800 // eventually add possibility of excluding some points (like singularity)
801 double dfval = gradFunc[ipar];
802 if (!CheckInfNaNValue(dfval)) {
803 break; // exit loop on parameters
804 }
805
806 // calculate derivative point contribution
807 pointContribution[ipar] = -2.0 * (y - fval) * invError * invError * gradFunc[ipar];
808 }
809
810 if (ipar < npar) {
811 // case loop was broken for an overflow in the gradient calculation
812 isPointRejected[i] = true;
813 }
814
815 return pointContribution;
816 };
817
818 // Vertically reduce the set of vectors by summing its equally-indexed components
819 auto redFunction = [&](const std::vector<std::vector<double>> &pointContributions) {
820 std::vector<double> result(npar);
821
822 for (auto const &pointContribution : pointContributions) {
823 for (unsigned int parameterIndex = 0; parameterIndex < npar; parameterIndex++)
825 }
826
827 return result;
828 };
829
830 std::vector<double> g(npar);
831
832#ifndef R__USE_IMT
833 // If IMT is disabled, force the execution policy to the serial case
835 Warning("FitUtil::EvaluateChi2Gradient", "Multithread execution policy requires IMT, which is disabled. Changing "
836 "to ROOT::EExecutionPolicy::kSequential.");
838 }
839#endif
840
842 std::vector<std::vector<double>> allGradients(initialNPoints);
843 for (unsigned int i = 0; i < initialNPoints; ++i) {
845 }
847 }
848#ifdef R__USE_IMT
853 }
854#endif
855 // else if(executionPolicy == ROOT::Fit::kMultiprocess){
856 // ROOT::TProcessExecutor pool;
857 // g = pool.MapReduce(mapFunction, ROOT::TSeq<unsigned>(0, n), redFunction);
858 // }
859 else {
860 Error("FitUtil::EvaluateChi2Gradient",
861 "Execution policy unknown. Available choices:\n 0: Serial (default)\n 1: MultiThread (requires IMT)\n");
862 }
863
864#ifndef R__USE_IMT
865 //to fix compiler warning
866 (void)nChunks;
867#endif
868
869 // correct the number of points
871
872 if (std::any_of(isPointRejected.begin(), isPointRejected.end(), [](bool point) { return point; })) {
873 unsigned nRejected = std::accumulate(isPointRejected.begin(), isPointRejected.end(), 0);
876
877 if (nPoints < npar)
878 MATH_ERROR_MSG("FitUtil::EvaluateChi2Gradient",
879 "Error - too many points rejected for overflow in gradient calculation");
880 }
881
882 // copy result
883 std::copy(g.begin(), g.end(), grad);
884}
885
886//______________________________________________________________________________________________________
887//
888// Log Likelihood functions
889//_______________________________________________________________________________________________________
890
891// utility function used by the likelihoods
892
893// for LogLikelihood functions
894
895double FitUtil::EvaluatePdf(const IModelFunction & func, const UnBinData & data, const double * p, unsigned int i, double * g, double * /*h*/, bool hasGrad, bool) {
896 // evaluate the pdf contribution to the generic logl function in case of bin data
897 // return actually the log of the pdf and its derivatives
898
899
900 //func.SetParameters(p);
901
902 const double * x = data.Coords(i);
903 double fval = func ( x, p );
905 //return
906 if (g == nullptr) return logPdf;
907
908 const IGradModelFunction * gfunc = (hasGrad) ?
909 dynamic_cast<const IGradModelFunction *>( &func) : nullptr;
910
911 // gradient calculation
912 if (gfunc) {
913 //case function provides gradient
914 gfunc->ParameterGradient( x , p, g );
915 }
916 else {
917 // estimate gradient numerically with simple 2 point rule
918 // should probably calculate gradient of log(pdf) is more stable numerically
919 SimpleGradientCalculator gc(func.NPar(), func);
920 gc.ParameterGradient(x, p, fval, g );
921 }
922 // divide gradient by function value since returning the logs
923 for (unsigned int ipar = 0; ipar < func.NPar(); ++ipar) {
924 g[ipar] /= fval; // this should be checked against infinities
925 }
926
927#ifdef DEBUG
928 std::cout << x[i] << "\t";
929 std::cout << "\tpar = [ " << func.NPar() << " ] = ";
930 for (unsigned int ipar = 0; ipar < func.NPar(); ++ipar)
931 std::cout << p[ipar] << "\t";
932 std::cout << "\tfval = " << fval;
933 std::cout << "\tgrad = [ ";
934 for (unsigned int ipar = 0; ipar < func.NPar(); ++ipar)
935 std::cout << g[ipar] << "\t";
936 std::cout << " ] " << std::endl;
937#endif
938
939
940 return logPdf;
941}
942
943double FitUtil::EvaluateLogL(const IModelFunction &func, const UnBinData &data, const double *p,
944 int iWeight, bool extended, unsigned int &nPoints,
946{
947 // evaluate the LogLikelihood
948
949 unsigned int n = data.Size();
950
951 //unsigned int nRejected = 0;
952
953 bool normalizeFunc = false;
954
955 // set parameters of the function to cache integral value
956#ifdef USE_PARAMCACHE
957 (const_cast<IModelFunctionTempl<double> &>(func)).SetParameters(p);
958#endif
959
960 nPoints = data.Size(); // npoints
961
962#ifdef R__USE_IMT
963 // in case parameter needs to be propagated to user function use trick to set parameters by calling one time the function
964 // this will be done in sequential mode and parameters can be set in a thread safe manner
965 if (!normalizeFunc) {
966 if (data.NDim() == 1) {
967 const double * x = data.GetCoordComponent(0,0);
968 func( x, p);
969 }
970 else {
971 std::vector<double> x(data.NDim());
972 for (unsigned int j = 0; j < data.NDim(); ++j)
973 x[j] = *data.GetCoordComponent(0, j);
974 func( x.data(), p);
975 }
976 }
977#endif
978
979 double norm = 1.0;
980 if (normalizeFunc) {
981 // compute integral of the function
982 std::vector<double> xmin(data.NDim());
983 std::vector<double> xmax(data.NDim());
984 IntegralEvaluator<> igEval(func, p, true);
985 // compute integral in the ranges where is defined
986 if (data.Range().Size() > 0) {
987 norm = 0;
988 for (unsigned int ir = 0; ir < data.Range().Size(); ++ir) {
989 data.Range().GetRange(&xmin[0], &xmax[0], ir);
990 norm += igEval.Integral(xmin.data(), xmax.data());
991 }
992 } else {
993 // use (-inf +inf)
994 data.Range().GetRange(&xmin[0], &xmax[0]);
995 // check if funcition is zero at +- inf
996 if (func(xmin.data(), p) != 0 || func(xmax.data(), p) != 0) {
997 MATH_ERROR_MSG("FitUtil::EvaluateLogLikelihood",
998 "A range has not been set and the function is not zero at +/- inf");
999 return 0;
1000 }
1001 norm = igEval.Integral(&xmin[0], &xmax[0]);
1002 }
1003 }
1004
1005 // needed to compute effective global weight in case of extended likelihood
1006
1007 auto mapFunction = [&](const unsigned i) {
1008 double W = 0;
1009 double W2 = 0;
1010 double fval = 0;
1011
1012 if (data.NDim() > 1) {
1013 std::vector<double> x(data.NDim());
1014 for (unsigned int j = 0; j < data.NDim(); ++j)
1015 x[j] = *data.GetCoordComponent(i, j);
1016#ifdef USE_PARAMCACHE
1017 fval = func(x.data());
1018#else
1019 fval = func(x.data(), p);
1020#endif
1021
1022 // one -dim case
1023 } else {
1024 const auto x = data.GetCoordComponent(i, 0);
1025#ifdef USE_PARAMCACHE
1026 fval = func(x);
1027#else
1028 fval = func(x, p);
1029#endif
1030 }
1031
1032 if (normalizeFunc)
1033 fval = fval * (1 / norm);
1034
1035 // function EvalLog protects against negative or too small values of fval
1037 if (iWeight > 0) {
1038 double weight = data.Weight(i);
1039 logval *= weight;
1040 if (iWeight == 2) {
1041 logval *= weight; // use square of weights in likelihood
1042 if (!extended) {
1043 // needed sum of weights and sum of weight square if likelkihood is extended
1044 W = weight;
1045 W2 = weight * weight;
1046 }
1047 }
1048 }
1049 return LikelihoodAux<double>(logval, W, W2);
1050 };
1051
1052#ifdef R__USE_IMT
1053 // auto redFunction = [](const std::vector<LikelihoodAux<double>> & objs){
1054 // return std::accumulate(objs.begin(), objs.end(), LikelihoodAux<double>(0.0,0.0,0.0),
1055 // [](const LikelihoodAux<double> &l1, const LikelihoodAux<double> &l2){
1056 // return l1+l2;
1057 // });
1058 // };
1059 // do not use std::accumulate to be sure to maintain always the same order
1060 auto redFunction = [](const std::vector<LikelihoodAux<double>> & objs){
1061 auto l0 = LikelihoodAux<double>(0.0,0.0,0.0);
1062 for ( auto & l : objs ) {
1063 l0 = l0 + l;
1064 }
1065 return l0;
1066 };
1067#else
1068 (void)nChunks;
1069
1070 // If IMT is disabled, force the execution policy to the serial case
1072 Warning("FitUtil::EvaluateLogL", "Multithread execution policy requires IMT, which is disabled. Changing "
1073 "to ROOT::EExecutionPolicy::kSequential.");
1075 }
1076#endif
1077
1078 double logl{};
1079 double sumW{};
1080 double sumW2{};
1082 for (unsigned int i=0; i<n; ++i) {
1083 auto resArray = mapFunction(i);
1084 logl+=resArray.logvalue;
1085 sumW+=resArray.weight;
1086 sumW2+=resArray.weight2;
1087 }
1088#ifdef R__USE_IMT
1091 auto chunks = nChunks !=0? nChunks: setAutomaticChunking(data.Size());
1093 logl=resArray.logvalue;
1094 sumW=resArray.weight;
1095 sumW2=resArray.weight2;
1096#endif
1097// } else if(executionPolicy == ROOT::Fit::kMultiProcess){
1098 // ROOT::TProcessExecutor pool;
1099 // res = pool.MapReduce(mapFunction, ROOT::TSeq<unsigned>(0, n), redFunction);
1100 } else{
1101 Error("FitUtil::EvaluateLogL","Execution policy unknown. Available choices:\n ROOT::EExecutionPolicy::kSequential (default)\n ROOT::EExecutionPolicy::kMultiThread (requires IMT)\n");
1102 }
1103
1104 if (extended) {
1105 // add Poisson extended term
1106 double extendedTerm = 0; // extended term in likelihood
1107 double nuTot = 0;
1108 // nuTot is integral of function in the range
1109 // if function has been normalized integral has been already computed
1110 if (!normalizeFunc) {
1111 IntegralEvaluator<> igEval( func, p, true);
1112 std::vector<double> xmin(data.NDim());
1113 std::vector<double> xmax(data.NDim());
1114
1115 // compute integral in the ranges where is defined
1116 if (data.Range().Size() > 0 ) {
1117 nuTot = 0;
1118 for (unsigned int ir = 0; ir < data.Range().Size(); ++ir) {
1119 data.Range().GetRange(&xmin[0],&xmax[0],ir);
1120 nuTot += igEval.Integral(xmin.data(),xmax.data());
1121 }
1122 } else {
1123 // use (-inf +inf)
1124 data.Range().GetRange(&xmin[0],&xmax[0]);
1125 // check if function is zero at +- inf
1126 if (func(xmin.data(), p) != 0 || func(xmax.data(), p) != 0) {
1127 MATH_ERROR_MSG("FitUtil::EvaluateLogLikelihood","A range has not been set and the function is not zero at +/- inf");
1128 return 0;
1129 }
1130 nuTot = igEval.Integral(&xmin[0],&xmax[0]);
1131 }
1132
1133 // force to be last parameter value
1134 //nutot = p[func.NDim()-1];
1135 if (iWeight != 2)
1136 extendedTerm = - nuTot; // no need to add in this case n log(nu) since is already computed before
1137 else {
1138 // case use weight square in likelihood : compute total effective weight = sw2/sw
1139 // ignore for the moment case when sumW is zero
1140 extendedTerm = - (sumW2 / sumW) * nuTot;
1141 }
1142
1143 }
1144 else {
1145 nuTot = norm;
1146 extendedTerm = - nuTot + double(n) * ROOT::Math::Util::EvalLog( nuTot);
1147 // in case of weights need to use here sum of weights (to be done)
1148 }
1149 logl += extendedTerm;
1150
1151 }
1152
1153#ifdef DEBUG
1154 std::cout << "Evaluated log L for parameters (";
1155 for (unsigned int ip = 0; ip < func.NPar(); ++ip)
1156 std::cout << " " << p[ip];
1157 std::cout << ") fval = " << -logl << std::endl;
1158#endif
1159
1160 return -logl;
1161}
1162
1163void FitUtil::EvaluateLogLGradient(const IModelFunction &f, const UnBinData &data, const double *p, double *grad,
1164 unsigned int &nPoints, ROOT::EExecutionPolicy executionPolicy, unsigned nChunks)
1165{
1166 // evaluate the gradient of the log likelihood function
1167
1168 const IGradModelFunction *fg = dynamic_cast<const IGradModelFunction *>(&f);
1169 assert(fg != nullptr); // must be called by a grad function
1170
1171 const IGradModelFunction &func = *fg;
1172
1173 unsigned int npar = func.NPar();
1174 unsigned initialNPoints = data.Size();
1175
1176 (const_cast<IGradModelFunction &>(func)).SetParameters(p);
1177
1178#ifdef DEBUG
1179 std::cout << "\n===> Evaluate Gradient for parameters ";
1180 for (unsigned int ip = 0; ip < npar; ++ip)
1181 std::cout << " " << p[ip];
1182 std::cout << "\n";
1183#endif
1184
1185 const double kdmax1 = std::sqrt(std::numeric_limits<double>::max());
1186 const double kdmax2 = std::numeric_limits<double>::max() / (4 * initialNPoints);
1187
1188 auto mapFunction = [&](const unsigned int i) {
1189 std::vector<double> gradFunc(npar);
1190 std::vector<double> pointContribution(npar);
1191
1192
1193 const double * x = nullptr;
1194 std::vector<double> xc;
1195 if (data.NDim() > 1) {
1196 xc.resize(data.NDim() );
1197 for (unsigned int j = 0; j < data.NDim(); ++j)
1198 xc[j] = *data.GetCoordComponent(i, j);
1199 x = xc.data();
1200 } else {
1201 x = data.GetCoordComponent(i, 0);
1202 }
1203
1204 double fval = func(x, p);
1205 func.ParameterGradient(x, p, &gradFunc[0]);
1206
1207#ifdef DEBUG
1208 {
1210 if (i < 5 || (i > data.Size()-5) ) {
1211 if (data.NDim() > 1) std::cout << i << " x " << x[0] << " y " << x[1] << " func " << fval
1212 << " gradient " << gradFunc[0] << " " << gradFunc[1] << " " << gradFunc[3] << std::endl;
1213 else std::cout << i << " x " << x[0] << " gradient " << gradFunc[0] << " " << gradFunc[1] << " " << gradFunc[3] << std::endl;
1214 }
1215 }
1216#endif
1217
1218 for (unsigned int kpar = 0; kpar < npar; ++kpar) {
1219 if (fval > 0)
1221 else if (gradFunc[kpar] != 0) {
1222 double gg = kdmax1 * gradFunc[kpar];
1223 if (gg > 0)
1224 gg = std::min(gg, kdmax2);
1225 else
1226 gg = std::max(gg, -kdmax2);
1228 }
1229 // if func derivative is zero term is also zero so do not add in g[kpar]
1230 }
1231
1232 return pointContribution;
1233 };
1234
1235 // Vertically reduce the set of vectors by summing its equally-indexed components
1236 auto redFunction = [&](const std::vector<std::vector<double>> &pointContributions) {
1237 std::vector<double> result(npar);
1238
1239 for (auto const &pointContribution : pointContributions) {
1240 for (unsigned int parameterIndex = 0; parameterIndex < npar; parameterIndex++)
1242 }
1243
1244 return result;
1245 };
1246
1247 std::vector<double> g(npar);
1248
1249#ifndef R__USE_IMT
1250 // If IMT is disabled, force the execution policy to the serial case
1252 Warning("FitUtil::EvaluateLogLGradient", "Multithread execution policy requires IMT, which is disabled. Changing "
1253 "to ROOT::EExecutionPolicy::kSequential.");
1255 }
1256#endif
1257
1259 std::vector<std::vector<double>> allGradients(initialNPoints);
1260 for (unsigned int i = 0; i < initialNPoints; ++i) {
1261 allGradients[i] = mapFunction(i);
1262 }
1264 }
1265#ifdef R__USE_IMT
1270 }
1271#endif
1272 else {
1273 Error("FitUtil::EvaluateLogLGradient", "Execution policy unknown. Available choices:\n "
1274 "ROOT::EExecutionPolicy::kSequential (default)\n "
1275 "ROOT::EExecutionPolicy::kMultiThread (requires IMT)\n");
1276 }
1277
1278#ifndef R__USE_IMT
1279 // to fix compiler warning
1280 (void)nChunks;
1281#endif
1282
1283 // copy result
1284 std::copy(g.begin(), g.end(), grad);
1285 nPoints = data.Size(); // npoints
1286
1287#ifdef DEBUG
1288 std::cout << "FitUtil.cxx : Final gradient ";
1289 for (unsigned int param = 0; param < npar; param++) {
1290 std::cout << " " << grad[param];
1291 }
1292 std::cout << "\n";
1293#endif
1294}
1295//_________________________________________________________________________________________________
1296// for binned log likelihood functions
1297////////////////////////////////////////////////////////////////////////////////
1298/// evaluate the pdf (Poisson) contribution to the logl (return actually log of pdf)
1299/// and its gradient (gradient of log(pdf))
1300
1301double FitUtil::EvaluatePoissonBinPdf(const IModelFunction & func, const BinData & data, const double * p, unsigned int i, double * g, double * h, bool hasGrad, bool useFullHessian) {
1302 double y = 0;
1303 const double * x1 = data.GetPoint(i,y);
1304
1305 const DataOptions & fitOpt = data.Opt();
1306 bool useBinIntegral = fitOpt.fIntegral && data.HasBinEdges();
1307 bool useBinVolume = (fitOpt.fBinVolume && data.HasBinEdges());
1308
1310 const double * x2 = nullptr;
1311 // calculate the bin volume
1312 double binVolume = 1;
1313 std::vector<double> xc;
1314 if (useBinVolume) {
1315 unsigned int ndim = data.NDim();
1316 xc.resize(ndim);
1317 for (unsigned int j = 0; j < ndim; ++j) {
1318 double x2j = data.GetBinUpEdgeComponent(i, j);
1319 binVolume *= std::abs( x2j-x1[j] );
1320 xc[j] = 0.5*(x2j+ x1[j]);
1321 }
1322 // normalize the bin volume using a reference value
1323 binVolume /= data.RefVolume();
1324 }
1325
1326 const double * x = (useBinVolume) ? &xc.front() : x1;
1327
1328 double fval0 = 0;
1329 if (!useBinIntegral ) {
1330 fval0 = func ( x, p );
1331 }
1332 else {
1333 // calculate integral normalized (divided by bin volume)
1334 std::vector<double> vx2(data.NDim());
1335 data.GetBinUpEdgeCoordinates(i, vx2.data());
1336 fval0 = igEval( x1, vx2.data() ) ;
1337 }
1338 double fval = fval0;
1339 if (useBinVolume) fval = fval0*binVolume;
1340
1341 // logPdf for Poisson: ignore constant term depending on N
1342 fval = std::max(fval, 0.0); // avoid negative or too small values
1343 double nlogPdf = fval;
1344 if (y > 0.0) {
1345 // include also constants due to saturate model (see Baker-Cousins paper)
1347 }
1348
1349 if (g == nullptr) return nlogPdf;
1350
1351 unsigned int npar = func.NPar();
1352 const IGradModelFunction * gfunc = (hasGrad) ?
1353 dynamic_cast<const IGradModelFunction *>( &func) : nullptr;
1354
1355 // for full Hessian we need a gradient function and not bin intgegral computation
1356 if (useFullHessian && (!gfunc || useBinIntegral || (gfunc && !gfunc->HasParameterHessian())))
1357 return std::numeric_limits<double>::quiet_NaN();
1358
1359 // gradient calculation
1360 if (gfunc) {
1361 //case function provides gradient
1362 if (!useBinIntegral ) {
1363 gfunc->ParameterGradient( x , p, g );
1364 if (useFullHessian && h) {
1365 if (!gfunc->HasParameterHessian())
1366 return std::numeric_limits<double>::quiet_NaN();
1367 bool goodHessFunc = gfunc->ParameterHessian(x , p, h);
1368 if (!goodHessFunc) {
1369 return std::numeric_limits<double>::quiet_NaN();
1370 }
1371 }
1372 }
1373 else {
1374 // needs to calculate the integral for each partial derivative
1376 }
1377
1378 }
1379 else {
1380 SimpleGradientCalculator gc(func.NPar(), func);
1381 if (!useBinIntegral )
1382 gc.ParameterGradient(x, p, fval0, g);
1383 else {
1384 // needs to calculate the integral for each partial derivative
1386 }
1387 }
1388 // correct g[] do be derivative of poisson term. We compute already derivative w.r.t. LL
1389 double coeffGrad = (fval > 0) ? (1. - y/fval) : ( (y > 0) ? std::sqrt( std::numeric_limits<double>::max() ) : 1. );
1390 double coeffHess = (fval > 0) ? y/(fval*fval) : ( (y > 0) ? std::sqrt( std::numeric_limits<double>::max() ) : 0. );
1391 if (useBinVolume) {
1392 coeffGrad *= binVolume;
1393 coeffHess *= binVolume*binVolume;
1394 }
1395 for (unsigned int k = 0; k < npar; ++k) {
1396 // compute also approximate Hessian (excluding term with second derivative of model function)
1397 if (h) {
1398 for (unsigned int l = k; l < npar; ++l) {
1399 unsigned int idx = k + l * (l + 1) / 2;
1400 if (useFullHessian) {
1401 h[idx] *= coeffGrad; // h contains first model function derivatives
1402 }
1403 else {
1404 h[idx] = 0;
1405 }
1406 // add term deoending on only gradient of model function
1407 h[idx] += coeffHess * g[k]*g[l]; // g are model function derivatives
1408 }
1409 }
1410 // compute gradient of NLL element
1411 // and apply bin volume correction if needed
1412 g[k] *= coeffGrad;
1413 if (useBinVolume)
1414 g[k] *= binVolume;
1415 }
1416
1417#ifdef DEBUG
1418 std::cout << "x = " << x[0] << " y " << y << " fval " << fval << " logPdf = " << nlogPdf << " gradient : ";
1419 for (unsigned int ipar = 0; ipar < npar; ++ipar)
1420 std::cout << g[ipar] << "\t";
1421 if (h) {
1422 std::cout << "\thessian : ";
1423 for (unsigned int ipar = 0; ipar < npar; ++ipar) {
1424 std::cout << " {";
1425 for (unsigned int jpar = 0; jpar <= ipar; ++jpar) {
1426 std::cout << h[ipar + jpar * (jpar + 1) / 2] << "\t";
1427 }
1428 std::cout << "}";
1429 }
1430 }
1431 std::cout << std::endl;
1432#endif
1433#undef DEBUG
1434
1435 return nlogPdf;
1436}
1437
1438double FitUtil::EvaluatePoissonLogL(const IModelFunction &func, const BinData &data, const double *p, int iWeight,
1440 unsigned nChunks)
1441{
1442 // evaluate the Poisson Log Likelihood
1443 // for binned likelihood fits
1444 // this is Sum ( f(x_i) - y_i * log( f (x_i) ) )
1445 // add as well constant term for saturated model to make it like a Chi2/2
1446 // by default is extended. If extended is false the fit is not extended and
1447 // the global poisson term is removed (i.e is a binomial fit)
1448 // (remember that in this case one needs to have a function with a fixed normalization
1449 // like in a non extended unbinned fit)
1450 //
1451 // if use Weight use a weighted dataset
1452 // iWeight = 1 ==> logL = Sum( w f(x_i) )
1453 // case of iWeight==1 is actually identical to weight==0
1454 // iWeight = 2 ==> logL = Sum( w*w * f(x_i) )
1455 //
1456 // nPoints returns the points where bin content is not zero
1457
1458
1459 unsigned int n = data.Size();
1460
1461#ifdef USE_PARAMCACHE
1462 (const_cast<IModelFunction &>(func)).SetParameters(p);
1463#endif
1464
1465 nPoints = data.Size(); // npoints
1466
1467
1468 // get fit option and check case of using integral of bins
1469 const DataOptions &fitOpt = data.Opt();
1470 bool useBinIntegral = fitOpt.fIntegral && data.HasBinEdges();
1471 bool useBinVolume = (fitOpt.fBinVolume && data.HasBinEdges());
1472 bool useW2 = (iWeight == 2);
1473
1474 // normalize if needed by a reference volume value
1475 double wrefVolume = 1.0;
1476 if (useBinVolume) {
1477 if (fitOpt.fNormBinVolume) wrefVolume /= data.RefVolume();
1478 }
1479
1480//#define DEBUG
1481#ifdef DEBUG
1482 std::cout << "Evaluate PoissonLogL for params = [ ";
1483 for (unsigned int j = 0; j < func.NPar(); ++j) std::cout << p[j] << " , ";
1484 std::cout << "] - data size = " << n << " useBinIntegral " << useBinIntegral << " useBinVolume "
1485 << useBinVolume << " useW2 " << useW2 << " wrefVolume = " << wrefVolume << std::endl;
1486#endif
1487
1488
1491 // do not use GSL integrator which is not thread safe
1493 }
1494#ifdef USE_PARAMCACHE
1496#else
1498#endif
1499
1500 auto mapFunction = [&](const unsigned i) {
1501 auto x1 = data.GetCoordComponent(i, 0);
1502 auto y = *data.ValuePtr(i);
1503
1504 const double *x = nullptr;
1505 std::vector<double> xc;
1506 double fval = 0;
1507 double binVolume = 1.0;
1508
1509 if (useBinVolume) {
1510 unsigned int ndim = data.NDim();
1511 xc.resize(data.NDim());
1512 for (unsigned int j = 0; j < ndim; ++j) {
1513 double xx = *data.GetCoordComponent(i, j);
1514 double x2 = data.GetBinUpEdgeComponent(i, j);
1515 binVolume *= std::abs(x2 - xx);
1516 xc[j] = (useBinIntegral) ? xx : 0.5 * (x2 + xx);
1517 }
1518 x = xc.data();
1519 // normalize the bin volume using a reference value
1520 binVolume *= wrefVolume;
1521 } else if (data.NDim() > 1) {
1522 xc.resize(data.NDim());
1523 xc[0] = *x1;
1524 for (unsigned int j = 1; j < data.NDim(); ++j) {
1525 xc[j] = *data.GetCoordComponent(i, j);
1526 }
1527 x = xc.data();
1528 } else {
1529 x = x1;
1530 }
1531
1532 if (!useBinIntegral) {
1533#ifdef USE_PARAMCACHE
1534 fval = func(x);
1535#else
1536 fval = func(x, p);
1537#endif
1538 } else {
1539 // calculate integral (normalized by bin volume)
1540 // need to set function and parameters here in case loop is parallelized
1541 std::vector<double> x2(data.NDim());
1542 data.GetBinUpEdgeCoordinates(i, x2.data());
1543 fval = igEval(x, x2.data());
1544 }
1545 if (useBinVolume) fval *= binVolume;
1546
1547
1548
1549#ifdef DEBUG
1550 int NSAMPLE = 100;
1551 if (i % NSAMPLE == 0) {
1552 std::cout << "evt " << i << " x = [ ";
1553 for (unsigned int j = 0; j < func.NDim(); ++j) std::cout << x[j] << " , ";
1554 std::cout << "] ";
1555 if (fitOpt.fIntegral) {
1556 std::cout << "x2 = [ ";
1557 for (unsigned int j = 0; j < func.NDim(); ++j) std::cout << data.GetBinUpEdgeComponent(i, j) << " , ";
1558 std::cout << "] ";
1559 }
1560 std::cout << " y = " << y << " fval = " << fval << std::endl;
1561 }
1562#endif
1563
1564
1565 // EvalLog protects against 0 values of fval but don't want to add in the -log sum
1566 // negative values of fval
1567 fval = std::max(fval, 0.0);
1568
1569 double nloglike = 0; // negative loglikelihood
1570 if (useW2) {
1571 // apply weight correction . Effective weight is error^2/ y
1572 // and expected events in bins is fval/weight
1573 // can apply correction only when y is not zero otherwise weight is undefined
1574 // (in case of weighted likelihood I don't care about the constant term due to
1575 // the saturated model)
1576
1577 // use for the empty bins the global weight
1578 double weight = 1.0;
1579 if (y != 0) {
1580 double error = data.Error(i);
1581 weight = (error * error) / y; // this is the bin effective weight
1582 nloglike -= weight * y * ( ROOT::Math::Util::EvalLog(fval/y) );
1583 }
1584 else {
1585 // for empty bin use the average weight computed from the total data weight
1586 weight = data.SumOfError2()/ data.SumOfContent();
1587 }
1588 if (extended) {
1589 nloglike += weight * ( fval - y);
1590 }
1591
1592 } else {
1593 // standard case no weights or iWeight=1
1594 // this is needed for Poisson likelihood (which are extended and not for multinomial)
1595 // the formula below include constant term due to likelihood of saturated model (f(x) = y)
1596 // (same formula as in Baker-Cousins paper, page 439 except a factor of 2
1597 if (extended) nloglike = fval - y;
1598
1599 if (y > 0) {
1601 }
1602 }
1603#ifdef DEBUG
1604 {
1606 std::cout << " nll = " << nloglike << std::endl;
1607 }
1608#endif
1609 return nloglike;
1610 };
1611
1612#ifdef R__USE_IMT
1613 auto redFunction = [](const std::vector<double> &objs) {
1614 return std::accumulate(objs.begin(), objs.end(), double{});
1615 };
1616#else
1617 (void)nChunks;
1618
1619 // If IMT is disabled, force the execution policy to the serial case
1621 Warning("FitUtil::EvaluatePoissonLogL", "Multithread execution policy requires IMT, which is disabled. Changing "
1622 "to ROOT::EExecutionPolicy::kSequential.");
1624 }
1625#endif
1626
1627 double res{};
1629 for (unsigned int i = 0; i < n; ++i) {
1630 res += mapFunction(i);
1631 }
1632#ifdef R__USE_IMT
1635 auto chunks = nChunks != 0 ? nChunks : setAutomaticChunking(data.Size());
1636 res = pool.MapReduce(mapFunction, ROOT::TSeq<unsigned>(0, n), redFunction, chunks);
1637#endif
1638 // } else if(executionPolicy == ROOT::Fit::kMultitProcess){
1639 // ROOT::TProcessExecutor pool;
1640 // res = pool.MapReduce(mapFunction, ROOT::TSeq<unsigned>(0, n), redFunction);
1641 } else {
1642 Error("FitUtil::EvaluatePoissonLogL",
1643 "Execution policy unknown. Available choices:\n ROOT::EExecutionPolicy::kSequential (default)\n ROOT::EExecutionPolicy::kMultiThread (requires IMT)\n");
1644 }
1645
1646#ifdef DEBUG
1647 std::cout << "Loglikelihood = " << res << std::endl;
1648#endif
1649
1650 return res;
1651}
1652
1653void FitUtil::EvaluatePoissonLogLGradient(const IModelFunction &f, const BinData &data, const double *p, double *grad,
1654 unsigned int &, ROOT::EExecutionPolicy executionPolicy, unsigned nChunks)
1655{
1656 // evaluate the gradient of the Poisson log likelihood function
1657
1658 const IGradModelFunction *fg = dynamic_cast<const IGradModelFunction *>(&f);
1659 assert(fg != nullptr); // must be called by a grad function
1660
1661 const IGradModelFunction &func = *fg;
1662
1663#ifdef USE_PARAMCACHE
1664 (const_cast<IGradModelFunction &>(func)).SetParameters(p);
1665#endif
1666
1667 const DataOptions &fitOpt = data.Opt();
1668 bool useBinIntegral = fitOpt.fIntegral && data.HasBinEdges();
1669 bool useBinVolume = (fitOpt.fBinVolume && data.HasBinEdges());
1670
1671 double wrefVolume = 1.0;
1672 if (useBinVolume && fitOpt.fNormBinVolume)
1673 wrefVolume /= data.RefVolume();
1674
1677 // do not use GSL integrator which is not thread safe
1679 }
1680
1682
1683 unsigned int npar = func.NPar();
1684 unsigned initialNPoints = data.Size();
1685
1686 auto mapFunction = [&](const unsigned int i) {
1687 // set all vector values to zero
1688 std::vector<double> gradFunc(npar);
1689 std::vector<double> pointContribution(npar);
1690
1691 const auto x1 = data.GetCoordComponent(i, 0);
1692 const auto y = data.Value(i);
1693 auto invError = data.Error(i);
1694
1695 invError = (invError != 0.0) ? 1.0 / invError : 1;
1696
1697 double fval = 0;
1698
1699 const double *x = nullptr;
1700 std::vector<double> xc;
1701
1702 unsigned ndim = data.NDim();
1703 double binVolume = 1.0;
1704 if (useBinVolume) {
1705
1706 xc.resize(ndim);
1707
1708 for (unsigned int j = 0; j < ndim; ++j) {
1709 double x1_j = *data.GetCoordComponent(i, j);
1710 double x2_j = data.GetBinUpEdgeComponent(i, j);
1711 binVolume *= std::abs(x2_j - x1_j);
1712 xc[j] = (useBinIntegral) ? x1_j : 0.5 * (x2_j + x1_j);
1713 }
1714
1715 x = xc.data();
1716
1717 // normalize the bin volume using a reference value
1718 binVolume *= wrefVolume;
1719 } else if (ndim > 1) {
1720 xc.resize(ndim);
1721 xc[0] = *x1;
1722 for (unsigned int j = 1; j < ndim; ++j)
1723 xc[j] = *data.GetCoordComponent(i, j);
1724 x = xc.data();
1725 } else {
1726 x = x1;
1727 }
1728
1729 if (!useBinIntegral) {
1730 fval = func(x, p);
1731 func.ParameterGradient(x, p, &gradFunc[0]);
1732 } else {
1733 // calculate integral (normalized by bin volume)
1734 // need to set function and parameters here in case loop is parallelized
1735 std::vector<double> x2(data.NDim());
1736 data.GetBinUpEdgeCoordinates(i, x2.data());
1737 fval = igEval(x, x2.data());
1738 CalculateGradientIntegral(func, x, x2.data(), p, &gradFunc[0]);
1739 }
1740 if (useBinVolume)
1741 fval *= binVolume;
1742
1743#ifdef DEBUG
1744 {
1746 if (i < 5 || (i > data.Size()-5) ) {
1747 if (data.NDim() > 1) std::cout << i << " x " << x[0] << " y " << x[1] << " func " << fval
1748 << " gradient " << gradFunc[0] << " " << gradFunc[1] << " " << gradFunc[3] << std::endl;
1749 else std::cout << i << " x " << x[0] << " gradient " << gradFunc[0] << " " << gradFunc[1] << " " << gradFunc[3] << std::endl;
1750 }
1751 }
1752#endif
1753
1754 // correct the gradient
1755 for (unsigned int ipar = 0; ipar < npar; ++ipar) {
1756
1757 // correct gradient for bin volumes
1758 if (useBinVolume)
1759 gradFunc[ipar] *= binVolume;
1760
1761 // df/dp * (1. - y/f )
1762 if (fval > 0)
1763 pointContribution[ipar] = gradFunc[ipar] * (1. - y / fval);
1764 else if (gradFunc[ipar] != 0) {
1765 const double kdmax1 = std::sqrt(std::numeric_limits<double>::max());
1766 const double kdmax2 = std::numeric_limits<double>::max() / (4 * initialNPoints);
1767 double gg = kdmax1 * gradFunc[ipar];
1768 if (gg > 0)
1769 gg = std::min(gg, kdmax2);
1770 else
1771 gg = std::max(gg, -kdmax2);
1772 pointContribution[ipar] = -gg;
1773 }
1774 }
1775
1776
1777 return pointContribution;
1778 };
1779
1780 // Vertically reduce the set of vectors by summing its equally-indexed components
1781 auto redFunction = [&](const std::vector<std::vector<double>> &pointContributions) {
1782 std::vector<double> result(npar);
1783
1784 for (auto const &pointContribution : pointContributions) {
1785 for (unsigned int parameterIndex = 0; parameterIndex < npar; parameterIndex++)
1787 }
1788
1789 return result;
1790 };
1791
1792 std::vector<double> g(npar);
1793
1794#ifndef R__USE_IMT
1795 // If IMT is disabled, force the execution policy to the serial case
1797 Warning("FitUtil::EvaluatePoissonLogLGradient",
1798 "Multithread execution policy requires IMT, which is disabled. Changing "
1799 "to ROOT::EExecutionPolicy::kSequential.");
1801 }
1802#endif
1803
1805 std::vector<std::vector<double>> allGradients(initialNPoints);
1806 for (unsigned int i = 0; i < initialNPoints; ++i) {
1807 allGradients[i] = mapFunction(i);
1808 }
1810 }
1811#ifdef R__USE_IMT
1816 }
1817#endif
1818
1819 // else if(executionPolicy == ROOT::Fit::kMultiprocess){
1820 // ROOT::TProcessExecutor pool;
1821 // g = pool.MapReduce(mapFunction, ROOT::TSeq<unsigned>(0, n), redFunction);
1822 // }
1823 else {
1824 Error("FitUtil::EvaluatePoissonLogLGradient",
1825 "Execution policy unknown. Available choices:\n 0: Serial (default)\n 1: MultiThread (requires IMT)\n");
1826 }
1827
1828#ifndef R__USE_IMT
1829 //to fix compiler warning
1830 (void)nChunks;
1831#endif
1832
1833 // copy result
1834 std::copy(g.begin(), g.end(), grad);
1835
1836#ifdef DEBUG
1837 std::cout << "***** Final gradient : ";
1838 for (unsigned int ii = 0; ii< npar; ++ii) std::cout << grad[ii] << " ";
1839 std::cout << "\n";
1840#endif
1841
1842}
1843
1844
1845unsigned FitUtil::setAutomaticChunking(unsigned nEvents){
1847 if (nEvents/ncpu < 1000) return ncpu;
1848 return nEvents/1000;
1849 //return ((nEvents/ncpu + 1) % 1000) *40 ; //arbitrary formula
1850}
1851
1852#ifdef R__HAS_STD_EXPERIMENTAL_SIMD
1853namespace FitUtil {
1854
1855namespace {
1856
1857template <class T>
1858T NumericMax()
1859{
1860 return T{std::numeric_limits<typename T::value_type>::max()};
1861}
1862
1863template <typename V, typename S>
1864void Load(V &v, S const *ptr)
1865{
1866 for (size_t i = 0; i < V::size(); ++i)
1867 v[i] = ptr[i];
1868}
1869
1870template <typename T>
1871auto ReduceAdd(const T &v)
1872{
1873 typename T::value_type result(0);
1874 for (size_t i = 0; i < T::size(); ++i) {
1875 result += v[i];
1876 }
1877 return result;
1878}
1879
1880template <typename T>
1881bool MaskEmpty(T mask)
1882{
1883 for (size_t i = 0; i < T::size(); ++i)
1884 if (mask[i])
1885 return false;
1886 return true;
1887}
1888
1889template <class T = ROOT::Double_v>
1890auto Int2Mask(unsigned i)
1891{
1892 T x;
1893 for (unsigned j = 0; j < T::size(); j++) {
1894 x[j] = j;
1895 }
1896 return x < T(i);
1897}
1898
1899} // namespace
1900
1901double Evaluate<Double_v>::EvalChi2(const IModelFunctionTempl<Double_v> &func, const BinData &data, const double *p,
1903{
1904 // evaluate the chi2 given a vectorized function reference , the data and returns the value and also in nPoints
1905 // the actual number of used points
1906 // normal chi2 using only error on values (from fitting histogram)
1907 // optionally the integral of function in the bin is used
1908
1909 // Info("EvalChi2","Using vectorized implementation %d",(int) data.Opt().fIntegral);
1910
1911 unsigned int n = data.Size();
1912 nPoints = data.Size(); // npoints
1913
1914 // set parameters of the function to cache integral value
1915#ifdef USE_PARAMCACHE
1916 (const_cast<IModelFunctionTempl<Double_v> &>(func)).SetParameters(p);
1917#endif
1918 // do not cache parameter values (it is not thread safe)
1919 // func.SetParameters(p);
1920
1921 // get fit option and check case if using integral of bins
1922 const DataOptions &fitOpt = data.Opt();
1923 if (fitOpt.fBinVolume || fitOpt.fIntegral || fitOpt.fExpErrors)
1924 Error("FitUtil::EvaluateChi2",
1925 "The vectorized implementation doesn't support Integrals, BinVolume or ExpErrors\n. Aborting operation.");
1926
1927 (const_cast<IModelFunctionTempl<Double_v> &>(func)).SetParameters(p);
1928
1929 double maxResValue = std::numeric_limits<double>::max() / n;
1930 std::vector<double> ones{1., 1., 1., 1.};
1931 auto vecSize = Double_v::size();
1932
1933 auto mapFunction = [&](unsigned int i) {
1934 // in case of no error in y invError=1 is returned
1936 Load(x1, data.GetCoordComponent(i * vecSize, 0));
1937 Load(y, data.ValuePtr(i * vecSize));
1938 const auto invError = data.ErrorPtr(i * vecSize);
1939 auto invErrorptr = (invError != nullptr) ? invError : &ones.front();
1940 Load(invErrorVec, invErrorptr);
1941
1942 const Double_v *x;
1943 std::vector<Double_v> xc;
1944 if (data.NDim() > 1) {
1945 xc.resize(data.NDim());
1946 xc[0] = x1;
1947 for (unsigned int j = 1; j < data.NDim(); ++j)
1948 Load(xc[j], data.GetCoordComponent(i * vecSize, j));
1949 x = xc.data();
1950 } else {
1951 x = &x1;
1952 }
1953
1954 Double_v fval{};
1955
1956#ifdef USE_PARAMCACHE
1957 fval = func(x);
1958#else
1959 fval = func(x, p);
1960#endif
1961
1962 Double_v tmp = (y - fval) * invErrorVec;
1963 Double_v chi2 = tmp * tmp;
1964
1965 // avoid infinity or nan in chi2 values due to wrong function values
1967
1968 return chi2;
1969 };
1970
1971 auto redFunction = [](const std::vector<Double_v> &objs) {
1972 return std::accumulate(objs.begin(), objs.end(), Double_v{});
1973 };
1974
1975#ifndef R__USE_IMT
1976 (void)nChunks;
1977
1978 // If IMT is disabled, force the execution policy to the serial case
1980 Warning("FitUtil::EvaluateChi2", "Multithread execution policy requires IMT, which is disabled. Changing "
1981 "to ::ROOT::EExecutionPolicy::kSequential.");
1983 }
1984#endif
1985
1986 Double_v res{};
1989 res = pool.MapReduce(mapFunction, ROOT::TSeq<unsigned>(0, data.Size() / vecSize), redFunction);
1990#ifdef R__USE_IMT
1993 auto chunks = nChunks != 0 ? nChunks : setAutomaticChunking(data.Size() / vecSize);
1994 res = pool.MapReduce(mapFunction, ROOT::TSeq<unsigned>(0, data.Size() / vecSize), redFunction, chunks);
1995#endif
1996 } else {
1997 Error("FitUtil::EvaluateChi2",
1998 "Execution policy unknown. Available choices:\n ::ROOT::EExecutionPolicy::kSequential (default)\n "
1999 "::ROOT::EExecutionPolicy::kMultiThread (requires IMT)\n");
2000 }
2001
2002 // Last SIMD vector of elements (if padding needed)
2003 if (data.Size() % vecSize != 0)
2004 where(Int2Mask(data.Size() % vecSize), res) = res + mapFunction(data.Size() / vecSize);
2005
2006 return ReduceAdd(res);
2007}
2008
2009double Evaluate<Double_v>::EvalLogL(const IModelFunctionTempl<Double_v> &func, const UnBinData &data,
2010 const double *const p, int iWeight, bool extended, unsigned int &nPoints,
2012{
2013 // evaluate the LogLikelihood
2014 unsigned int n = data.Size();
2015 nPoints = data.Size(); // npoints
2016
2017 // unsigned int nRejected = 0;
2018 bool normalizeFunc = false;
2019
2020 // set parameters of the function to cache integral value
2021#ifdef USE_PARAMCACHE
2022 (const_cast<IModelFunctionTempl<Double_v> &>(func)).SetParameters(p);
2023#endif
2024
2025#ifdef R__USE_IMT
2026 // in case parameter needs to be propagated to user function use trick to set parameters by calling one time the
2027 // function this will be done in sequential mode and parameters can be set in a thread safe manner
2028 if (!normalizeFunc) {
2029 if (data.NDim() == 1) {
2030 Double_v x;
2031 Load(x, data.GetCoordComponent(0, 0));
2032 func(&x, p);
2033 } else {
2034 std::vector<Double_v> x(data.NDim());
2035 for (unsigned int j = 0; j < data.NDim(); ++j)
2036 Load(x[j], data.GetCoordComponent(0, j));
2037 func(x.data(), p);
2038 }
2039 }
2040#endif
2041
2042 // this is needed if function must be normalized
2043 double norm = 1.0;
2044 if (normalizeFunc) {
2045 // compute integral of the function
2046 std::vector<double> xmin(data.NDim());
2047 std::vector<double> xmax(data.NDim());
2049 // compute integral in the ranges where is defined
2050 if (data.Range().Size() > 0) {
2051 norm = 0;
2052 for (unsigned int ir = 0; ir < data.Range().Size(); ++ir) {
2053 data.Range().GetRange(&xmin[0], &xmax[0], ir);
2054 norm += igEval.Integral(xmin.data(), xmax.data());
2055 }
2056 } else {
2057 // use (-inf +inf)
2058 data.Range().GetRange(&xmin[0], &xmax[0]);
2059 // check if function is zero at +- inf
2062 Load(xmin_v, xmin.data());
2063 Load(xmax_v, xmax.data());
2064 if (ReduceAdd(func(&xmin_v, p)) != 0 || ReduceAdd(func(&xmax_v, p)) != 0) {
2065 MATH_ERROR_MSG("FitUtil::EvaluateLogLikelihood",
2066 "A range has not been set and the function is not zero at +/- inf");
2067 return 0;
2068 }
2069 norm = igEval.Integral(&xmin[0], &xmax[0]);
2070 }
2071 }
2072
2073 // needed to compute effective global weight in case of extended likelihood
2074
2075 auto vecSize = Double_v::size();
2076 unsigned int numVectors = n / vecSize;
2077
2078 auto mapFunction = [&, p](const unsigned i) {
2079 Double_v W{};
2080 Double_v W2{};
2081 Double_v fval{};
2082
2083 (void)p; /* avoid unused lambda capture warning if PARAMCACHE is disabled */
2084
2085 Double_v x1;
2086 Load(x1, data.GetCoordComponent(i * vecSize, 0));
2087 const Double_v *x = nullptr;
2088 unsigned int ndim = data.NDim();
2089 std::vector<Double_v> xc;
2090 if (ndim > 1) {
2091 xc.resize(ndim);
2092 xc[0] = x1;
2093 for (unsigned int j = 1; j < ndim; ++j)
2094 Load(xc[j], data.GetCoordComponent(i * vecSize, j));
2095 x = xc.data();
2096 } else {
2097 x = &x1;
2098 }
2099
2100#ifdef USE_PARAMCACHE
2101 fval = func(x);
2102#else
2103 fval = func(x, p);
2104#endif
2105
2106#ifdef DEBUG_FITUTIL
2107 if (i < 5 || (i > numVectors - 5)) {
2108 if (ndim == 1)
2109 std::cout << i << " x " << x[0] << " fval = " << fval;
2110 else
2111 std::cout << i << " x " << x[0] << " y " << x[1] << " fval = " << fval;
2112 }
2113#endif
2114
2115 if (normalizeFunc)
2116 fval = fval * (1 / norm);
2117
2118 // function EvalLog protects against negative or too small values of fval
2120 if (iWeight > 0) {
2121 Double_v weight{};
2122 if (data.WeightsPtr(i) == nullptr)
2123 weight = 1;
2124 else
2125 Load(weight, data.WeightsPtr(i * vecSize));
2126 logval *= weight;
2127 if (iWeight == 2) {
2128 logval *= weight; // use square of weights in likelihood
2129 if (!extended) {
2130 // needed sum of weights and sum of weight square if likelkihood is extended
2131 W = weight;
2132 W2 = weight * weight;
2133 }
2134 }
2135 }
2136#ifdef DEBUG_FITUTIL
2137 if (i < 5 || (i > numVectors - 5)) {
2138 std::cout << " " << fval << " logfval " << logval << std::endl;
2139 }
2140#endif
2141
2143 };
2144
2145 auto redFunction = [](const std::vector<LikelihoodAux<Double_v>> &objs) {
2146 return std::accumulate(
2148 [](const LikelihoodAux<Double_v> &l1, const LikelihoodAux<Double_v> &l2) { return l1 + l2; });
2149 };
2150
2151#ifndef R__USE_IMT
2152 (void)nChunks;
2153
2154 // If IMT is disabled, force the execution policy to the serial case
2156 Warning("FitUtil::EvaluateLogL", "Multithread execution policy requires IMT, which is disabled. Changing "
2157 "to ::ROOT::EExecutionPolicy::kSequential.");
2159 }
2160#endif
2161
2162 Double_v logl_v{};
2163 Double_v sumW_v{};
2164 Double_v sumW2_v{};
2169#ifdef R__USE_IMT
2174#endif
2175 } else {
2176 Error("FitUtil::EvaluateLogL",
2177 "Execution policy unknown. Available choices:\n ::ROOT::EExecutionPolicy::kSequential (default)\n "
2178 "::ROOT::EExecutionPolicy::kMultiThread (requires IMT)\n");
2179 }
2180
2181 logl_v = resArray.logvalue;
2182 sumW_v = resArray.weight;
2183 sumW2_v = resArray.weight2;
2184
2185 // Compute the contribution from the remaining points ( Last padded SIMD vector of elements )
2186 unsigned int remainingPoints = n % vecSize;
2187 if (remainingPoints > 0) {
2189 // Add the contribution from the valid remaining points and store the result in the output variable
2194 }
2195
2196 // reduce vector type to double.
2197 double logl = ReduceAdd(logl_v);
2198 double sumW = ReduceAdd(sumW_v);
2199 double sumW2 = ReduceAdd(sumW2_v);
2200
2201 if (extended) {
2202 // add Poisson extended term
2203 double extendedTerm = 0; // extended term in likelihood
2204 double nuTot = 0;
2205 // nuTot is integral of function in the range
2206 // if function has been normalized integral has been already computed
2207 if (!normalizeFunc) {
2209 std::vector<double> xmin(data.NDim());
2210 std::vector<double> xmax(data.NDim());
2211
2212 // compute integral in the ranges where is defined
2213 if (data.Range().Size() > 0) {
2214 nuTot = 0;
2215 for (unsigned int ir = 0; ir < data.Range().Size(); ++ir) {
2216 data.Range().GetRange(&xmin[0], &xmax[0], ir);
2217 nuTot += igEval.Integral(xmin.data(), xmax.data());
2218 }
2219 } else {
2220 // use (-inf +inf)
2221 data.Range().GetRange(&xmin[0], &xmax[0]);
2222 // check if function is zero at +- inf
2224 Load(xmin_v, xmin.data());
2225 Load(xmax_v, xmax.data());
2226 if (ReduceAdd(func(&xmin_v, p)) != 0 || ReduceAdd(func(&xmax_v, p)) != 0) {
2227 MATH_ERROR_MSG("FitUtil::EvaluateLogLikelihood",
2228 "A range has not been set and the function is not zero at +/- inf");
2229 return 0;
2230 }
2231 nuTot = igEval.Integral(&xmin[0], &xmax[0]);
2232 }
2233
2234 // force to be last parameter value
2235 // nutot = p[func.NDim()-1];
2236 if (iWeight != 2)
2237 extendedTerm = -nuTot; // no need to add in this case n log(nu) since is already computed before
2238 else {
2239 // case use weight square in likelihood : compute total effective weight = sw2/sw
2240 // ignore for the moment case when sumW is zero
2241 extendedTerm = -(sumW2 / sumW) * nuTot;
2242 }
2243
2244 } else {
2245 nuTot = norm;
2246 extendedTerm = -nuTot + double(n) * ROOT::Math::Util::EvalLog(nuTot);
2247 // in case of weights need to use here sum of weights (to be done)
2248 }
2249
2250 logl += extendedTerm;
2251 }
2252
2253#ifdef DEBUG_FITUTIL
2254 std::cout << "Evaluated log L for parameters (";
2255 for (unsigned int ip = 0; ip < func.NPar(); ++ip)
2256 std::cout << " " << p[ip];
2257 std::cout << ") nll = " << -logl << std::endl;
2258#endif
2259
2260 return -logl;
2261}
2262
2263double Evaluate<Double_v>::EvalPoissonLogL(const IModelFunctionTempl<Double_v> &func, const BinData &data,
2264 const double *p, int iWeight, bool extended, unsigned int,
2266{
2267 // evaluate the Poisson Log Likelihood
2268 // for binned likelihood fits
2269 // this is Sum ( f(x_i) - y_i * log( f (x_i) ) )
2270 // add as well constant term for saturated model to make it like a Chi2/2
2271 // by default is extended. If extended is false the fit is not extended and
2272 // the global poisson term is removed (i.e is a binomial fit)
2273 // (remember that in this case one needs to have a function with a fixed normalization
2274 // like in a non extended binned fit)
2275 //
2276 // if use Weight use a weighted dataset
2277 // iWeight = 1 ==> logL = Sum( w f(x_i) )
2278 // case of iWeight==1 is actually identical to weight==0
2279 // iWeight = 2 ==> logL = Sum( w*w * f(x_i) )
2280 //
2281
2282#ifdef USE_PARAMCACHE
2283 (const_cast<IModelFunctionTempl<Double_v> &>(func)).SetParameters(p);
2284#endif
2285 auto vecSize = Double_v::size();
2286 // get fit option and check case of using integral of bins
2287 const DataOptions &fitOpt = data.Opt();
2288 if (fitOpt.fExpErrors || fitOpt.fIntegral)
2289 Error("FitUtil::EvaluateChi2",
2290 "The vectorized implementation doesn't support Integrals or BinVolume\n. Aborting operation.");
2291 bool useW2 = (iWeight == 2);
2292
2293 auto mapFunction = [&](unsigned int i) {
2294 Double_v y;
2295 Load(y, data.ValuePtr(i * vecSize));
2296 Double_v fval{};
2297
2298 if (data.NDim() > 1) {
2299 std::vector<Double_v> x(data.NDim());
2300 for (unsigned int j = 0; j < data.NDim(); ++j)
2301 Load(x[j], data.GetCoordComponent(i * vecSize, j));
2302#ifdef USE_PARAMCACHE
2303 fval = func(x.data());
2304#else
2305 fval = func(x.data(), p);
2306#endif
2307 // one -dim case
2308 } else {
2309 Double_v x;
2310 Load(x, data.GetCoordComponent(i * vecSize, 0));
2311#ifdef USE_PARAMCACHE
2312 fval = func(&x);
2313#else
2314 fval = func(&x, p);
2315#endif
2316 }
2317
2318 // EvalLog protects against 0 values of fval but don't want to add in the -log sum
2319 // negative values of fval
2320 where(fval < 0.0, fval) = 0.0;
2321
2322 Double_v nloglike{}; // negative loglikelihood
2323
2324 if (useW2) {
2325 // apply weight correction . Effective weight is error^2/ y
2326 // and expected events in bins is fval/weight
2327 // can apply correction only when y is not zero otherwise weight is undefined
2328 // (in case of weighted likelihood I don't care about the constant term due to
2329 // the saturated model)
2331 Double_v error = 0.0;
2332 Load(error, data.ErrorPtr(i * vecSize));
2333 // for empty bin use the average weight computed from the total data weight
2334 auto m = y != 0.0;
2335 Double_v weight{};
2336 where(m, weight) = (error * error) / y;
2337 where(!m, weight) = Double_v{data.SumOfError2() / data.SumOfContent()};
2338 if (extended) {
2339 nloglike = weight * (fval - y);
2340 }
2341 where(y != 0, nloglike) =
2343
2344 } else {
2345 // standard case no weights or iWeight=1
2346 // this is needed for Poisson likelihood (which are extended and not for multinomial)
2347 // the formula below include constant term due to likelihood of saturated model (f(x) = y)
2348 // (same formula as in Baker-Cousins paper, page 439 except a factor of 2
2349 if (extended)
2350 nloglike = fval - y;
2351
2353 }
2354
2355 return nloglike;
2356 };
2357
2358#ifdef R__USE_IMT
2359 auto redFunction = [](const std::vector<Double_v> &objs) {
2360 return std::accumulate(objs.begin(), objs.end(), Double_v{});
2361 };
2362#else
2363 (void)nChunks;
2364
2365 // If IMT is disabled, force the execution policy to the serial case
2367 Warning("FitUtil::Evaluate<T>::EvalPoissonLogL",
2368 "Multithread execution policy requires IMT, which is disabled. Changing "
2369 "to ::ROOT::EExecutionPolicy::kSequential.");
2371 }
2372#endif
2373
2374 Double_v res{};
2376 for (unsigned int i = 0; i < (data.Size() / vecSize); i++) {
2377 res += mapFunction(i);
2378 }
2379#ifdef R__USE_IMT
2382 auto chunks = nChunks != 0 ? nChunks : setAutomaticChunking(data.Size() / vecSize);
2383 res = pool.MapReduce(mapFunction, ROOT::TSeq<unsigned>(0, data.Size() / vecSize), redFunction, chunks);
2384#endif
2385 } else {
2386 Error("FitUtil::Evaluate<T>::EvalPoissonLogL",
2387 "Execution policy unknown. Available choices:\n ::ROOT::EExecutionPolicy::kSequential (default)\n "
2388 "::ROOT::EExecutionPolicy::kMultiThread (requires IMT)\n");
2389 }
2390
2391 // Last padded SIMD vector of elements
2392 if (data.Size() % vecSize != 0)
2393 where(Int2Mask(data.Size() % vecSize), res) = res + mapFunction(data.Size() / vecSize);
2394
2395 return ReduceAdd(res);
2396}
2397
2398namespace {
2399
2400// Compute a mask to filter out infinite numbers and NaN values.
2401// The argument rval is updated so infinite numbers and NaN values are replaced by
2402// maximum finite values (preserving the original sign).
2404{
2406
2407 // Case +inf or nan
2409
2410 // Case -inf
2411 where(!mask && rval < 0, rval) = -NumericMax<Double_v>();
2412
2413 return mask;
2414}
2415
2416} // namespace
2417
2418void Evaluate<Double_v>::EvalChi2Gradient(const IModelFunctionTempl<Double_v> &f, const BinData &data, const double *p,
2419 double *grad, unsigned int &nPoints, ::ROOT::EExecutionPolicy executionPolicy,
2420 unsigned nChunks)
2421{
2422 // evaluate the gradient of the chi2 function
2423 // this function is used when the model function knows how to calculate the derivative and we can
2424 // avoid that the minimizer re-computes them
2425 //
2426 // case of chi2 effective (errors on coordinate) is not supported
2427
2428 if (data.HaveCoordErrors()) {
2429 MATH_ERROR_MSG("FitUtil::EvaluateChi2Gradient",
2430 "Error on the coordinates are not used in calculating Chi2 gradient");
2431 return; // it will assert otherwise later in GetPoint
2432 }
2433
2434 auto *fg = dynamic_cast<const IGradModelFunctionTempl<Double_v> *>(&f);
2435 assert(fg != nullptr); // must be called by a gradient function
2436
2437 auto &func = *fg;
2438
2439 const DataOptions &fitOpt = data.Opt();
2440 if (fitOpt.fBinVolume || fitOpt.fIntegral || fitOpt.fExpErrors)
2441 Error("FitUtil::EvaluateChi2Gradient", "The vectorized implementation doesn't support Integrals,"
2442 "BinVolume or ExpErrors\n. Aborting operation.");
2443
2444 unsigned int npar = func.NPar();
2445 auto vecSize = Double_v::size();
2446 unsigned initialNPoints = data.Size();
2447 unsigned numVectors = initialNPoints / vecSize;
2448
2449 // numVectors + 1 because of the padded data (call to mapFunction with i = numVectors after the main loop)
2450 std::vector<Double_v::mask_type> validPointsMasks(numVectors + 1);
2451
2452 auto mapFunction = [&](const unsigned int i) {
2453 // set all vector values to zero
2454 std::vector<Double_v> gradFunc(npar);
2455 std::vector<Double_v> pointContributionVec(npar);
2456
2458
2459 Load(x1, data.GetCoordComponent(i * vecSize, 0));
2460 Load(y, data.ValuePtr(i * vecSize));
2461 const auto invErrorPtr = data.ErrorPtr(i * vecSize);
2462
2463 if (invErrorPtr == nullptr)
2464 invError = 1;
2465 else
2466 Load(invError, invErrorPtr);
2467
2468 // TODO: Check error options and invert if needed
2469
2470 Double_v fval = 0;
2471
2472 const Double_v *x = nullptr;
2473
2474 unsigned int ndim = data.NDim();
2475 // need to declare vector outside if statement
2476 // otherwise pointer will be invalid
2477 std::vector<Double_v> xc;
2478 if (ndim > 1) {
2479 xc.resize(ndim);
2480 xc[0] = x1;
2481 for (unsigned int j = 1; j < ndim; ++j)
2482 Load(xc[j], data.GetCoordComponent(i * vecSize, j));
2483 x = xc.data();
2484 } else {
2485 x = &x1;
2486 }
2487
2488 fval = func(x, p);
2489 func.ParameterGradient(x, p, &gradFunc[0]);
2490
2492 if (MaskEmpty(validPointsMasks[i])) {
2493 // Return a zero contribution to all partial derivatives on behalf of the current points
2494 return pointContributionVec;
2495 }
2496
2497 // loop on the parameters
2498 for (unsigned int ipar = 0; ipar < npar; ++ipar) {
2499 // avoid singularity in the function (infinity and nan ) in the chi2 sum
2500 // eventually add possibility of excluding some points (like singularity)
2502
2503 if (MaskEmpty(validPointsMasks[i])) {
2504 break; // exit loop on parameters
2505 }
2506
2507 // calculate derivative point contribution (only for valid points)
2509 -2.0 * (y - fval) * invError * invError * gradFunc[ipar];
2510 }
2511
2512 return pointContributionVec;
2513 };
2514
2515 // Reduce the set of vectors by summing its equally-indexed components
2516 auto redFunction = [&](const std::vector<std::vector<Double_v>> &partialResults) {
2517 std::vector<Double_v> result(npar);
2518
2519 for (auto const &pointContributionVec : partialResults) {
2520 for (unsigned int parameterIndex = 0; parameterIndex < npar; parameterIndex++)
2522 }
2523
2524 return result;
2525 };
2526
2527 std::vector<Double_v> gVec(npar);
2528 std::vector<double> g(npar);
2529
2530#ifndef R__USE_IMT
2531 // to fix compiler warning
2532 (void)nChunks;
2533
2534 // If IMT is disabled, force the execution policy to the serial case
2536 Warning("FitUtil::EvaluateChi2Gradient", "Multithread execution policy requires IMT, which is disabled. Changing "
2537 "to ::ROOT::EExecutionPolicy::kSequential.");
2539 }
2540#endif
2541
2545 }
2546#ifdef R__USE_IMT
2551 }
2552#endif
2553 else {
2554 Error("FitUtil::EvaluateChi2Gradient",
2555 "Execution policy unknown. Available choices:\n 0: Serial (default)\n 1: MultiThread (requires IMT)\n");
2556 }
2557
2558 // Compute the contribution from the remaining points
2559 unsigned int remainingPoints = initialNPoints % vecSize;
2560 if (remainingPoints > 0) {
2562 // Add the contribution from the valid remaining points and store the result in the output variable
2564 for (unsigned int param = 0; param < npar; param++) {
2565 where(remainingMask, gVec[param]) = gVec[param] + remainingPointsContribution[param];
2566 }
2567 }
2568 // reduce final gradient result from T to double
2569 for (unsigned int param = 0; param < npar; param++) {
2570 grad[param] = ReduceAdd(gVec[param]);
2571 }
2572
2573 // correct the number of points
2575
2576 if (std::any_of(validPointsMasks.begin(), validPointsMasks.end(), [](auto validPoints) {
2577 // Check if the mask is not full
2578 for (size_t i = 0; i < Double_v::mask_type::size(); ++i)
2579 if (!validPoints[i])
2580 return true;
2581 return false;
2582 })) {
2583 unsigned nRejected = 0;
2584
2585 for (const auto &mask : validPointsMasks) {
2586 for (unsigned int i = 0; i < vecSize; i++) {
2587 nRejected += !mask[i];
2588 }
2589 }
2590
2593
2594 if (nPoints < npar) {
2595 MATH_ERROR_MSG("FitUtil::EvaluateChi2Gradient",
2596 "Too many points rejected for overflow in gradient calculation");
2597 }
2598 }
2599}
2600
2601void Evaluate<Double_v>::EvalPoissonLogLGradient(const IModelFunctionTempl<Double_v> &f, const BinData &data,
2602 const double *p, double *grad, unsigned int &,
2604{
2605 // evaluate the gradient of the Poisson log likelihood function
2606
2607 auto *fg = dynamic_cast<const IGradModelFunctionTempl<Double_v> *>(&f);
2608 assert(fg != nullptr); // must be called by a grad function
2609
2610 auto &func = *fg;
2611
2612 (const_cast<IGradModelFunctionTempl<Double_v> &>(func)).SetParameters(p);
2613
2614 const DataOptions &fitOpt = data.Opt();
2615 if (fitOpt.fBinVolume || fitOpt.fIntegral || fitOpt.fExpErrors)
2616 Error("FitUtil::EvaluatePoissonLogLGradient", "The vectorized implementation doesn't support Integrals,"
2617 "BinVolume or ExpErrors\n. Aborting operation.");
2618
2619 unsigned int npar = func.NPar();
2620 auto vecSize = Double_v::size();
2621 unsigned initialNPoints = data.Size();
2622 unsigned numVectors = initialNPoints / vecSize;
2623
2624 auto mapFunction = [&](const unsigned int i) {
2625 // set all vector values to zero
2626 std::vector<Double_v> gradFunc(npar);
2627 std::vector<Double_v> pointContributionVec(npar);
2628
2629 Double_v x1, y;
2630
2631 Load(x1, data.GetCoordComponent(i * vecSize, 0));
2632 Load(y, data.ValuePtr(i * vecSize));
2633
2634 Double_v fval = 0;
2635
2636 const Double_v *x = nullptr;
2637
2638 unsigned ndim = data.NDim();
2639 std::vector<Double_v> xc;
2640 if (ndim > 1) {
2641 xc.resize(ndim);
2642 xc[0] = x1;
2643 for (unsigned int j = 1; j < ndim; ++j)
2644 Load(xc[j], data.GetCoordComponent(i * vecSize, j));
2645 x = xc.data();
2646 } else {
2647 x = &x1;
2648 }
2649
2650 fval = func(x, p);
2651 func.ParameterGradient(x, p, &gradFunc[0]);
2652
2653 // correct the gradient
2654 for (unsigned int ipar = 0; ipar < npar; ++ipar) {
2655 auto positiveValuesMask = fval > 0;
2656
2657 // df/dp * (1. - y/f )
2658 where(positiveValuesMask, pointContributionVec[ipar]) = gradFunc[ipar] * (1. - y / fval);
2659
2661
2662 if (!MaskEmpty(validNegativeValuesMask)) {
2663 const Double_v kdmax1 = sqrt(NumericMax<Double_v>());
2665 Double_v gg = kdmax1 * gradFunc[ipar];
2666 auto mask = gg > 0;
2667 where(mask, pointContributionVec[ipar]) = min(gg, kdmax2);
2668 where(!mask, pointContributionVec[ipar]) = max(gg, -kdmax2);
2670 }
2671 }
2672
2673#ifdef DEBUG_FITUTIL
2674 {
2676 if (i < 5 || (i > data.Size() - 5)) {
2677 if (data.NDim() > 1)
2678 std::cout << i << " x " << x[0] << " y " << x[1];
2679 else
2680 std::cout << i << " x " << x[0];
2681 std::cout << " func " << fval << " gradient ";
2682 for (unsigned int ii = 0; ii < npar; ++ii)
2683 std::cout << " " << pointContributionVec[ii];
2684 std::cout << "\n";
2685 }
2686 }
2687#endif
2688
2689 return pointContributionVec;
2690 };
2691
2692 // Vertically reduce the set of vectors by summing its equally-indexed components
2693 auto redFunction = [&](const std::vector<std::vector<Double_v>> &partialResults) {
2694 std::vector<Double_v> result(npar);
2695
2696 for (auto const &pointContributionVec : partialResults) {
2697 for (unsigned int parameterIndex = 0; parameterIndex < npar; parameterIndex++)
2699 }
2700
2701 return result;
2702 };
2703
2704 std::vector<Double_v> gVec(npar);
2705
2706#ifndef R__USE_IMT
2707 // to fix compiler warning
2708 (void)nChunks;
2709
2710 // If IMT is disabled, force the execution policy to the serial case
2712 Warning("FitUtil::EvaluatePoissonLogLGradient",
2713 "Multithread execution policy requires IMT, which is disabled. Changing "
2714 "to ::ROOT::EExecutionPolicy::kSequential.");
2716 }
2717#endif
2718
2722 }
2723#ifdef R__USE_IMT
2728 }
2729#endif
2730 else {
2731 Error("FitUtil::EvaluatePoissonLogLGradient", "Execution policy unknown. Available choices:\n "
2732 "::ROOT::EExecutionPolicy::kSequential (default)\n "
2733 "::ROOT::EExecutionPolicy::kMultiThread (requires IMT)\n");
2734 }
2735
2736 // Compute the contribution from the remaining points
2737 unsigned int remainingPoints = initialNPoints % vecSize;
2738 if (remainingPoints > 0) {
2740 // Add the contribution from the valid remaining points and store the result in the output variable
2742 for (unsigned int param = 0; param < npar; param++) {
2743 where(remainingMask, gVec[param]) = gVec[param] + remainingPointsContribution[param];
2744 }
2745 }
2746 // reduce final gradient result from T to double
2747 for (unsigned int param = 0; param < npar; param++) {
2748 grad[param] = ReduceAdd(gVec[param]);
2749 }
2750
2751#ifdef DEBUG_FITUTIL
2752 std::cout << "***** Final gradient : ";
2753 for (unsigned int ii = 0; ii < npar; ++ii)
2754 std::cout << grad[ii] << " ";
2755 std::cout << "\n";
2756#endif
2757}
2758
2759void Evaluate<Double_v>::EvalLogLGradient(const IModelFunctionTempl<Double_v> &f, const UnBinData &data,
2760 const double *p, double *grad, unsigned int &,
2762{
2763 // evaluate the gradient of the log likelihood function
2764
2765 auto *fg = dynamic_cast<const IGradModelFunctionTempl<Double_v> *>(&f);
2766 assert(fg != nullptr); // must be called by a grad function
2767
2768 auto &func = *fg;
2769
2770 unsigned int npar = func.NPar();
2771 auto vecSize = Double_v::size();
2772 unsigned initialNPoints = data.Size();
2773 unsigned numVectors = initialNPoints / vecSize;
2774
2775#ifdef DEBUG_FITUTIL
2776 std::cout << "\n===> Evaluate Gradient for parameters ";
2777 for (unsigned int ip = 0; ip < npar; ++ip)
2778 std::cout << " " << p[ip];
2779 std::cout << "\n";
2780#endif
2781
2782 (const_cast<IGradModelFunctionTempl<Double_v> &>(func)).SetParameters(p);
2783
2784 const Double_v kdmax1 = sqrt(NumericMax<Double_v>());
2786
2787 auto mapFunction = [&](const unsigned int i) {
2788 std::vector<Double_v> gradFunc(npar);
2789 std::vector<Double_v> pointContributionVec(npar);
2790
2791 Double_v x1;
2792 Load(x1, data.GetCoordComponent(i * vecSize, 0));
2793
2794 const Double_v *x = nullptr;
2795
2796 unsigned int ndim = data.NDim();
2797 std::vector<Double_v> xc(ndim);
2798 if (ndim > 1) {
2799 xc.resize(ndim);
2800 xc[0] = x1;
2801 for (unsigned int j = 1; j < ndim; ++j)
2802 Load(xc[j], data.GetCoordComponent(i * vecSize, j));
2803 x = xc.data();
2804 } else {
2805 x = &x1;
2806 }
2807
2808 Double_v fval = func(x, p);
2809 func.ParameterGradient(x, p, &gradFunc[0]);
2810
2811#ifdef DEBUG_FITUTIL
2812 if (i < 5 || (i > numVectors - 5)) {
2813 if (ndim > 1)
2814 std::cout << i << " x " << x[0] << " y " << x[1] << " gradient " << gradFunc[0] << " " << gradFunc[1]
2815 << " " << gradFunc[3] << std::endl;
2816 else
2817 std::cout << i << " x " << x[0] << " gradient " << gradFunc[0] << " " << gradFunc[1] << " "
2818 << gradFunc[3] << std::endl;
2819 }
2820#endif
2821
2822 auto positiveValues = fval > 0;
2823
2824 for (unsigned int kpar = 0; kpar < npar; ++kpar) {
2825 if (!MaskEmpty(positiveValues)) {
2827 }
2828
2830 if (!MaskEmpty(nonZeroGradientValues)) {
2832 auto mask = nonZeroGradientValues && gg > 0;
2835 }
2836 // if func derivative is zero term is also zero so do not add in g[kpar]
2837 }
2838
2839 return pointContributionVec;
2840 };
2841
2842 // Vertically reduce the set of vectors by summing its equally-indexed components
2843 auto redFunction = [&](const std::vector<std::vector<Double_v>> &pointContributions) {
2844 std::vector<Double_v> result(npar);
2845
2846 for (auto const &pointContributionVec : pointContributions) {
2847 for (unsigned int parameterIndex = 0; parameterIndex < npar; parameterIndex++)
2849 }
2850
2851 return result;
2852 };
2853
2854 std::vector<Double_v> gVec(npar);
2855 std::vector<double> g(npar);
2856
2857#ifndef R__USE_IMT
2858 // to fix compiler warning
2859 (void)nChunks;
2860
2861 // If IMT is disabled, force the execution policy to the serial case
2863 Warning("FitUtil::EvaluateLogLGradient", "Multithread execution policy requires IMT, which is disabled. Changing "
2864 "to ::ROOT::EExecutionPolicy::kSequential.");
2866 }
2867#endif
2868
2872 }
2873#ifdef R__USE_IMT
2878 }
2879#endif
2880 else {
2881 Error("FitUtil::EvaluateLogLGradient", "Execution policy unknown. Available choices:\n "
2882 "::ROOT::EExecutionPolicy::kSequential (default)\n "
2883 "::ROOT::EExecutionPolicy::kMultiThread (requires IMT)\n");
2884 }
2885
2886 // Compute the contribution from the remaining points
2887 unsigned int remainingPoints = initialNPoints % vecSize;
2888 if (remainingPoints > 0) {
2890 // Add the contribution from the valid remaining points and store the result in the output variable
2892 for (unsigned int param = 0; param < npar; param++) {
2893 where(remainingMask, gVec[param]) = gVec[param] + remainingPointsContribution[param];
2894 }
2895 }
2896 // reduce final gradient result from T to double
2897 for (unsigned int param = 0; param < npar; param++) {
2898 grad[param] = ReduceAdd(gVec[param]);
2899 }
2900
2901#ifdef DEBUG_FITUTIL
2902 std::cout << "Final gradient ";
2903 for (unsigned int param = 0; param < npar; param++) {
2904 std::cout << " " << grad[param];
2905 }
2906 std::cout << "\n";
2907#endif
2908}
2909
2910} // namespace FitUtil
2911
2912#endif // R__HAS_STD_EXPERIMENTAL_SIMD
2913}
2914
2915} // 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:1301
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:549
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)
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:895
bool CheckInfNaNValue(double &rval)
Definition FitUtil.cxx:185
unsigned setAutomaticChunking(unsigned nEvents)
Definition FitUtil.cxx:1845
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:607
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