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/// Convert `@i`-style references to `x[i]`.
76void convertArobaseReferences(std::string &formula)
77{
78 bool match = false;
79 for (std::size_t i = 0; i < formula.size(); ++i) {
80 if (match && !isdigit(formula[i])) {
81 formula.insert(formula.begin() + i, ']');
82 i += 1;
83 match = false;
84 } else if (!match && formula[i] == '@') {
85 formula[i] = 'x';
86 formula.insert(formula.begin() + i + 1, '[');
87 i += 1;
88 match = true;
89 }
90 }
91 if (match)
92 formula += ']';
93}
94
95/// Replace all occurrences of `what` with `with` inside of `inOut`.
96void replaceAll(std::string &inOut, std::string_view what, std::string_view with)
97{
98 for (std::string::size_type pos{}; inOut.npos != (pos = inOut.find(what.data(), pos, what.length()));
99 pos += with.length()) {
100 inOut.replace(pos, what.length(), with.data(), with.length());
101 }
102}
103
104/// Find the word boundaries with a static std::regex and return a bool vector
105/// flagging their positions. The end of the string is considered a word
106/// boundary.
107std::vector<bool> getWordBoundaryFlags(std::string const &s)
108{
109 static const std::regex r{"\\b"};
110 std::vector<bool> out(s.size() + 1);
111
112 for (auto i = std::sregex_iterator(s.begin(), s.end(), r); i != std::sregex_iterator(); ++i) {
113 std::smatch m = *i;
115 }
116
117 // The end of a string is also a word boundary
118 out[s.size()] = true;
119
120 return out;
121}
122
123// Receiving a RooConstVar as parameter having a numeric value as name, checks if the numeric value
124// of the name is equal to the value of the RooConstVar
125//
126// E.g.
127// RooConstVar("2.1", "const1", 2.1) returns true
128// RooConstVar("3.4", "const2", 2.1) returns false
130{
131 // Extract the value from the RooAbsArg
132 std::stringstream ss;
133 ss << _rooAbsArg;
134 double nameValue, actualValue;
135 try {
136 nameValue = std::stod(_rooAbsArg.GetName());
137 actualValue = std::stod(ss.str());
138 } catch (const std::invalid_argument &e) {
139 std::stringstream ssExc;
140 ssExc << "RooConstVar named " << _rooAbsArg.GetName() << " has name or value that "
141 << "cannot be converted to number";
142 throw std::invalid_argument(ssExc.str());
143 } catch (const std::out_of_range &e) {
144 std::stringstream ssExc;
145 ssExc << "RooConstVar named " << _rooAbsArg.GetName() << " has numeric name or value that "
146 << "gets out of a double range";
147 throw std::out_of_range(ssExc.str());
148 }
149
150 return nameValue == actualValue;
151}
152
153/// Replace all named references with "x[i]"-style.
154void replaceVarNamesWithIndexStyle(std::string &formula, RooArgList const &varList)
155{
156 std::vector<bool> isWordBoundary = getWordBoundaryFlags(formula);
157 for (unsigned int i = 0; i < varList.size(); ++i) {
158 std::string_view varName = varList[i].GetName();
159
160 // If the RooAbsArg has a number as name, we perform checks
161 std::string varNameStr{varName};
162 static const std::regex pureNumberNameRegex("^\\s*\\d+(\\.\\d+)?\\s*$");
163 if (std::regex_match(varNameStr, pureNumberNameRegex)) { // Name is a number
164 // Get class name
165 std::stringstream classNameSs;
166 varList[i].printClassName(classNameSs);
167 // If the RooAbsArg is a RooConstVar having (double)name == value
168 // we don't perform substitution
169 if (classNameSs.str() == "RooConstVar" && isNumericNameValid(varList[i])) {
170 continue;
171 } else {
172 std::stringstream exceptionSs;
173 exceptionSs << "Variable '" << varName << "' is not a valid argument for RooFormulaVar. "
174 << "Variables with a name that is a number can only be of type RooConstVar "
175 << "and have value equal to the name";
176 throw std::invalid_argument(exceptionSs.str());
177 }
178 }
179
180 std::stringstream replacementStream;
181 replacementStream << "x[" << i << "]";
182 std::string replacement = replacementStream.str();
183
184 for (std::string::size_type pos{}; formula.npos != (pos = formula.find(varName.data(), pos, varName.length()));
185 pos += replacement.size()) {
186
187 std::string::size_type next = pos + varName.length();
188
189 // The matched variable name has to be surrounded by word boundaries
190 // std::cout << pos << " " << next << std::endl;
191 if (!isWordBoundary[pos] || !isWordBoundary[next])
192 continue;
193
194 // Veto '[' and ']' as next characters. If the variable is called `x`
195 // or `0`, this might otherwise replace `x[0]`.
196 if (next < formula.size() && (formula[next] == '[' || formula[next] == ']')) {
197 continue;
198 }
199
200 // As we replace substrings in the middle of the string, we also have
201 // to update the word boundary flag vector. Note that we don't care
202 // the word boundaries in the `x[i]` are correct, as it has already
203 // been replaced.
204 std::size_t nOld = varName.length();
205 std::size_t nNew = replacement.size();
206 auto wbIter = isWordBoundary.begin() + pos;
207 if (nNew > nOld) {
208 isWordBoundary.insert(wbIter + nOld, nNew - nOld, false);
209 } else if (nNew < nOld) {
211 }
212
213 // Do the actual replacement
214 formula.replace(pos, varName.length(), replacement);
215 }
216
217 oocxcoutD(static_cast<TObject *>(nullptr), InputArguments)
218 << "Preprocessing formula: replace named references: " << varName << " --> " << replacement << "\n\t"
219 << formula << std::endl;
220 }
221}
222
223////////////////////////////////////////////////////////////////////////////////
224/// From the internal representation, construct a formula by replacing all index place holders
225/// with the names of the variables that are being used to evaluate it, and return it as string.
226std::string reconstructFormula(std::string internalRepr, RooArgList const& args) {
227 const auto nArgs = args.size();
228 for (unsigned int i = 0; i < nArgs; ++i) {
229 const auto& var = args[i];
230 std::stringstream regexStr;
231 regexStr << "x\\[" << i << "\\]|@" << i;
232 std::regex regex(regexStr.str());
233
234 std::string replacement = std::string("[") + var.GetName() + "]";
235 internalRepr = std::regex_replace(internalRepr, regex, replacement);
236 }
237
238 return internalRepr;
239}
240
241////////////////////////////////////////////////////////////////////////////////
242/// From the internal representation, construct a null-formula by replacing all
243/// index place holders with zeroes, and return it as string
244std::string reconstructNullFormula(std::string internalRepr, RooArgList const& args) {
245 const auto nArgs = args.size();
246 for (unsigned int i = 0; i < nArgs; ++i) {
247 std::stringstream regexStr;
248 regexStr << "x\\[" << i << "\\]|@" << i;
249 std::regex regex(regexStr.str());
250
251 std::string replacement = "1e-18";
252 internalRepr = std::regex_replace(internalRepr, regex, replacement);
253 }
254
255 return internalRepr;
256}
257
258}
259
260////////////////////////////////////////////////////////////////////////////////
261/// Construct a new formula.
262/// \param[in] name Name of the formula.
263/// \param[in] formula Formula to be evaluated. Parameters/observables are identified by name
264/// or ordinal position in `varList`.
265/// \param[in] varList List of variables to be passed to the formula.
266/// \param[in] checkVariables Unused parameter.
267RooFormula::RooFormula(const char *name, const char *formula, const RooArgList &varList, bool /*checkVariables*/)
268 : TNamed(name, formula)
269{
270 _origList.add(varList);
271 _varIsUsed.resize(varList.size());
272
273 installFormulaOrThrow(formula);
274
275 if (_tFormula == nullptr)
276 return;
277
278 const std::string processedFormula(_tFormula->GetTitle());
279
280 std::set<unsigned int> matchedOrdinals;
281 static const std::regex newOrdinalRegex("\\bx\\[([0-9]+)\\]");
284 assert(matchIt->size() == 2);
285 std::stringstream matchString((*matchIt)[1]);
286 unsigned int i;
287 matchString >> i;
288
289 matchedOrdinals.insert(i);
290 }
291
292 for (unsigned int i : matchedOrdinals) {
293 _varIsUsed[i] = true;
294 }
295}
296
297////////////////////////////////////////////////////////////////////////////////
298/// Copy constructor
299RooFormula::RooFormula(const RooFormula& other, const char* name) :
300 TNamed(name ? name : other.GetName(), other.GetTitle()),
302{
303 _origList.add(other._origList);
304
305 std::unique_ptr<TFormula> newTF;
306 if (other._tFormula) {
307 newTF = std::make_unique<TFormula>(*other._tFormula);
308 newTF->SetName(GetName());
309 }
310
311 _tFormula = std::move(newTF);
312}
313
314////////////////////////////////////////////////////////////////////////////////
315/// Process given formula by replacing all ordinal and name references by
316/// `x[i]`, where `i` matches the position of the argument in `_origList`.
317/// Further, references to category states such as `leptonMulti:one` are replaced
318/// by the category index.
319std::string RooFormula::processFormula(std::string formula) const {
320
321 // WARNING to developers: people use RooFormula a lot via RooGenericPdf and
322 // RooFormulaVar! Performance matters here. Avoid non-static std::regex,
323 // because constructing these can become a bottleneck because of the regex
324 // compilation.
325
326 cxcoutD(InputArguments) << "Preprocessing formula step 1: find category tags (catName::catState) in "
327 << formula << std::endl;
328
329 // Step 1: Find all category tags and the corresponding index numbers
330 static const std::regex categoryReg("(\\w+)::(\\w+)");
331 std::map<std::string, int> categoryStates;
332 for (sregex_iterator matchIt = sregex_iterator(formula.begin(), formula.end(), categoryReg);
334 assert(matchIt->size() == 3);
335 const std::string fullMatch = (*matchIt)[0];
336 const std::string catName = (*matchIt)[1];
337 const std::string catState = (*matchIt)[2];
338
339 const auto catVariable = dynamic_cast<const RooAbsCategory*>(_origList.find(catName.c_str()));
340 if (!catVariable) {
341 cxcoutD(InputArguments) << "Formula " << GetName() << " uses '::' to reference a category state as '" << fullMatch
342 << "' but a category '" << catName << "' cannot be found in the input variables." << std::endl;
343 continue;
344 }
345
346 if (!catVariable->hasLabel(catState)) {
347 coutE(InputArguments) << "Formula " << GetName() << " uses '::' to reference a category state as '" << fullMatch
348 << "' but the category '" << catName << "' does not seem to have the state '" << catState << "'." << std::endl;
349 throw std::invalid_argument(formula);
350 }
351 const int catNum = catVariable->lookupIndex(catState);
352
354 cxcoutD(InputArguments) << "\n\t" << fullMatch << "\tname=" << catName << "\tstate=" << catState << "=" << catNum;
355 }
356 cxcoutD(InputArguments) << "-- End of category tags --"<< std::endl;
357
358 // Step 2: Replace all category tags
359 for (const auto& catState : categoryStates) {
360 replaceAll(formula, catState.first, std::to_string(catState.second));
361 }
362
363 cxcoutD(InputArguments) << "Preprocessing formula step 2: replace category tags\n\t" << formula << std::endl;
364
365 // Step 3: Convert `@i`-style references to `x[i]`
367
368 cxcoutD(InputArguments) << "Preprocessing formula step 3: replace '@'-references\n\t" << formula << std::endl;
369
370 // Step 4: Replace all named references with "x[i]"-style
372
373 cxcoutD(InputArguments) << "Final formula:\n\t" << formula << std::endl;
374
375 return formula;
376}
377
378////////////////////////////////////////////////////////////////////////////////
379/// Analyse internal formula to find out which variables are actually in use.
380RooArgList RooFormula::usedVariables() const {
381 RooArgList useList;
382
383 for (std::size_t i = 0; i < _varIsUsed.size(); ++i) {
384 if (_varIsUsed[i]) {
385 useList.add(_origList[i]);
386 }
387 }
388
389 return useList;
390}
391
392////////////////////////////////////////////////////////////////////////////////
393/// Change used variables to those with the same name in given list.
394/// \param[in] newDeps New dependents to replace the old ones.
395/// \param[in] mustReplaceAll Will yield an error if one dependent does not have a replacement.
396/// \param[in] nameChange Passed down to RooAbsArg::findNewServer(const RooAbsCollection&, bool) const.
397bool RooFormula::changeDependents(const RooAbsCollection& newDeps, bool mustReplaceAll, bool nameChange)
398{
399 //Change current servers to new servers with the same name given in list
400 bool errorStat = false;
401
402 // We only consider the usedVariables() for replacement, because only these
403 // are registered as servers.
404 for (const auto arg : usedVariables()) {
405 RooAbsReal* replace = static_cast<RooAbsReal*>(arg->findNewServer(newDeps,nameChange)) ;
406 if (replace) {
407 _origList.replace(*arg, *replace);
408
409 if (arg->getStringAttribute("origName")) {
410 replace->setStringAttribute("origName",arg->getStringAttribute("origName")) ;
411 } else {
412 replace->setStringAttribute("origName",arg->GetName()) ;
413 }
414
415 } else if (mustReplaceAll) {
416 coutE(LinkStateMgmt) << __func__ << ": cannot find replacement for " << arg->GetName() << std::endl;
417 errorStat = true;
418 }
419 }
420
421 return errorStat;
422}
423
424////////////////////////////////////////////////////////////////////////////////
425/// Evaluate the internal TFormula.
426///
427/// First, all variables serving this instance are evaluated given the normalisation set,
428/// and then the formula is evaluated.
429/// \param[in] nset Normalisation set passed to evaluation of arguments serving values.
430/// \return The result of the evaluation.
431double RooFormula::eval(const RooArgSet* nset) const
432{
433 if (!_tFormula) {
434 coutF(Eval) << __func__ << " (" << GetName() << "): Formula didn't compile: " << GetTitle() << std::endl;
435 std::string what = "Formula ";
436 what += GetTitle();
437 what += " didn't compile.";
438 throw std::runtime_error(what);
439 }
440
441 std::vector<double> pars;
442 pars.reserve(_origList.size());
443 for (unsigned int i = 0; i < _origList.size(); ++i) {
444 if (_origList[i].isCategory()) {
445 const auto& cat = static_cast<RooAbsCategory&>(_origList[i]);
446 pars.push_back(cat.getCurrentIndex());
447 } else {
448 const auto& real = static_cast<RooAbsReal&>(_origList[i]);
449 pars.push_back(real.getVal(nset));
450 }
451 }
452
453 return _tFormula->EvalPar(pars.data());
454}
455
456void RooFormula::doEval(RooArgList const &actualVars, RooFit::EvalContext &ctx) const
457{
458 std::span<double> output = ctx.output();
459
460 const int nPars = _origList.size();
461 std::vector<std::span<const double>> inputSpans(nPars);
462 int iActual = 0;
463 for (int i = 0; i < nPars; i++) {
464 if(_varIsUsed[i]) {
465 std::span<const double> rhs = ctx.at(static_cast<const RooAbsReal *>(&actualVars[iActual]));
466 inputSpans[i] = rhs;
467 ++iActual;
468 }
469 }
470
471 std::vector<double> pars(nPars);
472 for (size_t i = 0; i < output.size(); i++) {
473 for (int j = 0; j < nPars; j++) {
474 pars[j] = inputSpans[j].size() > 1 ? inputSpans[j][i] : inputSpans[j][0];
475 }
476 output[i] = _tFormula->EvalPar(pars.data());
477 }
478}
479
480////////////////////////////////////////////////////////////////////////////////
481/// Printing interface
482
483void RooFormula::printMultiline(ostream& os, Int_t /*contents*/, bool /*verbose*/, TString indent) const
484{
485 os << indent << "--- RooFormula ---" << std::endl;
486 os << indent << " Formula: '" << GetTitle() << "'" << std::endl;
487 os << indent << " Interpretation: '" << reconstructFormula(GetTitle(), _origList) << "'" << std::endl;
488 indent.Append(" ");
489 os << indent << "Servers: " << _origList << "\n";
490 os << indent << "In use : " << actualDependents() << std::endl;
491}
492
493////////////////////////////////////////////////////////////////////////////////
494/// Check that the formula compiles, and also fulfills the assumptions.
495///
496void RooFormula::installFormulaOrThrow(const std::string& formula) {
497 const std::string processedFormula = processFormula(formula);
498
499 cxcoutD(InputArguments) << "RooFormula '" << GetName() << "' will be compiled as "
500 << "\n\t" << processedFormula
501 << "\n and used as"
503 << "\n with the parameters " << _origList << std::endl;
504
505 auto theFormula = std::make_unique<TFormula>(GetName(), processedFormula.c_str(), /*addToGlobList=*/false);
506
507 if (!theFormula || !theFormula->IsValid()) {
508 std::stringstream msg;
509 msg << "RooFormula '" << GetName() << "' did not compile or is invalid."
510 << "\nInput:\n\t" << formula
511 << "\nPassed over to TFormula:\n\t" << processedFormula << std::endl;
512 coutF(InputArguments) << msg.str();
513 throw std::runtime_error(msg.str());
514 }
515
516 if (theFormula && theFormula->GetNdim() != 0) {
517 TFormula nullFormula{"nullFormula", reconstructNullFormula(processedFormula, _origList).c_str(), /*addToGlobList=*/false};
518 const auto nullDim = nullFormula.GetNdim();
519 if (nullDim != 0) {
520 // TFormula thinks that we have an n-dimensional formula (n>0), but it shouldn't, as
521 // these vars should have been replaced by zeroes in reconstructNullFormula
522 // since RooFit only uses the syntax x[0], x[1], x[2], ...
523 // This can happen e.g. with variables x,y,z,t that were not supplied in arglist.
524 std::stringstream msg;
525 msg << "TFormula interprets the formula " << formula << " as " << theFormula->GetNdim()+nullDim << "-dimensional with undefined variable(s) {";
526 for (auto i=0; i < nullDim; ++i) {
527 msg << nullFormula.GetVarName(i) << ",";
528 }
529 msg << "}, which could not be supplied by RooFit."
530 << "\nThe formula must be modified, or those variables must be supplied in the list of variables." << std::endl;
531 coutF(InputArguments) << msg.str();
532 throw std::invalid_argument(msg.str());
533 }
534 }
535
536 _tFormula = std::move(theFormula);
537}
538
539/// \endcond
#define e(i)
Definition RSha256.hxx:103
#define cxcoutD(a)
#define oocxcoutD(o, a)
#define coutF(a)
#define coutE(a)
int Int_t
Signed integer 4 bytes (int)
Definition RtypesCore.h:59
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
const_iterator end() const
Common abstract base class for objects that represent a value and a "shape" in RooFit.
Definition RooAbsArg.h:76
void setStringAttribute(const Text_t *key, const Text_t *value)
Associate string 'value' to this object under key 'key'.
A space to attach TBranches.
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:63
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()
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:138
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()