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