Logo ROOT  
Reference Guide
Loading...
Searching...
No Matches
RooFitImplHelpers.cxx
Go to the documentation of this file.
1/// \cond ROOFIT_INTERNAL
2
3/*
4 * Project: RooFit
5 * Author:
6 * Jonas Rembser, CERN 2023
7 *
8 * Copyright (c) 2023, CERN
9 *
10 * Redistribution and use in source and binary forms,
11 * with or without modification, are permitted according to the terms
12 * listed in LICENSE (http://roofit.sourceforge.net/license.txt)
13 */
14
15#include <RooFitImplHelpers.h>
16
17#include <RooAbsCategory.h>
18#include <RooAbsData.h>
19#include <RooAbsPdf.h>
20#include <RooAbsRealLValue.h>
21#include <RooArgList.h>
22#include <RooDataHist.h>
23#include <RooDataSet.h>
24#include <RooProdPdf.h>
25#include <RooRealSumPdf.h>
26#include <RooSimultaneous.h>
27
28#include <ROOT/StringUtils.hxx>
29#include <TClass.h>
30
31#include <unordered_map>
32
33namespace RooHelpers {
34
35namespace {
36std::pair<double, double> getBinningInterval(RooAbsBinning const &binning)
37{
38 if (!binning.isParameterized()) {
39 return {binning.lowBound(), binning.highBound()};
40 } else {
41 return {binning.lowBoundFunc()->getVal(), binning.highBoundFunc()->getVal()};
42 }
43}
44} // namespace
45
46/// Get the lower and upper bound of parameter range if arg can be casted to RooAbsRealLValue.
47/// If no range with rangeName is defined for the argument, this will check if a binning of the
48/// same name exists and return the interval covered by the binning.
49/// Returns `{-infinity, infinity}` if argument can't be casted to RooAbsRealLValue* or if no
50/// range or binning with the requested name exists.
51/// \param[in] arg RooAbsArg for which to get the range.
52/// \param[in] rangeName The name of the range.
53std::pair<double, double> getRangeOrBinningInterval(RooAbsArg const *arg, const char *rangeName)
54{
55 auto rlv = dynamic_cast<RooAbsRealLValue const *>(arg);
56 if (rlv) {
57 if (rangeName && rlv->hasRange(rangeName)) {
58 return {rlv->getMin(rangeName), rlv->getMax(rangeName)};
59 } else if (auto binning = rlv->getBinningPtr(rangeName)) {
60 return getBinningInterval(*binning);
61 }
62 }
63 return {-std::numeric_limits<double>::infinity(), +std::numeric_limits<double>::infinity()};
64}
65
66/// Check if there is any overlap when a list of ranges is applied to a set of observables.
67/// \param[in] observables The observables to check for overlap
68/// \param[in] rangeNames The names of the ranges.
69bool checkIfRangesOverlap(RooArgSet const &observables, std::vector<std::string> const &rangeNames)
70{
71 // cache the range limits in a flat vector
72 std::vector<std::pair<double, double>> limits;
73 limits.reserve(rangeNames.size() * observables.size());
74
75 for (auto const &range : rangeNames) {
76 for (auto const &obs : observables) {
77 if (dynamic_cast<RooAbsCategory const *>(obs)) {
78 // Nothing to be done for category observables
79 } else if (auto *rlv = dynamic_cast<RooAbsRealLValue const *>(obs)) {
80 limits.emplace_back(rlv->getMin(range.c_str()), rlv->getMax(range.c_str()));
81 } else {
82 throw std::logic_error(
83 "Classes that represent observables are expected to inherit from RooAbsRealLValue or RooAbsCategory!");
84 }
85 }
86 }
87
88 auto nRanges = rangeNames.size();
89 auto nObs = limits.size() / nRanges; // number of observables that are not categories
90
91 // loop over pairs of ranges
92 for (size_t ir1 = 0; ir1 < nRanges; ++ir1) {
93 for (size_t ir2 = ir1 + 1; ir2 < nRanges; ++ir2) {
94
95 // Loop over observables. If all observables have overlapping limits for
96 // these ranges, the hypercubes defining the range are overlapping and we
97 // can return `true`.
98 size_t overlaps = 0;
99 for (size_t io1 = 0; io1 < nObs; ++io1) {
100 auto r1 = limits[ir1 * nObs + io1];
101 auto r2 = limits[ir2 * nObs + io1];
102 overlaps +=
103 (r1.second > r2.first && r1.first < r2.second) || (r2.second > r1.first && r2.first < r1.second);
104 }
105 if (overlaps == nObs)
106 return true;
107 }
108 }
109
110 return false;
111}
112
113/// Create a string with all sorted names of RooArgSet elements separated by delimiters.
114/// \param[in] argSet The input RooArgSet.
115std::string getColonSeparatedNameString(RooArgSet const &argSet, char delim)
116{
117
118 RooArgList tmp(argSet);
119 tmp.sort();
120
121 std::string content;
122 for (auto const &arg : tmp) {
123 content += arg->GetName();
124 content += delim;
125 }
126 if (!content.empty()) {
127 content.pop_back();
128 }
129 return content;
130}
131
132/// Construct a RooArgSet of objects in a RooArgSet whose names match to those
133/// in the names string.
134/// \param[in] argSet The input RooArgSet.
135/// \param[in] names The names of the objects to select in a colon-separated string.
136RooArgSet selectFromArgSet(RooArgSet const &argSet, std::string const &names)
137{
138 RooArgSet output;
139 for (auto const &name : ROOT::Split(names, ":")) {
140 if (auto arg = argSet.find(name.c_str()))
141 output.add(*arg);
142 }
143 return output;
144}
145
146std::string getRangeNameForSimComponent(std::string const &rangeName, bool splitRange, std::string const &catName)
147{
148 if (splitRange && !rangeName.empty()) {
149 std::string out;
150 auto tokens = ROOT::Split(rangeName, ",");
151 for (std::string const &token : tokens) {
152 out += token + "_" + catName + ",";
153 }
154 out.pop_back(); // to remove the last comma
155 return out;
156 }
157
158 return rangeName;
159}
160
161BinnedLOutput getBinnedL(RooAbsPdf const &pdf)
162{
163 if (pdf.getAttribute("BinnedLikelihood") && pdf.IsA()->InheritsFrom(RooRealSumPdf::Class())) {
164 // Simplest case: top-level of component is a RooRealSumPdf
165 return {const_cast<RooAbsPdf *>(&pdf), true};
166 } else if (pdf.IsA()->InheritsFrom(RooProdPdf::Class())) {
167 // Default case: top-level pdf is a product of RooRealSumPdf and other pdfs
168 for (RooAbsArg *component : static_cast<RooProdPdf const &>(pdf).pdfList()) {
169 if (component->getAttribute("BinnedLikelihood") && component->IsA()->InheritsFrom(RooRealSumPdf::Class())) {
170 return {static_cast<RooAbsPdf *>(component), true};
171 }
172 if (component->getAttribute("MAIN_MEASUREMENT")) {
173 // not really a binned pdf, but this prevents a (potentially) long list of subsidiary measurements to be
174 // passed to the slave calculator
175 return {static_cast<RooAbsPdf *>(component), false};
176 }
177 }
178 }
179 return {nullptr, false};
180}
181
182/// Get the topologically-sorted list of all nodes in the computation graph.
183void getSortedComputationGraph(RooAbsArg const &func, RooArgSet &out)
184{
185 // Get the set of nodes in the computation graph. Do the detour via
186 // RooArgList to avoid deduplication done after adding each element.
187 RooArgList serverList;
188 func.treeNodeServerList(&serverList, nullptr, true, true, false, true);
189 // If we fill the servers in reverse order, they are approximately in
190 // topological order so we save a bit of work in sortTopologically().
191 out.add(serverList.rbegin(), serverList.rend(), /*silent=*/true);
192 // Sort nodes topologically: the servers of any node will be before that
193 // node in the collection.
194 out.sortTopologically();
195}
196
197namespace Detail {
198
199namespace {
200
201using ToCloneList = std::vector<RooAbsArg const *>;
202using ToCloneMap = std::unordered_map<TNamed const *, RooAbsArg const *>;
203
204// Add clones of servers of given argument to end of list
205void addServerClonesToList(const RooAbsArg &var, ToCloneList &outlist, ToCloneMap &outmap, bool deepCopy,
206 RooArgSet const *observables)
207{
208 if (outmap.find(var.namePtr()) != outmap.end()) {
209 return;
210 }
211
212 if (observables && var.isFundamental() && !observables->find(var)) {
213 return;
214 }
215
216 outmap[var.namePtr()] = &var;
217 outlist.push_back(&var);
218
219 if (deepCopy) {
220 for (const auto server : var.servers()) {
221 addServerClonesToList(*server, outlist, outmap, deepCopy, observables);
222 }
223 }
224}
225
226} // namespace
227
228/// Implementation of RooAbsCollection::snapshot() with some extra parameters.
229/// to be used in other RooHelpers functions.
230/// param[in] input The input collection.
231/// param[in] output The output collection.
232/// param[in] deepCopy If the whole computation graph should be cloned recursively.
233/// param[in] observables If this is not a nullptr, only the fundamental
234/// variables that are in observables are deep cloned.
235bool snapshotImpl(RooAbsCollection const &input, RooAbsCollection &output, bool deepCopy, RooArgSet const *observables)
236{
237 // Figure out what needs to be cloned
238 ToCloneList toCloneList;
239 ToCloneMap toCloneMap;
240 for (RooAbsArg *orig : input) {
241 addServerClonesToList(*orig, toCloneList, toCloneMap, deepCopy, observables);
242 }
243
244 // Actually do the cloning
245 output.reserve(toCloneList.size());
246 for (RooAbsArg const *arg : toCloneList) {
247 std::unique_ptr<RooAbsArg> serverClone{static_cast<RooAbsArg *>(arg->Clone())};
248 serverClone->setAttribute("SnapShot_ExtRefClone");
249 output.addOwned(std::move(serverClone));
250 }
251
252 // Redirect all server connections to internal list members
253 for (RooAbsArg *var : output) {
254 var->redirectServers(output, deepCopy && !observables);
255 }
256
257 return false;
258}
259
260RooAbsArg *cloneTreeWithSameParametersImpl(RooAbsArg const &arg, RooArgSet const *observables)
261{
262 // Clone tree using snapshot
263 RooArgSet clonedNodes;
264 snapshotImpl(RooArgSet(arg), clonedNodes, true, observables);
265
266 // Find the head node in the cloneSet
267 RooAbsArg *head = clonedNodes.find(arg);
268 assert(head);
269
270 // We better to release the ownership before removing the "head". Otherwise,
271 // "head" might also be deleted as the clonedNodes collection owns it.
272 // (Actually this does not happen because even an owning collection doesn't
273 // delete the element when removed by pointer lookup, but it's better not to
274 // rely on this unexpected fact).
275 clonedNodes.releaseOwnership();
276
277 // Remove the head node from the cloneSet
278 // To release it from the set ownership
279 clonedNodes.remove(*head);
280
281 // Add the set as owned component of the head
282 head->addOwnedComponents(std::move(clonedNodes));
283
284 return head;
285}
286
287} // namespace Detail
288
289} // namespace RooHelpers
290
291namespace RooFit::Detail {
292
293/// Transform a string into a valid C++ variable name by replacing forbidden
294/// characters with underscores.
295/// @param in The input string.
296/// @return A new string valid variable name.
297std::string makeValidVarName(std::string const &in)
298{
299 std::string out;
300 if (std::isdigit(in[0])) {
301 out += '_';
302 }
303 out += in;
304 std::transform(out.begin(), out.end(), out.begin(), [](char c) { return std::isalnum(c) ? c : '_'; });
305 return out;
306}
307
308/// Replace all occurrences of `what` with `with` inside of `inOut`.
309void replaceAll(std::string &inOut, std::string_view what, std::string_view with)
310{
311 for (std::string::size_type pos{}; inOut.npos != (pos = inOut.find(what.data(), pos, what.length()));
312 pos += with.length()) {
313 inOut.replace(pos, what.length(), with.data(), with.length());
314 }
315}
316
317std::string makeSliceCutString(RooArgSet const &sliceDataSet)
318{
319 std::stringstream cutString;
320 bool first = true;
321 for (RooAbsArg *sliceVar : sliceDataSet) {
322 if (!first) {
323 cutString << "&&";
324 } else {
325 first = false;
326 }
327
328 if (auto *real = dynamic_cast<RooAbsRealLValue *>(sliceVar)) {
329 cutString << real->GetName() << "==" << real->getVal();
330 } else if (auto *cat = dynamic_cast<RooAbsCategoryLValue *>(sliceVar)) {
331 cutString << cat->GetName() << "==" << cat->getCurrentIndex();
332 }
333 }
334 return cutString.str();
335}
336
337} // namespace RooFit::Detail
338
339namespace {
340
341double stringToDoubleImpl(std::stringstream &ss)
342{
343 double output;
344 if (!(ss >> output)) {
345 throw std::invalid_argument("Conversion to floating point failed");
346 }
347 return output;
348}
349
350} // namespace
351
352double toDouble(const char *s)
353{
354 std::stringstream ss(s);
355 return stringToDoubleImpl(ss);
356}
357
358double toDouble(const std::string &s)
359{
360 std::stringstream ss(s);
361 return stringToDoubleImpl(ss);
362}
363
364/// \endcond
#define c(i)
Definition RSha256.hxx:101
double toDouble(const char *s)
char name[80]
Definition TGX11.cxx:148
const TNamed * namePtr() const
De-duplicated pointer to this object's name.
Definition RooAbsArg.h:502
bool addOwnedComponents(const RooAbsCollection &comps)
Take ownership of the contents of 'comps'.
bool redirectServers(const RooAbsCollection &newServerList, bool mustReplaceAll=false, bool nameChange=false, bool isRecursionStep=false)
Replace all direct servers of this object with the new servers in newServerList.
const RefCountList_t & servers() const
List of all servers of this object.
Definition RooAbsArg.h:145
bool getAttribute(const Text_t *name) const
Check if a named attribute is set. By default, all attributes are unset.
TObject * Clone(const char *newname=nullptr) const override
Make a clone of an object using the Streamer facility.
Definition RooAbsArg.h:88
virtual bool isFundamental() const
Is this object a fundamental type that can be added to a dataset?
Definition RooAbsArg.h:175
void treeNodeServerList(RooAbsCollection *list, const RooAbsArg *arg=nullptr, bool doBranch=true, bool doLeaf=true, bool valueOnly=false, bool recurseNonDerived=false) const
Fill supplied list with nodes of the arg tree, following all server links, starting with ourself as t...
virtual bool isParameterized() const
Interface function.
virtual double highBound() const =0
virtual double lowBound() const =0
virtual RooAbsReal * highBoundFunc() const
Return pointer to RooAbsReal parameterized upper bound, if any.
virtual RooAbsReal * lowBoundFunc() const
Return pointer to RooAbsReal parameterized lower bound, if any.
virtual bool remove(const RooAbsArg &var, bool silent=false, bool matchByNameOnly=false)
Remove the specified argument from our list.
void sortTopologically()
Sort collection topologically: the servers of any RooAbsArg will be before that RooAbsArg in the coll...
Storage_t::const_reverse_iterator rend() const
virtual bool add(const RooAbsArg &var, bool silent=false)
Add the specified argument to list.
Storage_t::const_reverse_iterator rbegin() const
Storage_t::size_type size() const
void reserve(Storage_t::size_type count)
virtual bool addOwned(RooAbsArg &var, bool silent=false)
Add an argument and transfer the ownership to the collection.
RooAbsArg * find(const char *name) const
Find object with given name in list.
TClass * IsA() const override
Definition RooAbsPdf.h:345
double getVal(const RooArgSet *normalisationSet=nullptr) const
Evaluate object.
Definition RooAbsReal.h:107
static TClass * Class()
static TClass * Class()
Bool_t InheritsFrom(const char *cl) const override
Return kTRUE if this class inherits from a class with name "classname".
Definition TClass.cxx:4932
const char * GetName() const override
Returns name of object.
Definition TNamed.h:49
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::string makeValidVarName(std::string const &in)
void replaceAll(std::string &inOut, std::string_view what, std::string_view with)
std::string makeSliceCutString(RooArgSet const &sliceDataSet)
RooAbsArg * cloneTreeWithSameParametersImpl(RooAbsArg const &arg, RooArgSet const *observables)
bool snapshotImpl(RooAbsCollection const &input, RooAbsCollection &output, bool deepCopy, RooArgSet const *observables)
RooArgSet selectFromArgSet(RooArgSet const &, std::string const &names)
bool checkIfRangesOverlap(RooArgSet const &observables, std::vector< std::string > const &rangeNames)
std::string getRangeNameForSimComponent(std::string const &rangeName, bool splitRange, std::string const &catName)
void getSortedComputationGraph(RooAbsArg const &func, RooArgSet &out)
BinnedLOutput getBinnedL(RooAbsPdf const &pdf)
std::string getColonSeparatedNameString(RooArgSet const &argSet, char delim=':')
std::pair< double, double > getRangeOrBinningInterval(RooAbsArg const *arg, const char *rangeName)
static const char * what
Definition stlLoader.cc:5