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 <limits>
59#include <algorithm>
60#include <cmath>
61#include <iostream>
62#include "TMath.h"
63#include "snprintf.h"
64#include "RooKeysPdf.h"
65#include "RooRealVar.h"
66#include "RooRandom.h"
67#include "RooDataSet.h"
68
69#include "TError.h"
70
71
72const double RooKeysPdf::_nSigma = std::sqrt(-2. *
73 std::log(std::numeric_limits<double>::epsilon()));
74
75////////////////////////////////////////////////////////////////////////////////
76/// coverity[UNINIT_CTOR]
77
81
82////////////////////////////////////////////////////////////////////////////////
83/// Construct a kernel estimation pdf of the observable `xpdf` from its
84/// distribution in `data`.
85///
86/// \param[in] name Name of the pdf.
87/// \param[in] title Title of the pdf, used for plotting.
88/// \param[in] xpdf Observable the pdf is defined in. Its range sets the
89/// boundaries used for the mirror correction and for the
90/// internal binned lookup table.
91/// \param[in] data Dataset whose distribution of `xpdf` is modelled. The width
92/// of each kernel is adapted to the local event density.
93/// \param[in] mirror Optional boundary correction, see the Mirror enum.
94/// \param[in] rho Overall scale factor for the kernel width (default 1);
95/// larger values give a smoother estimate.
96
97RooKeysPdf::RooKeysPdf(const char *name, const char *title, RooAbsReal &xpdf, RooDataSet &data, Mirror mirror, double rho)
99{
100}
101
102////////////////////////////////////////////////////////////////////////////////
103/// As above, but reading the input values from a dataset variable `xdata` that
104/// can be different from the observable `xpdf` the pdf depends on.
105///
106/// \param[in] name Name of the pdf.
107/// \param[in] title Title of the pdf, used for plotting.
108/// \param[in] xpdf Observable the pdf is defined in.
109/// \param[in] xdata Variable in `data` whose distribution is modelled. Its
110/// range sets the boundaries used for the mirror correction
111/// and for the internal binned lookup table.
112/// \param[in] data Dataset holding the values of `xdata` to model.
113/// \param[in] mirror Optional boundary correction, see the Mirror enum.
114/// \param[in] rho Overall scale factor for the kernel width (default 1);
115/// larger values give a smoother estimate.
116
118 Mirror mirror, double rho)
119 : RooAbsPdf(name, title),
120 _x("x", "Observable", this, xpdf),
121 _mirrorLeft(mirror == MirrorLeft || mirror == MirrorBoth || mirror == MirrorLeftAsymRight),
122 _mirrorRight(mirror == MirrorRight || mirror == MirrorBoth || mirror == MirrorAsymLeftRight),
123 _asymLeft(mirror == MirrorAsymLeft || mirror == MirrorAsymLeftRight || mirror == MirrorAsymBoth),
124 _asymRight(mirror == MirrorAsymRight || mirror == MirrorLeftAsymRight || mirror == MirrorAsymBoth),
125 _lo(xdata.getMin()),
126 _hi(xdata.getMax()),
127 _binWidth((_hi - _lo) / (_nPoints - 1)),
128 _rho(rho)
129{
130 snprintf(_varName, 128,"%s", xdata.GetName());
131
132 // form the lookup table
134}
135
136////////////////////////////////////////////////////////////////////////////////
137
139 : RooAbsPdf(other, name),
140 _x("x", this, other._x),
141 _nEvents(other._nEvents),
142 _mirrorLeft(other._mirrorLeft),
143 _mirrorRight(other._mirrorRight),
144 _asymLeft(other._asymLeft),
145 _asymRight(other._asymRight),
146 _lo(other._lo),
147 _hi(other._hi),
148 _binWidth(other._binWidth),
149 _rho(other._rho)
150{
151 // cache stuff about x
152 snprintf(_varName, 128, "%s", other._varName );
153
154 // copy over data and weights... not necessary, commented out for speed
155// _dataPts = new double[_nEvents];
156// _weights = new double[_nEvents];
157// for (Int_t i= 0; i<_nEvents; i++) {
158// _dataPts[i]= other._dataPts[i];
159// _weights[i]= other._weights[i];
160// }
161
162 // copy over the lookup table
163 for (Int_t i= 0; i<_nPoints+1; i++)
164 _lookupTable[i]= other._lookupTable[i];
165
166}
167
168////////////////////////////////////////////////////////////////////////////////
169
171 delete[] _dataPts;
172 delete[] _dataWgts;
173 delete[] _weights;
174
175}
176
177////////////////////////////////////////////////////////////////////////////////
178/// small helper structure
179
180namespace {
181 struct Data {
182 double x;
183 double w;
184 };
185 // helper to order two Data structures
186 struct cmp {
187 inline bool operator()(const struct Data& a, const struct Data& b) const
188 { return a.x < b.x; }
189 };
190}
192 delete[] _dataPts;
193 delete[] _dataWgts;
194 delete[] _weights;
195
196 std::vector<Data> tmp;
197 tmp.reserve((1 + _mirrorLeft + _mirrorRight) * data.numEntries());
198 double x0 = 0.;
199 double x1 = 0.;
200 double x2 = 0.;
201 _sumWgt = 0.;
202 // read the data set into tmp and accumulate some statistics
203 RooRealVar& real = static_cast<RooRealVar&>(data.get()->operator[](_varName));
204 for (Int_t i = 0; i < data.numEntries(); ++i) {
205 data.get(i);
206 const double x = real.getVal();
207 const double w = data.weight();
208 x0 += w;
209 x1 += w * x;
210 x2 += w * x * x;
212
213 Data p;
214 p.x = x, p.w = w;
215 tmp.push_back(p);
216 if (_mirrorLeft) {
217 p.x = 2. * _lo - x;
218 tmp.push_back(p);
219 }
220 if (_mirrorRight) {
221 p.x = 2. * _hi - x;
222 tmp.push_back(p);
223 }
224 }
225 // sort the entire data set so that values of x are increasing
226 std::sort(tmp.begin(), tmp.end(), cmp());
227
228 // copy the sorted data set to its final destination
229 _nEvents = tmp.size();
230 _dataPts = new double[_nEvents];
231 _dataWgts = new double[_nEvents];
232 for (unsigned i = 0; i < tmp.size(); ++i) {
233 _dataPts[i] = tmp[i].x;
234 _dataWgts[i] = tmp[i].w;
235 }
236 {
237 // free tmp
238 std::vector<Data> tmp2;
239 tmp2.swap(tmp);
240 }
241
242 double meanv=x1/x0;
243 double sigmav=std::sqrt(x2/x0-meanv*meanv);
244 double h=std::pow(double(4)/double(3),0.2)*std::pow(_sumWgt,-0.2)*_rho;
245 double hmin=h*sigmav*std::sqrt(2.)/10;
246 // Dividing by 2*sqrt(3) = sqrt(12) turns a width into the standard deviation
247 // of a uniform distribution of that width. Per the original author, this goes
248 // back to inputs that were finely binned histograms rather than unbinned data:
249 // entries spread uniformly over a bin get aggregated into a single sample with
250 // no variance, so the bin width was taken as the spread of that sample.
251 //
252 // Beware that no bin width enters the expression below, so that rationale does
253 // not map onto the code as it stands: what remains is an extra factor of
254 // sqrt(12) with respect to hep-ex/0011057, kept for backwards compatibility.
255 // The same factor appears in RooNDKeysPdf::calculateBandWidth().
256 double norm=h*std::sqrt(sigmav * _sumWgt)/(2.0*std::sqrt(3.0));
257
258 _weights=new double[_nEvents];
259 for(Int_t j=0;j<_nEvents;++j) {
260 _weights[j] = norm / std::sqrt(_dataWgts[j] * g(_dataPts[j],h*sigmav));
261 if (_weights[j]<hmin) _weights[j]=hmin;
262 }
263
264 // The idea below is that beyond nSigma sigma, the value of the exponential
265 // in the Gaussian is well below the machine precision of a double, so it
266 // does not contribute any more. That way, we can limit how many bins of the
267 // binned approximation in _lookupTable we have to touch when filling it.
268 for (Int_t i=0;i<_nPoints+1;++i) _lookupTable[i] = 0.;
269 for(Int_t j=0;j<_nEvents;++j) {
270 const double xlo = std::min(_hi,
271 std::max(_lo, _dataPts[j] - _nSigma * _weights[j]));
272 const double xhi = std::max(_lo,
273 std::min(_hi, _dataPts[j] + _nSigma * _weights[j]));
274 if (xlo >= xhi) continue;
275 const double chi2incr = _binWidth / _weights[j] / std::sqrt(2.);
276 const double weightratio = _dataWgts[j] / _weights[j];
277 const Int_t binlo = static_cast<Int_t>(std::floor((xlo - _lo) / _binWidth));
278 const Int_t binhi = static_cast<Int_t>(_nPoints - std::floor((_hi - xhi) / _binWidth));
279 const double x = (double(_nPoints - binlo) * _lo +
280 double(binlo) * _hi) / double(_nPoints);
281 double chi = (x - _dataPts[j]) / _weights[j] / std::sqrt(2.);
282 for (Int_t k = binlo; k <= binhi; ++k, chi += chi2incr) {
283 _lookupTable[k] += weightratio * std::exp(- chi * chi);
284 }
285 }
286 if (_asymLeft) {
287 for(Int_t j=0;j<_nEvents;++j) {
288 const double xlo = std::min(_hi,
289 std::max(_lo, 2. * _lo - _dataPts[j] + _nSigma * _weights[j]));
290 const double xhi = std::max(_lo,
291 std::min(_hi, 2. * _lo - _dataPts[j] - _nSigma * _weights[j]));
292 if (xlo >= xhi) continue;
293 const double chi2incr = _binWidth / _weights[j] / std::sqrt(2.);
294 const double weightratio = _dataWgts[j] / _weights[j];
295 const Int_t binlo = static_cast<Int_t>(std::floor((xlo - _lo) / _binWidth));
296 const Int_t binhi = static_cast<Int_t>(_nPoints - std::floor((_hi - xhi) / _binWidth));
297 const double x = (double(_nPoints - binlo) * _lo +
298 double(binlo) * _hi) / double(_nPoints);
299 double chi = (x - (2. * _lo - _dataPts[j])) / _weights[j] / std::sqrt(2.);
300 for (Int_t k = binlo; k <= binhi; ++k, chi += chi2incr) {
301 _lookupTable[k] -= weightratio * std::exp(- chi * chi);
302 }
303 }
304 }
305 if (_asymRight) {
306 for(Int_t j=0;j<_nEvents;++j) {
307 const double xlo = std::min(_hi,
308 std::max(_lo, 2. * _hi - _dataPts[j] + _nSigma * _weights[j]));
309 const double xhi = std::max(_lo,
310 std::min(_hi, 2. * _hi - _dataPts[j] - _nSigma * _weights[j]));
311 if (xlo >= xhi) continue;
312 const double chi2incr = _binWidth / _weights[j] / std::sqrt(2.);
313 const double weightratio = _dataWgts[j] / _weights[j];
314 const Int_t binlo = static_cast<Int_t>(std::floor((xlo - _lo) / _binWidth));
315 const Int_t binhi = static_cast<Int_t>(_nPoints - std::floor((_hi - xhi) / _binWidth));
316 const double x = (double(_nPoints - binlo) * _lo +
317 double(binlo) * _hi) / double(_nPoints);
318 double chi = (x - (2. * _hi - _dataPts[j])) / _weights[j] / std::sqrt(2.);
319 for (Int_t k = binlo; k <= binhi; ++k, chi += chi2incr) {
320 _lookupTable[k] -= weightratio * std::exp(- chi * chi);
321 }
322 }
323 }
324 static const double sqrt2pi(std::sqrt(2*TMath::Pi()));
325 for (Int_t i=0;i<_nPoints+1;++i)
327}
328
329////////////////////////////////////////////////////////////////////////////////
330
331double RooKeysPdf::evaluate() const {
332 Int_t i = (Int_t)floor((double(_x)-_lo)/_binWidth);
333 if (i<0) {
334// cerr << "got point below lower bound:"
335// << double(_x) << " < " << _lo
336// << " -- performing linear extrapolation..." << std::endl;
337 i=0;
338 }
339 if (i>_nPoints-1) {
340// cerr << "got point above upper bound:"
341// << double(_x) << " > " << _hi
342// << " -- performing linear extrapolation..." << std::endl;
343 i=_nPoints-1;
344 }
345 double dx = (double(_x)-(_lo+i*_binWidth))/_binWidth;
346
347 // for now do simple linear interpolation.
348 // one day replace by splines...
349 double ret = (_lookupTable[i]+dx*(_lookupTable[i+1]-_lookupTable[i]));
350 if (ret<0) ret=0 ;
351 return ret ;
352}
353
355 RooArgSet& allVars, RooArgSet& analVars, const char* /* rangeName */) const
356{
357 if (matchArgs(allVars, analVars, _x)) return 1;
358 return 0;
359}
360
361double RooKeysPdf::analyticalIntegral(Int_t code, const char* rangeName) const
362{
363 R__ASSERT(1 == code);
364 // this code is based on _lookupTable and uses linear interpolation, just as
365 // evaluate(); integration is done using the trapez rule
366 const double xmin = std::max(_lo, _x.min(rangeName));
367 const double xmax = std::min(_hi, _x.max(rangeName));
368 const Int_t imin = (Int_t)floor((xmin - _lo) / _binWidth);
369 const Int_t imax = std::min((Int_t)floor((xmax - _lo) / _binWidth),
370 _nPoints - 1);
371 double sum = 0.;
372 // sum up complete bins in middle
373 if (imin + 1 < imax)
375 for (Int_t i = imin + 2; i < imax; ++i)
376 sum += 2. * _lookupTable[i];
377 sum *= _binWidth * 0.5;
378 // treat incomplete bins
379 const double dxmin = (xmin - (_lo + imin * _binWidth)) / _binWidth;
380 const double dxmax = (xmax - (_lo + imax * _binWidth)) / _binWidth;
381 if (imin < imax) {
382 // first bin
383 sum += _binWidth * (1. - dxmin) * 0.5 * (_lookupTable[imin + 1] +
386 // last bin
387 sum += _binWidth * dxmax * 0.5 * (_lookupTable[imax] +
390 } else if (imin == imax) {
391 // first bin == last bin
392 sum += _binWidth * (dxmax - dxmin) * 0.5 * (
397 }
398 return sum;
399}
400
402{
403 if (vars.contains(*_x.absArg())) return 1;
404 return 0;
405}
406
407double RooKeysPdf::maxVal(Int_t code) const
408{
409 R__ASSERT(1 == code);
410 double max = -std::numeric_limits<double>::max();
411 for (Int_t i = 0; i <= _nPoints; ++i)
412 if (max < _lookupTable[i]) max = _lookupTable[i];
413 return max;
414}
415
416////////////////////////////////////////////////////////////////////////////////
417
418double RooKeysPdf::g(double x,double sigmav) const {
419 double y=0;
420 // since data is sorted, we can be a little faster because we know which data
421 // points contribute
422 double* it = std::lower_bound(_dataPts, _dataPts + _nEvents,
423 x - _nSigma * sigmav);
424 if (it >= (_dataPts + _nEvents)) return 0.;
425 double* iend = std::upper_bound(it, _dataPts + _nEvents,
426 x + _nSigma * sigmav);
427 for ( ; it < iend; ++it) {
428 const double r = (x - *it) / sigmav;
429 y += std::exp(-0.5 * r * r);
430 }
431
432 static const double sqrt2pi(std::sqrt(2*TMath::Pi()));
433 return y/(sigmav*sqrt2pi);
434}
#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:125
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:148
float xmin
float xmax
#define snprintf
Definition civetweb.c:1579
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