Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
RooFormula.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 RooFormula.cxx
21\class RooFormula
22\ingroup Roofitcore
23
24Internally uses ROOT's TFormula to compute user-defined expressions of RooAbsArgs.
25
26The string expression can be any valid TFormula expression referring to the
27listed servers either by name or by their ordinal list position. These three are
28forms equivalent:
29```
30 RooFormula("formula", "x*y", RooArgList(x,y)) or
31 RooFormula("formula", "@0*@1", RooArgList(x,y))
32 RooFormula("formula", "x[0]*x[1]", RooArgList(x,y))
33```
34Note that `x[i]` is an expression reserved for TFormula. If a variable with
35the name `x` is given, the RooFormula interprets `x` as a variable name,
36but `x[i]` as an index in the list of variables.
37
38### Category expressions
39State information of RooAbsCategories can be accessed using the '::' operator,
40*i.e.*, `tagCat::Kaon` will resolve to the numerical value of
41the `Kaon` state of the RooAbsCategory object named `tagCat`.
42
43A formula to switch between lepton categories could look like this:
44```
45 RooFormula("formulaWithCat",
46 "x * (leptonMulti == leptonMulti::one) + y * (leptonMulti == leptonMulti::two)",
47 RooArgList(x, y, leptonMulti));
48```
49
50### Debugging a formula that won't compile
51When the formula is preprocessed, RooFit can print information in the debug stream.
52These can be retrieved by activating the RooFit::MsgLevel `RooFit::DEBUG`
53and the RooFit::MsgTopic `RooFit::InputArguments`.
54Check the tutorial rf506_msgservice.C for details.
55**/
56
57#include "RooFormula.h"
58#include "RooAbsReal.h"
59#include "RooAbsCategory.h"
60#include "RooArgList.h"
61#include "RooMsgService.h"
62#include "RooBatchCompute.h"
63
64#include "TObjString.h"
65
66#include <memory>
67#include <regex>
68#include <sstream>
69#include <cctype>
70
71using std::sregex_iterator, std::ostream;
72
73namespace {
74
75////////////////////////////////////////////////////////////////////////////////
76/// Find all input arguments which are categories, and save this information in
77/// with the names of the variables that are being used to evaluate it.
78std::vector<bool> findCategoryServers(const RooAbsCollection& collection) {
79 std::vector<bool> output;
80 output.reserve(collection.size());
81
82 for (unsigned int i = 0; i < collection.size(); ++i) {
83 output.push_back(collection[i]->InheritsFrom(RooAbsCategory::Class()));
84 }
85
86 return output;
87}
88
89/// Convert `@i`-style references to `x[i]`.
90void convertArobaseReferences(std::string &formula)
91{
92 bool match = false;
93 for (std::size_t i = 0; i < formula.size(); ++i) {
94 if (match && !isdigit(formula[i])) {
95 formula.insert(formula.begin() + i, ']');
96 i += 1;
97 match = false;
98 } else if (!match && formula[i] == '@') {
99 formula[i] = 'x';
100 formula.insert(formula.begin() + i + 1, '[');
101 i += 1;
102 match = true;
103 }
104 }
105 if (match)
106 formula += ']';
107}
108
109/// Replace all occurrences of `what` with `with` inside of `inOut`.
110void replaceAll(std::string &inOut, std::string_view what, std::string_view with)
111{
112 for (std::string::size_type pos{}; inOut.npos != (pos = inOut.find(what.data(), pos, what.length()));
113 pos += with.length()) {
114 inOut.replace(pos, what.length(), with.data(), with.length());
115 }
116}
117
118/// Find the word boundaries with a static std::regex and return a bool vector
119/// flagging their positions. The end of the string is considered a word
120/// boundary.
121std::vector<bool> getWordBoundaryFlags(std::string const &s)
122{
123 static const std::regex r{"\\b"};
124 std::vector<bool> out(s.size() + 1);
125
126 for (auto i = std::sregex_iterator(s.begin(), s.end(), r); i != std::sregex_iterator(); ++i) {
127 std::smatch m = *i;
128 m.position()] = true;
129 }
130
131 // The end of a string is also a word boundary
132 out[s.size()] = true;
133
134 return out;
135}
136
137/// Replace all named references with "x[i]"-style.
138void replaceVarNamesWithIndexStyle(std::string &formula, RooArgList const &varList)
139{
140 std::vector<bool> isWordBoundary = getWordBoundaryFlags(formula);
141 for (unsigned int i = 0; i < varList.size(); ++i) {
142 std::string_view varName = varList[i].GetName();
143
144 std::stringstream replacementStream;
145 replacementStream << "x[" << i << "]";
146 std::string replacement = replacementStream.str();
147
148 for (std::string::size_type pos{}; formula.npos != (pos = formula.find(varName.data(), pos, varName.length()));
149 pos += replacement.size()) {
150
151 std::string::size_type next = pos + varName.length();
152
153 // The matched variable name has to be surrounded by word boundaries
154 // std::cout << pos << " " << next << std::endl;
155 if (!isWordBoundary[pos] || !isWordBoundary[next])
156 continue;
157
158 // Veto '[' and ']' as next characters. If the variable is called `x`
159 // or `0`, this might otherwise replace `x[0]`.
160 if (next < formula.size() && (formula[next] == '[' || formula[next] == ']')) {
161 continue;
162 }
163
164 // As we replace substrings in the middle of the string, we also have
165 // to update the word boundary flag vector. Note that we don't care
166 // the word boundaries in the `x[i]` are correct, as it has already
167 // been replaced.
168 std::size_t nOld = varName.length();
169 std::size_t nNew = replacement.size();
170 auto wbIter = isWordBoundary.begin() + pos;
171 if (nNew > nOld) {
172 isWordBoundary.insert(wbIter + nOld, nNew - nOld, false);
173 } else if (nNew < nOld) {
175 }
176
177 // Do the actual replacement
178 formula.replace(pos, varName.length(), replacement);
179 }
180
181 oocxcoutD(static_cast<TObject *>(nullptr), InputArguments)
182 << "Preprocessing formula: replace named references: " << varName << " --> " << replacement << "\n\t"
183 << formula << std::endl;
184 }
185}
186
187}
188
189////////////////////////////////////////////////////////////////////////////////
190/// Construct a new formula.
191/// \param[in] name Name of the formula.
192/// \param[in] formula Formula to be evaluated. Parameters/observables are identified by name
193/// or ordinal position in `varList`.
194/// \param[in] varList List of variables to be passed to the formula.
195/// \param[in] checkVariables Unused parameter.
196RooFormula::RooFormula(const char *name, const char *formula, const RooArgList &varList, bool /*checkVariables*/)
197 : TNamed(name, formula)
198{
199 _origList.add(varList);
201
202 installFormulaOrThrow(formula);
203}
204
205////////////////////////////////////////////////////////////////////////////////
206/// Copy constructor
207RooFormula::RooFormula(const RooFormula& other, const char* name) :
208 TNamed(name ? name : other.GetName(), other.GetTitle()), RooPrintable(other)
209{
210 _origList.add(other._origList);
212
213 std::unique_ptr<TFormula> newTF;
214 if (other._tFormula) {
215 newTF = std::make_unique<TFormula>(*other._tFormula);
216 newTF->SetName(GetName());
217 }
218
219 _tFormula = std::move(newTF);
220}
221
222
223////////////////////////////////////////////////////////////////////////////////
224/// Process given formula by replacing all ordinal and name references by
225/// `x[i]`, where `i` matches the position of the argument in `_origList`.
226/// Further, references to category states such as `leptonMulti:one` are replaced
227/// by the category index.
228std::string RooFormula::processFormula(std::string formula) const {
229
230 // WARNING to developers: people use RooFormula a lot via RooGenericPdf and
231 // RooFormulaVar! Performance matters here. Avoid non-static std::regex,
232 // because constructing these can become a bottleneck because of the regex
233 // compilation.
234
235 cxcoutD(InputArguments) << "Preprocessing formula step 1: find category tags (catName::catState) in "
236 << formula << std::endl;
237
238 // Step 1: Find all category tags and the corresponding index numbers
239 static const std::regex categoryReg("(\\w+)::(\\w+)");
240 std::map<std::string, int> categoryStates;
241 for (sregex_iterator matchIt = sregex_iterator(formula.begin(), formula.end(), categoryReg);
243 assert(matchIt->size() == 3);
244 const std::string fullMatch = (*matchIt)[0];
245 const std::string catName = (*matchIt)[1];
246 const std::string catState = (*matchIt)[2];
247
248 const auto catVariable = dynamic_cast<const RooAbsCategory*>(_origList.find(catName.c_str()));
249 if (!catVariable) {
250 cxcoutD(InputArguments) << "Formula " << GetName() << " uses '::' to reference a category state as '" << fullMatch
251 << "' but a category '" << catName << "' cannot be found in the input variables." << std::endl;
252 continue;
253 }
254
255 if (!catVariable->hasLabel(catState)) {
256 coutE(InputArguments) << "Formula " << GetName() << " uses '::' to reference a category state as '" << fullMatch
257 << "' but the category '" << catName << "' does not seem to have the state '" << catState << "'." << std::endl;
258 throw std::invalid_argument(formula);
259 }
260 const int catNum = catVariable->lookupIndex(catState);
261
263 cxcoutD(InputArguments) << "\n\t" << fullMatch << "\tname=" << catName << "\tstate=" << catState << "=" << catNum;
264 }
265 cxcoutD(InputArguments) << "-- End of category tags --"<< std::endl;
266
267 // Step 2: Replace all category tags
268 for (const auto& catState : categoryStates) {
269 replaceAll(formula, catState.first, std::to_string(catState.second));
270 }
271
272 cxcoutD(InputArguments) << "Preprocessing formula step 2: replace category tags\n\t" << formula << std::endl;
273
274 // Step 3: Convert `@i`-style references to `x[i]`
276
277 cxcoutD(InputArguments) << "Preprocessing formula step 3: replace '@'-references\n\t" << formula << std::endl;
278
279 // Step 4: Replace all named references with "x[i]"-style
281
282 cxcoutD(InputArguments) << "Final formula:\n\t" << formula << std::endl;
283
284 return formula;
285}
286
287
288////////////////////////////////////////////////////////////////////////////////
289/// Analyse internal formula to find out which variables are actually in use.
290RooArgList RooFormula::usedVariables() const {
291 RooArgList useList;
292 if (_tFormula == nullptr)
293 return useList;
294
295 const std::string formula(_tFormula->GetTitle());
296
297 std::set<unsigned int> matchedOrdinals;
298 static const std::regex newOrdinalRegex("\\bx\\[([0-9]+)\\]");
299 for (sregex_iterator matchIt = sregex_iterator(formula.begin(), formula.end(), newOrdinalRegex);
301 assert(matchIt->size() == 2);
302 std::stringstream matchString((*matchIt)[1]);
303 unsigned int i;
304 matchString >> i;
305
306 matchedOrdinals.insert(i);
307 }
308
309 for (unsigned int i : matchedOrdinals) {
310 useList.add(_origList[i]);
311 }
312
313 return useList;
314}
315
316
317////////////////////////////////////////////////////////////////////////////////
318/// From the internal representation, construct a formula by replacing all index place holders
319/// with the names of the variables that are being used to evaluate it.
320std::string RooFormula::reconstructFormula(std::string internalRepr) const {
321 for (unsigned int i = 0; i < _origList.size(); ++i) {
322 const auto& var = _origList[i];
323 std::stringstream regexStr;
324 regexStr << "x\\[" << i << "\\]|@" << i;
325 std::regex regex(regexStr.str());
326
327 std::string replacement = std::string("[") + var.GetName() + "]";
328 internalRepr = std::regex_replace(internalRepr, regex, replacement);
329 }
330
331 return internalRepr;
332}
333
334
335void RooFormula::dump() const
336{
337 printMultiline(std::cout, 0);
338}
339
340
341////////////////////////////////////////////////////////////////////////////////
342/// Change used variables to those with the same name in given list.
343/// \param[in] newDeps New dependents to replace the old ones.
344/// \param[in] mustReplaceAll Will yield an error if one dependent does not have a replacement.
345/// \param[in] nameChange Passed down to RooAbsArg::findNewServer(const RooAbsCollection&, bool) const.
346bool RooFormula::changeDependents(const RooAbsCollection& newDeps, bool mustReplaceAll, bool nameChange)
347{
348 //Change current servers to new servers with the same name given in list
349 bool errorStat = false;
350
351 // We only consider the usedVariables() for replacement, because only these
352 // are registered as servers.
353 for (const auto arg : usedVariables()) {
354 RooAbsReal* replace = static_cast<RooAbsReal*>(arg->findNewServer(newDeps,nameChange)) ;
355 if (replace) {
356 _origList.replace(*arg, *replace);
357
358 if (arg->getStringAttribute("origName")) {
359 replace->setStringAttribute("origName",arg->getStringAttribute("origName")) ;
360 } else {
361 replace->setStringAttribute("origName",arg->GetName()) ;
362 }
363
364 } else if (mustReplaceAll) {
365 coutE(LinkStateMgmt) << __func__ << ": cannot find replacement for " << arg->GetName() << std::endl;
366 errorStat = true;
367 }
368 }
369
371
372 return errorStat;
373}
374
375
376
377////////////////////////////////////////////////////////////////////////////////
378/// Evaluate the internal TFormula.
379///
380/// First, all variables serving this instance are evaluated given the normalisation set,
381/// and then the formula is evaluated.
382/// \param[in] nset Normalisation set passed to evaluation of arguments serving values.
383/// \return The result of the evaluation.
384double RooFormula::eval(const RooArgSet* nset) const
385{
386 if (!_tFormula) {
387 coutF(Eval) << __func__ << " (" << GetName() << "): Formula didn't compile: " << GetTitle() << std::endl;
388 std::string what = "Formula ";
389 what += GetTitle();
390 what += " didn't compile.";
391 throw std::runtime_error(what);
392 }
393
394 std::vector<double> pars;
395 pars.reserve(_origList.size());
396 for (unsigned int i = 0; i < _origList.size(); ++i) {
397 if (_isCategory[i]) {
398 const auto& cat = static_cast<RooAbsCategory&>(_origList[i]);
399 pars.push_back(cat.getCurrentIndex());
400 } else {
401 const auto& real = static_cast<RooAbsReal&>(_origList[i]);
402 pars.push_back(real.getVal(nset));
403 }
404 }
405
406 return _tFormula->EvalPar(pars.data());
407}
408
409void RooFormula::doEval(RooFit::EvalContext &ctx) const
410{
411 std::span<double> output = ctx.output();
412
413 const int nPars = _origList.size();
414 std::vector<std::span<const double>> inputSpans(nPars);
415 for (int i = 0; i < nPars; i++) {
416 std::span<const double> rhs = ctx.at(static_cast<const RooAbsReal *>(&_origList[i]));
417 inputSpans[i] = rhs;
418 }
419
420 std::vector<double> pars(nPars);
421 for (size_t i = 0; i < output.size(); i++) {
422 for (int j = 0; j < nPars; j++) {
423 pars[j] = inputSpans[j].size() > 1 ? inputSpans[j][i] : inputSpans[j][0];
424 }
425 output[i] = _tFormula->EvalPar(pars.data());
426 }
427}
428
429////////////////////////////////////////////////////////////////////////////////
430/// Printing interface
431
432void RooFormula::printMultiline(ostream& os, Int_t /*contents*/, bool /*verbose*/, TString indent) const
433{
434 os << indent << "--- RooFormula ---" << std::endl;
435 os << indent << " Formula: '" << GetTitle() << "'" << std::endl;
436 os << indent << " Interpretation: '" << reconstructFormula(GetTitle()) << "'" << std::endl;
437 indent.Append(" ");
438 os << indent << "Servers: " << _origList << "\n";
439 os << indent << "In use : " << actualDependents() << std::endl;
440}
441
442
443////////////////////////////////////////////////////////////////////////////////
444/// Print value of formula
445
446void RooFormula::printValue(ostream& os) const
447{
448 os << const_cast<RooFormula*>(this)->eval(nullptr) ;
449}
450
451
452////////////////////////////////////////////////////////////////////////////////
453/// Print name of formula
454
455void RooFormula::printName(ostream& os) const
456{
457 os << GetName() ;
458}
459
460
461////////////////////////////////////////////////////////////////////////////////
462/// Print title of formula
463
464void RooFormula::printTitle(ostream& os) const
465{
466 os << GetTitle() ;
467}
468
469
470////////////////////////////////////////////////////////////////////////////////
471/// Print class name of formula
472
473void RooFormula::printClassName(ostream& os) const
474{
475 os << ClassName() ;
476}
477
478
479////////////////////////////////////////////////////////////////////////////////
480/// Print arguments of formula, i.e. dependents that are actually used
481
482void RooFormula::printArgs(ostream& os) const
483{
484 os << "[ actualVars=";
485 for (const auto arg : usedVariables()) {
486 os << " " << arg->GetName();
487 }
488 os << " ]";
489}
490
491
492////////////////////////////////////////////////////////////////////////////////
493/// Check that the formula compiles, and also fulfills the assumptions.
494///
495void RooFormula::installFormulaOrThrow(const std::string& formula) {
496 const std::string processedFormula = processFormula(formula);
497
498 cxcoutD(InputArguments) << "RooFormula '" << GetName() << "' will be compiled as "
499 << "\n\t" << processedFormula
500 << "\n and used as"
502 << "\n with the parameters " << _origList << std::endl;
503
504 auto theFormula = std::make_unique<TFormula>(GetName(), processedFormula.c_str(), false);
505
506 if (!theFormula || !theFormula->IsValid()) {
507 std::stringstream msg;
508 msg << "RooFormula '" << GetName() << "' did not compile or is invalid."
509 << "\nInput:\n\t" << formula
510 << "\nPassed over to TFormula:\n\t" << processedFormula << std::endl;
511 coutF(InputArguments) << msg.str();
512 throw std::runtime_error(msg.str());
513 }
514
515 if (theFormula && theFormula->GetNdim() != 1) {
516 // TFormula thinks that we have a multi-dimensional formula, e.g. with variables x,y,z,t.
517 // We have to check now that this is not the case, as RooFit only uses the syntax x[0], x[1], x[2], ...
518 bool haveProblem = false;
519 std::stringstream msg;
520 msg << "TFormula interprets the formula " << formula << " as " << theFormula->GetNdim() << "-dimensional with the variable(s) {";
521 for (int i=1; i < theFormula->GetNdim(); ++i) {
522 const TString varName = theFormula->GetVarName(i);
523 if (varName.BeginsWith("x[") && varName[varName.Length()-1] == ']')
524 continue;
525
526 haveProblem = true;
527 msg << theFormula->GetVarName(i) << ",";
528 }
529 if (haveProblem) {
530 msg << "}, which could not be supplied by RooFit."
531 << "\nThe formula must be modified, or those variables must be supplied in the list of variables." << std::endl;
532 coutF(InputArguments) << msg.str();
533 throw std::invalid_argument(msg.str());
534 }
535 }
536
537 _tFormula = std::move(theFormula);
538}
539
540/// \endcond
#define cxcoutD(a)
#define oocxcoutD(o, a)
#define coutF(a)
#define coutE(a)
int Int_t
Definition RtypesCore.h:45
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.
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t r
char name[80]
Definition TGX11.cxx:110
const_iterator begin() const
void setStringAttribute(const Text_t *key, const Text_t *value)
Associate string 'value' to this object under key 'key'.
A space to attach TBranches.
static TClass * Class()
Abstract container object that can hold multiple RooAbsArg objects.
virtual bool add(const RooAbsArg &var, bool silent=false)
Add the specified argument to list.
Abstract base class for objects that represent a real value and implements functionality common to al...
Definition RooAbsReal.h:59
RooArgList is a container object that can hold multiple RooAbsArg objects.
Definition RooArgList.h:22
RooArgSet is a container object that can hold multiple RooAbsArg objects.
Definition RooArgSet.h:24
std::span< const double > at(RooAbsArg const *arg, RooAbsArg const *caller=nullptr)
std::span< double > output()
A 'mix-in' base class that define the standard RooFit plotting and printing methods.
The TNamed class is the base class for all named ROOT classes.
Definition TNamed.h:29
Mother of all ROOT objects.
Definition TObject.h:41
Basic string class.
Definition TString.h:139
RooCmdArg ClassName(const char *name)
void replaceAll(std::string &inOut, std::string_view what, std::string_view with)
static const char * what
Definition stlLoader.cc:5
TMarker m
Definition textangle.C:8
static void output()