Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RF] Re-implement multi-range fits #11455

Merged
merged 9 commits into from
Oct 3, 2022
2 changes: 1 addition & 1 deletion roofit/roofitcore/inc/RooAddModel.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ class RooAddModel : public RooResolutionModel {


mutable RooObjCacheManager _projCacheMgr ; ///<! Manager of cache with coefficient projections and transformations
AddCacheElem* getProjCache(const RooArgSet* nset, const RooArgSet* iset=nullptr, const char* rangeName=nullptr) const ;
AddCacheElem* getProjCache(const RooArgSet* nset, const RooArgSet* iset=nullptr) const ;
void updateCoefficients(AddCacheElem& cache, const RooArgSet* nset) const ;

typedef RooArgList* pRooArgList ;
Expand Down
2 changes: 1 addition & 1 deletion roofit/roofitcore/inc/RooAddPdf.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ class RooAddPdf : public RooAbsPdf {


mutable RooObjCacheManager _projCacheMgr ; //! Manager of cache with coefficient projections and transformations
AddCacheElem* getProjCache(const RooArgSet* nset, const RooArgSet* iset=nullptr, const char* rangeName=nullptr) const ;
AddCacheElem* getProjCache(const RooArgSet* nset, const RooArgSet* iset=nullptr) const ;
void updateCoefficients(AddCacheElem& cache, const RooArgSet* nset, bool syncCoefValues=true) const ;


Expand Down
2 changes: 1 addition & 1 deletion roofit/roofitcore/inc/RooBinSamplingPdf.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ class RooBinSamplingPdf : public RooAbsPdf {
}

/// Forwards to the PDF's implementation.
bool selfNormalized() const override { return _pdf->selfNormalized(); }
bool selfNormalized() const override { return true; }

/// Forwards to the PDF's implementation.
RooAbsReal* createIntegral(const RooArgSet& iset,
Expand Down
5 changes: 2 additions & 3 deletions roofit/roofitcore/inc/RooHelpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -132,10 +132,9 @@ class ChangeOperModeRAII {

std::pair<double, double> getRangeOrBinningInterval(RooAbsArg const* arg, const char* rangeName);

bool checkIfRangesOverlap(RooAbsPdf const& pdf,
bool checkIfRangesOverlap(RooArgSet const& observables,
RooAbsData const& data,
std::vector<std::string> const& rangeNames,
bool splitRange);
std::vector<std::string> const& rangeNames);

std::string getColonSeparatedNameString(RooArgSet const& argSet);
RooArgSet selectFromArgSet(RooArgSet const&, std::string const& names);
Expand Down
3 changes: 1 addition & 2 deletions roofit/roofitcore/res/RooNLLVarNew.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class RooNLLVarNew : public RooAbsReal {

RooNLLVarNew(){};
RooNLLVarNew(const char *name, const char *title, RooAbsPdf &pdf, RooArgSet const &observables, bool isExtended,
std::string const &rangeName, bool doOffset);
bool doOffset);
RooNLLVarNew(const RooNLLVarNew &other, const char *name = nullptr);
TObject *clone(const char *newname) const override { return new RooNLLVarNew(*this, newname); }

Expand Down Expand Up @@ -68,7 +68,6 @@ class RooNLLVarNew : public RooAbsReal {
std::string _prefix;
RooTemplateProxy<RooAbsReal> _weightVar;
RooTemplateProxy<RooAbsReal> _weightSquaredVar;
std::unique_ptr<RooTemplateProxy<RooAbsReal>> _fractionInRange;
mutable std::vector<double> _binw; ///<!
mutable std::vector<double> _logProbasBuffer; ///<!
mutable ROOT::Math::KahanSum<double> _offset = 0.0; ///<! Offset as KahanSum to avoid loss of precision
Expand Down
15 changes: 11 additions & 4 deletions roofit/roofitcore/src/BatchModeHelpers.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,10 @@ std::unique_ptr<RooAbsArg> createSimultaneousNLL(RooSimultaneous const &simPdf,
auto const &catName = catItem.first;
if (RooAbsPdf *pdf = simPdf.getPdf(catName.c_str())) {
auto name = std::string("nll_") + pdf->GetName();
auto nll = std::make_unique<RooNLLVarNew>(name.c_str(), name.c_str(), *pdf, observables, isExtended, rangeName,
doOffset);
if (!rangeName.empty()) {
pdf->setNormRange(rangeName.c_str());
}
auto nll = std::make_unique<RooNLLVarNew>(name.c_str(), name.c_str(), *pdf, observables, isExtended, doOffset);
// Rename the observables and weights
newObservables.add(nll->prefixObservableAndWeightNames(std::string("_") + catName + "_"));
nllTerms.addOwned(std::move(nll));
Expand Down Expand Up @@ -152,6 +154,11 @@ RooFit::BatchModeHelpers::createNLL(RooAbsPdf &pdf, RooAbsData &data, std::uniqu
observables.remove(projDeps, true, true);
obsClones.remove(projDeps, true, true);

// Set the normalization range
if (!rangeName.empty()) {
pdfClone->setNormRange(rangeName.c_str());
}

oocxcoutI(&pdf, Fitting) << "RooAbsPdf::fitTo(" << pdf.GetName()
<< ") fixing normalization set for coefficient determination to observables in data"
<< "\n";
Expand Down Expand Up @@ -183,8 +190,8 @@ RooFit::BatchModeHelpers::createNLL(RooAbsPdf &pdf, RooAbsData &data, std::uniqu
// Warning! This mutates "obsClones"
nllTerms.addOwned(createSimultaneousNLL(*simPdf, obsClones, isExtended, rangeName, doOffset));
} else {
nllTerms.addOwned(std::make_unique<RooNLLVarNew>("RooNLLVarNew", "RooNLLVarNew", finalPdf, obsClones, isExtended,
rangeName, doOffset));
nllTerms.addOwned(
std::make_unique<RooNLLVarNew>("RooNLLVarNew", "RooNLLVarNew", finalPdf, obsClones, isExtended, doOffset));
}
if (constraints) {
nllTerms.addOwned(std::move(constraints));
Expand Down
6 changes: 1 addition & 5 deletions roofit/roofitcore/src/RooAbsData.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -498,13 +498,9 @@ RooAbsData* RooAbsData::reduce(const RooCmdArg& arg1,const RooCmdArg& arg2,const
RooFormulaVar cutVarTmp(cutSpec,cutSpec,*get()) ;
ret = reduceEng(varSubset,&cutVarTmp,cutRange,nStart,nStop) ;

} else if (cutVar) {

ret = reduceEng(varSubset,cutVar,cutRange,nStart,nStop) ;

} else {

ret = reduceEng(varSubset,0,cutRange,nStart,nStop) ;
ret = reduceEng(varSubset,cutVar,cutRange,nStart,nStop) ;

}

Expand Down
50 changes: 23 additions & 27 deletions roofit/roofitcore/src/RooAbsOptTestStatistic.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ parallelized calculation of test statistics.
#include "RooVectorDataStore.h"
#include "RooBinSamplingPdf.h"

#include "ROOT/StringUtils.hxx"

using namespace std;

ClassImp(RooAbsOptTestStatistic);
Expand Down Expand Up @@ -290,16 +292,27 @@ void RooAbsOptTestStatistic::initSlave(RooAbsReal& real, RooAbsData& indata, con
// ******************************************************************

std::unique_ptr<RooArgSet> origObsSet( real.getObservables(indata) );
RooArgSet* dataObsSet = (RooArgSet*) _dataClone->get() ;
if (rangeName && strlen(rangeName)) {
cxcoutI(Fitting) << "RooAbsOptTestStatistic::ctor(" << GetName() << ") constructing test statistic for sub-range named " << rangeName << endl ;

bool observablesKnowRange = false;
if(auto pdfClone = dynamic_cast<RooAbsPdf*>(_funcClone)) {
pdfClone->setNormRange(rangeName);
}

// Adjust FUNC normalization ranges to requested fitRange, store original ranges for RooAddPdf coefficient interpretation
for (const auto arg : *_funcObsSet) {

RooRealVar* realObs = dynamic_cast<RooRealVar*>(arg) ;
if (realObs) {
if (auto realObs = dynamic_cast<RooRealVar*>(arg)) {

auto tokens = ROOT::Split(rangeName, ",");
for(std::string const& token : tokens) {
if(!realObs->hasRange(token.c_str())) {
std::stringstream errMsg;
errMsg << "The observable \"" << realObs->GetName() << "\" doesn't define the requested range \""
<< token << "\". Replacing it with the default range." << std::endl;
coutW(Fitting) << errMsg.str() << std::endl;
}
}

auto transferRangeAndBinning = [&](RooRealVar & toVar, const char* toName, const char* fromName) {
toVar.setRange(toName, realObs->getMin(fromName),realObs->getMax(fromName));
Expand All @@ -312,22 +325,11 @@ void RooAbsOptTestStatistic::initSlave(RooAbsReal& real, RooAbsData& indata, con
}
};

observablesKnowRange |= realObs->hasRange(rangeName);

// If no explicit range is given for RooAddPdf coefficients, create explicit named range equivalent to original observables range
if (!(addCoefRangeName && strlen(addCoefRangeName))) {
transferRangeAndBinning(*realObs, Form("NormalizationRangeFor%s",rangeName), nullptr);
}

// Adjust range of function observable to those of given named range
transferRangeAndBinning(*realObs, nullptr, rangeName);

// Adjust range of data observable to those of given named range
RooRealVar* dataObs = (RooRealVar*) dataObsSet->find(realObs->GetName()) ;
transferRangeAndBinning(*dataObs, nullptr, rangeName);

// Keep track of list of fit ranges in string attribute fit range of original p.d.f.
if (!_splitRange) {
// Keep track of list of fit ranges in string attribute fit range of
// original PDF, but only do so if we don't do multi-range
// normalization. In this case, we could not define an equivalend
// single fit range.
if (!_splitRange && strchr(rangeName,',') == 0) {
const std::string fitRangeName = std::string("fit_") + GetName();
const char* origAttrib = real.getStringAttribute("fitrange") ;
std::string newAttr = origAttrib ? origAttrib : "";
Expand All @@ -343,9 +345,6 @@ void RooAbsOptTestStatistic::initSlave(RooAbsReal& real, RooAbsData& indata, con
}
}
}

if (!observablesKnowRange)
coutW(Fitting) << "None of the fit observables seem to know the range '" << rangeName << "'. This means that the full range will be used." << std::endl;
}


Expand All @@ -366,10 +365,6 @@ void RooAbsOptTestStatistic::initSlave(RooAbsReal& real, RooAbsData& indata, con
cxcoutI(Fitting) << "RooAbsOptTestStatistic::ctor(" << GetName()
<< ") fixing interpretation of coefficients of any RooAddPdf component to range " << addCoefRangeName << endl ;
_funcClone->fixAddCoefRange(addCoefRangeName,false) ;
} else {
cxcoutI(Fitting) << "RooAbsOptTestStatistic::ctor(" << GetName()
<< ") fixing interpretation of coefficients of any RooAddPdf to full domain of observables " << endl ;
_funcClone->fixAddCoefRange(Form("NormalizationRangeFor%s",rangeName),false) ;
}
}

Expand Down Expand Up @@ -859,3 +854,4 @@ void RooAbsOptTestStatistic::setUpBinSampling() {
const char* RooAbsOptTestStatistic::cacheUniqueSuffix() const {
return Form("_%lx", _dataClone->uniqueId().value()) ;
}

85 changes: 3 additions & 82 deletions roofit/roofitcore/src/RooAbsPdf.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -938,51 +938,6 @@ RooAbsReal* RooAbsPdf::createNLL(RooAbsData& data, const RooCmdArg& arg1, const
return createNLL(data,l) ;
}

namespace {

std::unique_ptr<RooAbsReal> createMultiRangeNLLCorrectionTerm(
RooAbsPdf const &pdf, RooAbsData const &data, std::string const &baseName, std::string const &rangeNames)
{
double sumEntriesTotal = 0.0;

RooArgList termList;
RooArgList integralList;

for (const auto &currentRangeName : ROOT::Split(rangeNames, ",")) {
const std::string currentName = baseName + "_" + currentRangeName;

auto sumEntriesCurrent = data.sumEntries("1", currentRangeName.c_str());
sumEntriesTotal += sumEntriesCurrent;

RooArgSet depList;
pdf.getObservables(data.get(), depList);
auto pdfIntegralCurrent = pdf.createIntegral(depList, &depList, nullptr, currentRangeName.c_str());

auto term = new RooFormulaVar((currentName + "_correctionTerm").c_str(),
(std::string("-(") + std::to_string(sumEntriesCurrent) + " * log(x[0]))").c_str(),
RooArgList(*pdfIntegralCurrent));

termList.add(*term);
integralList.add(*pdfIntegralCurrent);
}

auto integralFull = new RooAddition((baseName + "_correctionFullIntegralTerm").c_str(),
"integral",
integralList,
true);

auto fullRangeTerm = new RooFormulaVar((baseName + "_foobar").c_str(),
(std::string("(") + std::to_string(sumEntriesTotal) + " * log(x[0]))").c_str(),
RooArgList(*integralFull));

termList.add(*fullRangeTerm);
return std::unique_ptr<RooAbsReal>{
new RooAddition((baseName + "_correction").c_str(), "correction", termList, true)};
}


} // namespace


////////////////////////////////////////////////////////////////////////////////
/// Construct representation of -log(L) of PDFwith given dataset. If dataset is unbinned, an unbinned likelihood is constructed. If the dataset
Expand Down Expand Up @@ -1129,43 +1084,9 @@ RooAbsReal* RooAbsPdf::createNLL(RooAbsData& data, const RooLinkedList& cmdList)
cfg.integrateOverBinsPrecision = pc.getDouble("IntegrateBins");
cfg.binnedL = false;
cfg.takeGlobalObservablesFromData = takeGlobalObservablesFromData;
if (!rangeName || strchr(rangeName,',')==0) {
// Simple case: default range, or single restricted range
//cout<<"FK: Data test 1: "<<data.sumEntries()<<endl;

cfg.rangeName = rangeName ? rangeName : "";
nll = std::make_unique<RooNLLVar>(baseName.c_str(),"-log(likelihood)",*this,data,projDeps, ext, cfg);
static_cast<RooNLLVar&>(*nll).batchMode(batchMode == RooFit::BatchModeOption::Old);
} else {
// Composite case: multiple ranges
RooArgList nllList ;
auto tokens = ROOT::Split(rangeName, ",");
if (RooHelpers::checkIfRangesOverlap(*this, data, tokens, cfg.splitCutRange)) {
throw std::runtime_error(
std::string("Error in RooAbsPdf::createNLL! The ranges ") + rangeName + " are overlapping!");
}
for (const auto& token : tokens) {
cfg.rangeName = token;
auto nllComp = std::make_unique<RooNLLVar>((baseName + "_" + token).c_str(),"-log(likelihood)",
*this,data,projDeps,ext,cfg);
nllComp->batchMode(pc.getInt("BatchMode"));
nllList.addOwned(std::move(nllComp)) ;
}

if (!ext) {
// Each RooNLLVar was created with the normalization set corresponding to
// the subrange, not the union range like it should be. We have to add an
// extra term to cancel this normalization problem. However, this is
// only necessarry for the non-extended case, because adding an extension
// term to the individual NLLs as done here is mathematicall equivalent
// to adding the normalization correction terms plus a global extension
// term.
nllList.addOwned(createMultiRangeNLLCorrectionTerm(*this, data, baseName, rangeName));
}

nll = std::make_unique<RooAddition>(baseName.c_str(),"-log(likelihood)",nllList) ;
nll->addOwnedComponents(std::move(nllList));
}
cfg.rangeName = rangeName ? rangeName : "";
nll = std::make_unique<RooNLLVar>(baseName.c_str(),"-log(likelihood)",*this,data,projDeps, ext, cfg);
static_cast<RooNLLVar&>(*nll).batchMode(batchMode == RooFit::BatchModeOption::Old);
RooAbsReal::setEvalErrorLoggingMode(RooAbsReal::PrintErrors) ;

// Include constraints, if any, in likelihood
Expand Down
18 changes: 7 additions & 11 deletions roofit/roofitcore/src/RooAbsTestStatistic.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ combined in the main thread.
#include "RooRealSumPdf.h"
#include "RooAbsCategoryLValue.h"

#include "ROOT/StringUtils.hxx"

#include "TTimeStamp.h"
#include "TClass.h"
#include <string>
Expand Down Expand Up @@ -107,16 +109,6 @@ RooAbsTestStatistic::RooAbsTestStatistic(const char *name, const char *title, Ro
{
// Register all parameters as servers
_paramSet.add(*std::unique_ptr<RooArgSet>{real.getParameters(&data)});

if (cfg.rangeName.find(',') != std::string::npos) {
auto errorMsg = std::string("Ranges ") + cfg.rangeName
+ " were passed to the RooAbsTestStatistic with name \"" + name + "\", "
+ "but it doesn't support multiple comma-separated fit ranges!\n" +
+ "Instead, one should combine multiple RooAbsTestStatistic objects "
+ "(see RooAbsPdf::createNLL for an example with RooNLLVar).";
coutE(InputArguments) << errorMsg << std::endl;
throw std::invalid_argument(errorMsg);
}
}


Expand Down Expand Up @@ -535,7 +527,11 @@ void RooAbsTestStatistic::initSimMode(RooSimultaneous* simpdf, RooAbsData* data,
cfg.integrateOverBinsPrecision = thisAsRooAbsOptTestStatistic->_integrateBinsPrecision;
}
if (_splitRange && !rangeName.empty()) {
cfg.rangeName = rangeName + "_" + catName;
auto tokens = ROOT::Split(rangeName, ",");
for(std::string const& token : tokens) {
cfg.rangeName += token + "_" + catName + ",";
}
cfg.rangeName.pop_back(); // to remove the last comma
cfg.nCPU = _nCPU*(_mpinterl?-1:1);
} else {
cfg.rangeName = rangeName;
Expand Down
2 changes: 1 addition & 1 deletion roofit/roofitcore/src/RooAddGenContext.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ void RooAddGenContext::initGenerator(const RooArgSet &theEvent)
_pcache = amod->getProjCache(_vars.get()) ;
} else {
RooAddPdf* apdf = (RooAddPdf*) _pdf ;
_pcache = apdf->getProjCache(_vars.get(),nullptr,"FULL_RANGE_ADDGENCONTEXT") ;
_pcache = apdf->getProjCache(_vars.get(),nullptr) ;
}

// Forward initGenerator call to all components
Expand Down
Loading