Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
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 *
10 * and Stanford University. All rights reserved. *
11 * *
12 * Redistribution and use in source and binary forms, *
13 * with or without modification, are permitted according to the terms *
14 * listed in LICENSE (http://roofit.sourceforge.net/license.txt) *
15 *****************************************************************************/
16
17/**
18\class RooCategory
19\ingroup Roofitcore
20
21Object 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
311. A 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~~~
402. Like this:
41~~~{.cpp}
42RooCategory myCat("myCat", "Asymmetry");
43myCat["left"] = -1;
44myCat["right"] = 1;
45~~~
463. Or 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
71for advanced uses of categories.
72**/
73
74#include "RooCategory.h"
75
76#include "RooArgSet.h"
77#include "RooStreamParser.h"
78#include "RooMsgService.h"
79#include "RooHelpers.h"
82
83#include "ROOT/StringUtils.hxx"
84
85#include "TBuffer.h"
86#include "TString.h"
87#include "TList.h"
88
89#include <cstdlib>
90#include <iostream>
91#include <memory>
92
93using std::endl, std::istream, std::ostream;
94
95
96std::map<RooSharedProperties::UUID, std::weak_ptr<RooCategory::RangeMap_t>> RooCategory::_uuidToSharedRangeIOHelper; // Helper for restoring shared properties
97std::map<std::string, std::weak_ptr<RooCategory::RangeMap_t>> RooCategory::_sharedRangeIOHelper;
98
99
100////////////////////////////////////////////////////////////////////////////////
101
105
106
107
108////////////////////////////////////////////////////////////////////////////////
109/// Constructor. Types must be defined using defineType() before variable can be used
110RooCategory::RooCategory(const char *name, const char *title) :
112 _ranges{std::make_unique<RangeMap_t>()}
113{
114 setValueDirty() ;
115 setShapeDirty() ;
116}
117
118
119////////////////////////////////////////////////////////////////////////////////
120/// Create a new category and define allowed states.
121/// \param[in] name Name used to refer to this object.
122/// \param[in] title Title for e.g. plotting.
123/// \param[in] allowedStates Map of allowed states. Pass e.g. `{ {"0Lep", 0}, {"1Lep:, 1} }`
124RooCategory::RooCategory(const char* name, const char* title, const std::map<std::string, int>& allowedStates) :
126 _ranges{std::make_unique<RangeMap_t>()}
127{
129}
130
131
132
133////////////////////////////////////////////////////////////////////////////////
134/// Copy constructor
135
138 _ranges(other._ranges)
139{
140}
141
142
143////////////////////////////////////////////////////////////////////////////////
144/// Destructor
145
149
150
151
152
153////////////////////////////////////////////////////////////////////////////////
154/// Set value by specifying the index code of the desired state.
155/// If printError is set, a message will be printed if
156/// the specified index does not represent a valid state.
157/// \return bool signalling if an error occurred.
158bool RooCategory::setIndex(Int_t index, bool printError)
159{
160 if (!hasIndex(index)) {
161 if (printError) {
162 coutE(InputArguments) << "RooCategory: Trying to set invalid state " << index << " for category " << GetName() << std::endl;
163 }
164 return true;
165 }
166
169
170 return false;
171}
172
173
174
175////////////////////////////////////////////////////////////////////////////////
176/// Set value by specifying the name of the desired state.
177/// If printError is set, a message will be printed if
178/// the specified label does not represent a valid state.
179/// \return false on success.
180bool RooCategory::setLabel(const char* label, bool printError)
181{
182 const auto item = stateNames().find(label);
183 if (item != stateNames().end()) {
184 _currentIndex = item->second;
186 return false;
187 }
188
189 if (printError) {
190 coutE(InputArguments) << "Trying to set invalid state label '" << label << "' for category " << GetName() << std::endl;
191 }
192
193 return true;
194}
195
196
197
198////////////////////////////////////////////////////////////////////////////////
199/// Define a state with given name.
200/// The lowest available positive integer is assigned as index. Category
201/// state labels may not contain semicolons.
202/// \return True in case of an error.
203bool RooCategory::defineType(const std::string& label)
204{
205 if (label.find(';') != std::string::npos) {
206 coutE(InputArguments) << "RooCategory::defineType(" << GetName()
207 << "): semicolons not allowed in label name" << std::endl ;
208 return true;
209 }
210
212}
213
214
215////////////////////////////////////////////////////////////////////////////////
216/// Define a state with given name and index. Category
217/// state labels may not contain semicolons.
218/// \return True in case of error.
219bool RooCategory::defineType(const std::string& label, Int_t index)
220{
221 if (label.find(';') != std::string::npos) {
222 coutE(InputArguments) << "RooCategory::defineType(" << GetName()
223 << "): semicolons not allowed in label name" << std::endl ;
224 return true;
225 }
226
228}
229
230
231////////////////////////////////////////////////////////////////////////////////
232/// Define multiple states in a single call. Use like:
233/// ```
234/// myCat.defineTypes({ {"0Lep", 0}, {"1Lep", 1}, {"2Lep", 2}, {"3Lep", 3} });
235/// ```
236/// Note: When labels or indices are defined multiple times, an error message is printed,
237/// and the corresponding state is ignored.
238void RooCategory::defineTypes(const std::map<std::string, int>& allowedStates) {
239 for (const auto& nameAndIdx : allowedStates) {
240 defineType(nameAndIdx.first, nameAndIdx.second);
241 }
242}
243
244
245////////////////////////////////////////////////////////////////////////////////
246/// Access a named state. If a state with this name doesn't exist yet, the state is
247/// assigned the next available positive integer.
248/// \param[in] stateName Name of the state to be accessed.
249/// \return Reference to the category index. If no state exists, it will be created on the fly.
252 if (stateNames().count(stateName) == 0) {
253 _insertionOrder.push_back(stateName);
255
256 }
257
258 return stateNames()[stateName];
259}
260
261
262////////////////////////////////////////////////////////////////////////////////
263/// Return a reference to the map of state names to index states.
264/// This can be used to manipulate the category.
265/// \note Calling this function will **always** trigger recomputations of
266/// of **everything** that depends on this category, since in case the map gets
267/// manipulated, names or indices might change. Also, the order that states have
268/// been inserted in gets lost. This changes what is returned by getOrdinal().
269std::map<std::string, RooAbsCategory::value_type>& RooCategory::states() {
270 auto& theStates = stateNames();
273 _insertionOrder.clear();
274 return theStates;
275}
276
277
278////////////////////////////////////////////////////////////////////////////////
279/// Read object contents from given stream. If token is a decimal digit, try to
280/// find a corresponding state for it. If that succeeds, the state denoted by this
281/// index is used. Otherwise, interpret it as a label.
282bool RooCategory::readFromStream(istream& is, bool /*compact*/, bool verbose)
283{
284 // Read single token
285 RooStreamParser parser(is) ;
286 TString token = parser.readToken() ;
287
288 if (token.IsDec() && hasIndex(std::stoi(token.Data()))) {
289 return setIndex(std::stoi(token.Data()), verbose);
290 } else {
291 return setLabel(token,verbose) ;
292 }
293}
294
295
296
297////////////////////////////////////////////////////////////////////////////////
298/// compact only at the moment
299
300void RooCategory::writeToStream(ostream& os, bool compact) const
301{
302 if (compact) {
303 os << getCurrentIndex() ;
304 } else {
305 os << getCurrentLabel() ;
306 }
307}
308
309
310////////////////////////////////////////////////////////////////////////////////
311/// Clear the named range.
312/// \note This affects **all** copies of this category, because they are sharing
313/// range definitions. This ensures that categories inside a dataset and their
314/// counterparts on the outside will both see a modification of the range.
315void RooCategory::clearRange(const char* name, bool silent)
316{
317 std::map<std::string, std::vector<value_type>>::iterator item = _ranges->find(name);
318 if (item == _ranges->end()) {
319 if (!silent)
320 coutE(InputArguments) << "RooCategory::clearRange(" << GetName() << ") ERROR: must specify valid range name" << std::endl ;
321 return;
322 }
323
324 _ranges->erase(item);
325}
326
327
328////////////////////////////////////////////////////////////////////////////////
329
330void RooCategory::setRange(const char* name, const char* stateNameList)
331{
332 clearRange(name,true) ;
334}
335
336
337////////////////////////////////////////////////////////////////////////////////
338/// Add the given state to the given range.
339/// \note This creates or accesses a **shared** map with allowed ranges. All copies of this
340/// category will share this range such that a category inside a dataset and its
341/// counterpart on the outside will both see a modification of the range.
343 auto item = _ranges->find(name);
344 if (item == _ranges->end()) {
345 if (!name) {
346 coutE(Contents) << "RooCategory::addToRange(" << GetName()
347 << "): Need valid range name." << std::endl;
348 return;
349 }
350
351 item = _ranges->emplace(name, std::vector<value_type>()).first;
352 coutI(Contents) << "RooCategory::setRange(" << GetName()
353 << ") new range named '" << name << "' created for state " << stateIndex << std::endl ;
354 }
355
356 item->second.push_back(stateIndex);
357}
358
359
360////////////////////////////////////////////////////////////////////////////////
361/// Add the list of state names to the given range. State names can be separated
362/// with ','.
363/// \note This creates or accesses a **shared** map with allowed ranges. All copies of this
364/// category will share this range such that a category inside a dataset and its
365/// counterpart on the outside will both see a modification of the range.
366void RooCategory::addToRange(const char* name, const char* stateNameList)
367{
368 if (!stateNameList) {
369 coutE(InputArguments) << "RooCategory::setRange(" << GetName() << ") ERROR: must specify valid name and state name list" << std::endl ;
370 return;
371 }
372
373 // Parse list of state names, verify that each is valid and add them to the list
374 for (const auto& token : ROOT::Split(stateNameList, ",")) {
375 const value_type idx = lookupIndex(token);
376 if (idx != invalidCategory().second) {
377 addToRange(name, idx);
378 } else {
379 coutW(InputArguments) << "RooCategory::setRange(" << GetName() << ") WARNING: Ignoring invalid state name '"
380 << token << "' in state name list" << std::endl ;
381 }
382 }
383}
384
385
386////////////////////////////////////////////////////////////////////////////////
387/// Check if the state is in the given range.
388/// If no range is specified either as argument or if no range has been defined for this category
389/// (*i.e.*, the default range is meant), all category states count as being in range.
391 if (rangeName == nullptr || _ranges->empty())
392 return true;
393
394 const auto item = _ranges->find(rangeName);
395 if (item == _ranges->end())
396 return false;
397
398 const std::vector<value_type>& vec = item->second;
399 return std::find(vec.begin(), vec.end(), stateIndex) != vec.end();
400}
401
402
403////////////////////////////////////////////////////////////////////////////////
404/// Check if the state is in the given range.
405/// If no range is specified (*i.e.*, the default range), all category states count as being in range.
406/// This overload requires a name lookup. Recommend to use the category index with
407/// RooCategory::isStateInRange(const char*, RooAbsCategory::value_type) const.
408bool RooCategory::isStateInRange(const char* rangeName, const char* stateName) const
409{
410 // Check that both input arguments are not null pointers
411 if (!rangeName) {
412 return true;
413 }
414
415 if (!stateName) {
416 coutE(InputArguments) << "RooCategory::isStateInRange(" << GetName() << ") ERROR: must specify valid state name" << std::endl ;
417 return false;
418 }
419
421}
422
423
424////////////////////////////////////////////////////////////////////////////////
425
427{
428 UInt_t R__s;
429 UInt_t R__c;
430 if (R__b.IsReading()) {
431
432 Version_t R__v = R__b.ReadVersion(&R__s, &R__c);
433
434 if (R__v==1) {
436
437 // In v1, properties were a direct pointer:
439 R__b >> props;
441 // props was allocated by I/O system, we cannot delete here in case it gets reused
442
443 } else if (R__v == 2) {
445
446 // In v2, properties were written directly into the class buffer
447 auto props = std::make_unique<RooCategorySharedProperties>();
448 props->Streamer(R__b);
450
451 } else {
452 // Starting at v3, ranges are shared using a shared pointer, which cannot be read by ROOT's I/O.
453 // Instead, ranges are written as a normal pointer, and here we restore the sharing.
454 R__b.ReadClassBuffer(RooCategory::Class(), this, R__v, R__s, R__c);
455 installSharedRange(std::unique_ptr<RangeMap_t>(_rangesPointerForIO));
456 _rangesPointerForIO = nullptr;
457 }
458
459 R__b.CheckByteCount(R__s, R__c, RooCategory::IsA());
460
461 } else {
462 // Since we cannot write shared pointers yet, assign the shared ranges to a normal pointer,
463 // write, and restore.
464 if (_ranges)
466
467 R__b.WriteClassBuffer(RooCategory::Class(), this);
468 _rangesPointerForIO = nullptr;
469 }
470}
471
472
473/// When reading old versions of the class, we get instances of shared properties.
474/// Since these only contain ranges with numbers, just convert to vectors of numbers.
476 if (props == nullptr || (*props == RooCategorySharedProperties("00000000-0000-0000-0000-000000000000")))
477 return;
478
480 if (auto existingObject = weakPtr.lock()) {
481 // We know this range, start sharing
482 _ranges = std::move(existingObject);
483 } else {
484 // This range is unknown, make a new object
485 _ranges = std::make_unique<std::map<std::string, std::vector<value_type>>>();
486 auto& rangesMap = *_ranges;
487
488 // Copy the data:
489 for (auto * olist : static_range_cast<TList*>(props->_altRanges)) {
490 std::vector<value_type>& vec = rangesMap[olist->GetName()];
491
492
494 vec.push_back(ctype->getVal());
495 }
496 }
497
498 // Register the shared_ptr for future sharing
500 }
501}
502
503
504/// In current versions of the class, a map with ranges can be shared between instances.
505/// If an instance with the same name already uses the same map, the instances will start sharing.
506/// Otherwise, this instance will be registered, and future copies being read will share with this
507/// one.
508void RooCategory::installSharedRange(std::unique_ptr<RangeMap_t>&& rangeMap) {
509 if (rangeMap == nullptr)
510 return;
511
513 if (&a == &b)
514 return true;
515
516 if (a.size() != b.size())
517 return false;
518
519 for (const auto& itemA : a) {
520 const auto itemB = b.find(itemA.first);
521 if (itemB == b.end())
522 return false;
523
524 if (itemA.second != itemB->second)
525 return false;
526 }
527
528 return true;
529 };
530
531
533 auto existingMap = weakPtr.lock();
535 // We know this map, use the shared one.
536 _ranges = std::move(existingMap);
537 if (rangeMap.get() == _ranges.get()) {
538 // This happens when ROOT's IO has written the same pointer twice. We cannot delete now.
539 (void) rangeMap.release(); // NOLINT: clang-tidy is normally right that this leaks. Here, we need to leave the result unused, though.
540 }
541 } else {
542 // We don't know this map. Register for sharing.
543 _ranges = std::move(rangeMap);
545 }
546}
#define b(i)
Definition RSha256.hxx:100
#define a(i)
Definition RSha256.hxx:99
#define coutI(a)
#define coutW(a)
#define coutE(a)
short Version_t
Class version identifier (short)
Definition RtypesCore.h:80
ROOT::Detail::TRangeCast< T, true > TRangeDynCast
TRangeDynCast is an adapter class that allows the typed iteration through a TCollection.
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t index
char name[80]
Definition TGX11.cxx:148
const_iterator end() const
void setShapeDirty()
Notify that a shape-like property (e.g. binning) has changed.
Definition RooAbsArg.h:431
friend void RooRefArray::Streamer(TBuffer &)
void setValueDirty()
Mark the element dirty. This forces a re-evaluation when a value is requested.
Definition RooAbsArg.h:425
Abstract base class for objects that represent a discrete value that can be set from the outside,...
virtual const char * getCurrentLabel() const
Return label string of current state.
value_type _currentIndex
Current category state.
value_type nextAvailableStateIndex() const
static const decltype(_stateNames) ::value_type & invalidCategory()
A category state to signify an invalid category.
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.
std::vector< std::string > _insertionOrder
Keeps track in which order state numbers have been inserted. Make sure this is updated in recomputeSh...
const std::map< std::string, value_type > & stateNames() const
Access the map of state names to index numbers.
bool hasIndex(value_type index) const
Check if a state with index index exists.
value_type lookupIndex(const std::string &stateName) const
Find the index number corresponding to the state name.
Object to represent discrete states.
Definition RooCategory.h:28
RangeMap_t * _rangesPointerForIO
Pointer to the same object as _ranges, but not shared for I/O.
void addToRange(const char *rangeName, RooAbsCategory::value_type stateIndex)
Add the given state to the given range.
bool setIndex(Int_t index, bool printError=true) override
Set value by specifying the index code of the desired state.
void setRange(const char *rangeName, const char *stateNameList)
void defineTypes(const std::map< std::string, int > &allowedStates)
Define multiple states in a single call.
void writeToStream(std::ostream &os, bool compact) const override
compact only at the moment
static std::map< std::string, std::weak_ptr< RangeMap_t > > _sharedRangeIOHelper
Helper for restoring shared ranges from current versions of this class read from files....
TClass * IsA() const override
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.
void clearRange(const char *name, bool silent)
Clear the named range.
std::shared_ptr< RangeMap_t > _ranges
Map range names to allowed category states.
static std::map< RooSharedProperties::UUID, std::weak_ptr< RangeMap_t > > _uuidToSharedRangeIOHelper
Helper for restoring shared ranges from old versions of this class read from files....
value_type & operator[](const std::string &stateName)
Access a named state.
bool readFromStream(std::istream &is, bool compact, bool verbose=false) override
Read object contents from given stream.
bool setLabel(const char *label, bool printError=true) override
Set value by specifying the name of the desired state.
std::map< std::string, RooAbsCategory::value_type > & states()
Return a reference to the map of state names to index states.
bool isStateInRange(const char *rangeName, RooAbsCategory::value_type stateIndex) const
Check if the state is in the given range.
value_type getCurrentIndex() const final
Return current index.
Definition RooCategory.h:40
std::map< std::string, std::vector< value_type > > RangeMap_t
void installLegacySharedProp(const RooCategorySharedProperties *sp)
When reading old versions of the class, we get instances of shared properties.
~RooCategory() override
Destructor.
static TClass * Class()
TString readToken()
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:43
const char * GetName() const override
Returns name of object.
Definition TNamed.h:49
Basic string class.
Definition TString.h:138
std::vector< std::string > Split(std::string_view str, std::string_view delims, bool skipEmpty=false)
Splits a string at each character in delims.