Skip to content

Commit 3dcd9c7

Browse files
committed
[RF] RooFFTConvPdf: cache norm. val to avoid bookkeeping during scan
`RooFFTConvPdf::scanPdf()` repeatedly evaluates the component pdfs with `pdf.getVal(normSet)` while filling the FFT input buffers. For large scans this incurs unnecessary overhead from `RooAbsPdf::getValV()`'s normalization-set tracking and cache management, even though the normalization set is fixed for a given FFTCacheElem. This change caches the normalization integrals for both component pdf clones (normVal1, normVal2) when the FFTCacheElem is constructed. During scanning, the pdfs are then evaluated without passing a normalization set and are normalized manually using the cached integral values. To exactly reproduce `RooAbsPdf::getValV()` behaviour (including NaN-packing semantics), the `RooAbsPdf::normalizeWithNaNPacking(rawVal, normVal)` helper function was moved to an internal helper namespace. This reduces evaluation overhead in FFT convolutions while preserving bitwise-equivalent normalization behaviour.
1 parent 110fa7f commit 3dcd9c7

14 files changed

Lines changed: 78 additions & 60 deletions

File tree

roofit/histfactory/CMakeLists.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@ ROOT_STANDARD_LIBRARY_PACKAGE(HistFactory
5353
DICTIONARY_OPTIONS
5454
"-writeEmptyRootPCM"
5555
LIBRARIES
56-
RooBatchCompute
5756
${HISTFACTORY_XML_LIBRARIES}
5857
DEPENDENCIES
5958
RooFit

roofit/multiprocess/test/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# @author Patrick Bos, NL eScience Center, 2019-2022
22

33
add_library(RooFit_multiprocess_testing_utils INTERFACE)
4-
target_link_libraries(RooFit_multiprocess_testing_utils INTERFACE RooFitCore RooBatchCompute)
4+
target_link_libraries(RooFit_multiprocess_testing_utils INTERFACE RooFitCore)
55
target_include_directories(RooFit_multiprocess_testing_utils INTERFACE ${RooFitMultiProcess_INCLUDE_DIR})
66

77
ROOT_ADD_GTEST(test_RooFit_MultiProcess_Job test_Job.cxx LIBRARIES RooFitMultiProcess Core)

roofit/roofit/CMakeLists.txt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,6 @@ ROOT_STANDARD_LIBRARY_PACKAGE(RooFit
150150
"-writeEmptyRootPCM"
151151
LINKDEF
152152
LinkDef1.h
153-
LIBRARIES
154-
RooBatchCompute
155153
DEPENDENCIES
156154
Core
157155
RooFitCore

roofit/roofitcore/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -453,7 +453,6 @@ ROOT_STANDARD_LIBRARY_PACKAGE(RooFitCore
453453
DICTIONARY_OPTIONS
454454
"-writeEmptyRootPCM"
455455
LIBRARIES
456-
RooBatchCompute
457456
${EXTRA_LIBRARIES}
458457
DEPENDENCIES
459458
Core
@@ -464,6 +463,7 @@ ROOT_STANDARD_LIBRARY_PACKAGE(RooFitCore
464463
RIO
465464
MathCore
466465
Foam
466+
RooBatchCompute
467467
${EXTRA_DEPENDENCIES}
468468
LINKDEF
469469
inc/LinkDef.h

roofit/roofitcore/inc/RooAbsPdf.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -296,8 +296,6 @@ class RooAbsPdf : public RooAbsReal {
296296
return RooFit::getUniqueId(normSet).value() == _normSetId;
297297
}
298298

299-
double normalizeWithNaNPacking(double rawVal, double normVal) const;
300-
301299
RooPlot *plotOn(RooPlot *frame, PlotOpt o) const override;
302300

303301
friend class RooMCStudy ;

roofit/roofitcore/inc/RooFFTConvPdf.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ class RooFFTConvPdf : public RooAbsCachedPdf {
7979

8080
void calcParams() ;
8181

82-
std::vector<double> scanPdf(RooRealVar& obs, RooAbsPdf& pdf, const RooDataHist& hist, const RooArgSet& slicePos, Int_t& N, Int_t& N2, Int_t& zeroBin, double shift) const ;
82+
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 ;
8383

8484
class FFTCacheElem : public PdfCacheElem {
8585
public:
@@ -94,6 +94,9 @@ class RooFFTConvPdf : public RooAbsCachedPdf {
9494
std::unique_ptr<RooAbsPdf> pdf1Clone;
9595
std::unique_ptr<RooAbsPdf> pdf2Clone;
9696

97+
double normVal1 = 0.0;
98+
double normVal2 = 0.0;
99+
97100
std::unique_ptr<RooAbsBinning> histBinning;
98101
std::unique_ptr<RooAbsBinning> scanBinning;
99102
};

roofit/roofitcore/inc/RooFit/Detail/RooNormalizedPdf.h

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,7 @@ class RooNormalizedPdf : public RooAbsPdf {
8282
// still need it to support printing of the object.
8383
return getValV(nullptr);
8484
}
85-
double getValV(const RooArgSet * /*normSet*/) const override
86-
{
87-
return normalizeWithNaNPacking(_pdf->getVal(), _normIntegral->getVal());
88-
};
85+
double getValV(const RooArgSet * normSet) const override;
8986

9087
private:
9188
RooTemplateProxy<RooAbsPdf> _pdf;

roofit/roofitcore/res/RooFitImplHelpers.h

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,14 @@
1111
#ifndef RooFit_RooFitImplHelpers_h
1212
#define RooFit_RooFitImplHelpers_h
1313

14-
#include <RooMsgService.h>
1514
#include <RooAbsArg.h>
15+
#include <RooAbsPdf.h>
1616
#include <RooAbsReal.h>
17+
#include <RooMsgService.h>
18+
19+
#include <RooNaNPacker.h>
20+
21+
#include <TMath.h>
1722

1823
#include <sstream>
1924
#include <string>
@@ -104,6 +109,33 @@ void replaceAll(std::string &inOut, std::string_view what, std::string_view with
104109

105110
std::string makeSliceCutString(RooArgSet const &sliceDataSet);
106111

112+
// Inlined because this is called inside RooAbsPdf::getValV(), and therefore
113+
// performance critical.
114+
inline double normalizeWithNaNPacking(RooAbsPdf const &pdf, double rawVal, double normVal)
115+
{
116+
117+
if (normVal < 0. || (normVal == 0. && rawVal != 0)) {
118+
// Unreasonable normalisations. A zero integral can be tolerated if the function vanishes, though.
119+
const std::string msg = "p.d.f normalization integral is zero or negative: " + std::to_string(normVal);
120+
pdf.logEvalError(msg.c_str());
121+
return RooNaNPacker::packFloatIntoNaN(-normVal + (rawVal < 0. ? -rawVal : 0.));
122+
}
123+
124+
if (rawVal < 0.) {
125+
std::stringstream ss;
126+
ss << "p.d.f value is less than zero (" << rawVal << "), trying to recover";
127+
pdf.logEvalError(ss.str().c_str());
128+
return RooNaNPacker::packFloatIntoNaN(-rawVal);
129+
}
130+
131+
if (TMath::IsNaN(rawVal)) {
132+
pdf.logEvalError("p.d.f value is Not-a-Number");
133+
return rawVal;
134+
}
135+
136+
return (rawVal == 0. && normVal == 0.) ? 0. : rawVal / normVal;
137+
}
138+
107139
} // namespace RooFit::Detail
108140

109141
double toDouble(const char *s);

roofit/roofitcore/src/RooAbsPdf.cxx

Lines changed: 1 addition & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -286,34 +286,6 @@ RooAbsPdf::~RooAbsPdf()
286286
}
287287

288288

289-
double RooAbsPdf::normalizeWithNaNPacking(double rawVal, double normVal) const {
290-
291-
if (normVal < 0. || (normVal == 0. && rawVal != 0)) {
292-
//Unreasonable normalisations. A zero integral can be tolerated if the function vanishes, though.
293-
const std::string msg = "p.d.f normalization integral is zero or negative: " + std::to_string(normVal);
294-
logEvalError(msg.c_str());
295-
clearValueAndShapeDirty();
296-
return RooNaNPacker::packFloatIntoNaN(-normVal + (rawVal < 0. ? -rawVal : 0.));
297-
}
298-
299-
if (rawVal < 0.) {
300-
std::stringstream ss;
301-
ss << "p.d.f value is less than zero (" << rawVal << "), trying to recover";
302-
logEvalError(ss.str().c_str());
303-
clearValueAndShapeDirty();
304-
return RooNaNPacker::packFloatIntoNaN(-rawVal);
305-
}
306-
307-
if (TMath::IsNaN(rawVal)) {
308-
logEvalError("p.d.f value is Not-a-Number");
309-
clearValueAndShapeDirty();
310-
return rawVal;
311-
}
312-
313-
return (rawVal == 0. && normVal == 0.) ? 0. : rawVal / normVal;
314-
}
315-
316-
317289
////////////////////////////////////////////////////////////////////////////////
318290
/// Return current value, normalized by integrating over
319291
/// the observables in `nset`. If `nset` is 0, the unnormalized value
@@ -354,7 +326,7 @@ double RooAbsPdf::getValV(const RooArgSet* nset) const
354326
// Evaluate denominator
355327
const double normVal = _norm->getVal();
356328

357-
_value = normalizeWithNaNPacking(rawVal, normVal);
329+
_value = RooFit::Detail::normalizeWithNaNPacking(*this, rawVal, normVal);
358330

359331
clearValueAndShapeDirty();
360332
}

roofit/roofitcore/src/RooFFTConvPdf.cxx

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@
123123
#include "RooGlobalFunc.h"
124124
#include "RooConstVar.h"
125125
#include "RooUniformBinning.h"
126+
#include "RooFitImplHelpers.h"
126127

127128
#include "TClass.h"
128129
#include "TComplex.h"
@@ -399,6 +400,14 @@ RooFFTConvPdf::FFTCacheElem::FFTCacheElem(const RooFFTConvPdf& self, const RooAr
399400
pdf2Clone.reset(clonePdf2) ;
400401
}
401402

403+
// Cache normalization integral values, since we know they don't change for
404+
// the given normalization set in this cache object. When using this cache,
405+
// we evaluate the pdfs without normalization set and then do the
406+
// normalization manually using these cached values. This has less overhead
407+
// compared to letting RooAbsPdf::getVal(normSet) figure out if the normSet
408+
// has changed and get the caching right.
409+
normVal1 = pdf1Clone->getNorm(hist()->get());
410+
normVal2 = pdf2Clone->getNorm(hist()->get());
402411

403412
// Attach cloned pdf to all original parameters of self
404413
RooArgSet convObsSet{*convObs};
@@ -579,8 +588,9 @@ void RooFFTConvPdf::fillCacheSlice(FFTCacheElem& aux, const RooArgSet& slicePos)
579588

580589
RooRealVar* histX = static_cast<RooRealVar*>(cacheHist.get()->find(_x.arg().GetName())) ;
581590
if (_bufStrat==Extend) histX->setBinning(*aux.scanBinning) ;
582-
std::vector<double> input1 = scanPdf(const_cast<RooRealVar &>(static_cast<RooRealVar const&>(_x.arg())),*aux.pdf1Clone,cacheHist,slicePos,N,N2,binShift1,_shift1) ;
583-
std::vector<double> input2 = scanPdf(const_cast<RooRealVar &>(static_cast<RooRealVar const&>(_x.arg())),*aux.pdf2Clone,cacheHist,slicePos,N,N2,binShift2,_shift2) ;
591+
RooRealVar &xVar = const_cast<RooRealVar &>(static_cast<RooRealVar const&>(_x.arg()));
592+
std::vector<double> input1 = scanPdf(xVar,*aux.pdf1Clone,aux.normVal1,cacheHist,slicePos,N,N2,binShift1,_shift1) ;
593+
std::vector<double> input2 = scanPdf(xVar,*aux.pdf2Clone,aux.normVal2,cacheHist,slicePos,N,N2,binShift2,_shift2) ;
584594
if (_bufStrat==Extend) histX->setBinning(*aux.histBinning) ;
585595

586596
#ifndef ROOFIT_MATH_FFTW3
@@ -662,8 +672,9 @@ void RooFFTConvPdf::fillCacheSlice(FFTCacheElem& aux, const RooArgSet& slicePos)
662672
/// The return value is an array of doubles of length N2 with the sampled values. The caller takes ownership
663673
/// of the array
664674

665-
std::vector<double> RooFFTConvPdf::scanPdf(RooRealVar& obs, RooAbsPdf& pdf, const RooDataHist& hist, const RooArgSet& slicePos,
666-
Int_t& N, Int_t& N2, Int_t& zeroBin, double shift) const
675+
std::vector<double> RooFFTConvPdf::scanPdf(RooRealVar &obs, RooAbsPdf &pdf, double normVal, const RooDataHist &hist,
676+
const RooArgSet &slicePos, Int_t &N, Int_t &N2, Int_t &zeroBin,
677+
double shift) const
667678
{
668679

669680
RooRealVar* histX = static_cast<RooRealVar*>(hist.get()->find(obs.GetName())) ;
@@ -698,6 +709,12 @@ std::vector<double> RooFFTConvPdf::scanPdf(RooRealVar& obs, RooAbsPdf& pdf, con
698709
while(zeroBin>=N2) zeroBin-= N2 ;
699710
while(zeroBin<0) zeroBin+= N2 ;
700711

712+
// To mimic exactly the normalization code in RooAbsPdf::getValV()
713+
auto getPdfVal = [&]() {
714+
double rawVal = pdf.getVal();
715+
return RooFit::Detail::normalizeWithNaNPacking(pdf, rawVal, normVal);
716+
};
717+
701718
// First scan hist into temp array
702719
std::vector<double> tmp(N2);
703720
Int_t k(0) ;
@@ -707,7 +724,7 @@ std::vector<double> RooFFTConvPdf::scanPdf(RooRealVar& obs, RooAbsPdf& pdf, con
707724
// Sample entire extended range (N2 samples)
708725
for (k=0 ; k<N2 ; k++) {
709726
histX->setBin(k) ;
710-
tmp[k] = pdf.getVal(hist.get()) ;
727+
tmp[k] = getPdfVal();
711728
}
712729
break ;
713730

@@ -716,16 +733,16 @@ std::vector<double> RooFFTConvPdf::scanPdf(RooRealVar& obs, RooAbsPdf& pdf, con
716733
// bins with p.d.f. value at respective boundary
717734
{
718735
histX->setBin(0) ;
719-
double val = pdf.getVal(hist.get()) ;
736+
double val = getPdfVal();
720737
for (k=0 ; k<Nbuf ; k++) {
721738
tmp[k] = val ;
722739
}
723740
for (k=0 ; k<N ; k++) {
724741
histX->setBin(k) ;
725-
tmp[k+Nbuf] = pdf.getVal(hist.get()) ;
742+
tmp[k+Nbuf] = getPdfVal();
726743
}
727744
histX->setBin(N-1) ;
728-
val = pdf.getVal(hist.get()) ;
745+
val = getPdfVal();
729746
for (k=0 ; k<Nbuf ; k++) {
730747
tmp[N+Nbuf+k] = val ;
731748
}
@@ -737,13 +754,13 @@ std::vector<double> RooFFTConvPdf::scanPdf(RooRealVar& obs, RooAbsPdf& pdf, con
737754
// bins with mirror image of sampled range
738755
for (k=0 ; k<N ; k++) {
739756
histX->setBin(k) ;
740-
tmp[k+Nbuf] = pdf.getVal(hist.get()) ;
757+
tmp[k+Nbuf] = getPdfVal();
741758
}
742759
for (k=1 ; k<=Nbuf ; k++) {
743760
histX->setBin(k) ;
744-
tmp[Nbuf-k] = pdf.getVal(hist.get()) ;
761+
tmp[Nbuf-k] = getPdfVal();
745762
histX->setBin(N-k) ;
746-
tmp[Nbuf+N+k-1] = pdf.getVal(hist.get()) ;
763+
tmp[Nbuf+N+k-1] = getPdfVal();
747764
}
748765
break ;
749766
}

0 commit comments

Comments
 (0)