ROOT   Reference Guide
RooCategory.cxx
Go to the documentation of this file.
1/*****************************************************************************
2 * Project: RooFit *
3 * Package: RooFitCore *
4 * @(#)root/roofitcore:$Id$
5 * Authors: *
6 * WV, Wouter Verkerke, UC Santa Barbara, verkerke@slac.stanford.edu *
7 * DK, David Kirkby, UC Irvine, dkirkby@uci.edu *
8 * *
9 * Copyright (c) 2000-2005, Regents of the University of California *
11 * *
12 * Redistribution and use in source and binary forms, *
13 * with or without modification, are permitted according to the terms *
15 *****************************************************************************/
16
17/**
18\class RooCategory
19\ingroup Roofitcore
20
21RooCategory is an object to represent discrete states.
22States have names and index numbers, and the index numbers can be written into datasets and
23used in calculations.
24A category is "fundamental", i.e., its value doesn't depend on the value of other objects.
25(Objects in datasets cannot depend on other objects' values, they need to be self-consistent.)
26
27A category object can be used to *e.g.* conduct a simultaneous fit of
28the same observable in multiple categories.
29
30### Setting up a category
31A category can be set up like this:
32~~~{.cpp}
33RooCategory myCat("myCat", "Lepton multiplicity category", {
34 {"0Lep", 0},
35 {"1Lep", 1},
36 {"2Lep", 2},
37 {"3Lep", 3}
38});
39~~~
40Like this:
41~~~{.cpp}
42RooCategory myCat("myCat", "Asymmetry");
43myCat["left"] = -1;
44myCat["right"] = 1;
45~~~
46Or like this:
47~~~{.cpp}
48RooCategory myCat("myCat", "Asymmetry");
49myCat.defineType("left", -1);
50myCat.defineType("right", 1);
51~~~
52Inspect the pairs of state names and state numbers like this:
53~~~{.cpp}
54for (const auto& nameIdx : myCat) {
55 std::cout << nameIdx.first << " --> " << nameIdx.second << std::endl;
56}
57~~~
58
59### Changing category states
60Category states can be modified either by using the index state (faster) or state names.
61For example:
62~~~{.cpp}
63myCat.setIndex(5);
64myCat.setLabel("left");
65for (const auto& otherNameIdx : otherCat) {
66 myCat.setIndex(otherNameIdx);
67}
68~~~
69
70Also refer to \ref tutorial_roofit, especially rf404_categories.C for an introduction, and to rf405_realtocatfuncs.C and rf406_cattocatfuncs.C
72**/
73
74#include "RooCategory.h"
75
76#include "RooFit.h"
77#include "RooArgSet.h"
78#include "RooStreamParser.h"
79#include "RooMsgService.h"
80#include "RooTrace.h"
81#include "RooHelpers.h"
84
85#include "TBuffer.h"
86#include "TString.h"
87#include "ROOT/RMakeUnique.hxx"
88#include "TList.h"
89
90#include <iostream>
91#include <cstdlib>
92
93using namespace std;
94
96
97std::map<std::string, std::weak_ptr<RooCategory::RangeMap_t>> RooCategory::_uuidToSharedRangeIOHelper; // Helper for restoring shared properties
98std::map<std::string, std::weak_ptr<RooCategory::RangeMap_t>> RooCategory::_sharedRangeIOHelper;
99
100
101////////////////////////////////////////////////////////////////////////////////
102
104{
106}
107
108
109
110////////////////////////////////////////////////////////////////////////////////
111/// Constructor. Types must be defined using defineType() before variable can be used
112RooCategory::RooCategory(const char *name, const char *title) :
114 _ranges(new RangeMap_t())
115{
116 setValueDirty() ;
117 setShapeDirty() ;
119}
120
121
122////////////////////////////////////////////////////////////////////////////////
123/// Create a new category and define allowed states.
124/// \param[in] name Name used to refer to this object.
125/// \param[in] title Title for e.g. plotting.
126/// \param[in] allowedStates Map of allowed states. Pass e.g. { {"0Lep", 0}, {"1Lep:, 1} }
127RooCategory::RooCategory(const char* name, const char* title, const std::map<std::string, int>& allowedStates) :
129 _ranges(new RangeMap_t())
130{
131 defineTypes(allowedStates);
132}
133
134
135
136////////////////////////////////////////////////////////////////////////////////
137/// Copy constructor
138
139RooCategory::RooCategory(const RooCategory& other, const char* name) :
141 _ranges(other._ranges)
142{
144}
145
146
147////////////////////////////////////////////////////////////////////////////////
148/// Destructor
149
151{
153}
154
155
156
157
158////////////////////////////////////////////////////////////////////////////////
159/// Set value by specifying the index code of the desired state.
160/// If printError is set, a message will be printed if
161/// the specified index does not represent a valid state.
162/// \return bool signalling if an error occurred.
164{
165 if (!hasIndex(index)) {
166 if (printError) {
167 coutE(InputArguments) << "RooCategory: Trying to set invalid state " << index << " for category " << GetName() << std::endl;
168 }
169 return true;
170 }
171
172 _currentIndex = index;
174
175 return false;
176}
177
178
179
180////////////////////////////////////////////////////////////////////////////////
181/// Set value by specifying the name of the desired state.
182/// If printError is set, a message will be printed if
183/// the specified label does not represent a valid state.
184/// \return false on success.
185Bool_t RooCategory::setLabel(const char* label, Bool_t printError)
186{
187 const auto item = stateNames().find(label);
188 if (item != stateNames().end()) {
189 _currentIndex = item->second;
191 return false;
192 }
193
194 if (printError) {
195 coutE(InputArguments) << "Trying to set invalid state label '" << label << "' for category " << GetName() << std::endl;
196 }
197
198 return true;
199}
200
201
202
203////////////////////////////////////////////////////////////////////////////////
204/// Define a state with given name.
205/// The lowest available positive integer is assigned as index. Category
206/// state labels may not contain semicolons.
207/// \return True in case of an error.
208bool RooCategory::defineType(const std::string& label)
209{
210 if (label.find(';') != std::string::npos) {
211 coutE(InputArguments) << "RooCategory::defineType(" << GetName()
212 << "): semicolons not allowed in label name" << endl ;
213 return true;
214 }
215
217}
218
219
220////////////////////////////////////////////////////////////////////////////////
221/// Define a state with given name and index. Category
222/// state labels may not contain semicolons.
223/// \return True in case of error.
224bool RooCategory::defineType(const std::string& label, Int_t index)
225{
226 if (label.find(';') != std::string::npos) {
227 coutE(InputArguments) << "RooCategory::defineType(" << GetName()
228 << "): semicolons not allowed in label name" << endl ;
229 return true;
230 }
231
232 return RooAbsCategory::defineState(label, index) == invalidCategory();
233}
234
235
236////////////////////////////////////////////////////////////////////////////////
237/// Define multiple states in a single call. Use like:
238/// 
239/// myCat.defineTypes({ {"0Lep", 0}, {"1Lep", 1}, {"2Lep", 2}, {"3Lep", 3} });
240/// 
241/// Note: When labels or indices are defined multiple times, an error message is printed,
242/// and the corresponding state is ignored.
243void RooCategory::defineTypes(const std::map<std::string, int>& allowedStates) {
244 for (const auto& nameAndIdx : allowedStates) {
245 defineType(nameAndIdx.first, nameAndIdx.second);
246 }
247}
248
249
250////////////////////////////////////////////////////////////////////////////////
251/// Access a named state. If a state with this name doesn't exist yet, the state is
252/// assigned the next available positive integer.
253/// \param[in] stateName Name of the state to be accessed.
254/// \return Reference to the category index. If no state exists, it will be created on the fly.
257 if (stateNames().count(stateName) == 0) {
258 _insertionOrder.push_back(stateName);
259 return stateNames()[stateName] = nextAvailableStateIndex();
260
261 }
262
263 return stateNames()[stateName];
264}
265
266
267////////////////////////////////////////////////////////////////////////////////
268/// Return a reference to the map of state names to index states.
269/// This can be used to manipulate the category.
270/// \note Calling this function will **always** trigger recomputations of
271/// of **everything** that depends on this category, since in case the map gets
272/// manipulated, names or indices might change. Also, the order that states have
273/// been inserted in gets lost. This changes what is returned by getOrdinal().
274std::map<std::string, RooAbsCategory::value_type>& RooCategory::states() {
275 auto& theStates = stateNames();
278 _insertionOrder.clear();
279 return theStates;
280}
281
282
283////////////////////////////////////////////////////////////////////////////////
284/// Read object contents from given stream. If token is a decimal digit, try to
285/// find a corresponding state for it. If that succeeds, the state denoted by this
286/// index is used. Otherwise, interpret it as a label.
288{
290 RooStreamParser parser(is) ;
291 TString token = parser.readToken() ;
292
293 if (token.IsDec() && hasIndex(std::stoi(token.Data()))) {
294 return setIndex(std::stoi(token.Data()), verbose);
295 } else {
296 return setLabel(token,verbose) ;
297 }
298}
299
300
301
302////////////////////////////////////////////////////////////////////////////////
303/// compact only at the moment
304
305void RooCategory::writeToStream(ostream& os, Bool_t compact) const
306{
307 if (compact) {
308 os << getCurrentIndex() ;
309 } else {
310 os << getCurrentLabel() ;
311 }
312}
313
314
315////////////////////////////////////////////////////////////////////////////////
316/// Clear the named range.
317/// \note This affects **all** copies of this category, because they are sharing
318/// range definitions. This ensures that categories inside a dataset and their
319/// counterparts on the outside will both see a modification of the range.
320void RooCategory::clearRange(const char* name, Bool_t silent)
321{
322 std::map<std::string, std::vector<value_type>>::iterator item = _ranges->find(name);
323 if (item == _ranges->end()) {
324 if (!silent)
325 coutE(InputArguments) << "RooCategory::clearRange(" << GetName() << ") ERROR: must specify valid range name" << endl ;
326 return;
327 }
328
329 _ranges->erase(item);
330}
331
332
333////////////////////////////////////////////////////////////////////////////////
334
335void RooCategory::setRange(const char* name, const char* stateNameList)
336{
339}
340
341
342////////////////////////////////////////////////////////////////////////////////
343/// Add the given state to the given range.
344/// \note This creates or accesses a **shared** map with allowed ranges. All copies of this
345/// category will share this range such that a category inside a dataset and its
346/// counterpart on the outside will both see a modification of the range.
348 auto item = _ranges->find(name);
349 if (item == _ranges->end()) {
350 if (!name) {
351 coutE(Contents) << "RooCategory::addToRange(" << GetName()
352 << "): Need valid range name." << std::endl;
353 return;
354 }
355
356 item = _ranges->emplace(name, std::vector<value_type>()).first;
357 coutI(Contents) << "RooCategory::setRange(" << GetName()
358 << ") new range named '" << name << "' created for state " << stateIndex << endl ;
359 }
360
361 item->second.push_back(stateIndex);
362}
363
364
365////////////////////////////////////////////////////////////////////////////////
366/// Add the list of state names to the given range. State names can be separated
367/// with ','.
368/// \note This creates or accesses a **shared** map with allowed ranges. All copies of this
369/// category will share this range such that a category inside a dataset and its
370/// counterpart on the outside will both see a modification of the range.
371void RooCategory::addToRange(const char* name, const char* stateNameList)
372{
373 if (!stateNameList) {
374 coutE(InputArguments) << "RooCategory::setRange(" << GetName() << ") ERROR: must specify valid name and state name list" << endl ;
375 return;
376 }
377
378 // Parse list of state names, verify that each is valid and add them to the list
379 for (const auto& token : RooHelpers::tokenise(stateNameList, ",")) {
380 const value_type idx = lookupIndex(token);
381 if (idx != invalidCategory().second) {
383 } else {
384 coutW(InputArguments) << "RooCategory::setRange(" << GetName() << ") WARNING: Ignoring invalid state name '"
385 << token << "' in state name list" << endl ;
386 }
387 }
388}
389
390
391////////////////////////////////////////////////////////////////////////////////
392/// Check if the state is in the given range.
393/// If no range is specified either as argument or if no range has been defined for this category
394/// (*i.e.*, the default range is meant), all category states count as being in range.
395bool RooCategory::isStateInRange(const char* rangeName, RooAbsCategory::value_type stateIndex) const {
396 if (rangeName == nullptr || _ranges->empty())
397 return true;
398
399 const auto item = _ranges->find(rangeName);
400 if (item == _ranges->end())
401 return false;
402
403 const std::vector<value_type>& vec = item->second;
404 return std::find(vec.begin(), vec.end(), stateIndex) != vec.end();
405}
406
407
408////////////////////////////////////////////////////////////////////////////////
409/// Check if the state is in the given range.
410/// If no range is specified (*i.e.*, the default range), all category states count as being in range.
411/// This overload requires a name lookup. Recommend to use the category index with
412/// RooCategory::isStateInRange(const char*, RooAbsCategory::value_type) const.
413bool RooCategory::isStateInRange(const char* rangeName, const char* stateName) const
414{
415 // Check that both input arguments are not null pointers
416 if (!rangeName) {
417 return true;
418 }
419
420 if (!stateName) {
421 coutE(InputArguments) << "RooCategory::isStateInRange(" << GetName() << ") ERROR: must specify valid state name" << endl ;
422 return false;
423 }
424
425 return isStateInRange(rangeName, lookupIndex(stateName));
426}
427
428
429////////////////////////////////////////////////////////////////////////////////
430
431void RooCategory::Streamer(TBuffer &R__b)
432{
433 UInt_t R__s, R__c;
435
436 Version_t R__v = R__b.ReadVersion(&R__s, &R__c);
437
438 if (R__v==1) {
439 RooAbsCategoryLValue::Streamer(R__b);
440
441 // In v1, properties were a direct pointer:
442 RooCategorySharedProperties* props = nullptr;
443 R__b >> props;
445 // props was allocated by I/O system, we cannot delete here in case it gets reused
446
447 } else if (R__v == 2) {
448 RooAbsCategoryLValue::Streamer(R__b);
449
450 // In v2, properties were written directly into the class buffer
451 auto props = std::make_unique<RooCategorySharedProperties>();
452 props->Streamer(R__b);
453 installLegacySharedProp(props.get());
454
455 } else {
456 // Starting at v3, ranges are shared using a shared pointer, which cannot be read by ROOT's I/O.
457 // Instead, ranges are written as a normal pointer, and here we restore the sharing.
458 R__b.ReadClassBuffer(RooCategory::Class(), this, R__v, R__s, R__c);
459 installSharedRange(std::unique_ptr<RangeMap_t>(_rangesPointerForIO));
460 _rangesPointerForIO = nullptr;
461 }
462
463 R__b.CheckByteCount(R__s, R__c, RooCategory::IsA());
464
465 } else {
466 // Since we cannot write shared pointers yet, assign the shared ranges to a normal pointer,
467 // write, and restore.
468 if (_ranges)
470
472 _rangesPointerForIO = nullptr;
473 }
474}
475
476
477/// When reading old versions of the class, we get instances of shared properties.
478/// Since these only contain ranges with numbers, just convert to vectors of numbers.
480 if (props == nullptr || (*props == RooCategorySharedProperties("00000000-0000-0000-0000-000000000000")))
481 return;
482
483 auto& weakPtr = _uuidToSharedRangeIOHelper[props->asString().Data()];
484 if (auto existingObject = weakPtr.lock()) {
485 // We know this range, start sharing
486 _ranges = std::move(existingObject);
487 } else {
488 // This range is unknown, make a new object
489 _ranges = std::make_shared<std::map<std::string, std::vector<value_type>>>();
490 auto& rangesMap = *_ranges;
491
492 // Copy the data:
493 std::unique_ptr<TIterator> iter(props->_altRanges.MakeIterator());
494 while (TList* olist = (TList*)iter->Next()) {
495 std::vector<value_type>& vec = rangesMap[olist->GetName()];
496
497
498 std::unique_ptr<TIterator> citer(olist->MakeIterator());
499 while (RooCatType* ctype = (RooCatType*)citer->Next()) {
500 vec.push_back(ctype->getVal());
501 }
502 }
503
504 // Register the shared_ptr for future sharing
505 weakPtr = _ranges;
506 }
507}
508
509
510/// In current versions of the class, a map with ranges can be shared between instances.
511/// If an instance with the same name alreday uses the same map, the instances will start sharing.
512/// Otherwise, this instance will be registered, and future copies being read will share with this
513/// one.
514void RooCategory::installSharedRange(std::unique_ptr<RangeMap_t>&& rangeMap) {
515 if (rangeMap == nullptr)
516 return;
517
518 auto checkRangeMapsEqual = [](const RooCategory::RangeMap_t& a, const RooCategory::RangeMap_t& b) {
519 if (&a == &b)
520 return true;
521
522 if (a.size() != b.size())
523 return false;
524
525 auto vecsEqual = [](const std::vector<RooAbsCategory::value_type>& aa, const std::vector<RooAbsCategory::value_type>& bb) {
526 return aa.size() == bb.size() && std::equal(aa.begin(), aa.end(), bb.begin());
527 };
528
529 for (const auto& itemA : a) {
530 const auto itemB = b.find(itemA.first);
531 if (itemB == b.end())
532 return false;
533
534 if (!vecsEqual(itemA.second, itemB->second))
535 return false;
536 }
537
538 return true;
539 };
540
541
542 auto& weakPtr = _sharedRangeIOHelper[GetName()];
543 auto existingMap = weakPtr.lock();
544 if (existingMap && checkRangeMapsEqual(*rangeMap, *existingMap)) {
545 // We know this map, use the shared one.
546 _ranges = std::move(existingMap);
547 if (rangeMap.get() == existingMap.get()) {
548 // This happens when ROOT's IO has written the same pointer twice. We cannot delete now.
549 (void) rangeMap.release(); // clang-tidy is normally right that this leaks. Here, we need to leave the result unused, though.
550 }
551 } else {
552 // We don't know this map. Register for sharing.
553 _ranges = std::move(rangeMap);
554 weakPtr = _ranges;
555 }
556}
void Class()
Definition: Class.C:29
#define b(i)
Definition: RSha256.hxx:100
#define coutI(a)
Definition: RooMsgService.h:30
#define coutW(a)
Definition: RooMsgService.h:32
#define coutE(a)
Definition: RooMsgService.h:33
#define TRACE_DESTROY
Definition: RooTrace.h:23
#define TRACE_CREATE
Definition: RooTrace.h:22
short Version_t
Definition: RtypesCore.h:63
const Bool_t kTRUE
Definition: RtypesCore.h:89
#define ClassImp(name)
Definition: Rtypes.h:361
char name[80]
Definition: TGX11.cxx:109
typedef void((*Func_t)())
void setShapeDirty()
Notify that a shape-like property (e.g. binning) has changed.
Definition: RooAbsArg.h:492
void setValueDirty()
Mark the element dirty. This forces a re-evaluation when a value is requested.
Definition: RooAbsArg.h:487
RooAbsCategoryLValue is the common abstract base class for objects that represent a discrete value th...
virtual const char * getCurrentLabel() const
Return label string of current state.
value_type _currentIndex
value_type nextAvailableStateIndex() const
std::map< std::string, value_type >::const_iterator end() const
Iterator for category state names. Points to pairs of index and name.
virtual const std::map< std::string, RooAbsCategory::value_type >::value_type & defineState(const std::string &label)
Define a new state with given label.
bool hasIndex(value_type index) const
Check if a state with index index exists.
std::vector< std::string > _insertionOrder
Map state names to index numbers. Make sure state names are updated in recomputeShape().
const std::map< std::string, value_type > & stateNames() const
Access the map of state names to index numbers.
static const decltype(_stateNames) ::value_type & invalidCategory()
Is this category attached to a tree?
value_type lookupIndex(const std::string &stateName) const
Find the index number corresponding to the state name.
RooCatType is an auxilary class for RooAbsCategory and defines a a single category state.
RooCategorySharedProperties is the container for all properties that are shared between instance of R...
RooCategory is an object to represent discrete states.
Definition: RooCategory.h:23
RangeMap_t * _rangesPointerForIO
Definition: RooCategory.h:131
void addToRange(const char *rangeName, RooAbsCategory::value_type stateIndex)
Add the given state to the given range.
virtual void writeToStream(std::ostream &os, Bool_t compact) const override
compact only at the moment
void setRange(const char *rangeName, const char *stateNameList)
void defineTypes(const std::map< std::string, int > &allowedStates)
Define multiple states in a single call.
static std::map< std::string, std::weak_ptr< RangeMap_t > > _sharedRangeIOHelper
Definition: RooCategory.h:138
void installSharedRange(std::unique_ptr< RangeMap_t > &&rangeMap)
In current versions of the class, a map with ranges can be shared between instances.
bool defineType(const std::string &label)
Define a state with given name.
virtual ~RooCategory()
Destructor.
std::shared_ptr< RangeMap_t > _ranges
Map range names to allowed category states.
Definition: RooCategory.h:130
std::map< std::string, std::vector< value_type > > RangeMap_t
Definition: RooCategory.h:127
virtual Bool_t readFromStream(std::istream &is, Bool_t compact, Bool_t verbose=kFALSE) override
Read object contents from given stream.
virtual Bool_t setLabel(const char *label, bool printError=true) override
Set value by specifying the name of the desired state.
virtual value_type getCurrentIndex() const override final
Return current index.
Definition: RooCategory.h:35
value_type & operator[](const std::string &stateName)
Access a named state.
static std::map< std::string, std::weak_ptr< RangeMap_t > > _uuidToSharedRangeIOHelper
Definition: RooCategory.h:136
virtual Bool_t setIndex(Int_t index, bool printError=true) override
Set value by specifying the index code of the desired state.
void clearRange(const char *name, Bool_t silent)
Clear the named range.
std::map< std::string, RooAbsCategory::value_type > & states()
Return a reference to the map of state names to index states.
Bool_t isStateInRange(const char *rangeName, RooAbsCategory::value_type stateIndex) const
Check if the state is in the given range.
void installLegacySharedProp(const RooCategorySharedProperties *sp)
When reading old versions of the class, we get instances of shared properties.
TIterator * MakeIterator(Bool_t forward=kTRUE) const
Create a TIterator for this list.
TString asString() const
Read one token separated by any of the know punctuation characters This function recognizes and handl...
Buffer base class used for serializing objects.
Definition: TBuffer.h:42
virtual Int_t ReadClassBuffer(const TClass *cl, void *pointer, const TClass *onfile_class=0)=0
virtual Version_t ReadVersion(UInt_t *start=0, UInt_t *bcnt=0, const TClass *cl=0)=0
virtual Int_t CheckByteCount(UInt_t startpos, UInt_t bcnt, const TClass *clss)=0
Definition: TBuffer.h:85
virtual Int_t WriteClassBuffer(const TClass *cl, void *pointer)=0
Definition: TList.h:44
virtual const char * GetName() const
Returns name of object.
Definition: TNamed.h:47
Basic string class.
Definition: TString.h:131
Bool_t IsDec() const
Returns true if all characters in string are decimal digits (0-9).
Definition: TString.cxx:1873
const char * Data() const
Definition: TString.h:364
@ InputArguments
Definition: RooGlobalFunc.h:68
std::vector< std::string > tokenise(const std::string &str, const std::string &delims, bool returnEmptyToken=true)
Tokenise the string by splitting at the characters in delims.
Definition: RooHelpers.cxx:62
static constexpr double second
auto * a
Definition: textangle.C:12