Logo ROOT   6.30.04
Reference Guide
 All Namespaces Files Pages
RooFormula.cxx
Go to the documentation of this file.
1 /*****************************************************************************
2  * Project: RooFit *
3  * Package: RooFitCore *
4  * @(#)root/roofitcore:$Id$
5  * Authors: *
6  * WV, Wouter Verkerke, UC Santa Barbara, verkerke@slac.stanford.edu *
7  * DK, David Kirkby, UC Irvine, dkirkby@uci.edu *
8  * *
9  * Copyright (c) 2000-2005, Regents of the University of California *
10  * and Stanford University. All rights reserved. *
11  * *
12  * Redistribution and use in source and binary forms, *
13  * with or without modification, are permitted according to the terms *
14  * listed in LICENSE (http://roofit.sourceforge.net/license.txt) *
15  *****************************************************************************/
16 
17 /**
18 \file RooFormula.cxx
19 \class RooFormula
20 \ingroup Roofitcore
21 
22 RooFormula internally uses ROOT's TFormula to compute user-defined expressions
23 of RooAbsArgs.
24 
25 The string expression can be any valid TFormula expression referring to the
26 listed servers either by name or by their ordinal list position. These three are
27 forms equivalent:
28 ```
29  RooFormula("formula", "x*y", RooArgList(x,y)) or
30  RooFormula("formula", "@0*@1", RooArgList(x,y))
31  RooFormula("formula", "x[0]*x[1]", RooArgList(x,y))
32 ```
33 Note that `x[i]` is an expression reserved for TFormula. If a variable with
34 the name `x` is given, the RooFormula interprets `x[i]` as a list position,
35 but `x` without brackets as a variable name.
36 
37 ### Category expressions
38 State information of RooAbsCategories can be accessed using the '::' operator,
39 *e.g.*, `tagCat::Kaon` will resolve to the numerical value of
40 the `Kaon` state of the RooAbsCategory object named `tagCat`.
41 
42 A formula to switch between lepton categories could look like this:
43 ```
44  RooFormula("formulaWithCat",
45  "x * (leptonMulti == leptonMulti::one) + y * (leptonMulti == leptonMulti::two)",
46  RooArgList(x, y, leptonMulti));
47 ```
48 
49 ### Debugging a formula that won't compile
50 When the formula is preprocessed, RooFit prints some information in the message stream.
51 These can be retrieved by activating the RooFit::MsgLevel `RooFit::DEBUG`
52 and the RooFit::MsgTopic `RooFit::InputArguments`.
53 Check the tutorial rf506_msgservice.C for details.
54 **/
55 
56 #include "RooFormula.h"
57 
58 #include "RooFit.h"
59 #include "RooAbsReal.h"
60 #include "RooAbsCategory.h"
61 #include "RooArgList.h"
62 #include "RooMsgService.h"
63 #include "ROOT/RMakeUnique.hxx"
64 
65 #include <sstream>
66 #include <regex>
67 
68 using namespace std;
69 
70 ClassImp(RooFormula);
71 
72 
73 ////////////////////////////////////////////////////////////////////////////////
74 /// Default constructor
75 /// coverity[UNINIT_CTOR]
76 
77 RooFormula::RooFormula() : TNamed()
78 {
79 }
80 
81 
82 ////////////////////////////////////////////////////////////////////////////////
83 /// Construct a new formula.
84 /// \param[in] name Name of the formula.
85 /// \param[in] formula Formula to be evaluated. Parameters/observables are identified by name
86 /// or ordinal position in `varList`.
87 /// \param[in] varList List of variables to be passed to the formula.
88 /// \param[in] checkVariables Check that the variables being passed in the `varList` are used in
89 /// the formula expression.
90 RooFormula::RooFormula(const char* name, const char* formula, const RooArgList& varList,
91  bool checkVariables) :
92  TNamed(name, formula), _tFormula{nullptr}
93 {
94  _origList.add(varList);
95  _isCategory = findCategoryServers(_origList);
96 
97  std::string processedFormula = processFormula(formula);
98 
99  cxcoutD(InputArguments) << "RooFormula '" << GetName() << "' will be compiled as "
100  << "\n\t" << processedFormula
101  << "\n and used as"
102  << "\n\t" << reconstructFormula(processedFormula)
103  << "\n with the parameters " << _origList << endl;
104 
105 
106  if (!processedFormula.empty())
107  _tFormula = std::make_unique<TFormula>(name, processedFormula.c_str(), false);
108 
109  if (!_tFormula || !_tFormula->IsValid()) {
110  coutF(InputArguments) << "RooFormula '" << GetName() << "' did not compile."
111  << "\nInput:\n\t" << formula
112  << "\nProcessed:\n\t" << processedFormula << endl;
113  _tFormula.reset(nullptr);
114  }
115 
116  RooArgList useList = usedVariables();
117  if (checkVariables && _origList.size() != useList.size()) {
118  coutI(InputArguments) << "The formula " << GetName() << " claims to use the variables " << _origList
119  << " but only " << useList << " seem to be in use."
120  << "\n inputs: " << formula
121  << "\n interpretation: " << reconstructFormula(processedFormula) << std::endl;
122  }
123 }
124 
125 
126 
127 ////////////////////////////////////////////////////////////////////////////////
128 /// Copy constructor
129 RooFormula::RooFormula(const RooFormula& other, const char* name) :
130  TNamed(name ? name : other.GetName(), other.GetTitle()), RooPrintable(other)
131 {
132  _origList.add(other._origList);
133  _isCategory = findCategoryServers(_origList);
134 
135  TFormula* newTF = nullptr;
136  if (other._tFormula) {
137  newTF = new TFormula(*other._tFormula);
138  newTF->SetName(GetName());
139  }
140 
141  _tFormula.reset(newTF);
142 }
143 
144 #ifndef _MSC_VER
145 #if !defined(__GNUC__) || defined(__clang__) || (__GNUC__ > 4) || ( __GNUC__ == 4 && __GNUC_MINOR__ > 8)
146 #define ROOFORMULA_HAVE_STD_REGEX
147 ////////////////////////////////////////////////////////////////////////////////
148 /// Process given formula by replacing all ordinal and name references by
149 /// `x[i]`, where `i` matches the position of the argument in `_origList`.
150 /// Further, references to category states such as `leptonMulti:one` are replaced
151 /// by the category index.
152 std::string RooFormula::processFormula(std::string formula) const {
153 
154  cxcoutD(InputArguments) << "Preprocessing formula step 1: find category tags (catName::catState) in "
155  << formula << endl;
156 
157  // Step 1: Find all category tags and the corresponding index numbers
158  std::regex categoryReg("(\\w+)::(\\w+)");
159  std::map<std::string, int> categoryStates;
160  for (sregex_iterator matchIt = sregex_iterator(formula.begin(), formula.end(), categoryReg);
161  matchIt != sregex_iterator(); ++matchIt) {
162  assert(matchIt->size() == 3);
163  const std::string fullMatch = (*matchIt)[0];
164  const std::string catName = (*matchIt)[1];
165  const std::string catState = (*matchIt)[2];
166 
167  const auto catVariable = dynamic_cast<const RooAbsCategory*>(_origList.find(catName.c_str()));
168  if (!catVariable) {
169  cxcoutD(InputArguments) << "Formula " << GetName() << " uses '::' to reference a category state as '" << fullMatch
170  << "' but a category '" << catName << "' cannot be found in the input variables." << endl;
171  continue;
172  }
173 
174  const RooCatType* catType = catVariable->lookupType(catState.c_str(), false);
175  if (!catType) {
176  coutE(InputArguments) << "Formula " << GetName() << " uses '::' to reference a category state as '" << fullMatch
177  << "' but the category '" << catName << "' does not seem to have the state '" << catState << "'." << endl;
178  throw std::invalid_argument(formula);
179  }
180  const int catNum = catType->getVal();
181 
182  categoryStates[fullMatch] = catNum;
183  cxcoutD(InputArguments) << "\n\t" << fullMatch << "\tname=" << catName << "\tstate=" << catState << "=" << catNum;
184  }
185  cxcoutD(InputArguments) << "-- End of category tags --"<< endl;
186 
187  // Step 2: Replace all category tags
188  for (const auto& catState : categoryStates) {
189  std::stringstream replacement;
190  replacement << catState.second;
191  formula = std::regex_replace(formula, std::regex(catState.first), replacement.str());
192  }
193 
194  cxcoutD(InputArguments) << "Preprocessing formula step 2: replace category tags\n\t" << formula << endl;
195 
196  // Step 3: Convert `@i`-style references to `x[i]`
197  std::regex ordinalRegex("@([0-9]+)");
198  formula = std::regex_replace(formula, ordinalRegex, "x[$1]");
199 
200  cxcoutD(InputArguments) << "Preprocessing formula step 3: replace '@'-references\n\t" << formula << endl;
201 
202  // Step 4: Replace all named references with "x[i]"-style
203  for (unsigned int i = 0; i < _origList.size(); ++i) {
204  const auto& var = _origList[i];
205  std::string regex = "\\b";
206  regex += var.GetName();
207  regex += "\\b(?!\\[)"; //Negative lookahead. If the variable is called `x`, this might otherwise replace `x[0]`.
208  std::regex findParameterRegex(regex);
209 
210  std::stringstream replacement;
211  replacement << "x[" << i << "]";
212  formula = std::regex_replace(formula, findParameterRegex, replacement.str());
213 
214  cxcoutD(InputArguments) << "Preprocessing formula step 4: replace named references: "
215  << var.GetName() << " --> " << replacement.str()
216  << "\n\t" << formula << endl;
217  }
218 
219  cxcoutD(InputArguments) << "Final formula:\n\t" << formula << endl;
220 
221  return formula;
222 }
223 
224 
225 ////////////////////////////////////////////////////////////////////////////////
226 /// Analyse internal formula to find out which variables are actually in use.
227 RooArgList RooFormula::usedVariables() const {
228  RooArgList useList;
229  if (_tFormula == nullptr)
230  return useList;
231 
232  const std::string formula(_tFormula->GetTitle());
233 
234  std::set<unsigned int> matchedOrdinals;
235  std::regex newOrdinalRegex("\\bx\\[([0-9]+)\\]");
236  for (sregex_iterator matchIt = sregex_iterator(formula.begin(), formula.end(), newOrdinalRegex);
237  matchIt != sregex_iterator(); ++matchIt) {
238  assert(matchIt->size() == 2);
239  std::stringstream matchString((*matchIt)[1]);
240  unsigned int i;
241  matchString >> i;
242 
243  matchedOrdinals.insert(i);
244  }
245 
246  for (unsigned int i : matchedOrdinals) {
247  useList.add(_origList[i]);
248  }
249 
250  return useList;
251 }
252 
253 
254 ////////////////////////////////////////////////////////////////////////////////
255 /// From the internal representation, construct a formula by replacing all index place holders
256 /// with the names of the variables that are being used to evaluate it.
257 std::string RooFormula::reconstructFormula(std::string internalRepr) const {
258  for (unsigned int i = 0; i < _origList.size(); ++i) {
259  const auto& var = _origList[i];
260  std::stringstream regexStr;
261  regexStr << "x\\[" << i << "\\]|@" << i;
262  std::regex regex(regexStr.str());
263 
264  std::string replacement = std::string("[") + var.GetName() + "]";
265  internalRepr = std::regex_replace(internalRepr, regex, replacement);
266  }
267 
268  return internalRepr;
269 }
270 #endif //GCC < 4.9 Check
271 #endif //_MSC_VER
272 
273 ////////////////////////////////////////////////////////////////////////////////
274 /// Find all input arguments which are categories, and save this information in
275 /// with the names of the variables that are being used to evaluate it.
276 std::vector<bool> RooFormula::findCategoryServers(const RooAbsCollection& collection) const {
277  std::vector<bool> output;
278 
279  for (unsigned int i = 0; i < collection.size(); ++i) {
280  output.push_back(dynamic_cast<const RooAbsCategory*>(collection[i]));
281  }
282 
283  return output;
284 }
285 
286 
287 ////////////////////////////////////////////////////////////////////////////////
288 /// Recompile formula with new expression. In case of error, the old formula is
289 /// retained.
290 Bool_t RooFormula::reCompile(const char* newFormula)
291 {
292  std::string processed = processFormula(newFormula);
293  auto newTF = std::make_unique<TFormula>(GetName(), processed.c_str(), false);
294 
295  if (!newTF->IsValid()) {
296  coutE(InputArguments) << __func__ << ": new equation doesn't compile, formula unchanged" << endl;
297  return true;
298  }
299 
300  _tFormula = std::move(newTF);
301  SetTitle(newFormula);
302  return false;
303 }
304 
305 void RooFormula::dump() const
306 {
307  printMultiline(std::cout, 0);
308 }
309 
310 
311 ////////////////////////////////////////////////////////////////////////////////
312 /// Change used variables to those with the same name in given list.
313 /// \param[in] newDeps New dependents to replace the old ones.
314 /// \param[in] mustReplaceAll Will yield an error if one dependent does not have a replacement.
315 /// \param[in] nameChange Passed down to RooAbsArg::findNewServer(const RooAbsCollection&, Bool_t) const.
316 Bool_t RooFormula::changeDependents(const RooAbsCollection& newDeps, Bool_t mustReplaceAll, Bool_t nameChange)
317 {
318  //Change current servers to new servers with the same name given in list
319  bool errorStat = false;
320 
321  for (const auto arg : _origList) {
322  RooAbsReal* replace = (RooAbsReal*) arg->findNewServer(newDeps,nameChange) ;
323  if (replace) {
324  _origList.replace(*arg, *replace);
325 
326  if (arg->getStringAttribute("origName")) {
327  replace->setStringAttribute("origName",arg->getStringAttribute("origName")) ;
328  } else {
329  replace->setStringAttribute("origName",arg->GetName()) ;
330  }
331 
332  } else if (mustReplaceAll) {
333  coutE(LinkStateMgmt) << __func__ << ": cannot find replacement for " << arg->GetName() << endl;
334  errorStat = true;
335  }
336  }
337 
338  _isCategory = findCategoryServers(_origList);
339 
340  return errorStat;
341 }
342 
343 
344 
345 ////////////////////////////////////////////////////////////////////////////////
346 /// Evaluate the internal TFormula.
347 ///
348 /// First, all variables serving this instance are evaluated given the normalisation set,
349 /// and then the formula is evaluated.
350 /// \param[in] nset Normalisation set passed to evaluation of arguments serving values.
351 /// \return The result of the evaluation.
352 Double_t RooFormula::eval(const RooArgSet* nset) const
353 {
354  if (!_tFormula) {
355  coutF(Eval) << __func__ << " (" << GetName() << "): Formula didn't compile: " << GetTitle() << endl;
356  std::string what = "Formula ";
357  what += GetTitle();
358  what += " didn't compile.";
359  throw std::invalid_argument(what);
360  }
361 
362  std::vector<double> pars;
363  pars.reserve(_origList.size());
364  for (unsigned int i = 0; i < _origList.size(); ++i) {
365  if (_isCategory[i]) {
366  const auto& cat = static_cast<RooAbsCategory&>(_origList[i]);
367  pars.push_back(cat.getIndex());
368  } else {
369  const auto& real = static_cast<RooAbsReal&>(_origList[i]);
370  pars.push_back(real.getVal(nset));
371  }
372  }
373 
374  return _tFormula->EvalPar(pars.data());
375 }
376 
377 
378 ////////////////////////////////////////////////////////////////////////////////
379 /// Printing interface
380 
381 void RooFormula::printMultiline(ostream& os, Int_t /*contents*/, Bool_t /*verbose*/, TString indent) const
382 {
383  os << indent << "--- RooFormula ---" << endl;
384  os << indent << " Formula: '" << GetTitle() << "'" << endl;
385  os << indent << " Interpretation: '" << reconstructFormula(GetTitle()) << "'" << endl;
386  indent.Append(" ");
387  os << indent << "Servers: " << _origList << "\n";
388  os << indent << "In use : " << actualDependents() << endl;
389 }
390 
391 
392 ////////////////////////////////////////////////////////////////////////////////
393 /// Print value of formula
394 
395 void RooFormula::printValue(ostream& os) const
396 {
397  os << const_cast<RooFormula*>(this)->eval(0) ;
398 }
399 
400 
401 ////////////////////////////////////////////////////////////////////////////////
402 /// Print name of formula
403 
404 void RooFormula::printName(ostream& os) const
405 {
406  os << GetName() ;
407 }
408 
409 
410 ////////////////////////////////////////////////////////////////////////////////
411 /// Print title of formula
412 
413 void RooFormula::printTitle(ostream& os) const
414 {
415  os << GetTitle() ;
416 }
417 
418 
419 ////////////////////////////////////////////////////////////////////////////////
420 /// Print class name of formula
421 
422 void RooFormula::printClassName(ostream& os) const
423 {
424  os << IsA()->GetName() ;
425 }
426 
427 
428 ////////////////////////////////////////////////////////////////////////////////
429 /// Print arguments of formula, i.e. dependents that are actually used
430 
431 void RooFormula::printArgs(ostream& os) const
432 {
433  os << "[ actualVars=";
434  for (const auto arg : usedVariables()) {
435  os << " " << arg->GetName();
436  }
437  os << " ]";
438 }
439 
440 
441 
442 
443 
444 #ifndef ROOFORMULA_HAVE_STD_REGEX
445 /*
446  * g++ 4.8 doesn't support the std::regex. It has headers, but no implementations of the standard, leading to linker
447  * errors. As long as centos 7 needs to be supported, this forces us to have a legacy implementation.
448  */
449 
450 #include "TPRegexp.h"
451 
452 ////////////////////////////////////////////////////////////////////////////////
453 /// Process given formula by replacing all ordinal and name references by
454 /// `x[i]`, where `i` matches the position of the argument in `_origList`.
455 /// Further, references to category states such as `leptonMulti:one` are replaced
456 /// by the category index.
457 std::string RooFormula::processFormula(std::string formula) const {
458  TString formulaTString = formula.c_str();
459 
460  cxcoutD(InputArguments) << "Preprocessing formula step 1: find category tags (catName::catState) in "
461  << formulaTString.Data() << endl;
462 
463  // Step 1: Find all category tags and the corresponding index numbers
464  TPRegexp categoryReg("(\\w+)::(\\w+)");
465  std::map<std::string, int> categoryStates;
466  int offset = 0;
467  do {
468  std::unique_ptr<TObjArray> matches(categoryReg.MatchS(formulaTString, "", offset, 3));
469  if (matches->GetEntries() == 0)
470  break;
471 
472  std::string fullMatch = static_cast<TObjString*>(matches->At(0))->GetString().Data();
473  std::string catName = static_cast<TObjString*>(matches->At(1))->GetString().Data();
474  std::string catState = static_cast<TObjString*>(matches->At(2))->GetString().Data();
475  offset = formulaTString.Index(categoryReg, offset) + fullMatch.size();
476 
477  const auto catVariable = dynamic_cast<const RooAbsCategory*>(_origList.find(catName.c_str()));
478  if (!catVariable) {
479  cxcoutD(InputArguments) << "Formula " << GetName() << " uses '::' to reference a category state as '" << fullMatch
480  << "' but a category '" << catName << "' cannot be found in the input variables." << endl;
481  continue;
482  }
483 
484  const RooCatType* catType = catVariable->lookupType(catState.c_str(), false);
485  if (!catType) {
486  coutE(InputArguments) << "Formula " << GetName() << " uses '::' to reference a category state as '" << fullMatch
487  << "' but the category '" << catName << "' does not seem to have the state '" << catState << "'." << endl;
488  throw std::invalid_argument(formula);
489  }
490  const int catNum = catType->getVal();
491 
492  categoryStates[fullMatch] = catNum;
493  cxcoutD(InputArguments) << "\n\t" << fullMatch << "\tname=" << catName << "\tstate=" << catState << "=" << catNum;
494  } while (offset != -1);
495  cxcoutD(InputArguments) << "-- End of category tags --"<< endl;
496 
497  // Step 2: Replace all category tags
498  for (const auto& catState : categoryStates) {
499  std::stringstream replacement;
500  replacement << catState.second;
501  formulaTString.ReplaceAll(catState.first.c_str(), replacement.str().c_str());
502  }
503 
504  cxcoutD(InputArguments) << "Preprocessing formula step 2: replace category tags\n\t" << formulaTString.Data() << endl;
505 
506  // Step 3: Convert `@i`-style references to `x[i]`
507  TPRegexp ordinalRegex("@([0-9]+)");
508  int nsub = 0;
509  do {
510  nsub = ordinalRegex.Substitute(formulaTString, "x[$1]");
511  } while (nsub > 0);
512 
513  cxcoutD(InputArguments) << "Preprocessing formula step 3: replace '@'-references\n\t" << formulaTString.Data() << endl;
514 
515  // Step 4: Replace all named references with "x[i]"-style
516  for (unsigned int i = 0; i < _origList.size(); ++i) {
517  const auto& var = _origList[i];
518  TString regex = "\\b";
519  regex += var.GetName();
520  regex += "\\b([^[]|$)"; //Negative lookahead. If the variable is called `x`, this might otherwise replace `x[0]`.
521  TPRegexp findParameterRegex(regex);
522 
523  std::stringstream replacement;
524  replacement << "x[" << i << "]$1";
525  int nsub2 = 0;
526  do {
527  nsub2 = findParameterRegex.Substitute(formulaTString, replacement.str().c_str());
528  } while (nsub2 > 0);
529 
530  cxcoutD(InputArguments) << "Preprocessing formula step 4: replace named references: "
531  << var.GetName() << " --> " << replacement.str()
532  << "\n\t" << formulaTString.Data() << endl;
533  }
534 
535  cxcoutD(InputArguments) << "Final formula:\n\t" << formulaTString << endl;
536 
537  return formulaTString.Data();
538 }
539 
540 
541 ////////////////////////////////////////////////////////////////////////////////
542 /// Analyse internal formula to find out which variables are actually in use.
543 RooArgList RooFormula::usedVariables() const {
544  RooArgList useList;
545  if (_tFormula == nullptr)
546  return useList;
547 
548  const TString formulaTString = _tFormula->GetTitle();
549 
550  std::set<unsigned int> matchedOrdinals;
551  TPRegexp newOrdinalRegex("\\bx\\[([0-9]+)\\]");
552  int offset = 0;
553  do {
554  std::unique_ptr<TObjArray> matches(newOrdinalRegex.MatchS(formulaTString, "", offset, 2));
555  if (matches->GetEntries() == 0)
556  break;
557 
558  std::string fullMatch = static_cast<TObjString*>(matches->At(0))->GetString().Data();
559  std::string ordinal = static_cast<TObjString*>(matches->At(1))->GetString().Data();
560  offset = formulaTString.Index(newOrdinalRegex, offset) + fullMatch.size();
561 
562  std::stringstream matchString(ordinal.c_str());
563  unsigned int i;
564  matchString >> i;
565 
566  matchedOrdinals.insert(i);
567  } while (offset != -1);
568 
569  for (unsigned int i : matchedOrdinals) {
570  useList.add(_origList[i]);
571  }
572 
573  return useList;
574 }
575 
576 
577 ////////////////////////////////////////////////////////////////////////////////
578 /// From the internal representation, construct a formula by replacing all index place holders
579 /// with the names of the variables that are being used to evaluate it.
580 std::string RooFormula::reconstructFormula(std::string internalRepr) const {
581  TString internalReprT = internalRepr.c_str();
582 
583  for (unsigned int i = 0; i < _origList.size(); ++i) {
584  const auto& var = _origList[i];
585  std::stringstream regexStr;
586  regexStr << "x\\[" << i << "\\]|@" << i;
587  TPRegexp regex(regexStr.str().c_str());
588 
589  std::string replacement = std::string("[") + var.GetName() + "]";
590  regex.Substitute(internalReprT, replacement.c_str());
591  }
592 
593  return internalReprT.Data();
594 }
595 #endif //GCC < 4.9 Check