Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
RooKeysPdf.cxx
Go to the documentation of this file.
1/*****************************************************************************
2 * Project: RooFit *
3 * Package: RooFitModels *
4 * @(#)root/roofit:$Id$
5 * Authors: *
6 * GR, Gerhard Raven, UC San Diego, raven@slac.stanford.edu *
7 * DK, David Kirkby, UC Irvine, dkirkby@uci.edu *
8 * WV, Wouter Verkerke, UC Santa Barbara, verkerke@slac.stanford.edu *
9 * *
10 * Copyright (c) 2000-2005, Regents of the University of California *
11 * and Stanford University. All rights reserved. *
12 * *
13 * Redistribution and use in source and binary forms, *
14 * with or without modification, are permitted according to the terms *
15 * listed in LICENSE (http://roofit.sourceforge.net/license.txt) *
16 *****************************************************************************/
17
18/** \class RooKeysPdf
19 \ingroup Roofit
20
21Class RooKeysPdf implements a one-dimensional kernel estimation p.d.f which model the distribution
22of an arbitrary input dataset as a superposition of Gaussian kernels, one for each data point,
23each contributing 1/N to the total integral of the pdf.
24It was inspired by Kyle Cranmer's KEYS package, see
25[the original web page](https://web.archive.org/web/20020705034344/https://www-wisconsin.cern.ch/~cranmer/keys.html).
26
27\note KEYS stands for Kernel Estimating Your Shapes, see
28[the KEYS write-up](https://web.archive.org/web/20010604031632/http://www-wisconsin.cern.ch/~cranmer/KEYS.pdf).
29
30If the 'adaptive mode' is enabled, the width of the Gaussian is adaptively calculated from the
31local density of events, i.e. narrow for regions with high event density to preserve details and
32wide for regions with low event density to promote smoothness. The details of the general algorithm
33are described in the following paper:
34
35Cranmer KS, Kernel Estimation in High-Energy Physics.
36 Computer Physics Communications 136:198-207,2001 - e-Print Archive: hep-ex/0011057,
37 [doi:10.1016/S0010-4655(00)00243-5](https://doi.org/10.1016/S0010-4655(00)00243-5)
38
39The `rho` parameter (default 1) is an overall scale factor for the width of the
40kernels. Values larger than 1 make the kernels wider and give a smoother
41estimate, while values smaller than 1 make them narrower and keep more detail.
42The default corresponds to the usual normal-reference ("rule of thumb")
43bandwidth.
44
45Close to the edges of the observable range the estimate is biased: the kernels
46of events near an edge have no data on the other side to balance them, so the
47density "leaks" out of the range. The `mirror` parameter selects an optional
48boundary correction that reflects the data across an edge. Symmetric mirroring
49adds the reflected events, which is appropriate when the true density is flat at
50the boundary (the estimate keeps a non-zero value there, with zero slope). Asymmetric mirroring
51subtracts the reflected events, which is appropriate when the true density is
52expected to vanish at the boundary. See the RooKeysPdf::Mirror enum for the list
53of options.
54
55For a multi-dimensional version of this pdf, see RooNDKeysPdf.
56**/
57
58#include "TMath.h"
59#include "RooKeysPdf.h"
60#include "RooRealVar.h"
61#include "RooRandom.h"
62#include "RooDataSet.h"
63
64#include "TError.h"
65#include "TMath.h"
66
67#include <algorithm>
68#include <cmath>
69#include <cstdio>
70#include <iostream>
71#include <limits>
72
73
74const double RooKeysPdf::_nSigma = std::sqrt(-2. *
75 std::log(std::numeric_limits<double>::epsilon()));
76
77////////////////////////////////////////////////////////////////////////////////
78/// coverity[UNINIT_CTOR]
79
83
84////////////////////////////////////////////////////////////////////////////////
85/// Construct a kernel estimation pdf of the observable `xpdf` from its
86/// distribution in `data`.
87///
88/// \param[in] name Name of the pdf.
89/// \param[in] title Title of the pdf, used for plotting.
90/// \param[in] xpdf Observable the pdf is defined in. Its range sets the
91/// boundaries used for the mirror correction and for the
92/// internal binned lookup table.
93/// \param[in] data Dataset whose distribution of `xpdf` is modelled. The width
94/// of each kernel is adapted to the local event density.
95/// \param[in] mirror Optional boundary correction, see the Mirror enum.
96/// \param[in] rho Overall scale factor for the kernel width (default 1);
97/// larger values give a smoother estimate.
98
99RooKeysPdf::RooKeysPdf(const char *name, const char *title, RooAbsReal &xpdf, RooDataSet &data, Mirror mirror, double rho)
101{
102}
103
104////////////////////////////////////////////////////////////////////////////////
105/// As above, but reading the input values from a dataset variable `xdata` that
106/// can be different from the observable `xpdf` the pdf depends on.
107///
108/// \param[in] name Name of the pdf.
109/// \param[in] title Title of the pdf, used for plotting.
110/// \param[in] xpdf Observable the pdf is defined in.
111/// \param[in] xdata Variable in `data` whose distribution is modelled. Its
112/// range sets the boundaries used for the mirror correction
113/// and for the internal binned lookup table.
114/// \param[in] data Dataset holding the values of `xdata` to model.
115/// \param[in] mirror Optional boundary correction, see the Mirror enum.
116/// \param[in] rho Overall scale factor for the kernel width (default 1);
117/// larger values give a smoother estimate.
118
120 Mirror mirror, double rho)
121 : RooAbsPdf(name, title),
122 _x("x", "Observable", this, xpdf),
123 _mirrorLeft(mirror == MirrorLeft || mirror == MirrorBoth || mirror == MirrorLeftAsymRight),
124 _mirrorRight(mirror == MirrorRight || mirror == MirrorBoth || mirror == MirrorAsymLeftRight),
125 _asymLeft(mirror == MirrorAsymLeft || mirror == MirrorAsymLeftRight || mirror == MirrorAsymBoth),
126 _asymRight(mirror == MirrorAsymRight || mirror == MirrorLeftAsymRight || mirror == MirrorAsymBoth),
127 _lo(xdata.getMin()),
128 _hi(xdata.getMax()),
129 _binWidth((_hi - _lo) / (_nPoints - 1)),
130 _rho(rho)
131{
132 snprintf(_varName, 128,"%s", xdata.GetName());
133
134 // form the lookup table
136}
137
138////////////////////////////////////////////////////////////////////////////////
139
141 : RooAbsPdf(other, name),
142 _x("x", this, other._x),
143 _nEvents(other._nEvents),
144 _mirrorLeft(other._mirrorLeft),
145 _mirrorRight(other._mirrorRight),
146 _asymLeft(other._asymLeft),
147 _asymRight(other._asymRight),
148 _lo(other._lo),
149 _hi(other._hi),
150 _binWidth(other._binWidth),
151 _rho(other._rho)
152{
153 // cache stuff about x
154 snprintf(_varName, 128, "%s", other._varName );
155
156 // copy over data and weights... not necessary, commented out for speed
157// _dataPts = new double[_nEvents];
158// _weights = new double[_nEvents];
159// for (Int_t i= 0; i<_nEvents; i++) {
160// _dataPts[i]= other._dataPts[i];
161// _weights[i]= other._weights[i];
162// }
163
164 // copy over the lookup table
165 for (Int_t i= 0; i<_nPoints+1; i++)
166 _lookupTable[i]= other._lookupTable[i];
167
168}
169
170////////////////////////////////////////////////////////////////////////////////
171
173 delete[] _dataPts;
174 delete[] _dataWgts;
175 delete[] _weights;
176
177}
178
179////////////////////////////////////////////////////////////////////////////////
180/// small helper structure
181
182namespace {
183 struct Data {
184 double x;
185 double w;
186 };
187 // helper to order two Data structures
188 struct cmp {
189 inline bool operator()(const struct Data& a, const struct Data& b) const
190 { return a.x < b.x; }
191 };
192}
194 delete[] _dataPts;
195 delete[] _dataWgts;
196 delete[] _weights;
197
198 std::vector<Data> tmp;
199 tmp.reserve((1 + _mirrorLeft + _mirrorRight) * data.numEntries());
200 double x0 = 0.;
201 double x1 = 0.;
202 double x2 = 0.;
203 _sumWgt = 0.;
204 // read the data set into tmp and accumulate some statistics
205 RooRealVar& real = static_cast<RooRealVar&>(data.get()->operator[](_varName));
206 for (Int_t i = 0; i < data.numEntries(); ++i) {
207 data.get(i);
208 const double x = real.getVal();
209 const double w = data.weight();
210 x0 += w;
211 x1 += w * x;
212 x2 += w * x * x;
214
215 Data p;
216 p.x = x, p.w = w;
217 tmp.push_back(p);
218 if (_mirrorLeft) {
219 p.x = 2. * _lo - x;
220 tmp.push_back(p);
221 }
222 if (_mirrorRight) {
223 p.x = 2. * _hi - x;
224 tmp.push_back(p);
225 }
226 }
227 // sort the entire data set so that values of x are increasing
228 std::sort(tmp.begin(), tmp.end(), cmp());
229
230 // copy the sorted data set to its final destination
231 _nEvents = tmp.size();
232 _dataPts = new double[_nEvents];
233 _dataWgts = new double[_nEvents];
234 for (unsigned i = 0; i < tmp.size(); ++i) {
235 _dataPts[i] = tmp[i].x;
236 _dataWgts[i] = tmp[i].w;
237 }
238 {
239 // free tmp
240 std::vector<Data> tmp2;
241 tmp2.swap(tmp);
242 }
243
244 double meanv=x1/x0;
245 double sigmav=std::sqrt(x2/x0-meanv*meanv);
246 double h=std::pow(double(4)/double(3),0.2)*std::pow(_sumWgt,-0.2)*_rho;
247 double hmin=h*sigmav*std::sqrt(2.)/10;
248 // Dividing by 2*sqrt(3) = sqrt(12) turns a width into the standard deviation
249 // of a uniform distribution of that width. Per the original author, this goes
250 // back to inputs that were finely binned histograms rather than unbinned data:
251 // entries spread uniformly over a bin get aggregated into a single sample with
252 // no variance, so the bin width was taken as the spread of that sample.
253 //
254 // Beware that no bin width enters the expression below, so that rationale does
255 // not map onto the code as it stands: what remains is an extra factor of
256 // sqrt(12) with respect to hep-ex/0011057, kept for backwards compatibility.
257 // The same factor appears in RooNDKeysPdf::calculateBandWidth().
258 double norm=h*std::sqrt(sigmav * _sumWgt)/(2.0*std::sqrt(3.0));
259
260 _weights=new double[_nEvents];
261 for(Int_t j=0;j<_nEvents;++j) {
262 _weights[j] = norm / std::sqrt(_dataWgts[j] * g(_dataPts[j],h*sigmav));
263 if (_weights[j]<hmin) _weights[j]=hmin;
264 }
265
266 // The idea below is that beyond nSigma sigma, the value of the exponential
267 // in the Gaussian is well below the machine precision of a double, so it
268 // does not contribute any more. That way, we can limit how many bins of the
269 // binned approximation in _lookupTable we have to touch when filling it.
270 for (Int_t i=0;i<_nPoints+1;++i) _lookupTable[i] = 0.;
271 for(Int_t j=0;j<_nEvents;++j) {
272 const double xlo = std::min(_hi,
273 std::max(_lo, _dataPts[j] - _nSigma * _weights[j]));
274 const double xhi = std::max(_lo,
275 std::min(_hi, _dataPts[j] + _nSigma * _weights[j]));
276 if (xlo >= xhi) continue;
277 const double chi2incr = _binWidth / _weights[j] / std::sqrt(2.);
278 const double weightratio = _dataWgts[j] / _weights[j];
279 const Int_t binlo = static_cast<Int_t>(std::floor((xlo - _lo) / _binWidth));
280 const Int_t binhi = static_cast<Int_t>(_nPoints - std::floor((_hi - xhi) / _binWidth));
281 const double x = (double(_nPoints - binlo) * _lo +
282 double(binlo) * _hi) / double(_nPoints);
283 double chi = (x - _dataPts[j]) / _weights[j] / std::sqrt(2.);
284 for (Int_t k = binlo; k <= binhi; ++k, chi += chi2incr) {
285 _lookupTable[k] += weightratio * std::exp(- chi * chi);
286 }
287 }
288 if (_asymLeft) {
289 for(Int_t j=0;j<_nEvents;++j) {
290 const double xlo = std::min(_hi,
291 std::max(_lo, 2. * _lo - _dataPts[j] + _nSigma * _weights[j]));
292 const double xhi = std::max(_lo,
293 std::min(_hi, 2. * _lo - _dataPts[j] - _nSigma * _weights[j]));
294 if (xlo >= xhi) continue;
295 const double chi2incr = _binWidth / _weights[j] / std::sqrt(2.);
296 const double weightratio = _dataWgts[j] / _weights[j];
297 const Int_t binlo = static_cast<Int_t>(std::floor((xlo - _lo) / _binWidth));
298 const Int_t binhi = static_cast<Int_t>(_nPoints - std::floor((_hi - xhi) / _binWidth));
299 const double x = (double(_nPoints - binlo) * _lo +
300 double(binlo) * _hi) / double(_nPoints);
301 double chi = (x - (2. * _lo - _dataPts[j])) / _weights[j] / std::sqrt(2.);
302 for (Int_t k = binlo; k <= binhi; ++k, chi += chi2incr) {
303 _lookupTable[k] -= weightratio * std::exp(- chi * chi);
304 }
305 }
306 }
307 if (_asymRight) {
308 for(Int_t j=0;j<_nEvents;++j) {
309 const double xlo = std::min(_hi,
310 std::max(_lo, 2. * _hi - _dataPts[j] + _nSigma * _weights[j]));
311 const double xhi = std::max(_lo,
312 std::min(_hi, 2. * _hi - _dataPts[j] - _nSigma * _weights[j]));
313 if (xlo >= xhi) continue;
314 const double chi2incr = _binWidth / _weights[j] / std::sqrt(2.);
315 const double weightratio = _dataWgts[j] / _weights[j];
316 const Int_t binlo = static_cast<Int_t>(std::floor((xlo - _lo) / _binWidth));
317 const Int_t binhi = static_cast<Int_t>(_nPoints - std::floor((_hi - xhi) / _binWidth));
318 const double x = (double(_nPoints - binlo) * _lo +
319 double(binlo) * _hi) / double(_nPoints);
320 double chi = (x - (2. * _hi - _dataPts[j])) / _weights[j] / std::sqrt(2.);
321 for (Int_t k = binlo; k <= binhi; ++k, chi += chi2incr) {
322 _lookupTable[k] -= weightratio * std::exp(- chi * chi);
323 }
324 }
325 }
326 static const double sqrt2pi(std::sqrt(2*TMath::Pi()));
327 for (Int_t i=0;i<_nPoints+1;++i)
329}
330
331////////////////////////////////////////////////////////////////////////////////
332
333double RooKeysPdf::evaluate() const {
334 Int_t i = (Int_t)floor((double(_x)-_lo)/_binWidth);
335 if (i<0) {
336// cerr << "got point below lower bound:"
337// << double(_x) << " < " << _lo
338// << " -- performing linear extrapolation..." << std::endl;
339 i=0;
340 }
341 if (i>_nPoints-1) {
342// cerr << "got point above upper bound:"
343// << double(_x) << " > " << _hi
344// << " -- performing linear extrapolation..." << std::endl;
345 i=_nPoints-1;
346 }
347 double dx = (double(_x)-(_lo+i*_binWidth))/_binWidth;
348
349 // for now do simple linear interpolation.
350 // one day replace by splines...
351 double ret = (_lookupTable[i]+dx*(_lookupTable[i+1]-_lookupTable[i]));
352 if (ret<0) ret=0 ;
353 return ret ;
354}
355
357 RooArgSet& allVars, RooArgSet& analVars, const char* /* rangeName */) const
358{
359 if (matchArgs(allVars, analVars, _x)) return 1;
360 return 0;
361}
362
363double RooKeysPdf::analyticalIntegral(Int_t code, const char* rangeName) const
364{
365 R__ASSERT(1 == code);
366 // this code is based on _lookupTable and uses linear interpolation, just as
367 // evaluate(); integration is done using the trapez rule
368 const double xmin = std::max(_lo, _x.min(rangeName));
369 const double xmax = std::min(_hi, _x.max(rangeName));
370 const Int_t imin = (Int_t)floor((xmin - _lo) / _binWidth);
371 const Int_t imax = std::min((Int_t)floor((xmax - _lo) / _binWidth),
372 _nPoints - 1);
373 double sum = 0.;
374 // sum up complete bins in middle
375 if (imin + 1 < imax)
377 for (Int_t i = imin + 2; i < imax; ++i)
378 sum += 2. * _lookupTable[i];
379 sum *= _binWidth * 0.5;
380 // treat incomplete bins
381 const double dxmin = (xmin - (_lo + imin * _binWidth)) / _binWidth;
382 const double dxmax = (xmax - (_lo + imax * _binWidth)) / _binWidth;
383 if (imin < imax) {
384 // first bin
385 sum += _binWidth * (1. - dxmin) * 0.5 * (_lookupTable[imin + 1] +
388 // last bin
389 sum += _binWidth * dxmax * 0.5 * (_lookupTable[imax] +
392 } else if (imin == imax) {
393 // first bin == last bin
394 sum += _binWidth * (dxmax - dxmin) * 0.5 * (
399 }
400 return sum;
401}
402
404{
405 if (vars.contains(*_x.absArg())) return 1;
406 return 0;
407}
408
409double RooKeysPdf::maxVal(Int_t code) const
410{
411 R__ASSERT(1 == code);
412 double max = -std::numeric_limits<double>::max();
413 for (Int_t i = 0; i <= _nPoints; ++i)
414 if (max < _lookupTable[i]) max = _lookupTable[i];
415 return max;
416}
417
418////////////////////////////////////////////////////////////////////////////////
419
420double RooKeysPdf::g(double x,double sigmav) const {
421 double y=0;
422 // since data is sorted, we can be a little faster because we know which data
423 // points contribute
424 double* it = std::lower_bound(_dataPts, _dataPts + _nEvents,
425 x - _nSigma * sigmav);
426 if (it >= (_dataPts + _nEvents)) return 0.;
427 double* iend = std::upper_bound(it, _dataPts + _nEvents,
428 x + _nSigma * sigmav);
429 for ( ; it < iend; ++it) {
430 const double r = (x - *it) / sigmav;
431 y += std::exp(-0.5 * r * r);
432 }
433
434 static const double sqrt2pi(std::sqrt(2*TMath::Pi()));
435 return y/(sigmav*sqrt2pi);
436}
#define b(i)
Definition RSha256.hxx:100
#define g(i)
Definition RSha256.hxx:105
#define a(i)
Definition RSha256.hxx:99
#define h(i)
Definition RSha256.hxx:106
int Int_t
Signed integer 4 bytes (int)
Definition RtypesCore.h:60
ROOT::Detail::TRangeCast< T, true > TRangeDynCast
TRangeDynCast is an adapter class that allows the typed iteration through a TCollection.
#define R__ASSERT(e)
Checks condition e and reports a fatal error if it's false.
Definition TError.h:130
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 hmin
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 r
Option_t Option_t TPoint TPoint const char x2
Option_t Option_t TPoint TPoint const char x1
char name[80]
Definition TGX11.cxx:142
float xmin
float xmax
const_iterator begin() const
const_iterator end() const
bool contains(const char *name) const
Check if collection contains an argument with a specific name.
Abstract interface for all probability density functions.
Definition RooAbsPdf.h:32
Abstract base class for objects that represent a real value and implements functionality common to al...
Definition RooAbsReal.h:63
bool matchArgs(const RooArgSet &allDeps, RooArgSet &analDeps, const RooArgProxy &a, const Proxies &... proxies) const
Definition RooAbsReal.h:425
RooAbsArg * absArg() const
Return pointer to contained argument.
Definition RooArgProxy.h:46
RooArgSet is a container object that can hold multiple RooAbsArg objects.
Definition RooArgSet.h:24
Container class to hold unbinned data.
Definition RooDataSet.h:32
Class RooKeysPdf implements a one-dimensional kernel estimation p.d.f which model the distribution of...
Definition RooKeysPdf.h:25
double _binWidth
Definition RooKeysPdf.h:89
double _sumWgt
Definition RooKeysPdf.h:75
static constexpr int _nPoints
Definition RooKeysPdf.h:77
double * _dataWgts
Definition RooKeysPdf.h:73
RooKeysPdf()
coverity[UNINIT_CTOR]
double _lookupTable[_nPoints+1]
Definition RooKeysPdf.h:78
double maxVal(Int_t code) const override
Return maximum value for set of observables identified by code assigned in getMaxVal.
bool _mirrorRight
Definition RooKeysPdf.h:83
double _rho
Definition RooKeysPdf.h:90
double _hi
Definition RooKeysPdf.h:89
Char_t _varName[128]
Definition RooKeysPdf.h:88
double g(double x, double sigma) const
Int_t _nEvents
Definition RooKeysPdf.h:71
void LoadDataSet(RooDataSet &data)
Int_t getAnalyticalIntegral(RooArgSet &allVars, RooArgSet &analVars, const char *rangeName=nullptr) const override
Interface function getAnalyticalIntergral advertises the analytical integrals that are supported.
RooRealProxy _x
Definition RooKeysPdf.h:63
double * _weights
Definition RooKeysPdf.h:74
double analyticalIntegral(Int_t code, const char *rangeName=nullptr) const override
Implements the actual analytical integral(s) advertised by getAnalyticalIntegral.
bool _mirrorLeft
Definition RooKeysPdf.h:82
double evaluate() const override
Evaluate this PDF / function / constant. Needs to be overridden by all derived classes.
~RooKeysPdf() override
double _lo
Definition RooKeysPdf.h:89
Mirror
Boundary correction obtained by reflecting the data across the lower and/or upper edge of the observa...
Definition RooKeysPdf.h:31
double * _dataPts
Definition RooKeysPdf.h:72
bool _asymRight
Definition RooKeysPdf.h:85
static const double _nSigma
!
Definition RooKeysPdf.h:69
bool _asymLeft
Definition RooKeysPdf.h:84
Int_t getMaxVal(const RooArgSet &vars) const override
Advertise capability to determine maximum value of function for given set of observables.
Variable that can be changed from the outside.
Definition RooRealVar.h:37
double max(const char *rname=nullptr) const
Query upper limit of range. This requires the payload to be RooAbsRealLValue or derived.
double min(const char *rname=nullptr) const
Query lower limit of range. This requires the payload to be RooAbsRealLValue or derived.
Double_t y[n]
Definition legend1.C:17
Double_t x[n]
Definition legend1.C:17
constexpr Double_t Pi()
Definition TMath.h:40
static uint64_t sum(uint64_t i)
Definition Factory.cxx:2335