Logo ROOT  
Reference Guide
 
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
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/// From the internal representation, construct a formula by replacing all index place holders
189/// with the names of the variables that are being used to evaluate it, and return it as string.
190std::string reconstructFormula(std::string internalRepr, RooArgList const& args) {
191 const auto nArgs = args.size();
192 for (unsigned int i = 0; i < nArgs; ++i) {
193 const auto& var = args[i];
194 std::stringstream regexStr;
195 regexStr << "x\[" << i << "\]|@" << i;
196 std::regex regex(regexStr.str());
197
198 std::string replacement = std::string("[") + var.GetName() + "]";
199 internalRepr = std::regex_replace(internalRepr, regex, replacement);
200 }
201
202 return internalRepr;
203}
204
205////////////////////////////////////////////////////////////////////////////////
206/// From the internal representation, construct a null-formula by replacing all
207/// index place holders with zeroes, and return it as string
208std::string reconstructNullFormula(std::string internalRepr, RooArgList const& args) {
209 const auto nArgs = args.size();
210 for (unsigned int i = 0; i < nArgs; ++i) {
211 std::stringstream regexStr;
212 regexStr << "x\[" << i << "\]|@" << i;
213 std::regex regex(regexStr.str());
214
215 std::string replacement = "1e-18";
216 internalRepr = std::regex_replace(internalRepr, regex, replacement);
217 }
218
219 return internalRepr;
220}
221
222}
223
224////////////////////////////////////////////////////////////////////////////////
225/// Construct a new formula.
226/// \param[in] name Name of the formula.
227/// \param[in] formula Formula to be evaluated. Parameters/observables are identified by name
228/// or ordinal position in `varList`.
229/// \param[in] varList List of variables to be passed to the formula.
230/// \param[in] checkVariables Unused parameter.
231RooFormula::RooFormula(const char *name, const char *formula, const RooArgList &varList, bool /*checkVariables*/)
232 : TNamed(name, formula)
233{
234 _origList.add(varList);
236
237 installFormulaOrThrow(formula);
238}
239
240////////////////////////////////////////////////////////////////////////////////
241/// Copy constructor
242RooFormula::RooFormula(const RooFormula& other, const char* name) :
243 TNamed(name ? name : other.GetName(), other.GetTitle()), RooPrintable(other)
244{
245 _origList.add(other._origList);
247
248 std::unique_ptr<TFormula> newTF;
249 if (other._tFormula) {
250 newTF = std::make_unique<TFormula>(*other._tFormula);
251 newTF->SetName(GetName());
252 }
253
254 _tFormula = std::move(newTF);
255}
256
257////////////////////////////////////////////////////////////////////////////////
258/// Process given formula by replacing all ordinal and name references by
259/// `x[i]`, where `i` matches the position of the argument in `_origList`.
260/// Further, references to category states such as `leptonMulti:one` are replaced
261/// by the category index.
262std::string RooFormula::processFormula(std::string formula) const {
263
264 // WARNING to developers: people use RooFormula a lot via RooGenericPdf and
265 // RooFormulaVar! Performance matters here. Avoid non-static std::regex,
266 // because constructing these can become a bottleneck because of the regex
267 // compilation.
268
269 cxcoutD(InputArguments) << "Preprocessing formula step 1: find category tags (catName::catState) in "
270 << formula << std::endl;
271
272 // Step 1: Find all category tags and the corresponding index numbers
273 static const std::regex categoryReg("(\w+)::(\w+)");
274 std::map<std::string, int> categoryStates;
275 for (sregex_iterator matchIt = sregex_iterator(formula.begin(), formula.end(), categoryReg);
277 assert(matchIt->size() == 3);
278 const std::string fullMatch = (*matchIt)[0];
279 const std::string catName = (*matchIt)[1];
280 const std::string catState = (*matchIt)[2];
281
282 const auto catVariable = dynamic_cast<const RooAbsCategory*>(_origList.find(catName.c_str()));
283 if (!catVariable) {
284 cxcoutD(InputArguments) << "Formula " << GetName() << " uses '::' to reference a category state as '" << fullMatch
285 << "' but a category '" << catName << "' cannot be found in the input variables." << std::endl;
286 continue;
287 }
288
289 if (!catVariable->hasLabel(catState)) {
290 coutE(InputArguments) << "Formula " << GetName() << " uses '::' to reference a category state as '" << fullMatch
291 << "' but the category '" << catName << "' does not seem to have the state '" << catState << "'." << std::endl;
292 throw std::invalid_argument(formula);
293 }
294 const int catNum = catVariable->lookupIndex(catState);
295
297 cxcoutD(InputArguments) << "\n\t" << fullMatch << "\tname=" << catName << "\tstate=" << catState << "=" << catNum;
298 }
299 cxcoutD(InputArguments) << "-- End of category tags --"<< std::endl;
300
301 // Step 2: Replace all category tags
302 for (const auto& catState : categoryStates) {
303 replaceAll(formula, catState.first, std::to_string(catState.second));
304 }
305
306 cxcoutD(InputArguments) << "Preprocessing formula step 2: replace category tags\n\t" << formula << std::endl;
307
308 // Step 3: Convert `@i`-style references to `x[i]`
310
311 cxcoutD(InputArguments) << "Preprocessing formula step 3: replace '@'-references\n\t" << formula << std::endl;
312
313 // Step 4: Replace all named references with "x[i]"-style
315
316 cxcoutD(InputArguments) << "Final formula:\n\t" << formula << std::endl;
317
318 return formula;
319}
320
321////////////////////////////////////////////////////////////////////////////////
322/// Analyse internal formula to find out which variables are actually in use.
323RooArgList RooFormula::usedVariables() const {
324 RooArgList useList;
325 if (_tFormula == nullptr)
326 return useList;
327
328 const std::string formula(_tFormula->GetTitle());
329
330 std::set<unsigned int> matchedOrdinals;
331 static const std::regex newOrdinalRegex("\bx\[([0-9]+)\]");
332 for (sregex_iterator matchIt = sregex_iterator(formula.begin(), formula.end(), newOrdinalRegex);
334 assert(matchIt->size() == 2);
335 std::stringstream matchString((*matchIt)[1]);
336 unsigned int i;
337 matchString >> i;
338
339 matchedOrdinals.insert(i);
340 }
341
342 for (unsigned int i : matchedOrdinals) {
343 useList.add(_origList[i]);
344 }
345
346 return useList;
347}
348
349void RooFormula::dump() const
350{
351 printMultiline(std::cout, 0);
352}
353
354////////////////////////////////////////////////////////////////////////////////
355/// Change used variables to those with the same name in given list.
356/// \param[in] newDeps New dependents to replace the old ones.
357/// \param[in] mustReplaceAll Will yield an error if one dependent does not have a replacement.
358/// \param[in] nameChange Passed down to RooAbsArg::findNewServer(const RooAbsCollection&, bool) const.
359bool RooFormula::changeDependents(const RooAbsCollection& newDeps, bool mustReplaceAll, bool nameChange)
360{
361 //Change current servers to new servers with the same name given in list
362 bool errorStat = false;
363
364 // We only consider the usedVariables() for replacement, because only these
365 // are registered as servers.
366 for (const auto arg : usedVariables()) {
367 RooAbsReal* replace = static_cast<RooAbsReal*>(arg->findNewServer(newDeps,nameChange)) ;
368 if (replace) {
369 _origList.replace(*arg, *replace);
370
371 if (arg->getStringAttribute("origName")) {
372 replace->setStringAttribute("origName",arg->getStringAttribute("origName")) ;
373 } else {
374 replace->setStringAttribute("origName",arg->GetName()) ;
375 }
376
377 } else if (mustReplaceAll) {
378 coutE(LinkStateMgmt) << __func__ << ": cannot find replacement for " << arg->GetName() << std::endl;
379 errorStat = true;
380 }
381 }
382
384
385 return errorStat;
386}
387
388////////////////////////////////////////////////////////////////////////////////
389/// Evaluate the internal TFormula.
390///
391/// First, all variables serving this instance are evaluated given the normalisation set,
392/// and then the formula is evaluated.
393/// \param[in] nset Normalisation set passed to evaluation of arguments serving values.
394/// \return The result of the evaluation.
395double RooFormula::eval(const RooArgSet* nset) const
396{
397 if (!_tFormula) {
398 coutF(Eval) << __func__ << " (" << GetName() << "): Formula didn't compile: " << GetTitle() << std::endl;
399 std::string what = "Formula ";
400 what += GetTitle();
401 what += " didn't compile.";
402 throw std::runtime_error(what);
403 }
404
405 std::vector<double> pars;
406 pars.reserve(_origList.size());
407 for (unsigned int i = 0; i < _origList.size(); ++i) {
408 if (_isCategory[i]) {
409 const auto& cat = static_cast<RooAbsCategory&>(_origList[i]);
410 pars.push_back(cat.getCurrentIndex());
411 } else {
412 const auto& real = static_cast<RooAbsReal&>(_origList[i]);
413 pars.push_back(real.getVal(nset));
414 }
415 }
416
417 return _tFormula->EvalPar(pars.data());
418}
419
420void RooFormula::doEval(RooFit::EvalContext &ctx) const
421{
422 std::span<double> output = ctx.output();
423
424 const int nPars = _origList.size();
425 std::vector<std::span<const double>> inputSpans(nPars);
426 for (int i = 0; i < nPars; i++) {
427 std::span<const double> rhs = ctx.at(static_cast<const RooAbsReal *>(&_origList[i]));
428 inputSpans[i] = rhs;
429 }
430
431 std::vector<double> pars(nPars);
432 for (size_t i = 0; i < output.size(); i++) {
433 for (int j = 0; j < nPars; j++) {
434 pars[j] = inputSpans[j].size() > 1 ? inputSpans[j][i] : inputSpans[j][0];
435 }
436 output[i] = _tFormula->EvalPar(pars.data());
437 }
438}
439
440////////////////////////////////////////////////////////////////////////////////
441/// Printing interface
442
443void RooFormula::printMultiline(ostream& os, Int_t /*contents*/, bool /*verbose*/, TString indent) const
444{
445 os << indent << "--- RooFormula ---" << std::endl;
446 os << indent << " Formula: '" << GetTitle() << "'" << std::endl;
447 os << indent << " Interpretation: '" << reconstructFormula(GetTitle(), _origList) << "'" << std::endl;
448 indent.Append(" ");
449 os << indent << "Servers: " << _origList << "\n";
450 os << indent << "In use : " << actualDependents() << std::endl;
451}
452
453////////////////////////////////////////////////////////////////////////////////
454/// Print value of formula
455
456void RooFormula::printValue(ostream& os) const
457{
458 os << const_cast<RooFormula*>(this)->eval(nullptr) ;
459}
460
461////////////////////////////////////////////////////////////////////////////////
462/// Print name of formula
463
464void RooFormula::printName(ostream& os) const
465{
466 os << GetName() ;
467}
468
469////////////////////////////////////////////////////////////////////////////////
470/// Print title of formula
471
472void RooFormula::printTitle(ostream& os) const
473{
474 os << GetTitle() ;
475}
476
477////////////////////////////////////////////////////////////////////////////////
478/// Print class name of formula
479
480void RooFormula::printClassName(ostream& os) const
481{
482 os << ClassName() ;
483}
484
485////////////////////////////////////////////////////////////////////////////////
486/// Print arguments of formula, i.e. dependents that are actually used
487
488void RooFormula::printArgs(ostream& os) const
489{
490 os << "[ actualVars=";
491 for (const auto arg : usedVariables()) {
492 os << " " << arg->GetName();
493 }
494 os << " ]";
495}
496
497////////////////////////////////////////////////////////////////////////////////
498/// Check that the formula compiles, and also fulfills the assumptions.
499///
500void RooFormula::installFormulaOrThrow(const std::string& formula) {
501 const std::string processedFormula = processFormula(formula);
502
503 cxcoutD(InputArguments) << "RooFormula '" << GetName() << "' will be compiled as "
504 << "\n\t" << processedFormula
505 << "\n and used as"
507 << "\n with the parameters " << _origList << std::endl;
508
509 auto theFormula = std::make_unique<TFormula>(GetName(), processedFormula.c_str(), /*addToGlobList=*/false);
510
511 if (!theFormula || !theFormula->IsValid()) {
512 std::stringstream msg;
513 msg << "RooFormula '" << GetName() << "' did not compile or is invalid."
514 << "\nInput:\n\t" << formula
515 << "\nPassed over to TFormula:\n\t" << processedFormula << std::endl;
516 coutF(InputArguments) << msg.str();
517 throw std::runtime_error(msg.str());
518 }
519
520 if (theFormula && theFormula->GetNdim() != 0) {
521 TFormula nullFormula{"nullFormula", reconstructNullFormula(processedFormula, _origList).c_str(), /*addToGlobList=*/false};
522 const auto nullDim = nullFormula.GetNdim();
523 if (nullDim != 0) {
524 // TFormula thinks that we have an n-dimensional formula (n>0), but it shouldn't, as
525 // these vars should have been replaced by zeroes in reconstructNullFormula
526 // since RooFit only uses the syntax x[0], x[1], x[2], ...
527 // This can happen e.g. with variables x,y,z,t that were not supplied in arglist.
528 std::stringstream msg;
529 msg << "TFormula interprets the formula " << formula << " as " << theFormula->GetNdim()+nullDim << "-dimensional with undefined variable(s) {";
530 for (auto i=0; i < nullDim; ++i) {
531 msg << nullFormula.GetVarName(i) << ",";
532 }
533 msg << "}, which could not be supplied by RooFit."
534 << "\nThe formula must be modified, or those variables must be supplied in the list of variables." << std::endl;
535 coutF(InputArguments) << msg.str();
536 throw std::invalid_argument(msg.str());
537 }
538 }
539
540 _tFormula = std::move(theFormula);
541}
542
543/// \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.
Storage_t::size_type size() const
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 Formula class.
Definition TFormula.h:89
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()