Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
RooFFTConvPdf.cxx
Go to the documentation of this file.
1
2 /*****************************************************************************
3 * Project: RooFit *
4 * *
5 * Copyright (c) 2000-2005, Regents of the University of California *
6 * and Stanford University. All rights reserved. *
7 * *
8 * Redistribution and use in source and binary forms, *
9 * with or without modification, are permitted according to the terms *
10 * listed in LICENSE (http://roofit.sourceforge.net/license.txt) *
11 *****************************************************************************/
12
13//////////////////////////////////////////////////////////////////////////////
14/// \class RooFFTConvPdf
15/// \ingroup Roofitcore
16///
17/// This class implements a generic one-dimensional numeric convolution of two PDFs,
18/// and can convolve any two RooAbsPdfs. The class exploits the convolution theorem
19/// \f[
20/// f(x) * g(x) \rightarrow F(k_i) \cdot G(k_i)
21/// \f]
22/// to calculate the convolution by calculating a forward FFT of both input PDFs,
23/// multiplying the complex coefficients and performing the inverse FFT to get the
24/// result in the input space. The FFT is implemented internally so that no external
25/// FFT library (e.g. FFTW3) is required.
26///
27/// Note that the performance in terms of speed and stability of RooFFTConvPdf is
28/// vastly superior to that of RooNumConvPdf.
29///
30/// An important feature of FFT convolutions is that the observable is assumed to be
31/// cyclical. This is correct for cyclical observables such as angles,
32/// but does not hold in general. For non-cyclical variables, wrap-around artifacts may be
33/// encountered, *e.g.* if the PDF is zero at xMin and non-zero at xMax. A rising tail may appear at xMin.
34/// This is inevitable when using FFTs. A distribution with 3 bins therefore looks like:
35/// ```
36/// ... 0 1 2 0 1 2 0 1 2 ...
37/// ```
38///
39/// Therefore, if bins 0 and 2 are not equal, the FFT sees a cyclical function with a step at the 2|0 boundary, which causes
40/// artifacts in Fourier space.
41///
42/// The spillover or discontinuity can be reduced or eliminated by
43/// introducing a buffer zone in the FFT calculation. If this feature is activated (on by default),
44/// the sampling array for the FFT calculation is extended in both directions,
45/// and padded with the lowest/highest bin.
46/// Example:
47/// ```
48/// original: -5 -4 -3 -2 -1 0 +1 +2 +3 +4 +5
49/// add buffer zones: U U -5 -4 -3 -2 -1 0 +1 +2 +3 +4 +5 O O
50/// rotate: 0 +1 +2 +3 +4 +5 O O U U -5 -4 -3 -2 -1
51/// ```
52/// The buffer bins are stripped away when the FFT output values
53/// are transferred back to the p.d.f cache. The default buffer size is 10% of the
54/// observable domain size, and can be changed with the `setBufferFraction()` member function.
55///
56/// The RooFFTConvPdf uses caching inherited from a RooAbsCachedPdf. If it is
57/// evaluated for a particular value of x, the FFT and convolution is calculated
58/// for all bins in the observable space for the given choice of parameters,
59/// which are also stored in the cache. Subsequent evaluations for different values of the convolution observable and
60/// identical parameters will be retrieved from the cache. If one or more
61/// of the parameters change, the cache will be updated, *i.e.*, a new FFT runs.
62///
63/// The sampling density of the FFT is controlled by the binning of the
64/// the convolution observable, which can be changed using RooRealVar::setBins(N).
65/// For good results, N should be large (>=1000). Additional interpolation
66/// between the bins may improve the result if coarse binnings are chosen. These can be
67/// activated in the constructor or by calling `setInterpolationOrder()`.
68/// For N >> 1000, interpolation will not substantially improve the accuracy.
69///
70/// Additionial information on caching can be displayed by monitoring
71/// the message stream with topic "Caching" at the INFO level, *i.e.*
72/// by calling `RooMsgService::instance().addStream(RooMsgService::INFO,Topic("Caching"))`
73/// to see these message on stdout.
74///
75/// Multi-dimensional convolutions are not supported at the moment.
76///
77
78#include "RooFFTConvPdf.h"
79
80#include "RooAbsReal.h"
81#include "RooMsgService.h"
82#include "RooDataHist.h"
83#include "RooHistPdf.h"
84#include "RooRealVar.h"
85#include "RooGenContext.h"
86#include "RooConvGenContext.h"
87#include "RooBinning.h"
88#include "RooLinearVar.h"
89#include "RooCustomizer.h"
90#include "RooGlobalFunc.h"
91#include "RooConstVar.h"
92#include "RooUniformBinning.h"
93#include "RooFitImplHelpers.h"
94
95#include "TClass.h"
96#include "TMath.h"
97
98#include <cmath>
99#include <complex>
100#include <iostream>
101#include <vector>
102
103namespace {
104
105using FFTComplex = std::complex<double>;
106
107// Iterative in-place radix-2 Cooley-Tukey FFT. The length of `a` must be a power
108// of two. `sign` is -1 for the forward DFT and +1 for the backward DFT (no
109// normalization is applied in either direction, matching the FFTW convention).
110void fftRadix2(std::vector<FFTComplex> &a, int sign)
111{
112 const std::size_t n = a.size();
113 if (n < 2)
114 return;
115
116 // Bit-reversal permutation.
117 for (std::size_t i = 1, j = 0; i < n; ++i) {
118 std::size_t bit = n >> 1;
119 for (; j & bit; bit >>= 1) {
120 j ^= bit;
121 }
122 j ^= bit;
123 if (i < j) {
124 std::swap(a[i], a[j]);
125 }
126 }
127
128 // Twiddle factors of the largest stage: tw[k] = exp(sign * 2*pi*i * k / n)
129 // for k < n/2. A stage of length `len` uses the subset with stride n/len.
130 // Computing each factor directly (instead of advancing a running phase by
131 // repeated multiplication) keeps the round-off error at the epsilon level
132 // independently of n.
133 std::vector<FFTComplex> tw(n >> 1);
134 const double angle = sign * 2.0 * TMath::Pi() / static_cast<double>(n);
135 for (std::size_t k = 0; k < tw.size(); ++k) {
136 tw[k] = std::polar(1.0, angle * static_cast<double>(k));
137 }
138
139 // Butterfly stages.
140 for (std::size_t len = 2; len <= n; len <<= 1) {
141 const std::size_t half = len >> 1;
142 const std::size_t stride = n / len;
143 for (std::size_t i = 0; i < n; i += len) {
144 for (std::size_t k = 0; k < half; ++k) {
145 const FFTComplex u = a[i + k];
146 const FFTComplex v = a[i + k + half] * tw[k * stride];
147 a[i + k] = u + v;
148 a[i + k + half] = u - v;
149 }
150 }
151 }
152}
153
154// Replicates the FFTW r2c -> multiply -> c2r pipeline used by RooFFTConvPdf.
155// The convention is unnormalized (matching FFTW), so the output equals
156// n * (input1 ⊛ input2) where ⊛ denotes circular convolution. The absolute
157// scale does not matter since RooHistPdf renormalizes the cached histogram.
158//
159// If n is a power of two, the circular convolution is computed directly with
160// two transforms of length n. Otherwise, the inputs are zero-padded to the
161// smallest power of two m >= 2n - 1, which yields their linear convolution,
162// and the circular convolution is recovered exactly by folding the result
163// back modulo n. Both real inputs are packed into a single complex sequence
164// z = input1 + i*input2, so that one forward transform yields both spectra
165// via Hermitian symmetry.
166void doFFT(int n, double const *input1, double const *input2, double *output)
167{
168 if (n <= 0)
169 return;
170 const std::size_t N = static_cast<std::size_t>(n);
171
172 std::size_t m = N;
173 if ((N & (N - 1)) != 0) {
174 m = 1;
175 while (m < 2 * N - 1) {
176 m <<= 1;
177 }
178 }
179
180 std::vector<FFTComplex> z(m, FFTComplex(0.0, 0.0));
181 bool nonNegativeInputs = true;
182 for (std::size_t k = 0; k < N; ++k) {
183 nonNegativeInputs &= input1[k] >= 0.0 && input2[k] >= 0.0;
184 z[k] = FFTComplex(input1[k], input2[k]);
185 }
186 fftRadix2(z, -1);
187
188 // Unpack the spectra A = FFT(input1) and B = FFT(input2) from z and
189 // overwrite z with the product A*B. Both spectra are Hermitian
190 // (A[m-k] = conj(A[k])), and so is their product, so only half of the
191 // bins need to be computed. Each iteration reads and writes the pair
192 // (k, m-k), so the update can safely be done in place.
193 for (std::size_t k = 0; k <= m / 2; ++k) {
194 const std::size_t j = (m - k) & (m - 1); // (m - k) mod m
195 const FFTComplex zk = z[k];
196 const FFTComplex zjc = std::conj(z[j]);
197 const FFTComplex ak = 0.5 * (zk + zjc);
198 const FFTComplex bk = FFTComplex(0.0, -0.5) * (zk - zjc);
199 const FFTComplex pk = ak * bk;
200 z[k] = pk;
201 z[j] = std::conj(pk);
202 }
203 fftRadix2(z, +1);
204
205 // The unnormalized round trip yields m times the (linear) convolution.
206 // Rescale to the documented n * (input1 ⊛ input2) convention, and for the
207 // padded case fold the linear convolution (length 2n - 1 <= m) modulo n.
208 const double scale = static_cast<double>(N) / static_cast<double>(m);
209 for (std::size_t k = 0; k < N; ++k) {
210 double val = z[k].real();
211 if (m != N) {
212 val += z[k + N].real();
213 }
214 val *= scale;
215 // The convolution of two non-negative sequences is non-negative, but
216 // round-off can push bins whose true value is ~0 slightly negative,
217 // which RooFit later reports as pdf evaluation errors. Clamp those.
218 if (nonNegativeInputs && val < 0.0) {
219 val = 0.0;
220 }
221 output[k] = val;
222 }
223}
224
225} // namespace
226
227using std::endl, std::string, std::ostream;
228
229
230////////////////////////////////////////////////////////////////////////////////
231/// Constructor for numerical (FFT) convolution of PDFs.
232/// \param[in] name Name of this PDF
233/// \param[in] title Title for plotting this PDF
234/// \param[in] convVar Observable to convolve the PDFs in \attention Use a high number of bins (>= 1000) for good accuracy.
235/// \param[in] pdf1 First PDF to be convolved
236/// \param[in] pdf2 Second PDF to be convolved
237/// \param[in] ipOrder Order for interpolation between bins (since FFT is discrete)
238/// The binning used for the FFT sampling is controlled by the binning named "cache" in the convolution observable `convVar`.
239/// If such a binning is not set, the same number of bins as for `convVar` will be used.
240
241RooFFTConvPdf::RooFFTConvPdf(const char *name, const char *title, RooRealVar &convVar, RooAbsPdf &pdf1, RooAbsPdf &pdf2,
242 Int_t ipOrder)
243 : RooAbsCachedPdf(name, title, ipOrder),
244 _x("!x", "Convolution Variable", this, convVar),
245 _xprime("!xprime", "External Convolution Variable", this, false),
246 _pdf1("!pdf1", "pdf1", this, pdf1, false),
247 _pdf2("!pdf2", "pdf2", this, pdf2, false),
248 _params("!params", "effective parameters", this),
249 _bufFrac(0.1),
250 _bufStrat(Extend),
251 _shift1(0),
252 _shift2((convVar.getMax("cache") + convVar.getMin("cache")) / 2),
253 _cacheObs("!cacheObs", "Cached observables", this, false, false)
254{
255 prepareFFTBinning(convVar);
256
257 calcParams() ;
258
259}
260
261////////////////////////////////////////////////////////////////////////////////
262/// \copydoc RooFFTConvPdf(const char*, const char*, RooRealVar&, RooAbsPdf&, RooAbsPdf&, Int_t)
263/// \param[in] pdfConvVar If the variable used for convolution is a function, itself, pass the function here, and pass
264/// the convolution variable to `convVar`. See also rf210_angularconv.C in the <a
265/// href="https://root.cern/root/html/tutorials/roofit/index.html.">roofit tutorials</a>
266
267RooFFTConvPdf::RooFFTConvPdf(const char *name, const char *title, RooAbsReal &pdfConvVar, RooRealVar &convVar,
268 RooAbsPdf &pdf1, RooAbsPdf &pdf2, Int_t ipOrder)
269 : RooAbsCachedPdf(name, title, ipOrder),
270 _x("!x", "Convolution Variable", this, convVar, false, false),
271 _xprime("!xprime", "External Convolution Variable", this, pdfConvVar),
272 _pdf1("!pdf1", "pdf1", this, pdf1, false),
273 _pdf2("!pdf2", "pdf2", this, pdf2, false),
274 _params("!params", "effective parameters", this),
275 _bufFrac(0.1),
276 _bufStrat(Extend),
277 _shift1(0),
278 _shift2((convVar.getMax("cache") + convVar.getMin("cache")) / 2),
279 _cacheObs("!cacheObs", "Cached observables", this, false, false)
280{
281 prepareFFTBinning(convVar);
282
283 calcParams() ;
284}
285
286
287
288////////////////////////////////////////////////////////////////////////////////
289/// Copy constructor
290
292 RooAbsCachedPdf(other,name),
293 _x("!x",this,other._x),
294 _xprime("!xprime",this,other._xprime),
295 _pdf1("!pdf1",this,other._pdf1),
296 _pdf2("!pdf2",this,other._pdf2),
297 _params("!params",this,other._params),
298 _bufFrac(other._bufFrac),
299 _bufStrat(other._bufStrat),
300 _shift1(other._shift1),
301 _shift2(other._shift2),
302 _cacheObs("!cacheObs",this,other._cacheObs)
303 {
304 }
305
306
307
308////////////////////////////////////////////////////////////////////////////////
309/// Destructor
310
314
315
316////////////////////////////////////////////////////////////////////////////////
317/// Try to improve the binning and inform user if possible.
318/// With a 10% buffer fraction, 930 raw bins yield 1024 FFT bins,
319/// a sweet spot for the speed of FFTW.
320
322 if (!convVar.hasBinning("cache")) {
323 const RooAbsBinning& varBinning = convVar.getBinning();
324 const int optimal = static_cast<Int_t>(1024/(1.+_bufFrac));
325
326 //Can improve precision if binning is uniform
327 if (varBinning.numBins() < optimal && varBinning.isUniform()) {
328 coutI(Caching) << "Changing internal binning of variable '" << convVar.GetName()
329 << "' in FFT '" << fName << "'"
330 << " from " << varBinning.numBins()
331 << " to " << optimal << " to improve the precision of the numerical FFT."
332 << " This can be done manually by setting an additional binning named 'cache'." << std::endl;
333 convVar.setBinning(RooUniformBinning(varBinning.lowBound(), varBinning.highBound(), optimal, "cache"), "cache");
334 } else {
335 coutE(Caching) << "The internal binning of variable " << convVar.GetName()
336 << " is not uniform. The numerical FFT will likely yield wrong results." << std::endl;
337 convVar.setBinning(varBinning, "cache");
338 }
339 }
340}
341
342
343////////////////////////////////////////////////////////////////////////////////
344/// Return base name component for cache components in this case 'PDF1_CONV_PDF2'
345
347{
348 static TString name ;
349 name = _pdf1.arg().GetName() ;
350 name.Append("_CONV_") ;
351 name.Append(_pdf2.arg().GetName()) ;
352 return name.Data() ;
353}
354
355
356
357
358////////////////////////////////////////////////////////////////////////////////
359/// Return specialized cache subclass for FFT calculations
360
362{
363 return new FFTCacheElem(*this,nset) ;
364}
365
366
367
368
369////////////////////////////////////////////////////////////////////////////////
370/// Clone input pdf and attach to dataset
371
373 PdfCacheElem(self,nsetIn)
374{
375 RooAbsPdf* clonePdf1 = static_cast<RooAbsPdf*>(self._pdf1.arg().cloneTree()) ;
376 RooAbsPdf* clonePdf2 = static_cast<RooAbsPdf*>(self._pdf2.arg().cloneTree()) ;
377 clonePdf1->attachDataSet(*hist()) ;
378 clonePdf2->attachDataSet(*hist()) ;
379
380 // Shift observable
381 RooRealVar* convObs = static_cast<RooRealVar*>(hist()->get()->find(self._x.arg().GetName())) ;
382
383 // Install FFT reference range
384 string refName = Form("refrange_fft_%s",self.GetName()) ;
385 convObs->setRange(refName.c_str(),convObs->getMin(),convObs->getMax()) ;
386
387 if (self._shift1!=0) {
388 RooLinearVar* shiftObs1 = new RooLinearVar(Form("%s_shifted_FFTBuffer1",convObs->GetName()),"shiftObs1",
389 *convObs,RooFit::RooConst(1),RooFit::RooConst(-1*self._shift1)) ;
390
391 RooArgSet clonedBranches1 ;
392 RooCustomizer cust(*clonePdf1,"fft") ;
393 cust.replaceArg(*convObs,*shiftObs1) ;
394
395 pdf1Clone.reset(static_cast<RooAbsPdf*>(cust.build()));
396
397 pdf1Clone->addOwnedComponents(*shiftObs1) ;
398 pdf1Clone->addOwnedComponents(*clonePdf1) ;
399
400 } else {
401 pdf1Clone.reset(clonePdf1) ;
402 }
403
404 if (self._shift2!=0) {
405 RooLinearVar* shiftObs2 = new RooLinearVar(Form("%s_shifted_FFTBuffer2",convObs->GetName()),"shiftObs2",
406 *convObs,RooFit::RooConst(1),RooFit::RooConst(-1*self._shift2)) ;
407
408 RooArgSet clonedBranches2 ;
409 RooCustomizer cust(*clonePdf2,"fft") ;
410 cust.replaceArg(*convObs,*shiftObs2) ;
411
412 pdf1Clone->addOwnedComponents(*shiftObs2) ;
413 pdf1Clone->addOwnedComponents(*clonePdf2) ;
414
415 pdf2Clone.reset(static_cast<RooAbsPdf*>(cust.build()));
416
417 } else {
418 pdf2Clone.reset(clonePdf2) ;
419 }
420
421 // Cache normalization integral values, since we know they don't change for
422 // the given normalization set in this cache object. When using this cache,
423 // we evaluate the pdfs without normalization set and then do the
424 // normalization manually using these cached values. This has less overhead
425 // compared to letting RooAbsPdf::getVal(normSet) figure out if the normSet
426 // has changed and get the caching right.
427 normVal1 = pdf1Clone->getNorm(hist()->get());
428 normVal2 = pdf2Clone->getNorm(hist()->get());
429
430 // Attach cloned pdf to all original parameters of self
431 RooArgSet convObsSet{*convObs};
432 RooArgSet fftParams;
433 self.getParameters(&convObsSet, fftParams) ;
434
435 // Remove all cache histogram from fftParams as these
436 // observable need to remain attached to the histogram
437 fftParams.remove(*hist()->get(),true,true) ;
438
439 pdf1Clone->recursiveRedirectServers(fftParams) ;
440 pdf2Clone->recursiveRedirectServers(fftParams) ;
441 pdf1Clone->fixAddCoefRange(refName.c_str(), true) ;
442 pdf2Clone->fixAddCoefRange(refName.c_str(), true) ;
443
444 // Ensure that coefficients for Add PDFs are only interpreted with respect to the convolution observable
445 RooArgSet convSet(self._x.arg());
446 pdf1Clone->fixAddCoefNormalization(convSet, true);
447 pdf2Clone->fixAddCoefNormalization(convSet, true);
448
449 // Save copy of original histX binning and make alternate binning
450 // for extended range scanning
451
452 const Int_t N = convObs->numBins();
453 if (N < 900) {
454 oocoutW(&self, Eval) << "The FFT convolution '" << self.GetName() << "' will run with " << N
455 << " bins. A decent accuracy for difficult convolutions is typically only reached with n >= 1000. Suggest to increase the number"
456 " of bins of the observable '" << convObs->GetName() << "'." << std::endl;
457 }
458 Int_t Nbuf = static_cast<Int_t>((N*self.bufferFraction())/2 + 0.5) ;
459 double obw = (convObs->getMax() - convObs->getMin())/N ;
460 Int_t N2 = N+2*Nbuf ;
461
462 scanBinning = std::make_unique<RooUniformBinning>(convObs->getMin()-Nbuf*obw,convObs->getMax()+Nbuf*obw,N2);
463 histBinning.reset(convObs->getBinning().clone());
464
465 // Deactivate dirty state propagation on datahist observables
466 // and set all nodes on both pdfs to operMode AlwaysDirty
467 hist()->setDirtyProp(false) ;
468 convObs->setOperMode(ADirty,true) ;
469}
470
471
472////////////////////////////////////////////////////////////////////////////////
473/// Suffix for cache histogram (added in addition to suffix for cache name)
474
476{
477 return TString(Form("_BufFrac%3.1f_BufStrat%d",_bufFrac,_bufStrat)) ;
478}
479
480
481
482////////////////////////////////////////////////////////////////////////////////
483/// Returns all RooAbsArg objects contained in the cache element
484
486{
487 RooArgList ret(PdfCacheElem::containedArgs(a)) ;
488
489 ret.add(*pdf1Clone) ;
490 ret.add(*pdf2Clone) ;
491 if (pdf1Clone->ownedComponents()) {
492 ret.add(*pdf1Clone->ownedComponents()) ;
493 }
494 if (pdf2Clone->ownedComponents()) {
495 ret.add(*pdf2Clone->ownedComponents()) ;
496 }
497
498 return ret ;
499}
500
501
502
503////////////////////////////////////////////////////////////////////////////////
504/// Fill the contents of the cache the FFT convolution output
505
507{
508 RooDataHist& cacheHist = *cache.hist() ;
509
510 (static_cast<FFTCacheElem&>(cache)).pdf1Clone->setOperMode(ADirty,true) ;
511 (static_cast<FFTCacheElem&>(cache)).pdf2Clone->setOperMode(ADirty,true) ;
512
513 // Determine if there other observables than the convolution observable in the cache
514 RooArgSet otherObs ;
515 RooArgSet(*cacheHist.get()).snapshot(otherObs) ;
516
517 RooAbsArg* histArg = otherObs.find(_x.arg().GetName()) ;
518 if (histArg) {
519 otherObs.remove(*histArg,true,true) ;
520 }
521
522 //cout << "RooFFTConvPdf::fillCacheObject() otherObs = " << otherObs << std::endl ;
523
524 // Handle trivial scenario -- no other observables
525 if (otherObs.empty()) {
526 fillCacheSlice(static_cast<FFTCacheElem&>(cache),RooArgSet()) ;
527 return ;
528 }
529
530 // Handle cases where there are other cache slices
531 // Iterator over available slice positions and fill each
532
533 // Determine number of bins for each slice position observable
534 Int_t n = otherObs.size() ;
535 std::vector<int> binCur(n+1);
536 std::vector<int> binMax(n+1);
537 Int_t curObs = 0 ;
538
539 std::vector<RooAbsLValue*> obsLV(n);
540 Int_t i(0) ;
541 for (auto* lvarg : dynamic_range_cast<RooAbsLValue*>(otherObs)) {
542 obsLV[i] = lvarg ;
543 binCur[i] = 0 ;
544 // coverity[FORWARD_NULL]
545 binMax[i] = lvarg->numBins(binningName())-1 ;
546 i++ ;
547 }
548
549 bool loop(true) ;
550 while(loop) {
551 // Set current slice position
552 for (Int_t j=0 ; j<n ; j++) { obsLV[j]->setBin(binCur[j],binningName()) ; }
553
554// std::cout << "filling slice: bin of obsLV[0] = " << obsLV[0]->getBin() << std::endl ;
555
556 // Fill current slice
557 fillCacheSlice(static_cast<FFTCacheElem&>(cache),otherObs) ;
558
559 // Determine which iterator to increment
560 while(binCur[curObs]==binMax[curObs]) {
561
562 // Reset current iterator and consider next iterator ;
563 binCur[curObs]=0 ;
564 curObs++ ;
565
566 // master termination condition
567 if (curObs==n) {
568 loop=false ;
569 break ;
570 }
571 }
572
573 // Increment current iterator
574 binCur[curObs]++ ;
575 curObs=0 ;
576
577 }
578
579}
580
581
582////////////////////////////////////////////////////////////////////////////////
583/// Fill a slice of cachePdf with the output of the FFT convolution calculation
584
585void RooFFTConvPdf::fillCacheSlice(FFTCacheElem& aux, const RooArgSet& slicePos) const
586{
587 // Extract histogram that is the basis of the RooHistPdf
588 RooDataHist& cacheHist = *aux.hist() ;
589
590 // Sample array of input points from both pdfs
591 // Note that returned arrays have optional buffers zones below and above range ends
592 // to reduce cyclical effects and have been cyclically rotated so that bin containing
593 // zero value is at position zero. Example:
594 //
595 // original: -5 -4 -3 -2 -1 0 +1 +2 +3 +4 +5
596 // add buffer zones: U U -5 -4 -3 -2 -1 0 +1 +2 +3 +4 +5 O O
597 // rotate: 0 +1 +2 +3 +4 +5 O O U U -5 -4 -3 -2 -1
598 //
599 //
600
601 Int_t N;
602 Int_t N2;
603 Int_t binShift1;
604 Int_t binShift2;
605
606 RooRealVar* histX = static_cast<RooRealVar*>(cacheHist.get()->find(_x.arg().GetName())) ;
607 if (_bufStrat==Extend) histX->setBinning(*aux.scanBinning) ;
608 RooRealVar &xVar = const_cast<RooRealVar &>(static_cast<RooRealVar const&>(_x.arg()));
609 std::vector<double> input1 = scanPdf(xVar,*aux.pdf1Clone,aux.normVal1,cacheHist,slicePos,N,N2,binShift1,_shift1) ;
610 std::vector<double> input2 = scanPdf(xVar,*aux.pdf2Clone,aux.normVal2,cacheHist,slicePos,N,N2,binShift2,_shift2) ;
611 if (_bufStrat==Extend) histX->setBinning(*aux.histBinning) ;
612
613 std::vector<double> output(N2);
614 doFFT(N2, input1.data(), input2.data(), output.data());
615
616 Int_t totalShift = binShift1 + (N2-N)/2 ;
617
618 // Store FFT result in cache
619
620 std::unique_ptr<TIterator> iter{const_cast<RooDataHist&>(cacheHist).sliceIterator(const_cast<RooAbsReal&>(_x.arg()),slicePos)};
621 for (Int_t i =0 ; i<N ; i++) {
622
623 // Cyclically shift array back so that bin containing zero is back in zeroBin
624 Int_t j = i + totalShift ;
625 while (j<0) j+= N2 ;
626 while (j>=N2) j-= N2 ;
627
628 iter->Next() ;
629 const std::size_t binIdx = cacheHist.getIndex(*cacheHist.get(), /*fast=*/true);
630 cacheHist.set(binIdx, output[j], -1.);
631 }
632}
633
634
635////////////////////////////////////////////////////////////////////////////////
636/// Scan the values of 'pdf' in observable 'obs' using the bin values stored in 'hist' at slice position 'slicePos'
637/// N is filled with the number of bins defined in hist, N2 is filled with N plus the number of buffer bins
638/// The return value is an array of doubles of length N2 with the sampled values. The caller takes ownership
639/// of the array
640
641std::vector<double> RooFFTConvPdf::scanPdf(RooRealVar &obs, RooAbsPdf &pdf, double normVal, const RooDataHist &hist,
642 const RooArgSet &slicePos, Int_t &N, Int_t &N2, Int_t &zeroBin,
643 double shift) const
644{
645
646 RooRealVar* histX = static_cast<RooRealVar*>(hist.get()->find(obs.GetName())) ;
647
648 // Calculate number of buffer bins on each size to avoid cyclical flow
649 N = histX->numBins(binningName()) ;
650 Int_t Nbuf = static_cast<Int_t>((N*bufferFraction())/2 + 0.5) ;
651 N2 = N+2*Nbuf ;
652
653
654 // Allocate array of sampling size plus optional buffer zones
655 std::vector<double> array(N2);
656
657 // Set position of non-convolution observable to that of the cache slice that were are processing now
658 hist.get(slicePos) ;
659
660 // Find bin ID that contains zero value
661 zeroBin = 0 ;
662 if (histX->getMax()>=0 && histX->getMin()<=0) {
663 zeroBin = histX->getBinning().binNumber(0) ;
664 } else if (histX->getMin()>0) {
665 double bw = (histX->getMax() - histX->getMin())/N2 ;
666 zeroBin = Int_t(-histX->getMin()/bw) ;
667 } else {
668 double bw = (histX->getMax() - histX->getMin())/N2 ;
669 zeroBin = Int_t(-1*histX->getMax()/bw) ;
670 }
671
672 Int_t binShift = Int_t((N2* shift) / (histX->getMax()-histX->getMin())) ;
673
674 zeroBin += binShift ;
675 while(zeroBin>=N2) zeroBin-= N2 ;
676 while(zeroBin<0) zeroBin+= N2 ;
677
678 // To mimic exactly the normalization code in RooAbsPdf::getValV()
679 auto getPdfVal = [&]() {
680 double rawVal = pdf.getVal();
681 return RooFit::Detail::normalizeWithNaNPacking(pdf, rawVal, normVal);
682 };
683
684 // First scan hist into temp array
685 std::vector<double> tmp(N2);
686 Int_t k(0) ;
687 switch(_bufStrat) {
688
689 case Extend:
690 // Sample entire extended range (N2 samples)
691 for (k=0 ; k<N2 ; k++) {
692 histX->setBin(k) ;
693 tmp[k] = getPdfVal();
694 }
695 break ;
696
697 case Flat:
698 // Sample original range (N samples) and fill lower and upper buffer
699 // bins with p.d.f. value at respective boundary
700 {
701 histX->setBin(0) ;
702 double val = getPdfVal();
703 for (k=0 ; k<Nbuf ; k++) {
704 tmp[k] = val ;
705 }
706 for (k=0 ; k<N ; k++) {
707 histX->setBin(k) ;
708 tmp[k+Nbuf] = getPdfVal();
709 }
710 histX->setBin(N-1) ;
711 val = getPdfVal();
712 for (k=0 ; k<Nbuf ; k++) {
713 tmp[N+Nbuf+k] = val ;
714 }
715 }
716 break ;
717
718 case Mirror:
719 // Sample original range (N samples) and fill lower and upper buffer
720 // bins with mirror image of sampled range
721 for (k=0 ; k<N ; k++) {
722 histX->setBin(k) ;
723 tmp[k+Nbuf] = getPdfVal();
724 }
725 for (k=1 ; k<=Nbuf ; k++) {
726 histX->setBin(k) ;
727 tmp[Nbuf-k] = getPdfVal();
728 histX->setBin(N-k) ;
729 tmp[Nbuf+N+k-1] = getPdfVal();
730 }
731 break ;
732 }
733
734 // Scan function and store values in array
735 for (Int_t i=0 ; i<N2 ; i++) {
736 // Cyclically shift writing location by zero bin position
737 Int_t j = i - (zeroBin) ;
738 if (j<0) j+= N2 ;
739 if (j>=N2) j-= N2 ;
740 array[i] = tmp[j] ;
741 }
742
743 return array ;
744}
745
746
747
748////////////////////////////////////////////////////////////////////////////////
749/// Return the observables to be cached given the normalization set nset.
750///
751/// If the cache observable is in nset then this is
752/// - the convolution observable plus
753/// - any member of nset that is either a RooCategory,
754/// - or was previously specified through setCacheObservables().
755///
756/// In case the cache observable is *not* in nset, then it is
757/// - the convolution observable plus
758/// - all member of nset that are observables of this p.d.f.
759///
760
762{
763 // Get complete list of observables
764 auto obs1 = new RooArgSet{};
765 RooArgSet obs2;
766 _pdf1.arg().getObservables(&nset, *obs1) ;
767 _pdf2.arg().getObservables(&nset, obs2) ;
768 obs1->add(obs2, true) ;
769
770 // Check if convolution observable is in nset
771 if (nset.contains(_x.arg())) {
772
773 // Now strip out all non-category observables
774 RooArgSet killList ;
775 for(auto const& arg : *obs1) {
776 if (arg->IsA()->InheritsFrom(RooAbsReal::Class()) && !_cacheObs.find(arg->GetName())) {
777 killList.add(*arg) ;
778 }
779 }
780 obs1->remove(killList) ;
781
782 // And add back the convolution observables
783 obs1->add(_x.arg(),true) ;
784
785 obs1->add(_cacheObs) ;
786
787 } else {
788
789 // If cacheObs was filled, cache only observables in there
790 if (!_cacheObs.empty()) {
791 RooArgSet killList ;
792 for(auto const& arg : *obs1) {
793 if (arg->IsA()->InheritsFrom(RooAbsReal::Class()) && !_cacheObs.find(arg->GetName())) {
794 killList.add(*arg) ;
795 }
796 }
797 obs1->remove(killList) ;
798 }
799
800
801 // Make sure convolution observable is always in there
802 obs1->add(_x.arg(),true) ;
803
804 }
805
806 return RooFit::OwningPtr<RooArgSet>{obs1};
807}
808
809
810
811////////////////////////////////////////////////////////////////////////////////
812/// Return the parameters on which the cache depends given normalization
813/// set nset. For this p.d.f these are the parameters of the input p.d.f.
814/// but never the convolution variable, in case it is not part of nset.
815
817{
818 std::unique_ptr<RooArgSet> vars{getVariables()};
819 vars->remove(*std::unique_ptr<RooArgSet>{actualObservables(nset)});
820
821 return RooFit::makeOwningPtr(std::move(vars));
822}
823
824
825
826////////////////////////////////////////////////////////////////////////////////
827/// Return p.d.f. observable (which can be a function) to substitute given
828/// p.d.f. observable. Substitutes x by xprime if xprime is set.
829
831{
832 if (_xprime.absArg() && string(histObservable.GetName())==_x.absArg()->GetName()) {
833 return (*_xprime.absArg()) ;
834 }
835 return histObservable ;
836}
837
838
839
840////////////////////////////////////////////////////////////////////////////////
841/// Create appropriate generator context for this convolution. If both input p.d.f.s support
842/// internal generation, if it is safe to use them and if no observables other than the convolution
843/// observable are requested for generation, use the specialized convolution generator context
844/// which implements a smearing strategy in the convolution observable. If not return the
845/// regular accept/reject generator context
846
848 const RooArgSet* auxProto, bool verbose) const
849{
850 RooArgSet vars2(vars) ;
851 vars2.remove(_x.arg(),true,true) ;
852 Int_t numAddDep = vars2.size() ;
853
854 RooArgSet dummy ;
855 bool pdfCanDir = ((static_cast<RooAbsPdf const &>(_pdf1.arg())).getGenerator(_x.arg(), dummy) != 0 &&
856 (static_cast<RooAbsPdf const &>(_pdf1.arg())).isDirectGenSafe(_x.arg()));
857 bool resCanDir = ((static_cast<RooAbsPdf const &>(_pdf2.arg())).getGenerator(_x.arg(), dummy) != 0 &&
858 (static_cast<RooAbsPdf const &>(_pdf2.arg())).isDirectGenSafe(_x.arg()));
859
860 if (pdfCanDir) {
861 cxcoutI(Generation) << "RooFFTConvPdf::genContext() input p.d.f " << _pdf1.arg().GetName()
862 << " has internal generator that is safe to use in current context" << std::endl ;
863 }
864 if (resCanDir) {
865 cxcoutI(Generation) << "RooFFTConvPdf::genContext() input p.d.f. " << _pdf2.arg().GetName()
866 << " has internal generator that is safe to use in current context" << std::endl ;
867 }
868 if (numAddDep>0) {
869 cxcoutI(Generation) << "RooFFTConvPdf::genContext() generation requested for observables other than the convolution observable " << _x.arg().GetName() << std::endl ;
870 }
871
872
873 if (numAddDep>0 || !pdfCanDir || !resCanDir) {
874 // Any resolution model with more dependents than the convolution variable
875 // or pdf or resmodel do not support direct generation
876 cxcoutI(Generation) << "RooFFTConvPdf::genContext() selecting accept/reject generator context because one or both of the input "
877 << "p.d.f.s cannot use internal generator and/or "
878 << "observables other than the convolution variable are requested for generation" << std::endl ;
879 return new RooGenContext(*this,vars,prototype,auxProto,verbose) ;
880 }
881
882 // Any other resolution model: use specialized generator context
883 cxcoutI(Generation) << "RooFFTConvPdf::genContext() selecting specialized convolution generator context as both input "
884 << "p.d.fs are safe for internal generator and only "
885 << "the convolution observables is requested for generation" << std::endl ;
886 return new RooConvGenContext(*this,vars,prototype,auxProto,verbose) ;
887}
888
889
890
891////////////////////////////////////////////////////////////////////////////////
892/// Change the size of the buffer on either side of the observable range to `frac` times the
893/// size of the range of the convolution observable.
894
896{
897 if (frac<0) {
898 coutE(InputArguments) << "RooFFTConvPdf::setBufferFraction(" << GetName() << ") fraction should be greater than or equal to zero" << std::endl ;
899 return ;
900 }
901 _bufFrac = frac ;
902
903 // Sterilize the cache as certain partial results depend on buffer fraction
905}
906
907
908////////////////////////////////////////////////////////////////////////////////
909/// Change strategy to fill the overflow buffer on either side of the convolution observable range.
910///
911/// - `Extend` means is that the input p.d.f convolution observable range is widened to include the buffer range
912/// - `Flat` means that the buffer is filled with the p.d.f. value at the boundary of the observable range
913/// - `Mirror` means that the buffer is filled with a mirror image of the p.d.f. around the convolution observable boundary
914///
915/// The default strategy is extend. If one of the input p.d.f.s is a RooAddPdf, it is configured so that the interpretation
916/// range of the fraction coefficients is kept at the nominal convolutions observable range (instead of interpreting coefficients
917/// in the widened range including the buffer).
918
923
924
925
926////////////////////////////////////////////////////////////////////////////////
927/// Customized printing of arguments of a RooNumConvPdf to more intuitively reflect the contents of the
928/// product operator construction
929
930void RooFFTConvPdf::printMetaArgs(ostream& os) const
931{
932 os << _pdf1.arg().GetName() << "(" << _x.arg().GetName() << ") (*) " << _pdf2.arg().GetName() << "(" << _x.arg().GetName() << ") " ;
933}
934
935
936
937////////////////////////////////////////////////////////////////////////////////
938/// (Re)calculate effective parameters of this p.d.f.
939
941{
942 RooArgSet argSet{_x.arg()};
943 RooArgSet params1;
944 RooArgSet params2;
945 _pdf1.arg().getParameters(&argSet, params1);
946 _pdf2.arg().getParameters(&argSet, params2);
948 _params.add(params1) ;
949 _params.add(params2,true) ;
950}
#define a(i)
Definition RSha256.hxx:99
#define coutI(a)
#define cxcoutI(a)
#define oocoutW(o, a)
#define coutE(a)
int Int_t
Signed integer 4 bytes (int)
Definition RtypesCore.h:60
#define N
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 Int_t Int_t Window_t TString Int_t GCValues_t GetPrimarySelectionOwner GetDisplay GetScreen GetColormap GetNativeEvent const char const char dpyName wid window const char font_name cursor keysym reg const char only_if_exist regb h Point_t winding char text const char depth char const char Int_t count const char ColorStruct_t color const char Pixmap_t Pixmap_t PictureAttributes_t attr const char char ret_data h unsigned char height h Atom_t Int_t ULong_t ULong_t unsigned char prop_list Atom_t Atom_t Atom_t Time_t UChar_t len
Option_t Option_t TPoint TPoint angle
char name[80]
Definition TGX11.cxx:142
char * Form(const char *fmt,...)
Formats a string in a circular formatting buffer.
Definition TString.cxx:2571
Common abstract base class for objects that represent a value and a "shape" in RooFit.
Definition RooAbsArg.h:77
void setOperMode(OperMode mode, bool recurseADirty=true)
Set the operation mode of this node.
RooFit::OwningPtr< RooArgSet > getParameters(const RooAbsData *data, bool stripDisconnected=true) const
Create a list of leaf nodes in the arg tree starting with ourself as top node that don't match any of...
RooFit::OwningPtr< RooArgSet > getObservables(const RooArgSet &set, bool valueOnly=true) const
Given a set of possible observables, return the observables that this PDF depends on.
RooFit::OwningPtr< RooArgSet > getVariables(bool stripDisconnected=true) const
Return RooArgSet with all variables (tree leaf nodes of expression tree)
virtual RooAbsArg * cloneTree(const char *newname=nullptr) const
Clone tree expression of objects.
void attachDataSet(const RooAbsData &set)
Replace server nodes with names matching the dataset variable names with those data set variables,...
Abstract base class for RooRealVar binning definitions.
int binNumber(double x) const
Returns the bin number corresponding to the value x.
Int_t numBins() const
Return number of bins.
virtual bool isUniform() const
virtual double highBound() const =0
virtual double lowBound() const =0
virtual RooAbsBinning * clone(const char *name=nullptr) const =0
Abstract base class for p.d.f.s that need or want to cache their evaluate() output in a RooHistPdf de...
virtual const char * binningName() const
RooObjCacheManager _cacheMgr
! The cache manager
bool contains(const char *name) const
Check if collection contains an argument with a specific name.
virtual bool remove(const RooAbsArg &var, bool silent=false, bool matchByNameOnly=false)
Remove the specified argument from our list.
virtual bool add(const RooAbsArg &var, bool silent=false)
Add the specified argument to list.
Storage_t::size_type size() const
RooAbsArg * find(const char *name) const
Find object with given name in list.
void setDirtyProp(bool flag)
Control propagation of dirty flags from observables in dataset.
Abstract base class for generator contexts of RooAbsPdf objects.
Abstract interface for all probability density functions.
Definition RooAbsPdf.h:32
Int_t numBins(const char *rangeName=nullptr) const override
virtual double getMax(const char *name=nullptr) const
Get maximum of currently defined range.
void setBin(Int_t ibin, const char *rangeName=nullptr) override
Set value to center of bin 'ibin' of binning 'rangeName' (or of default binning if no range is specif...
virtual double getMin(const char *name=nullptr) const
Get minimum of currently defined range.
Abstract base class for objects that represent a real value and implements functionality common to al...
Definition RooAbsReal.h:63
double getVal(const RooArgSet *normalisationSet=nullptr) const
Evaluate object.
Definition RooAbsReal.h:107
static TClass * Class()
RooArgList is a container object that can hold multiple RooAbsArg objects.
Definition RooArgList.h:22
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
RooArgSet * snapshot(bool deepCopy=true) const
Use RooAbsCollection::snapshot(), but return as RooArgSet.
Definition RooArgSet.h:159
void removeAll() override
Remove all argument inset using remove(const RooAbsArg&).
bool add(const RooAbsArg &var, bool valueServer, bool shapeServer, bool silent)
Overloaded RooCollection_t::add() method insert object into set and registers object as server to own...
RooCustomizer is a factory class to produce clones of a prototype composite PDF object with the same ...
void replaceArg(const RooAbsArg &orig, const RooAbsArg &subst)
Replace any occurrence of arg 'orig' with arg 'subst'.
RooAbsArg * build(const char *masterCatState, bool verbose=false)
Build a clone of the prototype executing all registered 'replace' rules and 'split' rules for the mas...
Container class to hold N-dimensional binned data.
Definition RooDataHist.h:40
Int_t getIndex(const RooAbsCollection &coord, bool fast=false) const
Calculate bin number of the given coordinates.
void set(std::size_t binNumber, double weight, double wgtErr)
Set bin content of bin that was last loaded with get(std::size_t).
const RooArgSet * get() const override
Get bin centre of current bin.
Definition RooDataHist.h:82
Container class to hold unbinned data.
Definition RooDataSet.h:32
RooArgList containedArgs(Action) override
Returns all RooAbsArg objects contained in the cache element.
std::unique_ptr< RooAbsPdf > pdf2Clone
FFTCacheElem(const RooFFTConvPdf &self, const RooArgSet *nset)
Clone input pdf and attach to dataset.
std::unique_ptr< RooAbsBinning > histBinning
std::unique_ptr< RooAbsBinning > scanBinning
std::unique_ptr< RooAbsPdf > pdf1Clone
PDF for the numerical (FFT) convolution of two PDFs.
friend class RooConvGenContext
RooSetProxy _params
Effective parameters of this p.d.f.
BufStrat _bufStrat
void calcParams()
(Re)calculate effective parameters of this p.d.f.
void setBufferFraction(double frac)
Change the size of the buffer on either side of the observable range to frac times the size of the ra...
double bufferFraction() const
Return value of buffer fraction applied in FFT calculation array beyond either end of the observable ...
TString histNameSuffix() const override
Suffix for cache histogram (added in addition to suffix for cache name)
void prepareFFTBinning(RooRealVar &convVar) const
Try to improve the binning and inform user if possible.
void fillCacheSlice(FFTCacheElem &cache, const RooArgSet &slicePosition) const
Fill a slice of cachePdf with the output of the FFT convolution calculation.
RooRealProxy _xprime
Input function representing value of convolution observable.
RooRealProxy _pdf1
First input p.d.f.
RooRealProxy _x
Convolution observable.
const char * inputBaseName() const override
Return base name component for cache components in this case 'PDF1_CONV_PDF2'.
RooAbsArg & pdfObservable(RooAbsArg &histObservable) const override
Return p.d.f.
std::vector< double > scanPdf(RooRealVar &obs, RooAbsPdf &pdf, double normVal, const RooDataHist &hist, const RooArgSet &slicePos, Int_t &N, Int_t &N2, Int_t &zeroBin, double shift) const
Scan the values of 'pdf' in observable 'obs' using the bin values stored in 'hist' at slice position ...
~RooFFTConvPdf() override
Destructor.
RooFit::OwningPtr< RooArgSet > actualParameters(const RooArgSet &nset) const override
Return the parameters on which the cache depends given normalization set nset.
RooRealProxy _pdf2
Second input p.d.f.
void printMetaArgs(std::ostream &os) const override
Customized printing of arguments of a RooNumConvPdf to more intuitively reflect the contents of the p...
void setBufferStrategy(BufStrat bs)
Change strategy to fill the overflow buffer on either side of the convolution observable range.
PdfCacheElem * createCache(const RooArgSet *nset) const override
Return specialized cache subclass for FFT calculations.
RooAbsGenContext * genContext(const RooArgSet &vars, const RooDataSet *prototype=nullptr, const RooArgSet *auxProto=nullptr, bool verbose=false) const override
Create appropriate generator context for this convolution.
RooFit::OwningPtr< RooArgSet > actualObservables(const RooArgSet &nset) const override
Return the observables to be cached given the normalization set nset.
void fillCacheObject(PdfCacheElem &cache) const override
Fill the contents of the cache the FFT convolution output.
friend class FFTCacheElem
RooSetProxy _cacheObs
Non-convolution observables that are also cached.
Implements a universal generator context for all RooAbsPdf classes that do not have or need a special...
RooLinearVar is the most general form of a derived real-valued object that can be used by RooRealInte...
void sterilize() override
Clear the cache payload but retain slot mapping w.r.t to normalization and integration sets.
Variable that can be changed from the outside.
Definition RooRealVar.h:37
bool hasBinning(const char *name) const override
Returns true if variable has a binning named 'name'.
void setRange(const char *name, double min, double max, bool shared=true)
Set a fit or plotting range.
void setBinning(const RooAbsBinning &binning, const char *name=nullptr, bool shared=true)
Add given binning under name 'name' with this variable.
const RooAbsBinning & getBinning(const char *name=nullptr, bool verbose=true, bool createOnTheFly=false, bool shared=true) const override
Return binning definition with name.
const T & arg() const
Return reference to object held in proxy.
Implementation of RooAbsBinning that provides a uniform binning in 'n' bins between the range end poi...
const char * GetName() const override
Returns name of object.
Definition TNamed.h:49
TString fName
Definition TNamed.h:32
Basic string class.
Definition TString.h:137
RooConstVar & RooConst(double val)
const Int_t n
Definition legend1.C:16
double normalizeWithNaNPacking(RooAbsPdf const &pdf, double rawVal, double normVal)
T * OwningPtr
An alias for raw pointers for indicating that the return type of a RooFit function is an owning point...
Definition Config.h:35
OwningPtr< T > makeOwningPtr(std::unique_ptr< T > &&ptr)
Internal helper to turn a std::unique_ptr<T> into an OwningPtr.
Definition Config.h:40
constexpr Double_t Pi()
Definition TMath.h:40
TMarker m
Definition textangle.C:8