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;
114 m.position()] = true;
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/// Replace all named references with "x[i]"-style.
124void replaceVarNamesWithIndexStyle(std::string &formula, RooArgList const &varList)
125{
126 std::vector<bool> isWordBoundary = getWordBoundaryFlags(formula);
127 for (unsigned int i = 0; i < varList.size(); ++i) {
128 std::string_view varName = varList[i].GetName();
129
130 std::stringstream replacementStream;
131 replacementStream << "x[" << i << "]";
132 std::string replacement = replacementStream.str();
133
134 for (std::string::size_type pos{}; formula.npos != (pos = formula.find(varName.data(), pos, varName.length()));
135 pos += replacement.size()) {
136
137 std::string::size_type next = pos + varName.length();
138
139 // The matched variable name has to be surrounded by word boundaries
140 // std::cout << pos << " " << next << std::endl;
141 if (!isWordBoundary[pos] || !isWordBoundary[next])
142 continue;
143
144 // Veto '[' and ']' as next characters. If the variable is called `x`
145 // or `0`, this might otherwise replace `x[0]`.
146 if (next < formula.size() && (formula[next] == '[' || formula[next] == ']')) {
147 continue;
148 }
149
150 // As we replace substrings in the middle of the string, we also have
151 // to update the word boundary flag vector. Note that we don't care
152 // the word boundaries in the `x[i]` are correct, as it has already
153 // been replaced.
154 std::size_t nOld = varName.length();
155 std::size_t nNew = replacement.size();
156 auto wbIter = isWordBoundary.begin() + pos;
157 if (nNew > nOld) {
158 isWordBoundary.insert(wbIter + nOld, nNew - nOld, false);
159 } else if (nNew < nOld) {
161 }
162
163 // Do the actual replacement
164 formula.replace(pos, varName.length(), replacement);
165 }
166
167 oocxcoutD(static_cast<TObject *>(nullptr), InputArguments)
168 << "Preprocessing formula: replace named references: " << varName << " --> " << replacement << "\n\t"
169 << formula << std::endl;
170 }
171}
172
173////////////////////////////////////////////////////////////////////////////////
174/// From the internal representation, construct a formula by replacing all index place holders
175/// with the names of the variables that are being used to evaluate it, and return it as string.
176std::string reconstructFormula(std::string internalRepr, RooArgList const& args) {
177 const auto nArgs = args.size();
178 for (unsigned int i = 0; i < nArgs; ++i) {
179 const auto& var = args[i];
180 std::stringstream regexStr;
181 regexStr << "x\\[" << i << "\\]|@" << i;
182 std::regex regex(regexStr.str());
183
184 std::string replacement = std::string("[") + var.GetName() + "]";
185 internalRepr = std::regex_replace(internalRepr, regex, replacement);
186 }
187
188 return internalRepr;
189}
190
191////////////////////////////////////////////////////////////////////////////////
192/// From the internal representation, construct a null-formula by replacing all
193/// index place holders with zeroes, and return it as string
194std::string reconstructNullFormula(std::string internalRepr, RooArgList const& args) {
195 const auto nArgs = args.size();
196 for (unsigned int i = 0; i < nArgs; ++i) {
197 std::stringstream regexStr;
198 regexStr << "x\\[" << i << "\\]|@" << i;
199 std::regex regex(regexStr.str());
200
201 std::string replacement = "1e-18";
202 internalRepr = std::regex_replace(internalRepr, regex, replacement);
203 }
204
205 return internalRepr;
206}
207
208}
209
210////////////////////////////////////////////////////////////////////////////////
211/// Construct a new formula.
212/// \param[in] name Name of the formula.
213/// \param[in] formula Formula to be evaluated. Parameters/observables are identified by name
214/// or ordinal position in `varList`.
215/// \param[in] varList List of variables to be passed to the formula.
216/// \param[in] checkVariables Unused parameter.
217RooFormula::RooFormula(const char *name, const char *formula, const RooArgList &varList, bool /*checkVariables*/)
218 : TNamed(name, formula)
219{
220 _origList.add(varList);
221 _varIsUsed.resize(varList.size());
222
223 installFormulaOrThrow(formula);
224
225 if (_tFormula == nullptr)
226 return;
227
228 const std::string processedFormula(_tFormula->GetTitle());
229
230 std::set<unsigned int> matchedOrdinals;
231 static const std::regex newOrdinalRegex("\\bx\\[([0-9]+)\\]");
234 assert(matchIt->size() == 2);
235 std::stringstream matchString((*matchIt)[1]);
236 unsigned int i;
237 matchString >> i;
238
239 matchedOrdinals.insert(i);
240 }
241
242 for (unsigned int i : matchedOrdinals) {
243 _varIsUsed[i] = true;
244 }
245}
246
247////////////////////////////////////////////////////////////////////////////////
248/// Copy constructor
249RooFormula::RooFormula(const RooFormula& other, const char* name) :
250 TNamed(name ? name : other.GetName(), other.GetTitle()),
252{
253 _origList.add(other._origList);
254
255 std::unique_ptr<TFormula> newTF;
256 if (other._tFormula) {
257 newTF = std::make_unique<TFormula>(*other._tFormula);
258 newTF->SetName(GetName());
259 }
260
261 _tFormula = std::move(newTF);
262}
263
264////////////////////////////////////////////////////////////////////////////////
265/// Process given formula by replacing all ordinal and name references by
266/// `x[i]`, where `i` matches the position of the argument in `_origList`.
267/// Further, references to category states such as `leptonMulti:one` are replaced
268/// by the category index.
269std::string RooFormula::processFormula(std::string formula) const {
270
271 // WARNING to developers: people use RooFormula a lot via RooGenericPdf and
272 // RooFormulaVar! Performance matters here. Avoid non-static std::regex,
273 // because constructing these can become a bottleneck because of the regex
274 // compilation.
275
276 cxcoutD(InputArguments) << "Preprocessing formula step 1: find category tags (catName::catState) in "
277 << formula << std::endl;
278
279 // Step 1: Find all category tags and the corresponding index numbers
280 static const std::regex categoryReg("(\\w+)::(\\w+)");
281 std::map<std::string, int> categoryStates;
282 for (sregex_iterator matchIt = sregex_iterator(formula.begin(), formula.end(), categoryReg);
284 assert(matchIt->size() == 3);
285 const std::string fullMatch = (*matchIt)[0];
286 const std::string catName = (*matchIt)[1];
287 const std::string catState = (*matchIt)[2];
288
289 const auto catVariable = dynamic_cast<const RooAbsCategory*>(_origList.find(catName.c_str()));
290 if (!catVariable) {
291 cxcoutD(InputArguments) << "Formula " << GetName() << " uses '::' to reference a category state as '" << fullMatch
292 << "' but a category '" << catName << "' cannot be found in the input variables." << std::endl;
293 continue;
294 }
295
296 if (!catVariable->hasLabel(catState)) {
297 coutE(InputArguments) << "Formula " << GetName() << " uses '::' to reference a category state as '" << fullMatch
298 << "' but the category '" << catName << "' does not seem to have the state '" << catState << "'." << std::endl;
299 throw std::invalid_argument(formula);
300 }
301 const int catNum = catVariable->lookupIndex(catState);
302
304 cxcoutD(InputArguments) << "\n\t" << fullMatch << "\tname=" << catName << "\tstate=" << catState << "=" << catNum;
305 }
306 cxcoutD(InputArguments) << "-- End of category tags --"<< std::endl;
307
308 // Step 2: Replace all category tags
309 for (const auto& catState : categoryStates) {
310 replaceAll(formula, catState.first, std::to_string(catState.second));
311 }
312
313 cxcoutD(InputArguments) << "Preprocessing formula step 2: replace category tags\n\t" << formula << std::endl;
314
315 // Step 3: Convert `@i`-style references to `x[i]`
317
318 cxcoutD(InputArguments) << "Preprocessing formula step 3: replace '@'-references\n\t" << formula << std::endl;
319
320 // Step 4: Replace all named references with "x[i]"-style
322
323 cxcoutD(InputArguments) << "Final formula:\n\t" << formula << std::endl;
324
325 return formula;
326}
327
328////////////////////////////////////////////////////////////////////////////////
329/// Analyse internal formula to find out which variables are actually in use.
330RooArgList RooFormula::usedVariables() const {
331 RooArgList useList;
332
333 for (std::size_t i = 0; i < _varIsUsed.size(); ++i) {
334 if (_varIsUsed[i]) {
335 useList.add(_origList[i]);
336 }
337 }
338
339 return useList;
340}
341
342////////////////////////////////////////////////////////////////////////////////
343/// Change used variables to those with the same name in given list.
344/// \param[in] newDeps New dependents to replace the old ones.
345/// \param[in] mustReplaceAll Will yield an error if one dependent does not have a replacement.
346/// \param[in] nameChange Passed down to RooAbsArg::findNewServer(const RooAbsCollection&, bool) const.
347bool RooFormula::changeDependents(const RooAbsCollection& newDeps, bool mustReplaceAll, bool nameChange)
348{
349 //Change current servers to new servers with the same name given in list
350 bool errorStat = false;
351
352 // We only consider the usedVariables() for replacement, because only these
353 // are registered as servers.
354 for (const auto arg : usedVariables()) {
355 RooAbsReal* replace = static_cast<RooAbsReal*>(arg->findNewServer(newDeps,nameChange)) ;
356 if (replace) {
357 _origList.replace(*arg, *replace);
358
359 if (arg->getStringAttribute("origName")) {
360 replace->setStringAttribute("origName",arg->getStringAttribute("origName")) ;
361 } else {
362 replace->setStringAttribute("origName",arg->GetName()) ;
363 }
364
365 } else if (mustReplaceAll) {
366 coutE(LinkStateMgmt) << __func__ << ": cannot find replacement for " << arg->GetName() << std::endl;
367 errorStat = true;
368 }
369 }
370
371 return errorStat;
372}
373
374////////////////////////////////////////////////////////////////////////////////
375/// Evaluate the internal TFormula.
376///
377/// First, all variables serving this instance are evaluated given the normalisation set,
378/// and then the formula is evaluated.
379/// \param[in] nset Normalisation set passed to evaluation of arguments serving values.
380/// \return The result of the evaluation.
381double RooFormula::eval(const RooArgSet* nset) const
382{
383 if (!_tFormula) {
384 coutF(Eval) << __func__ << " (" << GetName() << "): Formula didn't compile: " << GetTitle() << std::endl;
385 std::string what = "Formula ";
386 what += GetTitle();
387 what += " didn't compile.";
388 throw std::runtime_error(what);
389 }
390
391 std::vector<double> pars;
392 pars.reserve(_origList.size());
393 for (unsigned int i = 0; i < _origList.size(); ++i) {
394 if (_origList[i].isCategory()) {
395 const auto& cat = static_cast<RooAbsCategory&>(_origList[i]);
396 pars.push_back(cat.getCurrentIndex());
397 } else {
398 const auto& real = static_cast<RooAbsReal&>(_origList[i]);
399 pars.push_back(real.getVal(nset));
400 }
401 }
402
403 return _tFormula->EvalPar(pars.data());
404}
405
406void RooFormula::doEval(RooArgList const &actualVars, RooFit::EvalContext &ctx) const
407{
408 std::span<double> output = ctx.output();
409
410 const int nPars = _origList.size();
411 std::vector<std::span<const double>> inputSpans(nPars);
412 int iActual = 0;
413 for (int i = 0; i < nPars; i++) {
414 if(_varIsUsed[i]) {
415 std::span<const double> rhs = ctx.at(static_cast<const RooAbsReal *>(&actualVars[iActual]));
416 inputSpans[i] = rhs;
417 ++iActual;
418 }
419 }
420
421 std::vector<double> pars(nPars);
422 for (size_t i = 0; i < output.size(); i++) {
423 for (int j = 0; j < nPars; j++) {
424 pars[j] = inputSpans[j].size() > 1 ? inputSpans[j][i] : inputSpans[j][0];
425 }
426 output[i] = _tFormula->EvalPar(pars.data());
427 }
428}
429
430////////////////////////////////////////////////////////////////////////////////
431/// Printing interface
432
433void RooFormula::printMultiline(ostream& os, Int_t /*contents*/, bool /*verbose*/, TString indent) const
434{
435 os << indent << "--- RooFormula ---" << std::endl;
436 os << indent << " Formula: '" << GetTitle() << "'" << std::endl;
437 os << indent << " Interpretation: '" << reconstructFormula(GetTitle(), _origList) << "'" << std::endl;
438 indent.Append(" ");
439 os << indent << "Servers: " << _origList << "\n";
440 os << indent << "In use : " << actualDependents() << std::endl;
441}
442
443////////////////////////////////////////////////////////////////////////////////
444/// Check that the formula compiles, and also fulfills the assumptions.
445///
446void RooFormula::installFormulaOrThrow(const std::string& formula) {
447 const std::string processedFormula = processFormula(formula);
448
449 cxcoutD(InputArguments) << "RooFormula '" << GetName() << "' will be compiled as "
450 << "\n\t" << processedFormula
451 << "\n and used as"
453 << "\n with the parameters " << _origList << std::endl;
454
455 auto theFormula = std::make_unique<TFormula>(GetName(), processedFormula.c_str(), /*addToGlobList=*/false);
456
457 if (!theFormula || !theFormula->IsValid()) {
458 std::stringstream msg;
459 msg << "RooFormula '" << GetName() << "' did not compile or is invalid."
460 << "\nInput:\n\t" << formula
461 << "\nPassed over to TFormula:\n\t" << processedFormula << std::endl;
462 coutF(InputArguments) << msg.str();
463 throw std::runtime_error(msg.str());
464 }
465
466 if (theFormula && theFormula->GetNdim() != 0) {
467 TFormula nullFormula{"nullFormula", reconstructNullFormula(processedFormula, _origList).c_str(), /*addToGlobList=*/false};
468 const auto nullDim = nullFormula.GetNdim();
469 if (nullDim != 0) {
470 // TFormula thinks that we have an n-dimensional formula (n>0), but it shouldn't, as
471 // these vars should have been replaced by zeroes in reconstructNullFormula
472 // since RooFit only uses the syntax x[0], x[1], x[2], ...
473 // This can happen e.g. with variables x,y,z,t that were not supplied in arglist.
474 std::stringstream msg;
475 msg << "TFormula interprets the formula " << formula << " as " << theFormula->GetNdim()+nullDim << "-dimensional with undefined variable(s) {";
476 for (auto i=0; i < nullDim; ++i) {
477 msg << nullFormula.GetVarName(i) << ",";
478 }
479 msg << "}, which could not be supplied by RooFit."
480 << "\nThe formula must be modified, or those variables must be supplied in the list of variables." << std::endl;
481 coutF(InputArguments) << msg.str();
482 throw std::invalid_argument(msg.str());
483 }
484 }
485
486 _tFormula = std::move(theFormula);
487}
488
489/// \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
const_iterator end() 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.
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()
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
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()