Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
RooAbsOptTestStatistic.cxx
Go to the documentation of this file.
1/// \cond ROOFIT_INTERNAL
2
3/*****************************************************************************
4 * Project: RooFit *
5 * Package: RooFitCore *
6 * @(#)root/roofitcore:$Id$
7 * Authors: *
8 * WV, Wouter Verkerke, UC Santa Barbara, verkerke@slac.stanford.edu *
9 * DK, David Kirkby, UC Irvine, dkirkby@uci.edu *
10 * *
11 * Copyright (c) 2000-2005, Regents of the University of California *
12 * and Stanford University. All rights reserved. *
13 * *
14 * Redistribution and use in source and binary forms, *
15 * with or without modification, are permitted according to the terms *
16 * listed in LICENSE (http://roofit.sourceforge.net/license.txt) *
17 *****************************************************************************/
18
19/**
20\file RooAbsOptTestStatistic.cxx
21\class RooAbsOptTestStatistic
22\ingroup Roofitcore
23
24Abstract base class for test
25statistics objects that evaluate a function or PDF at each point of a given
26dataset. This class provides generic optimizations, such as
27caching and precalculation of constant terms that can be made for
28all such quantities.
29
30Implementations should define evaluatePartition(), which calculates the
31value of a (sub)range of the dataset and optionally combinedValue(),
32which combines the values calculated for each partition. If combinedValue()
33is not overloaded, the default implementation will add the partition results
34to obtain the combined result.
35
36Support for calculation in partitions is needed to allow multi-core
37parallelized calculation of test statistics.
38**/
39
41
42#include "Riostream.h"
43#include "TClass.h"
44#include <cstring>
45
46#include "RooAbsData.h"
47#include "RooAbsDataStore.h"
48#include "RooAbsPdf.h"
49#include "RooAddPdf.h"
50#include "RooArgSet.h"
51#include "RooBinSamplingPdf.h"
52#include "RooBinning.h"
53#include "RooCategory.h"
54#include "RooDataHist.h"
55#include "RooDataSet.h"
56#include "RooErrorHandler.h"
57#include "RooFitImplHelpers.h"
58#include "RooGlobalFunc.h"
59#include "RooMsgService.h"
60#include "RooProdPdf.h"
61#include "RooProduct.h"
62#include "RooRealSumPdf.h"
63#include "RooRealVar.h"
64#include "RooVectorDataStore.h"
65
66#include "ROOT/StringUtils.hxx"
67
68using std::ostream;
69
70////////////////////////////////////////////////////////////////////////////////
71/// Create a test statistic, and optimise its calculation.
72/// \param[in] name Name of the instance.
73/// \param[in] title Title (for e.g. plotting).
74/// \param[in] real Function to evaluate.
75/// \param[in] indata Dataset for which to compute test statistic.
76/// \param[in] projDeps A set of projected observables.
77/// \param[in] cfg the statistic configuration
78///
79/// cfg contains:
80/// - rangeName If not null, only events in the dataset inside the range will be used in the test
81/// statistic calculation.
82/// - addCoefRangeName If not null, all RooAddPdf components of `real` will be
83/// instructed to fix their fraction definitions to the given named range.
84/// - nCPU If > 1, the test statistic calculation will be parallelised over multiple processes. By default, the data
85/// is split with 'bulk' partitioning (each process calculates a contiguous block of fraction 1/nCPU
86/// of the data). For binned data, this approach may be suboptimal as the number of bins with >0 entries
87/// in each processing block may vary greatly; thereby distributing the workload rather unevenly.
88/// - interleave Strategy how to distribute events among workers. If an interleave partitioning strategy is used where each partition
89/// i takes all bins for which (ibin % ncpu == i), an even distribution of work is more likely.
90/// - splitCutRange If true, a different rangeName constructed as `rangeName_{catName}` will be used
91/// as range definition for each index state of a RooSimultaneous.
92/// - cloneInputData Not used. Data is always cloned.
93/// - integrateOverBinsPrecision If > 0, PDF in binned fits are integrated over the bins. This sets the precision. If = 0,
94/// only unbinned PDFs fit to RooDataHist are integrated. If < 0, PDFs are never integrated.
95RooAbsOptTestStatistic::RooAbsOptTestStatistic(const char *name, const char *title, RooAbsReal &real,
96 RooAbsData &indata, const RooArgSet &projDeps,
97 RooAbsTestStatistic::Configuration const &cfg)
98 : RooAbsTestStatistic(name, title, real, indata, projDeps, cfg),
100{
101 // Don't do a thing in master mode
102 if (operMode() != Slave) {
103 return;
104 }
105
106 initSlave(real, indata, projDeps, _rangeName.c_str(), _addCoefRangeName.c_str());
107}
108
109////////////////////////////////////////////////////////////////////////////////
110/// Copy constructor
111
112RooAbsOptTestStatistic::RooAbsOptTestStatistic(const RooAbsOptTestStatistic &other, const char *name)
117{
118 // Don't do a thing in master mode
119 if (operMode() != Slave) {
120
121 if (other._normSet) {
122 _normSet = new RooArgSet;
123 other._normSet->snapshot(*_normSet);
124 }
125 return;
126 }
127
128 initSlave(*other._funcClone, *other._dataClone, other._projDeps ? *other._projDeps : RooArgSet(),
129 other._rangeName.c_str(), other._addCoefRangeName.c_str());
130}
131
132
133
134////////////////////////////////////////////////////////////////////////////////
135
136void RooAbsOptTestStatistic::initSlave(RooAbsReal& real, RooAbsData& indata, const RooArgSet& projDeps, const char* rangeName,
137 const char* addCoefRangeName) {
138 // ******************************************************************
139 // *** PART 1 *** Clone incoming pdf, attach to each other *
140 // ******************************************************************
141
142 // Clone FUNC
144 _funcCloneSet = nullptr ;
145
146 // Attach FUNC to data set
147 _funcObsSet = std::unique_ptr<RooArgSet>{_funcClone->getObservables(indata)}.release();
148
149 if (_funcClone->getAttribute("BinnedLikelihood")) {
150 _funcClone->setAttribute("BinnedLikelihoodActive") ;
151 }
152
153 // Mark all projected dependents as such
154 if (!projDeps.empty()) {
155 std::unique_ptr<RooArgSet> projDataDeps{_funcObsSet->selectCommon(projDeps)};
156 projDataDeps->setAttribAll("projectedDependent") ;
157 }
158
159 // If PDF is a RooProdPdf (with possible constraint terms)
160 // analyze pdf for actual parameters (i.e those in unconnected constraint terms should be
161 // ignored as here so that the test statistic will not be recalculated if those
162 // are changed
163 RooProdPdf* pdfWithCons = dynamic_cast<RooProdPdf*>(_funcClone) ;
164 if (pdfWithCons) {
165
166 std::unique_ptr<RooArgSet> connPars{pdfWithCons->getConnectedParameters(*indata.get())};
167 // Add connected parameters as servers
168 _paramSet.add(*connPars) ;
169
170 } else {
171 // Add parameters as servers
172 _funcClone->getParameters(indata.get(), _paramSet);
173 }
174
175 // Store normalization set
176 _normSet = new RooArgSet;
177 indata.get()->snapshot(*_normSet, false);
178
179 // Expand list of observables with any observables used in parameterized ranges.
180 // This NEEDS to be a counting loop since we are inserting during the loop.
181 for (std::size_t i = 0; i < _funcObsSet->size(); ++i) {
182 auto realDepRLV = dynamic_cast<const RooAbsRealLValue*>((*_funcObsSet)[i]);
183 if (realDepRLV && realDepRLV->isDerived()) {
185 realDepRLV->leafNodeServerList(&tmp2, nullptr, true);
186 _funcObsSet->add(tmp2,true);
187 }
188 }
189
190
191
192 // ******************************************************************
193 // *** PART 2 *** Clone and adjust incoming data, attach to PDF *
194 // ******************************************************************
195
196 // Check if the fit ranges of the dependents in the data and in the FUNC are consistent
197 const RooArgSet* dataDepSet = indata.get() ;
198 for (const auto arg : *_funcObsSet) {
199
200 // Check that both dataset and function argument are of type RooRealVar
201 RooRealVar* realReal = dynamic_cast<RooRealVar*>(arg) ;
202 if (!realReal) continue ;
203 RooRealVar* datReal = dynamic_cast<RooRealVar*>(dataDepSet->find(realReal->GetName())) ;
204 if (!datReal) continue ;
205
206 // Check that range of observables in pdf is equal or contained in range of observables in data
207
208 if (!realReal->getBinning().lowBoundFunc() && realReal->getMin()<(datReal->getMin()-1e-6)) {
209 coutE(InputArguments) << "RooAbsOptTestStatistic: ERROR minimum of FUNC observable " << arg->GetName()
210 << "(" << realReal->getMin() << ") is smaller than that of "
211 << arg->GetName() << " in the dataset (" << datReal->getMin() << ")" << std::endl ;
213 return ;
214 }
215
216 if (!realReal->getBinning().highBoundFunc() && realReal->getMax()>(datReal->getMax()+1e-6)) {
217 coutE(InputArguments) << "RooAbsOptTestStatistic: ERROR maximum of FUNC observable " << arg->GetName()
218 << " is larger than that of " << arg->GetName() << " in the dataset" << std::endl ;
220 return ;
221 }
222 }
223
224 // Copy data and strip entries lost by adjusted fit range, _dataClone ranges will be copied from realDepSet ranges
225 if (rangeName && strlen(rangeName)) {
226 _dataClone = std::unique_ptr<RooAbsData>{indata.reduce(RooFit::SelectVars(*_funcObsSet),RooFit::CutRange(rangeName))}.release();
227 } else {
228 _dataClone = static_cast<RooAbsData*>(indata.Clone()) ;
229 }
230 _ownData = true ;
231
232
233 // ******************************************************************
234 // *** PART 3 *** Make adjustments for fit ranges, if specified *
235 // ******************************************************************
236
237 std::unique_ptr<RooArgSet> origObsSet( real.getObservables(indata) );
238 if (rangeName && strlen(rangeName)) {
239 cxcoutI(Fitting) << "RooAbsOptTestStatistic::ctor(" << GetName() << ") constructing test statistic for sub-range named " << rangeName << std::endl ;
240
241 if(auto pdfClone = dynamic_cast<RooAbsPdf*>(_funcClone)) {
242 pdfClone->setNormRange(rangeName);
243 }
244
245 // Print warnings if the requested ranges are not available for the observable
246 for (const auto arg : *_funcObsSet) {
247
248 if (auto realObs = dynamic_cast<RooRealVar*>(arg)) {
249
250 auto tokens = ROOT::Split(rangeName, ",");
251 for(std::string const& token : tokens) {
252 if(!realObs->hasRange(token.c_str())) {
253 std::stringstream errMsg;
254 errMsg << "The observable \"" << realObs->GetName() << "\" doesn't define the requested range \""
255 << token << "\". Replacing it with the default range." << std::endl;
256 coutI(Fitting) << errMsg.str() << std::endl;
257 }
258 }
259 }
260 }
261 }
262
263
264 // ******************************************************************
265 // *** PART 3.2 *** Binned fits *
266 // ******************************************************************
267
269
270
271 // Fix RooAddPdf coefficients to original normalization range
272 if (rangeName && strlen(rangeName)) {
273
274 // WVE Remove projected dependents from normalization
275 _funcClone->fixAddCoefNormalization(*_dataClone->get(),false) ;
276
278 cxcoutI(Fitting) << "RooAbsOptTestStatistic::ctor(" << GetName()
279 << ") fixing interpretation of coefficients of any RooAddPdf component to range " << addCoefRangeName << std::endl ;
280 _funcClone->fixAddCoefRange(addCoefRangeName,false) ;
281 }
282 }
283
284
285 // This is deferred from part 2 - but must happen after part 3 - otherwise invalid bins cannot be properly marked in cacheValidEntries
286 _dataClone->attachBuffers(*_funcObsSet) ;
287 setEventCount(_dataClone->numEntries()) ;
288
289
290
291
292 // *********************************************************************
293 // *** PART 4 *** Adjust normalization range for projected observables *
294 // *********************************************************************
295
296 // Remove projected dependents from normalization set
297 if (!projDeps.empty()) {
298
299 _projDeps = new RooArgSet;
300 projDeps.snapshot(*_projDeps, false) ;
301
302 //RooArgSet* tobedel = (RooArgSet*) _normSet->selectCommon(*_projDeps) ;
303 _normSet->remove(*_projDeps,true,true) ;
304
305 // Mark all projected dependents as such
307 _funcObsSet->selectCommon(*_projDeps, projDataDeps);
308 projDataDeps.setAttribAll("projectedDependent") ;
309 }
310
311
312 coutI(Optimization) << "RooAbsOptTestStatistic::ctor(" << GetName() << ") optimizing internal clone of p.d.f for likelihood evaluation."
313 << "Lazy evaluation and associated change tracking will disabled for all nodes that depend on observables" << std::endl ;
314
315
316 // *********************************************************************
317 // *** PART 4 *** Finalization and activation of optimization *
318 // *********************************************************************
319
320 // Redirect pointers of base class to clone
321 _func = _funcClone ;
322 _data = _dataClone ;
323
324 _funcClone->getVal(_normSet) ;
325
327
328 // It would be unusual if the global observables are used in the likelihood
329 // outside of the constraint terms, but if they are we have to be consistent
330 // and also redirect them to the snapshots in the dataset if appropriate.
331 if(_takeGlobalObservablesFromData && _data->getGlobalObservables()) {
332 recursiveRedirectServers(*_data->getGlobalObservables()) ;
333 }
334
335}
336
337
338////////////////////////////////////////////////////////////////////////////////
339/// Destructor
340
341RooAbsOptTestStatistic::~RooAbsOptTestStatistic()
342{
343 if (operMode()==Slave) {
344 delete _funcClone ;
345 delete _funcObsSet ;
346 if (_projDeps) {
347 delete _projDeps ;
348 }
349 if (_ownData) {
350 delete _dataClone ;
351 }
352 }
353 delete _normSet ;
354}
355
356
357
358////////////////////////////////////////////////////////////////////////////////
359/// Method to combined test statistic results calculated into partitions into
360/// the global result. This default implementation adds the partition return
361/// values
362
363double RooAbsOptTestStatistic::combinedValue(RooAbsReal** array, Int_t n) const
364{
365 // Default implementation returns sum of components
366 double sum(0);
367 double carry(0);
368 for (Int_t i = 0; i < n; ++i) {
369 double y = array[i]->getValV();
370 carry += reinterpret_cast<RooAbsOptTestStatistic*>(array[i])->getCarry();
371 y -= carry;
372 const double t = sum + y;
373 carry = (t - sum) - y;
374 sum = t;
375 }
376 _evalCarry = carry;
377 return sum ;
378}
379
380
381
382////////////////////////////////////////////////////////////////////////////////
383/// Catch server redirect calls and forward to internal clone of function
384
385bool RooAbsOptTestStatistic::redirectServersHook(const RooAbsCollection& newServerList, bool mustReplaceAll, bool nameChange, bool isRecursive)
386{
387 RooAbsTestStatistic::redirectServersHook(newServerList,mustReplaceAll,nameChange,isRecursive) ;
388 if (operMode()!=Slave) return false ;
389 bool ret = _funcClone->recursiveRedirectServers(newServerList,false,nameChange) ;
391}
392
393
394
395////////////////////////////////////////////////////////////////////////////////
396/// Catch print hook function and forward to function clone
397
398void RooAbsOptTestStatistic::printCompactTreeHook(ostream& os, const char* indent)
399{
400 RooAbsTestStatistic::printCompactTreeHook(os,indent) ;
401 if (operMode()!=Slave) return ;
403 indent2 += "opt >>" ;
404 _funcClone->printCompactTree(os,indent2.Data()) ;
405 os << indent2 << " dataset clone = " << _dataClone << " first obs = " << _dataClone->get()->first() << std::endl ;
406}
407
408
409
410////////////////////////////////////////////////////////////////////////////////
411/// This method changes the value caching logic for all nodes that depends on any of the observables
412/// as defined by the given dataset. When evaluating a test statistic constructed from the RooAbsReal
413/// with a dataset the observables are guaranteed to change with every call, thus there is no point
414/// in tracking these changes which result in a net overhead. Thus for observable-dependent nodes,
415/// the evaluation mechanism is changed from being dependent on a 'valueDirty' flag to guaranteed evaluation.
416/// On the dataset side, the observables objects are modified to no longer send valueDirty messages
417/// to their client
418
419void RooAbsOptTestStatistic::optimizeCaching()
420{
421 // Trigger create of all object caches now in nodes that have deferred object creation
422 // so that cache contents can be processed immediately
423 _funcClone->getVal(_normSet) ;
424
425 // Set value caching mode for all nodes that depend on any of the observables to ADirty
426 _funcClone->optimizeCacheMode(*_funcObsSet) ;
427
428 // Disable propagation of dirty state flags for observables
429 _dataClone->setDirtyProp(false) ;
430}
431
432
433
434////////////////////////////////////////////////////////////////////////////////
435/// Change dataset that is used to given one. If cloneData is true, a clone of
436/// in the input dataset is made. If the test statistic was constructed with
437/// a range specification on the data, the cloneData argument is ignored and
438/// the data is always cloned.
439bool RooAbsOptTestStatistic::setDataSlave(RooAbsData& indata, bool cloneData, bool ownNewData)
440{
441
442 if (operMode()==SimMaster) {
443 return false ;
444 }
445
446
447 // If the current dataset is owned, transfer the ownership to unique pointer
448 // that will get out of scope at the end of this function. We can't delete it
449 // right now, because there might be global observables in the model that
450 // first need to be redirected to the new dataset with a later call to
451 // RooAbsArg::recursiveRedirectServers.
452 std::unique_ptr<RooAbsData> oldOwnedData;
453 if (_ownData) {
455 _dataClone = nullptr ;
456 }
457
458 if (!cloneData && !_rangeName.empty()) {
459 coutW(InputArguments) << "RooAbsOptTestStatistic::setData(" << GetName() << ") WARNING: test statistic was constructed with range selection on data, "
460 << "ignoring request to _not_ clone the input dataset" << std::endl ;
461 cloneData = true ;
462 }
463
464 if (cloneData) {
465 // Cloning input dataset
466 _dataClone = std::unique_ptr<RooAbsData>{indata.reduce(RooFit::SelectVars(*indata.get()),RooFit::CutRange(_rangeName.c_str()))}.release();
467 _ownData = true ;
468
469 } else {
470
471 // Taking input dataset
472 _dataClone = &indata ;
474
475 }
476
477 // Attach function clone to dataset
478 _dataClone->attachBuffers(*_funcObsSet) ;
479 _dataClone->setDirtyProp(false) ;
480 _data = _dataClone ;
481
482 // Adjust internal event count
483 setEventCount(indata.numEntries()) ;
484
485 setValueDirty() ;
486
487 // It would be unusual if the global observables are used in the likelihood
488 // outside of the constraint terms, but if they are we have to be consistent
489 // and also redirect them to the snapshots in the dataset if appropriate.
490 if(_takeGlobalObservablesFromData && _data->getGlobalObservables()) {
491 recursiveRedirectServers(*_data->getGlobalObservables()) ;
492 }
493
494 return true ;
495}
496
497
498
499
500////////////////////////////////////////////////////////////////////////////////
501
502RooAbsData& RooAbsOptTestStatistic::data()
503{
504 if (_sealed) {
505 bool notice = (sealNotice() && strlen(sealNotice())) ;
506 coutW(ObjectHandling) << "RooAbsOptTestStatistic::data(" << GetName()
507 << ") WARNING: object sealed by creator - access to data is not permitted: "
508 << (notice?sealNotice():"<no user notice>") << std::endl ;
509 static RooDataSet dummy ("dummy","dummy",RooArgSet()) ;
510 return dummy ;
511 }
512 return *_dataClone ;
513}
514
515
516////////////////////////////////////////////////////////////////////////////////
517
518const RooAbsData& RooAbsOptTestStatistic::data() const
519{
520 if (_sealed) {
521 bool notice = (sealNotice() && strlen(sealNotice())) ;
522 coutW(ObjectHandling) << "RooAbsOptTestStatistic::data(" << GetName()
523 << ") WARNING: object sealed by creator - access to data is not permitted: "
524 << (notice?sealNotice():"<no user notice>") << std::endl ;
525 static RooDataSet dummy ("dummy","dummy",RooArgSet()) ;
526 return dummy ;
527 }
528 return *_dataClone ;
529}
530
531
532////////////////////////////////////////////////////////////////////////////////
533/// Inspect PDF to find out if we are doing a binned fit to a 1-dimensional unbinned PDF.
534/// If this is the case, enable finer sampling of bins by wrapping PDF into a RooBinSamplingPdf.
535/// The member _integrateBinsPrecision decides how we act:
536/// - < 0: Don't do anything.
537/// - = 0: Only enable feature if fitting unbinned PDF to RooDataHist.
538/// - > 0: Enable as requested.
539void RooAbsOptTestStatistic::setUpBinSampling() {
540
541 auto& pdf = static_cast<RooAbsPdf&>(*_funcClone);
543 newPdf->addOwnedComponents(*_funcClone);
544 _funcClone = newPdf.release();
545 }
546
547}
548
549
550/// Returns a suffix string that is unique for RooAbsOptTestStatistic
551/// instances that don't share the same cloned input data object.
552const char* RooAbsOptTestStatistic::cacheUniqueSuffix() const {
553 return Form("_%lx", _dataClone->uniqueId().value()) ;
554}
555
556/// \endcond
#define e(i)
Definition RSha256.hxx:103
#define coutI(a)
#define cxcoutI(a)
#define coutW(a)
#define coutE(a)
int Int_t
Signed integer 4 bytes (int)
Definition RtypesCore.h:60
static void indent(ostringstream &buf, int indent_level)
ROOT::Detail::TRangeCast< T, true > TRangeDynCast
TRangeDynCast is an adapter class that allows the typed iteration through a TCollection.
char name[80]
Definition TGX11.cxx:142
char * Form(const char *fmt,...)
Formats a string in a circular formatting buffer.
Definition TString.cxx:2570
Abstract container object that can hold multiple RooAbsArg objects.
virtual bool remove(const RooAbsArg &var, bool silent=false, bool matchByNameOnly=false)
Remove the specified argument from our list.
Abstract base class for binned and unbinned datasets.
Definition RooAbsData.h:55
Abstract interface for all probability density functions.
Definition RooAbsPdf.h:32
Abstract base class for objects that represent a real value that may appear on the left hand side of ...
Abstract base class for objects that represent a real value and implements functionality common to al...
Definition RooAbsReal.h:63
virtual double getValV(const RooArgSet *normalisationSet=nullptr) const
Return value of object.
bool redirectServersHook(const RooAbsCollection &newServerList, bool mustReplaceAll, bool nameChange, bool isRecursiveStep) override
Function that is called at the end of redirectServers().
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
static std::unique_ptr< RooAbsPdf > create(RooAbsPdf &pdf, RooAbsData const &data, double precision)
Creates a wrapping RooBinSamplingPdf if appropriate.
Container class to hold unbinned data.
Definition RooDataSet.h:32
static void softAbort()
Soft abort function that interrupts macro execution but doesn't kill ROOT.
Efficient implementation of a product of PDFs of the form.
Definition RooProdPdf.h:35
Variable that can be changed from the outside.
Definition RooRealVar.h:37
const char * GetName() const override
Returns name of object.
Definition TNamed.h:49
Basic string class.
Definition TString.h:138
RooCmdArg SelectVars(const RooArgSet &vars)
RooCmdArg CutRange(const char *rangeName)
Double_t y[n]
Definition legend1.C:17
const Int_t n
Definition legend1.C:16
std::vector< std::string > Split(std::string_view str, std::string_view delims, bool skipEmpty=false)
Splits a string at each character in delims.
std::unique_ptr< T > cloneTreeWithSameParameters(T const &arg, RooArgSet const *observables=nullptr)
Clone RooAbsArg object and reattach to original parameters.
static uint64_t sum(uint64_t i)
Definition Factory.cxx:2335